mirror of https://github.com/status-im/consul.git
Merge pull request #11910 from hashicorp/dnephin/ca-provider-interface-for-ica-in-primary
ca: add support for an external trusted CA
This commit is contained in:
commit
771df290d7
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ca: support using an external root CA with the vault CA provider
|
||||
```
|
|
@ -9,11 +9,7 @@ import (
|
|||
"github.com/hashicorp/consul/agent/connect"
|
||||
)
|
||||
|
||||
func validateSetIntermediate(
|
||||
intermediatePEM, rootPEM string,
|
||||
currentPrivateKey string, // optional
|
||||
spiffeID *connect.SpiffeIDSigning,
|
||||
) error {
|
||||
func validateSetIntermediate(intermediatePEM, rootPEM string, spiffeID *connect.SpiffeIDSigning) error {
|
||||
// Get the key from the incoming intermediate cert so we can compare it
|
||||
// to the currently stored key.
|
||||
intermediate, err := connect.ParseCert(intermediatePEM)
|
||||
|
@ -21,26 +17,6 @@ func validateSetIntermediate(
|
|||
return fmt.Errorf("error parsing intermediate PEM: %v", err)
|
||||
}
|
||||
|
||||
if currentPrivateKey != "" {
|
||||
privKey, err := connect.ParseSigner(currentPrivateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare the two keys to make sure they match.
|
||||
b1, err := x509.MarshalPKIXPublicKey(intermediate.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b2, err := x509.MarshalPKIXPublicKey(privKey.Public())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(b1, b2) {
|
||||
return fmt.Errorf("intermediate cert is for a different private key")
|
||||
}
|
||||
}
|
||||
|
||||
// Validate the remaining fields and make sure the intermediate validates against
|
||||
// the given root cert.
|
||||
if !intermediate.IsCA {
|
||||
|
@ -65,6 +41,32 @@ func validateSetIntermediate(
|
|||
return nil
|
||||
}
|
||||
|
||||
func validateIntermediateSignedByPrivateKey(intermediatePEM string, privateKey string) error {
|
||||
intermediate, err := connect.ParseCert(intermediatePEM)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error parsing intermediate PEM: %v", err)
|
||||
}
|
||||
|
||||
privKey, err := connect.ParseSigner(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare the two keys to make sure they match.
|
||||
b1, err := x509.MarshalPKIXPublicKey(intermediate.PublicKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b2, err := x509.MarshalPKIXPublicKey(privKey.Public())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !bytes.Equal(b1, b2) {
|
||||
return fmt.Errorf("intermediate cert is for a different private key")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateSignIntermediate(csr *x509.CertificateRequest, spiffeID *connect.SpiffeIDSigning) error {
|
||||
// We explicitly _don't_ require that the CSR has a valid SPIFFE signing URI
|
||||
// SAN because AWS PCA doesn't let us set one :(. We need to relax it here
|
||||
|
|
|
@ -135,6 +135,7 @@ type PrimaryProvider interface {
|
|||
// the active intermediate. If multiple intermediates are needed to complete
|
||||
// the chain from the signing certificate back to the active root, they should
|
||||
// all by bundled here.
|
||||
// TODO: replace with GenerateLeafSigningCert (https://github.com/hashicorp/consul/issues/12386)
|
||||
GenerateIntermediate() (string, error)
|
||||
|
||||
// SignIntermediate will validate the CSR to ensure the trust domain in the
|
||||
|
@ -171,14 +172,20 @@ type PrimaryProvider interface {
|
|||
}
|
||||
|
||||
type SecondaryProvider interface {
|
||||
// GenerateIntermediateCSR generates a CSR for an intermediate CA
|
||||
// certificate, to be signed by the root of another datacenter. If IsPrimary was
|
||||
// set to true with Configure(), calling this is an error.
|
||||
// GenerateIntermediateCSR should return a CSR for an intermediate CA
|
||||
// certificate. The intermediate CA will be signed by the primary CA and
|
||||
// should be used by the provider to sign leaf certificates in the local
|
||||
// datacenter.
|
||||
//
|
||||
// After the certificate is signed, SecondaryProvider.SetIntermediate will
|
||||
// be called to store the intermediate CA.
|
||||
GenerateIntermediateCSR() (string, error)
|
||||
|
||||
// SetIntermediate sets the provider to use the given intermediate certificate
|
||||
// as well as the root it was signed by. This completes the initialization for
|
||||
// a provider where IsPrimary was set to false in Configure().
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -186,7 +193,12 @@ type SecondaryProvider interface {
|
|||
//
|
||||
// TODO: rename this struct
|
||||
type RootResult struct {
|
||||
// PEM encoded certificate that will be used as the primary CA.
|
||||
// PEM encoded bundle of CA certificates. The first certificate must be the
|
||||
// primary CA used to sign intermediates for secondary datacenters, and the
|
||||
// last certificate must be the trusted CA.
|
||||
//
|
||||
// If there is only a single certificate in the bundle then it will be used
|
||||
// as both the primary CA and the trusted CA.
|
||||
PEM string
|
||||
}
|
||||
|
||||
|
|
|
@ -253,12 +253,10 @@ func (c *ConsulProvider) SetIntermediate(intermediatePEM, rootPEM string) error
|
|||
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
|
||||
}
|
||||
|
||||
err = validateSetIntermediate(
|
||||
intermediatePEM, rootPEM,
|
||||
providerState.PrivateKey,
|
||||
c.spiffeID,
|
||||
)
|
||||
if err != nil {
|
||||
if err = validateSetIntermediate(intermediatePEM, rootPEM, c.spiffeID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateIntermediateSignedByPrivateKey(intermediatePEM, providerState.PrivateKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
_, err = v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
|
||||
resp, err := v.client.Logical().Write(v.config.RootPKIPath+"root/generate/internal", map[string]interface{}{
|
||||
"common_name": connect.CACN("vault", uid, v.clusterID, v.isPrimary),
|
||||
"uri_sans": v.spiffeID.URI().String(),
|
||||
"key_type": v.config.PrivateKeyType,
|
||||
|
@ -288,12 +288,10 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
|
||||
// retrieve the newly generated cert so that we can return it
|
||||
// TODO: is this already available from the Local().Write() above?
|
||||
rootPEM, err = v.getCA(v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return RootResult{}, err
|
||||
var ok bool
|
||||
rootPEM, ok = resp.Data["certificate"].(string)
|
||||
if !ok {
|
||||
return RootResult{}, fmt.Errorf("unexpected response from Vault: %v", resp.Data["certificate"])
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -302,7 +300,18 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return RootResult{PEM: rootPEM}, nil
|
||||
rootChain, err := v.getCAChain(v.config.RootPKIPath)
|
||||
if err != nil {
|
||||
return RootResult{}, err
|
||||
}
|
||||
|
||||
// Workaround for a bug in the Vault PKI API.
|
||||
// See https://github.com/hashicorp/vault/issues/13489
|
||||
if rootChain == "" {
|
||||
rootChain = rootPEM
|
||||
}
|
||||
|
||||
return RootResult{PEM: rootChain}, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediateCSR creates a private key and generates a CSR
|
||||
|
@ -402,8 +411,7 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
|
|||
return fmt.Errorf("cannot set an intermediate using another root in the primary datacenter")
|
||||
}
|
||||
|
||||
// the private key is in vault, so we can't use it in this validation
|
||||
err := validateSetIntermediate(intermediatePEM, rootPEM, "", v.spiffeID)
|
||||
err := validateSetIntermediate(intermediatePEM, rootPEM, v.spiffeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -468,6 +476,29 @@ func (v *VaultProvider) getCA(path string) (string, error) {
|
|||
return root, nil
|
||||
}
|
||||
|
||||
// TODO: refactor to remove duplication with getCA
|
||||
func (v *VaultProvider) getCAChain(path string) (string, error) {
|
||||
req := v.client.NewRequest("GET", "/v1/"+path+"/ca_chain")
|
||||
resp, err := v.client.RawRequest(req)
|
||||
if resp != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if resp != nil && resp.StatusCode == http.StatusNotFound {
|
||||
return "", ErrBackendNotMounted
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
raw, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
root := EnsureTrailingNewline(string(raw))
|
||||
return root, nil
|
||||
}
|
||||
|
||||
// GenerateIntermediate 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.
|
||||
|
@ -529,12 +560,7 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
|||
if !ok {
|
||||
return "", fmt.Errorf("certificate was not a string")
|
||||
}
|
||||
ca, ok := response.Data["issuing_ca"].(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("issuing_ca was not a string")
|
||||
}
|
||||
|
||||
return EnsureTrailingNewline(cert) + EnsureTrailingNewline(ca), nil
|
||||
return EnsureTrailingNewline(cert), nil
|
||||
}
|
||||
|
||||
// SignIntermediate returns a signed CA certificate with a path length constraint
|
||||
|
|
|
@ -20,11 +20,11 @@ const (
|
|||
DefaultIntermediateCertTTL = 24 * 365 * time.Hour
|
||||
)
|
||||
|
||||
func pemEncodeKey(key []byte, blockType string) (string, error) {
|
||||
func pemEncode(value []byte, blockType string) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: key}); err != nil {
|
||||
return "", fmt.Errorf("error encoding private key: %s", err)
|
||||
if err := pem.Encode(&buf, &pem.Block{Type: blockType, Bytes: value}); err != nil {
|
||||
return "", fmt.Errorf("error encoding value %v: %s", blockType, err)
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func generateRSAKey(keyBits int) (crypto.Signer, string, error) {
|
|||
}
|
||||
|
||||
bs := x509.MarshalPKCS1PrivateKey(pk)
|
||||
pemBlock, err := pemEncodeKey(bs, "RSA PRIVATE KEY")
|
||||
pemBlock, err := pemEncode(bs, "RSA PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func generateECDSAKey(keyBits int) (crypto.Signer, string, error) {
|
|||
return nil, "", fmt.Errorf("error marshaling ECDSA private key: %s", err)
|
||||
}
|
||||
|
||||
pemBlock, err := pemEncodeKey(bs, "EC PRIVATE KEY")
|
||||
pemBlock, err := pemEncode(bs, "EC PRIVATE KEY")
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
|
|
@ -56,6 +56,21 @@ func ParseLeafCerts(pemValue string) (*x509.Certificate, *x509.CertPool, error)
|
|||
return leaf, intermediates, nil
|
||||
}
|
||||
|
||||
// CertSubjects can be used in debugging to return the subject of each
|
||||
// certificate in the PEM bundle. Each subject is separated by a newline.
|
||||
func CertSubjects(pem string) string {
|
||||
certs, err := parseCerts(pem)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
var buf strings.Builder
|
||||
for _, cert := range certs {
|
||||
buf.WriteString(cert.Subject.String())
|
||||
buf.WriteString("\n")
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// ParseCerts parses the all x509 certificates from a PEM-encoded value.
|
||||
// The first returned cert is a leaf cert and any other ones are intermediates.
|
||||
//
|
||||
|
@ -90,21 +105,10 @@ func parseCerts(pemValue string) ([]*x509.Certificate, error) {
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// CalculateCertFingerprint parses the x509 certificate from a PEM-encoded value
|
||||
// and calculates the SHA-1 fingerprint.
|
||||
func CalculateCertFingerprint(pemValue string) (string, error) {
|
||||
// The _ result below is not an error but the remaining PEM bytes.
|
||||
block, _ := pem.Decode([]byte(pemValue))
|
||||
if block == nil {
|
||||
return "", fmt.Errorf("no PEM-encoded data found")
|
||||
}
|
||||
|
||||
if block.Type != "CERTIFICATE" {
|
||||
return "", fmt.Errorf("first PEM-block should be CERTIFICATE type")
|
||||
}
|
||||
|
||||
hash := sha1.Sum(block.Bytes)
|
||||
return HexString(hash[:]), nil
|
||||
// CalculateCertFingerprint calculates the SHA-1 fingerprint from the cert bytes.
|
||||
func CalculateCertFingerprint(cert []byte) string {
|
||||
hash := sha1.Sum(cert)
|
||||
return HexString(hash[:])
|
||||
}
|
||||
|
||||
// ParseSigner parses a crypto.Signer from a PEM-encoded key. The private key
|
||||
|
|
|
@ -112,10 +112,7 @@ func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int, ttl ti
|
|||
t.Fatalf("error encoding private key: %s", err)
|
||||
}
|
||||
result.RootCert = buf.String()
|
||||
result.ID, err = CalculateCertFingerprint(result.RootCert)
|
||||
if err != nil {
|
||||
t.Fatalf("error generating CA ID fingerprint: %s", err)
|
||||
}
|
||||
result.ID = CalculateCertFingerprint(bs)
|
||||
result.SerialNumber = uint64(sn.Int64())
|
||||
result.NotBefore = template.NotBefore.UTC()
|
||||
result.NotAfter = template.NotAfter.UTC()
|
||||
|
|
|
@ -253,28 +253,24 @@ func (c *CAManager) initializeCAConfig() (*structs.CAConfiguration, error) {
|
|||
return config, nil
|
||||
}
|
||||
|
||||
// parseCARoot returns a filled-in structs.CARoot from a raw PEM value.
|
||||
func parseCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error) {
|
||||
id, err := connect.CalculateCertFingerprint(pemValue)
|
||||
// newCARoot returns a filled-in structs.CARoot from a raw PEM value.
|
||||
func newCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error) {
|
||||
primaryCert, err := connect.ParseCert(pemValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing root fingerprint: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
rootCert, err := connect.ParseCert(pemValue)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing root cert: %v", err)
|
||||
}
|
||||
keyType, keyBits, err := connect.KeyInfoFromCert(rootCert)
|
||||
keyType, keyBits, err := connect.KeyInfoFromCert(primaryCert)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error extracting root key info: %v", err)
|
||||
}
|
||||
return &structs.CARoot{
|
||||
ID: id,
|
||||
Name: fmt.Sprintf("%s CA Root Cert", strings.Title(provider)),
|
||||
SerialNumber: rootCert.SerialNumber.Uint64(),
|
||||
SigningKeyID: connect.EncodeSigningKeyID(rootCert.SubjectKeyId),
|
||||
ID: connect.CalculateCertFingerprint(primaryCert.Raw),
|
||||
Name: fmt.Sprintf("%s CA Primary Cert", strings.Title(provider)),
|
||||
SerialNumber: primaryCert.SerialNumber.Uint64(),
|
||||
SigningKeyID: connect.EncodeSigningKeyID(primaryCert.SubjectKeyId),
|
||||
ExternalTrustDomain: clusterID,
|
||||
NotBefore: rootCert.NotBefore,
|
||||
NotAfter: rootCert.NotAfter,
|
||||
NotBefore: primaryCert.NotBefore,
|
||||
NotAfter: primaryCert.NotAfter,
|
||||
RootCert: pemValue,
|
||||
PrivateKeyType: keyType,
|
||||
PrivateKeyBits: keyBits,
|
||||
|
@ -435,7 +431,7 @@ func (c *CAManager) secondaryInitialize(provider ca.Provider, conf *structs.CACo
|
|||
}
|
||||
var roots structs.IndexedCARoots
|
||||
if err := c.delegate.forwardDC("ConnectCA.Roots", c.serverConf.PrimaryDatacenter, &args, &roots); err != nil {
|
||||
return err
|
||||
return fmt.Errorf("failed to get CA roots from primary DC: %w", err)
|
||||
}
|
||||
c.secondarySetPrimaryRoots(roots)
|
||||
|
||||
|
@ -487,12 +483,12 @@ func (c *CAManager) primaryInitialize(provider ca.Provider, conf *structs.CAConf
|
|||
return fmt.Errorf("error generating CA root certificate: %v", err)
|
||||
}
|
||||
|
||||
rootCA, err := parseCARoot(root.PEM, conf.Provider, conf.ClusterID)
|
||||
rootCA, err := newCARoot(root.PEM, conf.Provider, conf.ClusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Also create the intermediate CA, which is the one that actually signs leaf certs
|
||||
// TODO: https://github.com/hashicorp/consul/issues/12386
|
||||
interPEM, err := provider.GenerateIntermediate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error generating intermediate cert: %v", err)
|
||||
|
@ -887,7 +883,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
}
|
||||
|
||||
newRootPEM := providerRoot.PEM
|
||||
newActiveRoot, err := parseCARoot(newRootPEM, args.Config.Provider, args.Config.ClusterID)
|
||||
newActiveRoot, err := newCARoot(newRootPEM, args.Config.Provider, args.Config.ClusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -940,7 +936,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
// get a cross-signed certificate.
|
||||
// 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 parseCARoot, could we remove the second parse?
|
||||
// TODO: this cert is already parsed once in newCARoot, could we remove the second parse?
|
||||
newRoot, err := connect.ParseCert(newRootPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -980,6 +976,7 @@ func (c *CAManager) primaryUpdateRootCA(newProvider ca.Provider, args *structs.C
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: https://github.com/hashicorp/consul/issues/12386
|
||||
intermediate, err := newProvider.GenerateIntermediate()
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"fmt"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
@ -82,7 +83,6 @@ func TestCAManager_Initialize_Vault_Secondary_SharedVault(t *testing.T) {
|
|||
},
|
||||
}
|
||||
})
|
||||
defer serverDC2.Shutdown()
|
||||
joinWAN(t, serverDC2, serverDC1)
|
||||
testrpc.WaitForActiveCARoot(t, serverDC2.RPC, "dc2", nil)
|
||||
|
||||
|
@ -98,14 +98,25 @@ func TestCAManager_Initialize_Vault_Secondary_SharedVault(t *testing.T) {
|
|||
}
|
||||
|
||||
func verifyLeafCert(t *testing.T, root *structs.CARoot, leafCertPEM string) {
|
||||
t.Helper()
|
||||
roots := structs.IndexedCARoots{
|
||||
ActiveRootID: root.ID,
|
||||
Roots: []*structs.CARoot{root},
|
||||
}
|
||||
verifyLeafCertWithRoots(t, roots, leafCertPEM)
|
||||
}
|
||||
|
||||
func verifyLeafCertWithRoots(t *testing.T, roots structs.IndexedCARoots, leafCertPEM string) {
|
||||
t.Helper()
|
||||
leaf, intermediates, err := connect.ParseLeafCerts(leafCertPEM)
|
||||
require.NoError(t, err)
|
||||
|
||||
pool := x509.NewCertPool()
|
||||
ok := pool.AppendCertsFromPEM([]byte(root.RootCert))
|
||||
if !ok {
|
||||
t.Fatalf("Failed to add root CA PEM to cert pool")
|
||||
for _, r := range roots.Roots {
|
||||
ok := pool.AppendCertsFromPEM([]byte(r.RootCert))
|
||||
if !ok {
|
||||
t.Fatalf("Failed to add root CA PEM to cert pool")
|
||||
}
|
||||
}
|
||||
|
||||
// verify with intermediates from leaf CertPEM
|
||||
|
@ -118,10 +129,12 @@ func verifyLeafCert(t *testing.T, root *structs.CARoot, leafCertPEM string) {
|
|||
|
||||
// verify with intermediates from the CARoot
|
||||
intermediates = x509.NewCertPool()
|
||||
for _, intermediate := range root.IntermediateCerts {
|
||||
c, err := connect.ParseCert(intermediate)
|
||||
require.NoError(t, err)
|
||||
intermediates.AddCert(c)
|
||||
for _, r := range roots.Roots {
|
||||
for _, intermediate := range r.IntermediateCerts {
|
||||
c, err := connect.ParseCert(intermediate)
|
||||
require.NoError(t, err)
|
||||
intermediates.AddCert(c)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = leaf.Verify(x509.VerifyOptions{
|
||||
|
@ -618,7 +631,7 @@ func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
|||
generateExternalRootCA(t, vclient)
|
||||
|
||||
meshRootPath := "pki-root"
|
||||
primaryCert := setupPrimaryCA(t, vclient, meshRootPath)
|
||||
primaryCert := setupPrimaryCA(t, vclient, meshRootPath, "")
|
||||
|
||||
_, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
|
@ -628,14 +641,9 @@ func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
|||
"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")
|
||||
|
@ -665,10 +673,6 @@ func TestCAManager_Initialize_Vault_WithIntermediateAsPrimaryCA(t *testing.T) {
|
|||
"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,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
@ -704,10 +708,224 @@ func getLeafCert(t *testing.T, codec rpc.ClientCodec, trustDomain string, dc str
|
|||
cert := structs.IssuedCert{}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", &req, &cert)
|
||||
require.NoError(t, err)
|
||||
|
||||
return cert.CertPEM
|
||||
}
|
||||
|
||||
func TestCAManager_Initialize_Vault_WithExternalTrustedCA(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
}
|
||||
ca.SkipIfVaultNotPresent(t)
|
||||
|
||||
vault := ca.NewTestVaultServer(t)
|
||||
vclient := vault.Client()
|
||||
rootPEM := generateExternalRootCA(t, vclient)
|
||||
|
||||
primaryCAPath := "pki-primary"
|
||||
primaryCert := setupPrimaryCA(t, vclient, primaryCAPath, rootPEM)
|
||||
|
||||
_, serverDC1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.CAConfig = &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": primaryCAPath,
|
||||
"IntermediatePKIPath": "pki-intermediate/",
|
||||
},
|
||||
}
|
||||
})
|
||||
testrpc.WaitForTestAgent(t, serverDC1.RPC, "dc1")
|
||||
|
||||
var origLeaf string
|
||||
roots := structs.IndexedCARoots{}
|
||||
runStep(t, "verify primary DC", func(t *testing.T) {
|
||||
codec := rpcClient(t, serverDC1)
|
||||
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)
|
||||
require.Contains(t, roots.Roots[0].RootCert, rootPEM)
|
||||
|
||||
leafCert := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Active(), leafCert)
|
||||
origLeaf = leafCert
|
||||
})
|
||||
|
||||
_, serverDC2 := 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": "should-be-ignored",
|
||||
"IntermediatePKIPath": "pki-secondary/",
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
var origLeafSecondary string
|
||||
runStep(t, "start secondary DC", func(t *testing.T) {
|
||||
joinWAN(t, serverDC2, serverDC1)
|
||||
testrpc.WaitForActiveCARoot(t, serverDC2.RPC, "dc2", nil)
|
||||
|
||||
codec := rpcClient(t, serverDC2)
|
||||
roots = structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
|
||||
leafPEM := getLeafCert(t, codec, roots.TrustDomain, "dc2")
|
||||
verifyLeafCert(t, roots.Roots[0], leafPEM)
|
||||
origLeafSecondary = leafPEM
|
||||
})
|
||||
|
||||
runStep(t, "renew leaf signing CA in primary", func(t *testing.T) {
|
||||
previous := serverDC1.caManager.getLeafSigningCertFromRoot(roots.Active())
|
||||
|
||||
renewLeafSigningCert(t, serverDC1.caManager, serverDC1.caManager.primaryRenewIntermediate)
|
||||
|
||||
codec := rpcClient(t, serverDC1)
|
||||
roots = structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
require.Len(t, roots.Roots[0].IntermediateCerts, 2)
|
||||
|
||||
newCert := serverDC1.caManager.getLeafSigningCertFromRoot(roots.Active())
|
||||
require.NotEqual(t, previous, newCert)
|
||||
|
||||
leafPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Roots[0], leafPEM)
|
||||
|
||||
// original certs from old signing cert should still verify
|
||||
verifyLeafCert(t, roots.Roots[0], origLeaf)
|
||||
})
|
||||
|
||||
runStep(t, "renew leaf signing CA in secondary", func(t *testing.T) {
|
||||
previous := serverDC2.caManager.getLeafSigningCertFromRoot(roots.Active())
|
||||
|
||||
renewLeafSigningCert(t, serverDC2.caManager, serverDC2.caManager.secondaryRequestNewSigningCert)
|
||||
|
||||
codec := rpcClient(t, serverDC2)
|
||||
roots = structs.IndexedCARoots{}
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 1)
|
||||
// one intermediate from primary, two from secondary
|
||||
require.Len(t, roots.Roots[0].IntermediateCerts, 3)
|
||||
|
||||
newCert := serverDC1.caManager.getLeafSigningCertFromRoot(roots.Active())
|
||||
require.NotEqual(t, previous, newCert)
|
||||
|
||||
leafPEM := getLeafCert(t, codec, roots.TrustDomain, "dc2")
|
||||
verifyLeafCert(t, roots.Roots[0], leafPEM)
|
||||
|
||||
// original certs from old signing cert should still verify
|
||||
verifyLeafCert(t, roots.Roots[0], origLeaf)
|
||||
})
|
||||
|
||||
runStep(t, "rotate root by changing the provider", func(t *testing.T) {
|
||||
codec := rpcClient(t, serverDC1)
|
||||
req := &structs.CARequest{
|
||||
Op: structs.CAOpSetConfig,
|
||||
Config: &structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
},
|
||||
}
|
||||
var resp error
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", req, &resp)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
roots = structs.IndexedCARoots{}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 2)
|
||||
active := roots.Active()
|
||||
require.Len(t, active.IntermediateCerts, 1)
|
||||
|
||||
leafPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Active(), leafPEM)
|
||||
|
||||
// original certs from old root cert should still verify
|
||||
verifyLeafCertWithRoots(t, roots, origLeaf)
|
||||
|
||||
// original certs from secondary should still verify
|
||||
rootsSecondary := structs.IndexedCARoots{}
|
||||
r := &structs.DCSpecificRequest{Datacenter: "dc2"}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", r, &rootsSecondary)
|
||||
require.NoError(t, err)
|
||||
verifyLeafCertWithRoots(t, rootsSecondary, origLeafSecondary)
|
||||
})
|
||||
|
||||
runStep(t, "rotate to a different external root", func(t *testing.T) {
|
||||
setupPrimaryCA(t, vclient, "pki-primary-2/", rootPEM)
|
||||
|
||||
codec := rpcClient(t, serverDC1)
|
||||
req := &structs.CARequest{
|
||||
Op: structs.CAOpSetConfig,
|
||||
Config: &structs.CAConfiguration{
|
||||
Provider: "vault",
|
||||
Config: map[string]interface{}{
|
||||
"Address": vault.Addr,
|
||||
"Token": vault.RootToken,
|
||||
"RootPKIPath": "pki-primary-2/",
|
||||
"IntermediatePKIPath": "pki-intermediate-2/",
|
||||
},
|
||||
},
|
||||
}
|
||||
var resp error
|
||||
err := msgpackrpc.CallWithCodec(codec, "ConnectCA.ConfigurationSet", req, &resp)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, resp)
|
||||
|
||||
roots = structs.IndexedCARoots{}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", &structs.DCSpecificRequest{}, &roots)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, roots.Roots, 3)
|
||||
active := roots.Active()
|
||||
require.Len(t, active.IntermediateCerts, 2)
|
||||
|
||||
leafPEM := getLeafCert(t, codec, roots.TrustDomain, "dc1")
|
||||
verifyLeafCert(t, roots.Active(), leafPEM)
|
||||
|
||||
// original certs from old root cert should still verify
|
||||
verifyLeafCertWithRoots(t, roots, origLeaf)
|
||||
|
||||
// original certs from secondary should still verify
|
||||
rootsSecondary := structs.IndexedCARoots{}
|
||||
r := &structs.DCSpecificRequest{Datacenter: "dc2"}
|
||||
err = msgpackrpc.CallWithCodec(codec, "ConnectCA.Roots", r, &rootsSecondary)
|
||||
require.NoError(t, err)
|
||||
verifyLeafCertWithRoots(t, rootsSecondary, origLeafSecondary)
|
||||
})
|
||||
}
|
||||
|
||||
// renewLeafSigningCert mimics RenewIntermediate. This is unfortunate, but
|
||||
// necessary for now as there is no easy way to invoke that logic unconditionally.
|
||||
// Currently, it requires patching values and polling for the operation to
|
||||
// complete, which adds a lot of distractions to a test case.
|
||||
// With this function we can instead unconditionally rotate the leaf signing cert
|
||||
// synchronously.
|
||||
func renewLeafSigningCert(t *testing.T, manager *CAManager, fn func(ca.Provider, *structs.CARoot) error) {
|
||||
t.Helper()
|
||||
provider, _ := manager.getCAProvider()
|
||||
|
||||
store := manager.delegate.State()
|
||||
_, root, err := store.CARootActive(nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
activeRoot := root.Clone()
|
||||
err = fn(provider, activeRoot)
|
||||
require.NoError(t, err)
|
||||
err = manager.persistNewRootAndConfig(provider, activeRoot, nil)
|
||||
require.NoError(t, err)
|
||||
manager.setCAProvider(provider, activeRoot)
|
||||
}
|
||||
|
||||
func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount("corp", &vaultapi.MountInput{
|
||||
|
@ -725,10 +943,10 @@ func generateExternalRootCA(t *testing.T, client *vaultapi.Client) string {
|
|||
"ttl": "2400h",
|
||||
})
|
||||
require.NoError(t, err, "failed to generate root")
|
||||
return resp.Data["certificate"].(string)
|
||||
return ca.EnsureTrailingNewline(resp.Data["certificate"].(string))
|
||||
}
|
||||
|
||||
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string) string {
|
||||
func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string, rootPEM string) string {
|
||||
t.Helper()
|
||||
err := client.Sys().Mount(path, &vaultapi.MountInput{
|
||||
Type: "pki",
|
||||
|
@ -756,9 +974,13 @@ func setupPrimaryCA(t *testing.T, client *vaultapi.Client, path string) string {
|
|||
})
|
||||
require.NoError(t, err, "failed to sign intermediate")
|
||||
|
||||
var buf strings.Builder
|
||||
buf.WriteString(ca.EnsureTrailingNewline(intermediate.Data["certificate"].(string)))
|
||||
buf.WriteString(ca.EnsureTrailingNewline(rootPEM))
|
||||
|
||||
_, err = client.Logical().Write(path+"/intermediate/set-signed", map[string]interface{}{
|
||||
"certificate": intermediate.Data["certificate"],
|
||||
"certificate": buf.String(),
|
||||
})
|
||||
require.NoError(t, err, "failed to set signed intermediate")
|
||||
return ca.EnsureTrailingNewline(intermediate.Data["certificate"].(string))
|
||||
return ca.EnsureTrailingNewline(buf.String())
|
||||
}
|
||||
|
|
|
@ -15,11 +15,13 @@ import (
|
|||
msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc"
|
||||
uuid "github.com/hashicorp/go-uuid"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
"github.com/hashicorp/consul/agent/connect/ca"
|
||||
"github.com/hashicorp/consul/agent/structs"
|
||||
"github.com/hashicorp/consul/agent/token"
|
||||
"github.com/hashicorp/consul/sdk/testutil"
|
||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
)
|
||||
|
@ -1246,74 +1248,122 @@ func TestConnectCA_ConfigurationSet_PersistsRoots(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestParseCARoot(t *testing.T) {
|
||||
type test struct {
|
||||
name string
|
||||
pem string
|
||||
wantSerial uint64
|
||||
wantSigningKeyID string
|
||||
wantKeyType string
|
||||
wantKeyBits int
|
||||
wantErr bool
|
||||
func TestNewCARoot(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
pem string
|
||||
expected *structs.CARoot
|
||||
expectedErr string
|
||||
}
|
||||
// Test certs generated with
|
||||
|
||||
run := func(t *testing.T, tc testCase) {
|
||||
root, err := newCARoot(tc.pem, "provider-name", "cluster-id")
|
||||
if tc.expectedErr != "" {
|
||||
testutil.RequireErrorContains(t, err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.DeepEqual(t, root, tc.expected)
|
||||
}
|
||||
|
||||
// Test certs can be generated with
|
||||
// go run connect/certgen/certgen.go -out-dir /tmp/connect-certs -key-type ec -key-bits 384
|
||||
// for various key types. This does limit the exposure to formats that might
|
||||
// exist in external certificates which can be used as Connect CAs.
|
||||
// Specifically many other certs will have serial numbers that don't fit into
|
||||
// 64 bits but for reasons we truncate down to 64 bits which means our
|
||||
// `SerialNumber` will not match the one reported by openssl. We should
|
||||
// probably fix that at some point as it seems like a big footgun but it would
|
||||
// be a breaking API change to change the type to not be a JSON number and
|
||||
// JSON numbers don't even support the full range of a uint64...
|
||||
tests := []test{
|
||||
{"no cert", "", 0, "", "", 0, true},
|
||||
// serial generated with:
|
||||
// openssl x509 -noout -text
|
||||
testCases := []testCase{
|
||||
{
|
||||
name: "default cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 8341954965092507701,
|
||||
wantSigningKeyID: "97:4D:17:81:64:F8:B4:AF:05:E8:6C:79:C5:40:3B:0E:3E:8B:C0:AE:38:51:54:8A:2F:05:DB:E3:E8:E4:24:EC",
|
||||
wantKeyType: "ec",
|
||||
wantKeyBits: 256,
|
||||
wantErr: false,
|
||||
name: "no cert",
|
||||
expectedErr: "no PEM-encoded data found",
|
||||
},
|
||||
{
|
||||
name: "ec 384 cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 2935109425518279965,
|
||||
wantSigningKeyID: "0B:A0:88:9B:DC:95:31:51:2E:3D:D4:F9:42:D0:6A:A0:62:46:82:D2:7C:22:E7:29:A9:AA:E8:A5:8C:CF:C7:42",
|
||||
wantKeyType: "ec",
|
||||
wantKeyBits: 384,
|
||||
wantErr: false,
|
||||
name: "type=ec bits=256",
|
||||
pem: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "c9:1b:24:e0:89:63:1a:ba:22:01:f4:cf:bc:f1:c0:36:b2:6b:6c:3d",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 8341954965092507701,
|
||||
SigningKeyID: "97:4d:17:81:64:f8:b4:af:05:e8:6c:79:c5:40:3b:0e:3e:8b:c0:ae:38:51:54:8a:2f:05:db:e3:e8:e4:24:ec",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 46, 29, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 46, 29, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-ec-256-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rsa 4096 cert",
|
||||
// Watchout for indentations they will break PEM format
|
||||
pem: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
// Based on `openssl x509 -noout -text` report from the cert
|
||||
wantSerial: 5186695743100577491,
|
||||
wantSigningKeyID: "92:FA:CC:97:57:1E:31:84:A2:33:DD:9B:6A:A8:7C:FC:BE:E2:94:CA:AC:B3:33:17:39:3B:B8:67:9B:DC:C1:08",
|
||||
wantKeyType: "rsa",
|
||||
wantKeyBits: 4096,
|
||||
wantErr: false,
|
||||
name: "type=ec bits=384",
|
||||
pem: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "29:69:c4:0f:aa:8f:bd:07:31:0d:51:3b:45:62:3d:c0:b2:fc:c6:3f",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 2935109425518279965,
|
||||
SigningKeyID: "0b:a0:88:9b:dc:95:31:51:2e:3d:d4:f9:42:d0:6a:a0:62:46:82:d2:7c:22:e7:29:a9:aa:e8:a5:8c:cf:c7:42",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 55, 18, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 55, 18, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-ec-384-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 384,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "type=rsa bits=4096",
|
||||
pem: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "3a:6a:e3:e2:2d:44:85:5a:e9:44:3b:ef:d2:90:78:83:7f:61:a2:84",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 5186695743100577491,
|
||||
SigningKeyID: "92:fa:cc:97:57:1e:31:84:a2:33:dd:9b:6a:a8:7c:fc:be:e2:94:ca:ac:b3:33:17:39:3b:b8:67:9b:dc:c1:08",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2019, 10, 17, 11, 53, 15, 0, time.UTC),
|
||||
NotAfter: time.Date(2029, 10, 17, 11, 53, 15, 0, time.UTC),
|
||||
RootCert: readTestData(t, "cert-with-rsa-4096-key.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "rsa",
|
||||
PrivateKeyBits: 4096,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two certs in pem",
|
||||
pem: readTestData(t, "pem-with-two-certs.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "42:43:10:1f:71:6b:21:21:d1:10:49:d1:f0:41:78:8c:0a:77:ef:c0",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 17692800288680335732,
|
||||
SigningKeyID: "9d:5c:27:43:ce:58:7b:ca:3e:7d:c4:fb:b6:2e:b7:13:e9:a1:68:3e",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2022, 1, 5, 23, 22, 12, 0, time.UTC),
|
||||
NotAfter: time.Date(2022, 4, 7, 15, 22, 42, 0, time.UTC),
|
||||
RootCert: readTestData(t, "pem-with-two-certs.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "three certs in pem",
|
||||
pem: readTestData(t, "pem-with-three-certs.pem"),
|
||||
expected: &structs.CARoot{
|
||||
ID: "42:43:10:1f:71:6b:21:21:d1:10:49:d1:f0:41:78:8c:0a:77:ef:c0",
|
||||
Name: "Provider-Name CA Primary Cert",
|
||||
SerialNumber: 17692800288680335732,
|
||||
SigningKeyID: "9d:5c:27:43:ce:58:7b:ca:3e:7d:c4:fb:b6:2e:b7:13:e9:a1:68:3e",
|
||||
ExternalTrustDomain: "cluster-id",
|
||||
NotBefore: time.Date(2022, 1, 5, 23, 22, 12, 0, time.UTC),
|
||||
NotAfter: time.Date(2022, 4, 7, 15, 22, 42, 0, time.UTC),
|
||||
RootCert: readTestData(t, "pem-with-three-certs.pem"),
|
||||
Active: true,
|
||||
PrivateKeyType: "ec",
|
||||
PrivateKeyBits: 256,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
root, err := parseCARoot(tt.pem, "consul", "cluster")
|
||||
if tt.wantErr {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.wantSerial, root.SerialNumber)
|
||||
require.Equal(t, strings.ToLower(tt.wantSigningKeyID), root.SigningKeyID)
|
||||
require.Equal(t, tt.wantKeyType, root.PrivateKeyType)
|
||||
require.Equal(t, tt.wantKeyBits, root.PrivateKeyBits)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
run(t, tc)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,4 +9,4 @@ VR0jBCQwIoAgl00XgWT4tK8F6Gx5xUA7Dj6LwK44UVSKLwXb4+jkJOwwPwYDVR0R
|
|||
BDgwNoY0c3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1NTU1
|
||||
NTU1LmNvbnN1bDAKBggqhkjOPQQDAgNIADBFAiEA/x2MeYU5vCk2hwP7zlrv7bx3
|
||||
9zx5YSbn04sgP6sNK30CIEPfjxDGy6K2dPDckATboYkZVQ4CJpPd6WrgwQaHpWC9
|
||||
-----END CERTIFICATE-----
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
@ -11,4 +11,4 @@ MTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqGSM49
|
|||
BAMDA2gAMGUCMBT0orKHSATvulb6nRxVHq3OWOfmVgHu8VUCq9yuyAu1AAy/przY
|
||||
/U0ury3g8T4jhwIxAIoCqYwWSJMFb13DZAR3XY+aFssVP5+vzhlaulqtg+YqjpKP
|
||||
KzuCBpS3yUyAwWDphg==
|
||||
-----END CERTIFICATE-----
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
@ -28,4 +28,4 @@ RPw2YW6oqaMmZ9Uehxym8RDWqyyFPg9S0C73MTK7FitIROLW88hWKSpDDhFck/32
|
|||
FVaRL8cC0KVlMCFByL/o6u0AsRNCOux1q3BJEdmAh7VI84+SPgztHFkptR4VnlHZ
|
||||
kKTj2Mj/OylHHwhe6AU9pbtAGM6DtcqSjmd4wrkRX8WJDd/F3RlYZ8WhOToOj9gP
|
||||
ra4mUhGz/OlDg6vN9TSeVlb5Ap7c38KoCmmt2n+F/KUpe6V4L1QA5yfz0S8=
|
||||
-----END CERTIFICATE-----
|
||||
-----END CERTIFICATE-----
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICUjCCATqgAwIBAgIUQjOIDzaM7bGW8bU69Yl0H0C4WXQwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQwNzE1MjI0MlowFTETMBEGA1UEAxMKcHJpbWFyeSBDQTBZMBMGByqGSM49AgEG
|
||||
CCqGSM49AwEHA0IABEIcOmVSobge9pLDGh6rfyFg2+ilTFmo2ICv5vrgUfIZhi8O
|
||||
fwYz5WGb7qBPRdMw9kP8BWH/lCrn2W3Ax3x2E+2jYzBhMA4GA1UdDwEB/wQEAwIB
|
||||
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdXCdDzlh7yj59xPu2LrcT6aFo
|
||||
PjAfBgNVHSMEGDAWgBS+x+IFMFb+hCJy1OQzcdzJuwDVhDANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAWWBBoygbjUEtoueGuC+jHAlr+VOBwiQPJLQA+xtbCvWSn8yIx/M1RyhY
|
||||
0/6WLMzhYA1lQAIze8CgKzqoGXXIcHif3PRZ3mRUMNdV/qGUv0oHZBzTKZVySOIm
|
||||
MLIoq7WvyVdVNxyvRalhHxiQA1Hrh+zQKjXhVPM6dpG0duTNYit9kJCCeNDzRjWc
|
||||
a/GgFyeeYMTheU3eBR6Vp2A8hy2h5xw82ul8YLwX0bCtcP12XAUzj3jFqwt6RLxW
|
||||
Wc7rvsLfgimEfulQwo2WLPWZw8bJdnPvNcUFX8f2Zvqy0Jg6fELnxO+AdHnAnI9J
|
||||
WtJr0ImA95Hw8gGTzmXOddYVGHuGLA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHzCCAgegAwIBAgIUDaEOI5nsEt9abNBbJibbQt+VZQIwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQxNTIzMjI0MlowFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut/Gbr3MvypzEmRDTl7HGaSoVIydNEZNPqDD
|
||||
jh1lqMFywB4DujTmkWLYcPJJ0RTT2NsSakteti/e1DHCuBSU0t3Q3K1paTh8aVLx
|
||||
eK0IKNlCWqX5d1aYzCNZsRjJuQgPX6p/xcNGS+RS27jmRWPpvm6n1JfMvYRa7fF+
|
||||
HnKhGNO+hDbhkQO4s0V1U+unNhshKDhTW3mBLmAEb2OHLOEaUZtYSbqr1E9tYXgU
|
||||
DiYRkeWUpQXJ6pE91fmcaZFG0SxkqWnhe7GUa6wbb/vROWph4A1ZVHympBtOYwoJ
|
||||
eibcJjBZLrugZdix8kl8NDI7SuIM/P0x0m9WkNfhJ9vSgQXlaQIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvsfiBTBW
|
||||
/oQictTkM3HcybsA1YQwHwYDVR0jBBgwFoAUvsfiBTBW/oQictTkM3HcybsA1YQw
|
||||
DQYJKoZIhvcNAQELBQADggEBALUJWitOV4xAvfNB8Z20AQ+/hdXkWVgj1VBbd++v
|
||||
+X88q1TnueKAExU5o87MCjh9jMxalZqSVN9MUbQ4Xa+tmkjayizdpFaw6TbbaMIB
|
||||
Tgqq5ATXMnOdZd46QC764Po9R9+k9hk4dNIr5gk1ifXZDMy/7jSOVARvpwzr0cTx
|
||||
flRCTgZbcK10freoU7a74/YjEpG0wggGlR4aRWfm90Im9JM3aI55zAYQFzduf56c
|
||||
HXJDLgBtbOx/ceqVrkPdvYwP9Q34tKAMiheQ0G3tTxP3Xc87gh4UEDV02oHhcbqw
|
||||
WSm+8zTfGUlchowPRdqKE66urWTep+BA9c8zUqDdoq5lE9s=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIICGTCCAZ+gAwIBAgIIJhC6ZZyZ/lQwCgYIKoZIzj0EAwMwFDESMBAGA1UEAxMJ
|
||||
VGVzdCBDQSAxMB4XDTIyMDEwNTIzNDMyNVoXDTMyMDEwNTIzNDMyNVowFDESMBAG
|
||||
A1UEAxMJVGVzdCBDQSAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETEyAhuLLOcxy
|
||||
z2UHI7ePcB5AXL1o6mLwVfzyeaGfqUevzrFcLQ7WPiypZJW1KhOW5Q2bRgcjE8y3
|
||||
fN+B8D+KT4fPtaRLtUVX6aZ0LCROFdgWjVo2DCvCq5VQnCGjW8r0o4G9MIG6MA4G
|
||||
A1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCBU/reewmUW
|
||||
iduB8xxfW5clyUmrMewrWwtJuWPA/tFvTTArBgNVHSMEJDAigCBU/reewmUWiduB
|
||||
8xxfW5clyUmrMewrWwtJuWPA/tFvTTA/BgNVHREEODA2hjRzcGlmZmU6Ly8xMTEx
|
||||
MTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqGSM49
|
||||
BAMDA2gAMGUCMA4V/Iemelne4ZB+0glmxoKV6OPQ64oKkkrcy+vo1t1RZ+7jntRx
|
||||
mxAnY3S2m35boQIxAOARpY+qfR3U3JM+vMW9KO0/KqM+y1/uvIaOA0bQex2w8bfN
|
||||
V+QjFUDmjTT1dLpc7A==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,34 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICUjCCATqgAwIBAgIUQjOIDzaM7bGW8bU69Yl0H0C4WXQwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQwNzE1MjI0MlowFTETMBEGA1UEAxMKcHJpbWFyeSBDQTBZMBMGByqGSM49AgEG
|
||||
CCqGSM49AwEHA0IABEIcOmVSobge9pLDGh6rfyFg2+ilTFmo2ICv5vrgUfIZhi8O
|
||||
fwYz5WGb7qBPRdMw9kP8BWH/lCrn2W3Ax3x2E+2jYzBhMA4GA1UdDwEB/wQEAwIB
|
||||
BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSdXCdDzlh7yj59xPu2LrcT6aFo
|
||||
PjAfBgNVHSMEGDAWgBS+x+IFMFb+hCJy1OQzcdzJuwDVhDANBgkqhkiG9w0BAQsF
|
||||
AAOCAQEAWWBBoygbjUEtoueGuC+jHAlr+VOBwiQPJLQA+xtbCvWSn8yIx/M1RyhY
|
||||
0/6WLMzhYA1lQAIze8CgKzqoGXXIcHif3PRZ3mRUMNdV/qGUv0oHZBzTKZVySOIm
|
||||
MLIoq7WvyVdVNxyvRalhHxiQA1Hrh+zQKjXhVPM6dpG0duTNYit9kJCCeNDzRjWc
|
||||
a/GgFyeeYMTheU3eBR6Vp2A8hy2h5xw82ul8YLwX0bCtcP12XAUzj3jFqwt6RLxW
|
||||
Wc7rvsLfgimEfulQwo2WLPWZw8bJdnPvNcUFX8f2Zvqy0Jg6fELnxO+AdHnAnI9J
|
||||
WtJr0ImA95Hw8gGTzmXOddYVGHuGLA==
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDHzCCAgegAwIBAgIUDaEOI5nsEt9abNBbJibbQt+VZQIwDQYJKoZIhvcNAQEL
|
||||
BQAwFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMB4XDTIyMDEwNTIzMjIxMloXDTIy
|
||||
MDQxNTIzMjI0MlowFzEVMBMGA1UEAxMMY29ycG9yYXRlIENBMIIBIjANBgkqhkiG
|
||||
9w0BAQEFAAOCAQ8AMIIBCgKCAQEAut/Gbr3MvypzEmRDTl7HGaSoVIydNEZNPqDD
|
||||
jh1lqMFywB4DujTmkWLYcPJJ0RTT2NsSakteti/e1DHCuBSU0t3Q3K1paTh8aVLx
|
||||
eK0IKNlCWqX5d1aYzCNZsRjJuQgPX6p/xcNGS+RS27jmRWPpvm6n1JfMvYRa7fF+
|
||||
HnKhGNO+hDbhkQO4s0V1U+unNhshKDhTW3mBLmAEb2OHLOEaUZtYSbqr1E9tYXgU
|
||||
DiYRkeWUpQXJ6pE91fmcaZFG0SxkqWnhe7GUa6wbb/vROWph4A1ZVHympBtOYwoJ
|
||||
eibcJjBZLrugZdix8kl8NDI7SuIM/P0x0m9WkNfhJ9vSgQXlaQIDAQABo2MwYTAO
|
||||
BgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUvsfiBTBW
|
||||
/oQictTkM3HcybsA1YQwHwYDVR0jBBgwFoAUvsfiBTBW/oQictTkM3HcybsA1YQw
|
||||
DQYJKoZIhvcNAQELBQADggEBALUJWitOV4xAvfNB8Z20AQ+/hdXkWVgj1VBbd++v
|
||||
+X88q1TnueKAExU5o87MCjh9jMxalZqSVN9MUbQ4Xa+tmkjayizdpFaw6TbbaMIB
|
||||
Tgqq5ATXMnOdZd46QC764Po9R9+k9hk4dNIr5gk1ifXZDMy/7jSOVARvpwzr0cTx
|
||||
flRCTgZbcK10freoU7a74/YjEpG0wggGlR4aRWfm90Im9JM3aI55zAYQFzduf56c
|
||||
HXJDLgBtbOx/ceqVrkPdvYwP9Q34tKAMiheQ0G3tTxP3Xc87gh4UEDV02oHhcbqw
|
||||
WSm+8zTfGUlchowPRdqKE66urWTep+BA9c8zUqDdoq5lE9s=
|
||||
-----END CERTIFICATE-----
|
|
@ -66,14 +66,15 @@ func (r IndexedCARoots) Active() *CARoot {
|
|||
|
||||
// CARoot represents a root CA certificate that is trusted.
|
||||
type CARoot struct {
|
||||
// ID is a globally unique ID (UUID) representing this CA root.
|
||||
// ID is a globally unique ID (UUID) representing this CA chain. It is
|
||||
// calculated from the SHA1 of the primary CA certificate.
|
||||
ID string
|
||||
|
||||
// Name is a human-friendly name for this CA root. This value is
|
||||
// opaque to Consul and is not used for anything internally.
|
||||
Name string
|
||||
|
||||
// SerialNumber is the x509 serial number of the certificate.
|
||||
// SerialNumber is the x509 serial number of the primary CA certificate.
|
||||
SerialNumber uint64
|
||||
|
||||
// SigningKeyID is the connect.HexString encoded id of the public key that
|
||||
|
@ -96,9 +97,12 @@ type CARoot struct {
|
|||
// future flexibility.
|
||||
ExternalTrustDomain string
|
||||
|
||||
// Time validity bounds.
|
||||
// NotBefore is the x509.Certificate.NotBefore value of the primary CA
|
||||
// certificate. This value should generally be a time in the past.
|
||||
NotBefore time.Time
|
||||
NotAfter time.Time
|
||||
// NotAfter is the x509.Certificate.NotAfter value of the primary CA
|
||||
// certificate. This is the time when the certificate will expire.
|
||||
NotAfter time.Time
|
||||
|
||||
// RootCert is the PEM-encoded public certificate for the root CA. The
|
||||
// certificate is the same for all federated clusters.
|
||||
|
|
|
@ -116,16 +116,25 @@ The configuration options are listed below.
|
|||
|
||||
|
||||
- `RootPKIPath` / `root_pki_path` (`string: <required>`) - The path to
|
||||
a PKI secrets engine for the root certificate. If the path does not
|
||||
a PKI secrets engine for the root certificate.
|
||||
|
||||
If the path does not
|
||||
exist, Consul will mount a new PKI secrets engine at the specified path with the
|
||||
`RootCertTTL` value as the root certificate's TTL. If the `RootCertTTL` is not set,
|
||||
a [`max_lease_ttl`](https://www.vaultproject.io/api/system/mounts#max_lease_ttl)
|
||||
of 87600 hours, or 10 years is applied by default as of Consul 1.11 and later. Prior to Consul 1.11,
|
||||
the root certificate TTL was set to 8760 hour, or 1 year, and was not configurable.
|
||||
The root certificate will expire at the end of the specified period.
|
||||
|
||||
|
||||
When WAN Federation is enabled, each secondary datacenter must use the same Vault cluster and share the same `root_pki_path`
|
||||
with the primary datacenter.
|
||||
with the primary datacenter.
|
||||
|
||||
To use an intermediate certificate as the primary CA in Consul initialize the
|
||||
`RootPKIPath` in Vault with a PEM bundle. The first certificate in the bundle
|
||||
must be the intermediate certificate that Consul will use as the primary CA.
|
||||
The last certificate in the bundle must be a root certificate. The bundle
|
||||
must contain a valid chain, where each certificate is followed by the certificate
|
||||
that authorized it.
|
||||
|
||||
- `IntermediatePKIPath` / `intermediate_pki_path` (`string: <required>`) -
|
||||
The path to a PKI secrets engine for the generated intermediate certificate.
|
||||
|
|
Loading…
Reference in New Issue