Skip to content

Commit

Permalink
Detect Vault 1.11+ import in secondary datacenters and update default…
Browse files Browse the repository at this point in the history
… issuer (#15661)

The fix outlined and merged in #15253 fixed the issue as it occurs in the primary
DC. There is a similar issue that arises when vault is used as the Connect CA in a
secondary datacenter that is fixed by this PR.

Additionally: this PR adds support to run the existing suite of vault related integration
tests against the last 4 versions of vault (1.9, 1.10, 1.11, 1.12)
  • Loading branch information
rboyer committed Dec 5, 2022
1 parent 95bcfd2 commit 4940a72
Show file tree
Hide file tree
Showing 12 changed files with 163 additions and 47 deletions.
3 changes: 3 additions & 0 deletions .changelog/15661.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
connect: Fixed issue where using Vault 1.11+ as CA provider in a secondary datacenter would eventually break Intermediate CAs
```
26 changes: 20 additions & 6 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ references:
GIT_COMMITTER_NAME: circleci-consul
S3_ARTIFACT_BUCKET: consul-dev-artifacts-v2
BASH_ENV: .circleci/bash_env.sh
VAULT_BINARY_VERSION: 1.9.4
GO_VERSION: 1.19.2
envoy-versions: &supported_envoy_versions
- &default_envoy_version "1.21.5"
Expand All @@ -32,6 +31,11 @@ references:
- &default_nomad_version "1.3.3"
- "1.2.10"
- "1.1.16"
vault-versions: &supported_vault_versions
- &default_vault_version "1.12.2"
- "1.11.6"
- "1.10.9"
- "1.9.10"
images:
# When updating the Go version, remember to also update the versions in the
# workflows section for go-test-lib jobs.
Expand Down Expand Up @@ -587,7 +591,6 @@ jobs:
- setup_remote_docker
- run: make ci.dev-docker
- run: *notify-slack-failure

nomad-integration-test: &NOMAD_TESTS
docker:
- image: docker.mirror.hashicorp.services/cimg/go:1.19
Expand Down Expand Up @@ -935,19 +938,26 @@ jobs:
path: *TEST_RESULTS_DIR
- run: *notify-slack-failure

# run integration tests for the connect ca providers
test-connect-ca-providers:
# run integration tests for the connect ca providers with vault
vault-integration-test:
docker:
- image: *GOLANG_IMAGE
parameters:
vault-version:
type: enum
enum: *supported_vault_versions
default: *default_vault_version
environment:
<<: *ENVIRONMENT
steps:
VAULT_BINARY_VERSION: << parameters.vault-version >>
steps: &VAULT_INTEGRATION_TEST_STEPS
- run:
name: Install vault
command: |
wget -q -O /tmp/vault.zip https://releases.hashicorp.com/vault/${VAULT_BINARY_VERSION}/vault_${VAULT_BINARY_VERSION}_linux_amd64.zip
sudo unzip -d /usr/local/bin /tmp/vault.zip
rm -rf /tmp/vault*
vault version
- checkout
- run: go mod download
- run:
Expand Down Expand Up @@ -1067,7 +1077,6 @@ workflows:
name: "lint-32bit"
go-arch: "386"
<<: *filter-ignore-non-go-branches
- test-connect-ca-providers: *filter-ignore-non-go-branches
- go-test-arm64: *filter-ignore-non-go-branches
- dev-build: *filter-ignore-non-go-branches
- go-test:
Expand Down Expand Up @@ -1148,6 +1157,11 @@ workflows:
matrix:
parameters:
nomad-version: *supported_nomad_versions
- vault-integration-test:
matrix:
parameters:
vault-version: *supported_vault_versions
<<: *filter-ignore-non-go-branches
- envoy-integration-test:
requires:
- dev-build
Expand Down
25 changes: 16 additions & 9 deletions agent/connect/ca/mock_Provider.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions agent/connect/ca/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,17 @@ type SecondaryProvider interface {
//
// After the certificate is signed, SecondaryProvider.SetIntermediate will
// be called to store the intermediate CA.
GenerateIntermediateCSR() (string, error)
//
// The second return value is an opaque string meant to be passed back to
// the subsequent call to SetIntermediate.
GenerateIntermediateCSR() (string, string, error)

// SetIntermediate is called to store a newly signed leaf signing certificate and
// the chain of certificates back to the root CA certificate.
//
// The provider should save the certificates and use them to
// Provider.Sign leaf certificates.
SetIntermediate(intermediatePEM, rootPEM string) error
SetIntermediate(intermediatePEM, rootPEM, opaque string) error
}

// RootResult is the result returned by PrimaryProvider.GenerateRoot.
Expand Down
13 changes: 8 additions & 5 deletions agent/connect/ca/provider_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ type AWSProvider struct {
logger hclog.Logger
}

var _ Provider = (*AWSProvider)(nil)

// NewAWSProvider returns a new AWSProvider
func NewAWSProvider(logger hclog.Logger) *AWSProvider {
return &AWSProvider{logger: logger}
Expand Down Expand Up @@ -498,23 +500,24 @@ func (a *AWSProvider) signCSR(csrPEM string, templateARN string, ttl time.Durati
}

// GenerateIntermediateCSR implements Provider
func (a *AWSProvider) GenerateIntermediateCSR() (string, error) {
func (a *AWSProvider) GenerateIntermediateCSR() (string, string, error) {
if a.isPrimary {
return "", fmt.Errorf("provider is the root certificate authority, " +
return "", "", fmt.Errorf("provider is the root certificate authority, " +
"cannot generate an intermediate CSR")
}

err := a.ensureCA()
if err != nil {
return "", err
return "", "", err
}

// We should have the CA created now and should be able to generate the CSR.
return a.getCACSR()
pem, err := a.getCACSR()
return pem, "", err
}

// SetIntermediate implements Provider
func (a *AWSProvider) SetIntermediate(intermediatePEM string, rootPEM string) error {
func (a *AWSProvider) SetIntermediate(intermediatePEM string, rootPEM string, _ string) error {
err := a.ensureCA()
if err != nil {
return err
Expand Down
2 changes: 1 addition & 1 deletion agent/connect/ca/provider_aws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ func TestAWSBootstrapAndSignSecondary(t *testing.T) {
"ExistingARN": p2State[AWSStateCAARNKey],
})
p2 = testAWSProvider(t, cfg2)
require.NoError(t, p2.SetIntermediate(newIntPEM, newRootPEM))
require.NoError(t, p2.SetIntermediate(newIntPEM, newRootPEM, ""))

root, err = p1.GenerateRoot()
require.NoError(t, err)
Expand Down
18 changes: 10 additions & 8 deletions agent/connect/ca/provider_consul.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ type ConsulProvider struct {
sync.RWMutex
}

var _ Provider = (*ConsulProvider)(nil)

// NewConsulProvider returns a new ConsulProvider that is ready to be used.
func NewConsulProvider(delegate ConsulProviderStateDelegate, logger hclog.Logger) *ConsulProvider {
return &ConsulProvider{Delegate: delegate, logger: logger}
Expand Down Expand Up @@ -205,26 +207,26 @@ func (c *ConsulProvider) GenerateRoot() (RootResult, error) {

// GenerateIntermediateCSR creates a private key and generates a CSR
// for another datacenter's root to sign.
func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
func (c *ConsulProvider) GenerateIntermediateCSR() (string, string, error) {
providerState, err := c.getState()
if err != nil {
return "", err
return "", "", err
}

if c.isPrimary {
return "", fmt.Errorf("provider is the root certificate authority, " +
return "", "", fmt.Errorf("provider is the root certificate authority, " +
"cannot generate an intermediate CSR")
}

// Create a new private key and CSR.
signer, pk, err := connect.GeneratePrivateKeyWithConfig(c.config.PrivateKeyType, c.config.PrivateKeyBits)
if err != nil {
return "", err
return "", "", err
}

csr, err := connect.CreateCACSR(c.spiffeID, signer)
if err != nil {
return "", err
return "", "", err
}

// Write the new provider state to the store.
Expand All @@ -235,15 +237,15 @@ func (c *ConsulProvider) GenerateIntermediateCSR() (string, error) {
ProviderState: &newState,
}
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
return "", err
return "", "", err
}

return csr, nil
return csr, "", nil
}

// SetIntermediate validates that the given intermediate is for the right private key
// and writes the given intermediate and root certificates to the state.
func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM, _ string) error {
providerState, err := c.getState()
if err != nil {
return err
Expand Down
4 changes: 2 additions & 2 deletions agent/connect/ca/provider_consul_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func TestConsulProvider_SignIntermediate(t *testing.T) {
func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {

// Get the intermediate CSR from provider2.
csrPEM, err := provider2.GenerateIntermediateCSR()
csrPEM, opaque, err := provider2.GenerateIntermediateCSR()
require.NoError(t, err)
csr, err := connect.ParseCSR(csrPEM)
require.NoError(t, err)
Expand All @@ -430,7 +430,7 @@ func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {
rootPEM := root.PEM

// Give the new intermediate to provider2 to use.
require.NoError(t, provider2.SetIntermediate(intermediatePEM, rootPEM))
require.NoError(t, provider2.SetIntermediate(intermediatePEM, rootPEM, opaque))

// Have provider2 sign a leaf cert and make sure the chain is correct.
spiffeService := &connect.SpiffeIDService{
Expand Down
21 changes: 15 additions & 6 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ type VaultProvider struct {
isConsulMountedIntermediate bool
}

var _ Provider = (*VaultProvider)(nil)

func NewVaultProvider(logger hclog.Logger) *VaultProvider {
return &VaultProvider{
stopWatcher: func() {},
Expand Down Expand Up @@ -365,14 +367,13 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
// GenerateIntermediateCSR creates a private key and generates a CSR
// for another datacenter's root to sign, overwriting the intermediate backend
// in the process.
func (v *VaultProvider) GenerateIntermediateCSR() (string, error) {
func (v *VaultProvider) GenerateIntermediateCSR() (string, string, error) {
if v.isPrimary {
return "", fmt.Errorf("provider is the root certificate authority, " +
return "", "", fmt.Errorf("provider is the root certificate authority, " +
"cannot generate an intermediate CSR")
}

csr, _, err := v.generateIntermediateCSR()
return csr, err
return v.generateIntermediateCSR()
}

func (v *VaultProvider) setupIntermediatePKIPath() error {
Expand Down Expand Up @@ -486,7 +487,7 @@ func (v *VaultProvider) generateIntermediateCSR() (string, string, error) {

// SetIntermediate writes the incoming intermediate and root certificates to the
// intermediate backend (as a chain).
func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM, keyId string) error {
if v.isPrimary {
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
}
Expand All @@ -496,13 +497,21 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
return err
}

_, err = v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
importResp, err := v.writeNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
"certificate": intermediatePEM,
})
if err != nil {
return err
}

// Vault 1.11+ will return a non-nil response from intermediate/set-signed
if importResp != nil {
err := v.setDefaultIntermediateIssuer(importResp, keyId)
if err != nil {
return fmt.Errorf("failed to update default intermediate issuer: %w", err)
}
}

return nil
}

Expand Down

0 comments on commit 4940a72

Please sign in to comment.