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"
|
vaultapi "github.com/hashicorp/vault/api"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/lib/decode"
|
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/connect"
|
"github.com/hashicorp/consul/agent/connect"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
|
"github.com/hashicorp/consul/lib/decode"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
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")
|
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := validateSetIntermediate(
|
// the private key is in vault, so we can't use it in this validation
|
||||||
intermediatePEM, rootPEM,
|
err := validateSetIntermediate(intermediatePEM, rootPEM, "", v.spiffeID)
|
||||||
"", // we don't have access to the private key directly
|
|
||||||
v.spiffeID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = v.client.Logical().Write(v.config.IntermediatePKIPath+"intermediate/set-signed", map[string]interface{}{
|
_, 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -654,7 +654,7 @@ func (c *CAManager) secondaryInitializeIntermediateCA(provider ca.Provider, conf
|
||||||
}
|
}
|
||||||
|
|
||||||
if needsNewIntermediate {
|
if needsNewIntermediate {
|
||||||
if err := c.secondaryRenewIntermediate(provider, newActiveRoot); err != nil {
|
if err := c.secondaryRequestNewSigningCert(provider, newActiveRoot); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1028,9 +1028,11 @@ func (c *CAManager) primaryRenewIntermediate(provider ca.Provider, newActiveRoot
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// secondaryRenewIntermediate should only be called while the state lock is held by
|
// secondaryRequestNewSigningCert creates a Certificate Signing Request, sends
|
||||||
// setting the state to non-ready.
|
// the request to the primary, and stores the received certificate in the
|
||||||
func (c *CAManager) secondaryRenewIntermediate(provider ca.Provider, newActiveRoot *structs.CARoot) error {
|
// 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()
|
csr, err := provider.GenerateIntermediateCSR()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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.
|
// Enough time has passed, go ahead with getting a new intermediate.
|
||||||
renewalFunc := c.primaryRenewIntermediate
|
renewalFunc := c.primaryRenewIntermediate
|
||||||
if !isPrimary {
|
if !isPrimary {
|
||||||
renewalFunc = c.secondaryRenewIntermediate
|
renewalFunc = c.secondaryRequestNewSigningCert
|
||||||
}
|
}
|
||||||
errCh := make(chan error, 1)
|
errCh := make(chan error, 1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||||
|
vaultapi "github.com/hashicorp/vault/api"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"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)
|
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 {
|
func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc string) string {
|
||||||
pk, _, err := connect.GeneratePrivateKey()
|
pk, _, err := connect.GeneratePrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -624,3 +707,58 @@ func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc str
|
||||||
|
|
||||||
return cert.CertPEM
|
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