mirror of https://github.com/status-im/consul.git
Merge pull request #11783 from hashicorp/dnephin/ca-vault-root-as-intermediate
ca: add a test that uses an intermediate CA as the primary CA
This commit is contained in:
commit
df2a7e2ae5
|
@ -16,10 +16,9 @@ import (
|
|||
vaultapi "github.com/hashicorp/vault/api"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"github.com/hashicorp/consul/lib/decode"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/lib/decode"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -400,17 +399,14 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
|
|||
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
|
||||
}
|
||||
|
||||
err := validateSetIntermediate(
|
||||
intermediatePEM, rootPEM,
|
||||
"", // we don't have access to the private key directly
|
||||
v.spiffeID,
|
||||
)
|
||||
// the private key is in vault, so we can't use it in this validation
|
||||
err := validateSetIntermediate(intermediatePEM, rootPEM, "", v.spiffeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
|
||||
"certificate": fmt.Sprintf("%s\n%s", intermediatePEM, rootPEM),
|
||||
"certificate": intermediatePEM,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -654,7 +654,7 @@ func (c *CAManager) secondaryInitializeIntermediateCA(provider ca.Provider, conf
|
|||
}
|
||||
|
||||
if needsNewIntermediate {
|
||||
if err := c.secondaryRenewIntermediate(provider, newActiveRoot); err != nil {
|
||||
if err := c.secondaryRequestNewSigningCert(provider, newActiveRoot); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
|
@ -1028,9 +1028,11 @@ func (c *CAManager) primaryRenewIntermediate(provider ca.Provider, newActiveRoot
|
|||
return nil
|
||||
}
|
||||
|
||||
// secondaryRenewIntermediate should only be called while the state lock is held by
|
||||
// setting the state to non-ready.
|
||||
func (c *CAManager) secondaryRenewIntermediate(provider ca.Provider, newActiveRoot *structs.CARoot) error {
|
||||
// secondaryRequestNewSigningCert creates a Certificate Signing Request, sends
|
||||
// the request to the primary, and stores the received certificate in the
|
||||
// provider.
|
||||
// Should only be called while the state lock is held by setting the state to non-ready.
|
||||
func (c *CAManager) secondaryRequestNewSigningCert(provider ca.Provider, newActiveRoot *structs.CARoot) error {
|
||||
csr, err := provider.GenerateIntermediateCSR()
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -1145,7 +1147,7 @@ func (c *CAManager) RenewIntermediate(ctx context.Context, isPrimary bool) error
|
|||
// Enough time has passed, go ahead with getting a new intermediate.
|
||||
renewalFunc := c.primaryRenewIntermediate
|
||||
if !isPrimary {
|
||||
renewalFunc = c.secondaryRenewIntermediate
|
||||
renewalFunc = c.secondaryRequestNewSigningCert
|
||||
}
|
||||
errCh := make(chan error, 1)
|
||||
go func() {
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"time"
|
||||
|
||||
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||
vaultapi "github.com/hashicorp/vault/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
@ -606,6 +607,88 @@ func TestCAManager_UpdateConfiguration_Vault_Primary(t *testing.T) {
|
|||
require.Equal(t, connect.HexString(cert.SubjectKeyId), newRoot.SigningKeyID)
|
||||
}
|
||||
|
||||
func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
ca.SkipIfVaultNotPresent(t)
|
||||
|
||||
vault := ca.NewTestVaultServer(t)
|
||||
vclient := vault.Client()
|
||||
generateExternalRootCA(t, vclient)
|
||||
|
||||
meshRootPath := "pki-root"
|
||||
primaryCert := setupPrimaryCA(t, vclient, meshRootPath)
|
||||
|
||||
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": meshRootPath,
|
||||
"IntermediatePKIPath": "pki-intermediate/",
|
||||
// TODO: there are failures to init the CA system if these are not set
|
||||
// to the values of the already initialized CA.
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
},
|
||||
}
|
||||
})
|
||||
defer s1.Shutdown()
|
||||
|
||||
runStep(t, "check primary DC", func(t *testing.T) {
|
||||
testrpc.WaitForTestAgent(t, s1.RPC, "dc1")
|
||||
|
||||
codec := rpcClient(t, s1)
|
||||
roots := structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
require.Equal(t, primaryCert, roots.Roots[0].RootCert)
|
||||
|
||||
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
|
||||
})
|
||||
|
||||
// TODO: renew primary leaf signing cert
|
||||
// TODO: rotate root
|
||||
|
||||
runStep(t, "run secondary DC", func(t *testing.T) {
|
||||
_, sDC2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "dc2"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": meshRootPath,
|
||||
"IntermediatePKIPath": "pki-secondary/",
|
||||
// TODO: there are failures to init the CA system if these are not set
|
||||
// to the values of the already initialized CA.
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
},
|
||||
}
|
||||
})
|
||||
defer sDC2.Shutdown()
|
||||
joinWAN(t, sDC2, s1)
|
||||
testrpc.WaitForActiveCARoot(t, sDC2.RPC, "dc2", nil)
|
||||
|
||||
codec := rpcClient(t, sDC2)
|
||||
roots := structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
|
||||
leafCertPEM := getLeafCert(t, codec, roots.TrustDomain, "dc2")
|
||||
verifyLeafCert(t, roots.Roots[0], leafCertPEM)
|
||||
|
||||
// TODO: renew secondary leaf signing cert
|
||||
})
|
||||
}
|
||||
|
||||
func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc string) string {
|
||||
pk, _, err := connect.GeneratePrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
@ -624,3 +707,58 @@ func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc str
|
|||
|
||||
return cert.CertPEM
|
||||
}
|
||||
|
||||
func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount("corp", &vaultapi.MountInput{
|
||||
Type: "pki",
|
||||
Description: "External root, probably corporate CA",
|
||||
Config: vaultapi.MountConfigInput{
|
||||
MaxLeaseTTL: "2400h",
|
||||
DefaultLeaseTTL: "1h",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to mount")
|
||||
|
||||
resp, err := client.Logical().Write("corp/root/generate/internal", map[string]interface{}{
|
||||
"common_name": "corporate CA",
|
||||
"ttl": "2400h",
|
||||
})
|
||||
require.NoError(t, err, "failed to generate root")
|
||||
return resp.Data["certificate"].(string)
|
||||
}
|
||||
|
||||
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount(path, &vaultapi.MountInput{
|
||||
Type: "pki",
|
||||
Description: "primary CA for Consul CA",
|
||||
Config: vaultapi.MountConfigInput{
|
||||
MaxLeaseTTL: "2200h",
|
||||
DefaultLeaseTTL: "1h",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err, "failed to mount")
|
||||
|
||||
out, err := client.Logical().Write(path+"/intermediate/generate/internal", map[string]interface{}{
|
||||
"common_name": "primary CA",
|
||||
"ttl": "2200h",
|
||||
"key_type": "ec",
|
||||
"key_bits": 256,
|
||||
})
|
||||
require.NoError(t, err, "failed to generate root")
|
||||
|
||||
intermediate, err := client.Logical().Write("corp/root/sign-intermediate", map[string]interface{}{
|
||||
"csr": out.Data["csr"],
|
||||
"use_csr_values": true,
|
||||
"format": "pem_bundle",
|
||||
"ttl": "2200h",
|
||||
})
|
||||
require.NoError(t, err, "failed to sign intermediate")
|
||||
|
||||
_, err = client.Logical().Write(path+"/intermediate/set-signed", map[string]interface{}{
|
||||
"certificate": intermediate.Data["certificate"],
|
||||
})
|
||||
require.NoError(t, err, "failed to set signed intermediate")
|
||||
return ca.EnsureTrailingNewline(intermediate.Data["certificate"].(string))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue