Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setup intermediate_pki_path on secondary #8001

Merged
merged 1 commit into from
Jun 5, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ var ErrBackendNotMounted = fmt.Errorf("backend not mounted")
var ErrBackendNotInitialized = fmt.Errorf("backend not initialized")

type VaultProvider struct {
config *structs.VaultCAProviderConfig
client *vaultapi.Client
isPrimary bool
clusterID string
spiffeID *connect.SpiffeIDSigning
config *structs.VaultCAProviderConfig
client *vaultapi.Client
isPrimary bool
clusterID string
spiffeID *connect.SpiffeIDSigning
setupIntermediatePKIPathDone bool
}

func vaultTLSConfig(config *structs.VaultCAProviderConfig) *vaultapi.TLSConfig {
Expand Down Expand Up @@ -137,10 +138,13 @@ func (v *VaultProvider) GenerateIntermediateCSR() (string, error) {
return v.generateIntermediateCSR()
}

func (v *VaultProvider) generateIntermediateCSR() (string, error) {
func (v *VaultProvider) setupIntermediatePKIPath() error {
if v.setupIntermediatePKIPathDone {
return nil
}
mounts, err := v.client.Sys().ListMounts()
if err != nil {
return "", err
return err
}

// Mount the backend if it isn't mounted already.
Expand All @@ -154,15 +158,15 @@ func (v *VaultProvider) generateIntermediateCSR() (string, error) {
})

if err != nil {
return "", err
return err
}
}

// Create the role for issuing leaf certs if it doesn't exist yet
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
role, err := v.client.Logical().Read(rolePath)
if err != nil {
return "", err
return err
}
if role == nil {
_, err := v.client.Logical().Write(rolePath, map[string]interface{}{
Expand All @@ -174,9 +178,18 @@ func (v *VaultProvider) generateIntermediateCSR() (string, error) {
"require_cn": false,
})
if err != nil {
return "", err
return err
}
}
v.setupIntermediatePKIPathDone = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need a mutex or is it only accessed by a single goroutine?

Copy link
Member Author

@hanshasselberg hanshasselberg Jun 4, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two different flows.

The leader in the primary dc calls this the first time in initializeRootCA while holding caProviderReconfigurationLock:

// Also create the intermediate CA, which is the one that actually signs leaf certs
interPEM, err := provider.GenerateIntermediate()
if err != nil {
return fmt.Errorf("error generating intermediate cert: %v", err)
}

The leader in a secondary dc calls this the first time in initializeSecondaryCA while holding caProviderReconfigurationLock:

activeIntermediate, err := provider.ActiveIntermediate()
if err != nil {
return err
}

An error will prevent the initialization to move on and and it will be retried later. I think this is safe to do. However I considered using atomic just to make sure we don't shoot our foot in case the other code changes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

return nil
}

func (v *VaultProvider) generateIntermediateCSR() (string, error) {
err := v.setupIntermediatePKIPath()
if err != nil {
return "", err
}

// Generate a new intermediate CSR for the root to sign.
uid, err := connect.CompactUID()
Expand Down Expand Up @@ -231,7 +244,22 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {

// ActiveIntermediate returns the current intermediate certificate.
func (v *VaultProvider) ActiveIntermediate() (string, error) {
return v.getCA(v.config.IntermediatePKIPath)
if err := v.setupIntermediatePKIPath(); err != nil {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure it is the best way to make sure everything is setup in here, but I didn't find a better place. Using setupIntermediatePKIPathDone as a guard to make sure the setup is only done once.

return "", err
}

cert, err := v.getCA(v.config.IntermediatePKIPath)

// This error is expected when calling initializeSecondaryCA for the
// first time. It means that the backend is mounted and ready, but
// there is no intermediate.
// This error is swallowed because there is nothing the caller can do
// about it. The caller needs to handle the empty cert though and
// create an intermediate CA.
if err == ErrBackendNotInitialized {
return "", nil
}
return cert, err
}

// getCA returns the raw CA cert for the given endpoint if there is one.
Expand Down
14 changes: 14 additions & 0 deletions agent/connect/ca/provider_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ func TestVaultCAProvider_VaultTLSConfig(t *testing.T) {
require.Equal(config.TLSSkipVerify, tlsConfig.Insecure)
}

func TestVaultCAProvider_SecondaryActiveIntermediate(t *testing.T) {
t.Parallel()

skipIfVaultNotPresent(t)

provider, testVault := testVaultProviderWithConfig(t, false, nil)
defer testVault.Stop()
require := require.New(t)

cert, err := provider.ActiveIntermediate()
require.Empty(cert)
require.NoError(err)
}

func TestVaultCAProvider_Bootstrap(t *testing.T) {
t.Parallel()

Expand Down