mirror of https://github.com/status-im/consul.git
Fix bug with Vault CA provider (#18112)
Updating RootPKIPath but not IntermediatePKIPath would not update leaf signing certs with the new root. Unsure if this happens in practice but manual testing showed it is a bug that would break mesh and agent connections once the old root is pruned.
This commit is contained in:
parent
5208ea90e4
commit
747a4c73c1
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ca: Fixes a Vault CA provider bug where updating RootPKIPath but not IntermediatePKIPath would not renew leaf signing certificates
|
||||
```
|
|
@ -89,6 +89,30 @@ func (_m *MockProvider) CrossSignCA(_a0 *x509.Certificate) (string, error) {
|
|||
return r0, r1
|
||||
}
|
||||
|
||||
// GenerateCAChain provides a mock function with given fields:
|
||||
func (_m *MockProvider) GenerateCAChain() (string, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 string
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (string, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() string); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(string)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// GenerateIntermediateCSR provides a mock function with given fields:
|
||||
func (_m *MockProvider) GenerateIntermediateCSR() (string, string, error) {
|
||||
ret := _m.Called()
|
||||
|
@ -120,30 +144,6 @@ func (_m *MockProvider) GenerateIntermediateCSR() (string, string, error) {
|
|||
return r0, r1, r2
|
||||
}
|
||||
|
||||
// GenerateCAChain provides a mock function with given fields:
|
||||
func (_m *MockProvider) GenerateCAChain() (CAChainResult, error) {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 CAChainResult
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func() (CAChainResult, error)); ok {
|
||||
return rf()
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func() CAChainResult); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
r0 = ret.Get(0).(CAChainResult)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func() error); ok {
|
||||
r1 = rf()
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// SetIntermediate provides a mock function with given fields: intermediatePEM, rootPEM, opaque
|
||||
func (_m *MockProvider) SetIntermediate(intermediatePEM string, rootPEM string, opaque string) error {
|
||||
ret := _m.Called(intermediatePEM, rootPEM, opaque)
|
||||
|
|
|
@ -135,9 +135,12 @@ type PrimaryProvider interface {
|
|||
// provider.
|
||||
//
|
||||
// Depending on the provider and its configuration, GenerateCAChain may return
|
||||
// a single root certificate or a chain of certs. The provider should return an
|
||||
// existing CA chain if one exists or generate a new one and return it.
|
||||
GenerateCAChain() (CAChainResult, error)
|
||||
// a single root certificate or a chain of certs.
|
||||
// The first certificate must be the primary CA used to sign intermediates for
|
||||
// secondary datacenters, and the last certificate must be the trusted CA.
|
||||
// The provider should return an existing CA chain if one exists or generate a
|
||||
// new one and return it.
|
||||
GenerateCAChain() (string, error)
|
||||
|
||||
// SignIntermediate will validate the CSR to ensure the trust domain in the
|
||||
// URI SAN matches the local one and that basic constraints for a CA
|
||||
|
|
|
@ -140,19 +140,19 @@ func (a *AWSProvider) State() (map[string]string, error) {
|
|||
}
|
||||
|
||||
// GenerateCAChain implements Provider
|
||||
func (a *AWSProvider) GenerateCAChain() (CAChainResult, error) {
|
||||
func (a *AWSProvider) GenerateCAChain() (string, error) {
|
||||
if !a.isPrimary {
|
||||
return CAChainResult{}, fmt.Errorf("provider is not the root certificate authority")
|
||||
return "", fmt.Errorf("provider is not the root certificate authority")
|
||||
}
|
||||
|
||||
if err := a.ensureCA(); err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if a.rootPEM == "" {
|
||||
return CAChainResult{}, fmt.Errorf("AWS CA provider not fully Initialized")
|
||||
return "", fmt.Errorf("AWS CA provider not fully Initialized")
|
||||
}
|
||||
return CAChainResult{PEM: a.rootPEM}, nil
|
||||
return a.rootPEM, nil
|
||||
}
|
||||
|
||||
// ensureCA loads the CA resource to check it exists if configured by User or in
|
||||
|
|
|
@ -49,9 +49,8 @@ func TestAWSBootstrapAndSignPrimary(t *testing.T) {
|
|||
provider := testAWSProvider(t, testProviderConfigPrimary(t, cfg))
|
||||
defer provider.Cleanup(true, nil)
|
||||
|
||||
root, err := provider.GenerateCAChain()
|
||||
rootPEM, err := provider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
|
||||
// Ensure they use the right key type
|
||||
rootCert, err := connect.ParseCert(rootPEM)
|
||||
|
@ -76,9 +75,8 @@ func TestAWSBootstrapAndSignPrimary(t *testing.T) {
|
|||
provider := testAWSProvider(t, testProviderConfigPrimary(t, nil))
|
||||
defer provider.Cleanup(true, nil)
|
||||
|
||||
root, err := provider.GenerateCAChain()
|
||||
rootPEM, err := provider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
|
||||
// Ensure they use the right key type
|
||||
rootCert, err := connect.ParseCert(rootPEM)
|
||||
|
@ -111,9 +109,8 @@ func TestAWSBootstrapAndSignSecondary(t *testing.T) {
|
|||
|
||||
p1 := testAWSProvider(t, testProviderConfigPrimary(t, nil))
|
||||
defer p1.Cleanup(true, nil)
|
||||
root, err := p1.GenerateCAChain()
|
||||
rootPEM, err := p1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
|
||||
p2 := testAWSProvider(t, testProviderConfigSecondary(t, nil))
|
||||
defer p2.Cleanup(true, nil)
|
||||
|
@ -140,9 +137,8 @@ func TestAWSBootstrapAndSignSecondary(t *testing.T) {
|
|||
cfg1 := testProviderConfigPrimary(t, nil)
|
||||
cfg1.State = p1State
|
||||
p1 = testAWSProvider(t, cfg1)
|
||||
root, err := p1.GenerateCAChain()
|
||||
newRootPEM, err := p1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
newRootPEM := root.PEM
|
||||
|
||||
cfg2 := testProviderConfigPrimary(t, nil)
|
||||
cfg2.State = p2State
|
||||
|
@ -174,9 +170,8 @@ func TestAWSBootstrapAndSignSecondary(t *testing.T) {
|
|||
"ExistingARN": p1State[AWSStateCAARNKey],
|
||||
})
|
||||
p1 = testAWSProvider(t, cfg1)
|
||||
root, err := p1.GenerateCAChain()
|
||||
newRootPEM, err := p1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
newRootPEM := root.PEM
|
||||
|
||||
cfg2 := testProviderConfigPrimary(t, map[string]interface{}{
|
||||
"ExistingARN": p2State[AWSStateCAARNKey],
|
||||
|
@ -213,9 +208,8 @@ func TestAWSBootstrapAndSignSecondary(t *testing.T) {
|
|||
p2 = testAWSProvider(t, cfg2)
|
||||
require.NoError(t, p2.SetIntermediate(newIntPEM, newRootPEM, ""))
|
||||
|
||||
root, err = p1.GenerateCAChain()
|
||||
newRootPEM, err = p1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
newRootPEM = root.PEM
|
||||
newIntPEM, err = p2.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
|
|
|
@ -155,17 +155,17 @@ func (c *ConsulProvider) State() (map[string]string, error) {
|
|||
}
|
||||
|
||||
// GenerateCAChain initializes a new root certificate and private key if needed.
|
||||
func (c *ConsulProvider) GenerateCAChain() (CAChainResult, error) {
|
||||
func (c *ConsulProvider) GenerateCAChain() (string, error) {
|
||||
providerState, err := c.getState()
|
||||
if err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
if !c.isPrimary {
|
||||
return CAChainResult{}, fmt.Errorf("provider is not the root certificate authority")
|
||||
return "", fmt.Errorf("provider is not the root certificate authority")
|
||||
}
|
||||
if providerState.RootCert != "" {
|
||||
return CAChainResult{PEM: providerState.RootCert}, nil
|
||||
return providerState.RootCert, nil
|
||||
}
|
||||
|
||||
// Generate a private key if needed
|
||||
|
@ -173,7 +173,7 @@ func (c *ConsulProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
if c.config.PrivateKey == "" {
|
||||
_, pk, err := connect.GeneratePrivateKeyWithConfig(c.config.PrivateKeyType, c.config.PrivateKeyBits)
|
||||
if err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
newState.PrivateKey = pk
|
||||
} else {
|
||||
|
@ -184,12 +184,12 @@ func (c *ConsulProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
if c.config.RootCert == "" {
|
||||
nextSerial, err := c.incrementAndGetNextSerialNumber()
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("error computing next serial number: %v", err)
|
||||
return "", fmt.Errorf("error computing next serial number: %v", err)
|
||||
}
|
||||
|
||||
ca, err := c.generateCA(newState.PrivateKey, nextSerial, c.config.RootCertTTL)
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("error generating CA: %v", err)
|
||||
return "", fmt.Errorf("error generating CA: %v", err)
|
||||
}
|
||||
newState.RootCert = ca
|
||||
} else {
|
||||
|
@ -202,10 +202,10 @@ func (c *ConsulProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
ProviderState: &newState,
|
||||
}
|
||||
if _, err := c.Delegate.ApplyCARequest(args); err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return CAChainResult{PEM: newState.RootCert}, nil
|
||||
return newState.RootCert, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediateCSR creates a private key and generates a CSR
|
||||
|
|
|
@ -93,10 +93,10 @@ func TestConsulCAProvider_Bootstrap(t *testing.T) {
|
|||
// Intermediate should be the same cert.
|
||||
inter, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, root.PEM, inter)
|
||||
require.Equal(t, root, inter)
|
||||
|
||||
// Should be a valid cert
|
||||
parsed, err := connect.ParseCert(root.PEM)
|
||||
parsed, err := connect.ParseCert(root)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", conf.ClusterID))
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
|
@ -128,10 +128,10 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
|||
|
||||
root, err := provider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, root.PEM, rootCA.RootCert)
|
||||
require.Equal(t, root, rootCA.RootCert)
|
||||
|
||||
// Should be a valid cert
|
||||
parsed, err := connect.ParseCert(root.PEM)
|
||||
parsed, err := connect.ParseCert(root)
|
||||
require.NoError(t, err)
|
||||
|
||||
// test that the default root cert ttl was not applied to the provided cert
|
||||
|
@ -298,7 +298,7 @@ func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) {
|
|||
root, err := provider2.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
|
||||
newRoot, err := connect.ParseCert(root.PEM)
|
||||
newRoot, err := connect.ParseCert(root)
|
||||
require.NoError(t, err)
|
||||
oldSubject := newRoot.Subject.CommonName
|
||||
requireNotEncoded(t, newRoot.SubjectKeyId)
|
||||
|
@ -321,7 +321,7 @@ func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) {
|
|||
|
||||
p1Root, err := provider1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
oldRoot, err := connect.ParseCert(p1Root.PEM)
|
||||
oldRoot, err := connect.ParseCert(p1Root)
|
||||
require.NoError(t, err)
|
||||
requireNotEncoded(t, oldRoot.SubjectKeyId)
|
||||
requireNotEncoded(t, oldRoot.AuthorityKeyId)
|
||||
|
@ -385,7 +385,7 @@ func testCrossSignProvidersShouldFail(t *testing.T, provider1, provider2 Provide
|
|||
root, err := provider2.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
|
||||
newRoot, err := connect.ParseCert(root.PEM)
|
||||
newRoot, err := connect.ParseCert(root)
|
||||
require.NoError(t, err)
|
||||
requireNotEncoded(t, newRoot.SubjectKeyId)
|
||||
requireNotEncoded(t, newRoot.AuthorityKeyId)
|
||||
|
@ -454,7 +454,7 @@ func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {
|
|||
require.NoError(t, err)
|
||||
root, err := provider1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
rootPEM := root
|
||||
|
||||
// Give the new intermediate to provider2 to use.
|
||||
require.NoError(t, provider2.SetIntermediate(intermediatePEM, rootPEM, opaque))
|
||||
|
|
|
@ -280,9 +280,9 @@ func (v *VaultProvider) State() (map[string]string, error) {
|
|||
}
|
||||
|
||||
// GenerateCAChain mounts and initializes a new root PKI backend if needed.
|
||||
func (v *VaultProvider) GenerateCAChain() (CAChainResult, error) {
|
||||
func (v *VaultProvider) GenerateCAChain() (string, error) {
|
||||
if !v.isPrimary {
|
||||
return CAChainResult{}, fmt.Errorf("provider is not the root certificate authority")
|
||||
return "", fmt.Errorf("provider is not the root certificate authority")
|
||||
}
|
||||
|
||||
// Set up the root PKI backend if necessary.
|
||||
|
@ -302,7 +302,7 @@ func (v *VaultProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
},
|
||||
})
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("failed to mount root CA backend: %w", err)
|
||||
return "", fmt.Errorf("failed to mount root CA backend: %w", err)
|
||||
}
|
||||
|
||||
// We want to initialize afterwards
|
||||
|
@ -310,7 +310,7 @@ func (v *VaultProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
case ErrBackendNotInitialized:
|
||||
uid, err := connect.CompactUID()
|
||||
if err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
resp, err := v.writeNamespaced(v.config.RootPKINamespace, v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
|
||||
"common_name": connect.CACN("vault", uid, v.clusterID, v.isPrimary),
|
||||
|
@ -319,23 +319,23 @@ func (v *VaultProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
"key_bits": v.config.PrivateKeyBits,
|
||||
})
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("failed to initialize root CA: %w", err)
|
||||
return "", fmt.Errorf("failed to initialize root CA: %w", err)
|
||||
}
|
||||
var ok bool
|
||||
rootPEM, ok = resp.Data["certificate"].(string)
|
||||
if !ok {
|
||||
return CAChainResult{}, fmt.Errorf("unexpected response from Vault: %v", resp.Data["certificate"])
|
||||
return "", fmt.Errorf("unexpected response from Vault: %v", resp.Data["certificate"])
|
||||
}
|
||||
|
||||
default:
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("unexpected error while setting root PKI backend: %w", err)
|
||||
return "", fmt.Errorf("unexpected error while setting root PKI backend: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
rootChain, err := v.getCAChain(v.config.RootPKINamespace, v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return CAChainResult{}, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Workaround for a bug in the Vault PKI API.
|
||||
|
@ -344,18 +344,7 @@ func (v *VaultProvider) GenerateCAChain() (CAChainResult, error) {
|
|||
rootChain = rootPEM
|
||||
}
|
||||
|
||||
intermediate, err := v.ActiveLeafSigningCert()
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("error fetching active intermediate: %w", err)
|
||||
}
|
||||
if intermediate == "" {
|
||||
intermediate, err = v.GenerateLeafSigningCert()
|
||||
if err != nil {
|
||||
return CAChainResult{}, fmt.Errorf("error generating intermediate: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return CAChainResult{PEM: rootChain, IntermediatePEM: intermediate}, nil
|
||||
return rootChain, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediateCSR creates a private key and generates a CSR
|
||||
|
@ -582,7 +571,7 @@ func (v *VaultProvider) getCAChain(namespace, path string) (string, error) {
|
|||
return root, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediate mounts the configured intermediate PKI backend if
|
||||
// GenerateLeafSigningCert mounts the configured intermediate PKI backend if
|
||||
// necessary, then generates and signs a new CA CSR using the root PKI backend
|
||||
// and updates the intermediate backend to use that new certificate.
|
||||
func (v *VaultProvider) GenerateLeafSigningCert() (string, error) {
|
||||
|
|
|
@ -420,7 +420,7 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) {
|
|||
},
|
||||
certFunc: func(provider *VaultProvider) (string, error) {
|
||||
root, err := provider.GenerateCAChain()
|
||||
return root.PEM, err
|
||||
return root, err
|
||||
},
|
||||
backendPath: "pki-root/",
|
||||
rootCaCreation: true,
|
||||
|
@ -497,9 +497,8 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
|||
Service: "foo",
|
||||
}
|
||||
|
||||
root, err := provider.GenerateCAChain()
|
||||
rootPEM, err := provider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
assertCorrectKeyType(t, tc.KeyType, rootPEM)
|
||||
|
||||
intPEM, err := provider.ActiveLeafSigningCert()
|
||||
|
@ -600,9 +599,9 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
|
|||
})
|
||||
|
||||
testutil.RunStep(t, "init", func(t *testing.T) {
|
||||
root, err := provider1.GenerateCAChain()
|
||||
rootPEM, err := provider1.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
assertCorrectKeyType(t, tc.SigningKeyType, root.PEM)
|
||||
assertCorrectKeyType(t, tc.SigningKeyType, rootPEM)
|
||||
|
||||
intPEM, err := provider1.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
@ -628,9 +627,9 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
|
|||
})
|
||||
|
||||
testutil.RunStep(t, "swap", func(t *testing.T) {
|
||||
root, err := provider2.GenerateCAChain()
|
||||
rootPEM, err := provider2.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
assertCorrectKeyType(t, tc.CSRKeyType, root.PEM)
|
||||
assertCorrectKeyType(t, tc.CSRKeyType, rootPEM)
|
||||
|
||||
intPEM, err := provider2.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
@ -1147,14 +1146,14 @@ func TestVaultCAProvider_GenerateIntermediate(t *testing.T) {
|
|||
// This test was created to ensure that our calls to Vault
|
||||
// returns a new Intermediate certificate and further calls
|
||||
// to ActiveLeafSigningCert return the same new cert.
|
||||
new, err := provider.GenerateLeafSigningCert()
|
||||
newLeaf, err := provider.GenerateLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
newActive, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, new, newActive)
|
||||
require.NotEqual(t, orig, new)
|
||||
require.Equal(t, newLeaf, newActive)
|
||||
require.NotEqual(t, orig, newLeaf)
|
||||
}
|
||||
|
||||
func TestVaultCAProvider_AutoTidyExpiredIssuers(t *testing.T) {
|
||||
|
@ -1240,9 +1239,8 @@ func TestVaultCAProvider_GenerateIntermediate_inSecondary(t *testing.T) {
|
|||
// Sign the CSR with primaryProvider.
|
||||
intermediatePEM, err := primaryProvider.SignIntermediate(csr)
|
||||
require.NoError(t, err)
|
||||
root, err := primaryProvider.GenerateCAChain()
|
||||
rootPEM, err := primaryProvider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
|
||||
// Give the new intermediate to provider to use.
|
||||
require.NoError(t, provider.SetIntermediate(intermediatePEM, rootPEM, issuerID))
|
||||
|
@ -1261,9 +1259,8 @@ func TestVaultCAProvider_GenerateIntermediate_inSecondary(t *testing.T) {
|
|||
// Sign the CSR with primaryProvider.
|
||||
intermediatePEM, err := primaryProvider.SignIntermediate(csr)
|
||||
require.NoError(t, err)
|
||||
root, err := primaryProvider.GenerateCAChain()
|
||||
rootPEM, err := primaryProvider.GenerateCAChain()
|
||||
require.NoError(t, err)
|
||||
rootPEM := root.PEM
|
||||
|
||||
// Give the new intermediate to provider to use.
|
||||
require.NoError(t, provider.SetIntermediate(intermediatePEM, rootPEM, issuerID))
|
||||
|
|
|
@ -258,8 +258,8 @@ func (c *CAManager) initializeCAConfig() (*structs.CAConfiguration, error) {
|
|||
}
|
||||
|
||||
// newCARoot returns a filled-in structs.CARoot from a raw PEM value.
|
||||
func newCARoot(rootResult ca.CAChainResult, provider, clusterID string) (*structs.CARoot, error) {
|
||||
primaryCert, err := connect.ParseCert(rootResult.PEM)
|
||||
func newCARoot(caPem, provider, clusterID string) (*structs.CARoot, error) {
|
||||
primaryCert, err := connect.ParseCert(caPem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -275,17 +275,12 @@ func newCARoot(rootResult ca.CAChainResult, provider, clusterID string) (*struct
|
|||
ExternalTrustDomain: clusterID,
|
||||
NotBefore: primaryCert.NotBefore,
|
||||
NotAfter: primaryCert.NotAfter,
|
||||
RootCert: lib.EnsureTrailingNewline(rootResult.PEM),
|
||||
RootCert: lib.EnsureTrailingNewline(caPem),
|
||||
PrivateKeyType: keyType,
|
||||
PrivateKeyBits: keyBits,
|
||||
Active: true,
|
||||
}
|
||||
if rootResult.IntermediatePEM == "" {
|
||||
return caRoot, nil
|
||||
}
|
||||
if err := setLeafSigningCert(caRoot, rootResult.IntermediatePEM); err != nil {
|
||||
return nil, fmt.Errorf("error setting leaf signing cert: %w", err)
|
||||
}
|
||||
|
||||
return caRoot, nil
|
||||
}
|
||||
|
||||
|
@ -518,6 +513,19 @@ func (c *CAManager) primaryInitialize(provider ca.Provider, conf *structs.CAConf
|
|||
return err
|
||||
}
|
||||
|
||||
// provider may use intermediates for leaf signing in which case
|
||||
// we need to generate a leaf signing CA.
|
||||
if usesIntermediate, ok := provider.(ca.PrimaryUsesIntermediate); ok {
|
||||
leafPem, err := usesIntermediate.GenerateLeafSigningCert()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating new leaf signing cert: %w", err)
|
||||
}
|
||||
|
||||
if err := setLeafSigningCert(rootCA, leafPem); err != nil {
|
||||
return fmt.Errorf("error setting leaf signing cert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var rootUpdateRequired bool
|
||||
if len(rootCA.IntermediateCerts) > 0 {
|
||||
rootUpdateRequired = true
|
||||
|
@ -764,7 +772,6 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
|
|||
return err
|
||||
}
|
||||
|
||||
// Exit early if it's a no-op change
|
||||
state := c.delegate.State()
|
||||
_, config, err := state.CAConfig(nil)
|
||||
if err != nil {
|
||||
|
@ -780,6 +787,8 @@ func (c *CAManager) UpdateConfiguration(args *structs.CARequest) (reterr error)
|
|||
|
||||
// Don't allow users to change the ClusterID.
|
||||
args.Config.ClusterID = config.ClusterID
|
||||
|
||||
// Exit early if it's a no-op change
|
||||
if args.Config.Provider == config.Provider && reflect.DeepEqual(args.Config.Config, config.Config) {
|
||||
return nil
|
||||
}
|
||||
|
@ -866,26 +875,53 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
}
|
||||
args.Config.State = pState
|
||||
|
||||
providerRoot, err := newProvider.GenerateCAChain()
|
||||
caPEM, err := newProvider.GenerateCAChain()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating CA root certificate: %v", err)
|
||||
}
|
||||
|
||||
newRootPEM := providerRoot.PEM
|
||||
newActiveRoot, err := newCARoot(providerRoot, args.Config.Provider, args.Config.ClusterID)
|
||||
newActiveRoot, err := newCARoot(caPEM, args.Config.Provider, args.Config.ClusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetch the existing root CA to compare with the current one.
|
||||
state := c.delegate.State()
|
||||
// Compare the new provider's root CA ID to the current one. If they
|
||||
// match, just update the existing provider with the new config.
|
||||
// If they don't match, begin the root rotation process.
|
||||
_, root, err := state.CARootActive(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// provider may use intermediates for leaf signing in which case
|
||||
// we may need to generate a leaf signing CA if the root has changed.
|
||||
if usesIntermediate, ok := newProvider.(ca.PrimaryUsesIntermediate); ok {
|
||||
var leafPemFunc func() (string, error)
|
||||
if root != nil && root.ID == newActiveRoot.ID {
|
||||
// If Root ID is the same, we can reuse the existing leaf signing cert
|
||||
leafPemFunc = newProvider.ActiveLeafSigningCert
|
||||
} else {
|
||||
// If Root ID is different, we need to generate a new leaf signing cert
|
||||
// else the trust chain will break when the old root expires.
|
||||
leafPemFunc = usesIntermediate.GenerateLeafSigningCert
|
||||
}
|
||||
leafPem, err := leafPemFunc()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error fetching leaf signing cert: %w", err)
|
||||
}
|
||||
// newProvider.ActiveLeafSigningCert may return a blank leafPem so we
|
||||
// fall back to generating a new one just in case.
|
||||
if leafPem == "" {
|
||||
leafPem, err = usesIntermediate.GenerateLeafSigningCert()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating new leaf signing cert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := setLeafSigningCert(newActiveRoot, leafPem); err != nil {
|
||||
return fmt.Errorf("error setting leaf signing cert: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// If the root didn't change, just update the config and return.
|
||||
if root != nil && root.ID == newActiveRoot.ID {
|
||||
args.Op = structs.CAOpSetConfig
|
||||
|
@ -919,7 +955,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
// 3. Take the active root for the new provider and append the intermediate from step 2
|
||||
// to its list of intermediates.
|
||||
// TODO: this cert is already parsed once in newCARoot, could we remove the second parse?
|
||||
newRoot, err := connect.ParseCert(newRootPEM)
|
||||
newRoot, err := connect.ParseCert(caPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -259,8 +259,8 @@ type mockCAProvider struct {
|
|||
|
||||
func (m *mockCAProvider) Configure(cfg ca.ProviderConfig) error { return nil }
|
||||
func (m *mockCAProvider) State() (map[string]string, error) { return nil, nil }
|
||||
func (m *mockCAProvider) GenerateCAChain() (ca.CAChainResult, error) {
|
||||
return ca.CAChainResult{PEM: m.rootPEM}, nil
|
||||
func (m *mockCAProvider) GenerateCAChain() (string, error) {
|
||||
return m.rootPEM, nil
|
||||
}
|
||||
func (m *mockCAProvider) GenerateIntermediateCSR() (string, string, error) {
|
||||
m.callbackCh <- "provider/GenerateIntermediateCSR"
|
||||
|
@ -624,7 +624,7 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) {
|
|||
require.Equal(t, connect.HexString(cert.SubjectKeyId), origRoot.SigningKeyID)
|
||||
|
||||
t.Run("update config without changing root", func(t *testing.T) {
|
||||
err = s1.caManager.UpdateConfiguration(&structs.CARequest{
|
||||
require.NoError(t, s1.caManager.UpdateConfiguration(&structs.CARequest{
|
||||
Config: &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
|
@ -635,26 +635,81 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) {
|
|||
"CSRMaxPerSecond": 100,
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
_, sameRoot, err := s1.fsm.State().CARootActive(nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sameRoot.IntermediateCerts, 1)
|
||||
sameRoot.CreateIndex = s1.caManager.providerRoot.CreateIndex
|
||||
sameRoot.ModifyIndex = s1.caManager.providerRoot.ModifyIndex
|
||||
}))
|
||||
|
||||
cert, err := connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(sameRoot))
|
||||
_, newRoot, err := s1.fsm.State().CARootActive(nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connect.HexString(cert.SubjectKeyId), sameRoot.SigningKeyID)
|
||||
require.Len(t, newRoot.IntermediateCerts, 1)
|
||||
newRoot.CreateIndex = s1.caManager.providerRoot.CreateIndex
|
||||
newRoot.ModifyIndex = s1.caManager.providerRoot.ModifyIndex
|
||||
|
||||
require.Equal(t, origRoot, sameRoot)
|
||||
require.Equal(t, sameRoot, s1.caManager.providerRoot)
|
||||
orig, err := connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(newRoot))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, connect.HexString(orig.SubjectKeyId), newRoot.SigningKeyID)
|
||||
|
||||
require.Equal(t, origRoot, newRoot)
|
||||
require.Equal(t, newRoot, s1.caManager.providerRoot)
|
||||
})
|
||||
|
||||
t.Run("update config and change root", func(t *testing.T) {
|
||||
t.Run("update config and change root only", func(t *testing.T) {
|
||||
// Read the active leaf CA
|
||||
provider, _ := s1.caManager.getCAProvider()
|
||||
|
||||
before, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
vaultToken2 := ca.CreateVaultTokenWithAttrs(t, vault.Client(), &ca.VaultTokenAttributes{
|
||||
RootPath: "pki-root-2",
|
||||
IntermediatePath: "pki-intermediate-2",
|
||||
IntermediatePath: "pki-intermediate",
|
||||
ConsulManaged: true,
|
||||
WithSudo: true,
|
||||
})
|
||||
|
||||
require.NoError(t, s1.caManager.UpdateConfiguration(&structs.CARequest{
|
||||
Config: &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vaultToken2,
|
||||
"RootPKIPath": "pki-root-2/",
|
||||
"IntermediatePKIPath": "pki-intermediate/",
|
||||
},
|
||||
},
|
||||
}))
|
||||
|
||||
// fetch the new root from the state store to check that
|
||||
// raft apply has occurred.
|
||||
_, newRoot, err := s1.fsm.State().CARootActive(nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newRoot.IntermediateCerts, 2,
|
||||
"expected one cross-sign cert and one local leaf sign cert")
|
||||
|
||||
// Refresh provider
|
||||
provider, _ = s1.caManager.getCAProvider()
|
||||
|
||||
// Leaf signing cert should have been updated
|
||||
after, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEqual(t, before, after,
|
||||
"expected leaf signing cert to be changed after RootPKIPath was changed")
|
||||
|
||||
cert, err = connect.ParseCert(after)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID)
|
||||
})
|
||||
|
||||
t.Run("update config, change root and intermediate", func(t *testing.T) {
|
||||
// Read the active leaf CA
|
||||
provider, _ := s1.caManager.getCAProvider()
|
||||
|
||||
before, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
vaultToken3 := ca.CreateVaultTokenWithAttrs(t, vault.Client(), &ca.VaultTokenAttributes{
|
||||
RootPath: "pki-root-3",
|
||||
IntermediatePath: "pki-intermediate-3",
|
||||
ConsulManaged: true,
|
||||
})
|
||||
|
||||
|
@ -663,22 +718,34 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) {
|
|||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vaultToken2,
|
||||
"RootPKIPath": "pki-root-2/",
|
||||
"IntermediatePKIPath": "pki-intermediate-2/",
|
||||
"Token": vaultToken3,
|
||||
"RootPKIPath": "pki-root-3/",
|
||||
"IntermediatePKIPath": "pki-intermediate-3/",
|
||||
},
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// fetch the new root from the state store to check that
|
||||
// raft apply has occurred.
|
||||
_, newRoot, err := s1.fsm.State().CARootActive(nil)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, newRoot.IntermediateCerts, 2,
|
||||
"expected one cross-sign cert and one local leaf sign cert")
|
||||
require.NotEqual(t, origRoot.ID, newRoot.ID)
|
||||
|
||||
cert, err = connect.ParseCert(s1.caManager.getLeafSigningCertFromRoot(newRoot))
|
||||
// Refresh provider
|
||||
provider, _ = s1.caManager.getCAProvider()
|
||||
|
||||
// Leaf signing cert should have been updated
|
||||
after, err := provider.ActiveLeafSigningCert()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEqual(t, before, after,
|
||||
"expected leaf signing cert to be changed after RootPKIPath and IntermediatePKIPath were changed")
|
||||
|
||||
cert, err = connect.ParseCert(after)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1362,10 +1362,12 @@ func TestNewCARoot(t *testing.T) {
|
|||
}
|
||||
|
||||
run := func(t *testing.T, tc testCase) {
|
||||
root, err := newCARoot(ca.CAChainResult{
|
||||
PEM: tc.pem,
|
||||
IntermediatePEM: tc.intermediatePem,
|
||||
}, "provider-name", "cluster-id")
|
||||
root, err := newCARoot(
|
||||
tc.pem,
|
||||
"provider-name", "cluster-id")
|
||||
if tc.intermediatePem != "" {
|
||||
setLeafSigningCert(root, tc.intermediatePem)
|
||||
}
|
||||
if tc.expectedErr != "" {
|
||||
testutil.RequireErrorContains(t, err, tc.expectedErr)
|
||||
return
|
||||
|
|
Loading…
Reference in New Issue