Fix support for RSA CA keys in Connect. (#6638)
* Allow RSA CA certs for consul and vault providers to correctly sign EC leaf certs. * Ensure key type ad bits are populated from CA cert and clean up tests * Add integration test and fix error when initializing secondary CA with RSA key. * Add more tests, fix review feedback * Update docs with key type config and output * Apply suggestions from code review Co-Authored-By: R.B. Boyer <rb@hashicorp.com>
This commit is contained in:
parent
e0e3ec1f32
commit
5f405c3277
|
@ -525,6 +525,17 @@ func (c *ConnectCALeaf) generateNewLeaf(req *ConnectCALeafRequest,
|
|||
}
|
||||
|
||||
// Create a new private key
|
||||
|
||||
// TODO: for now we always generate EC keys on clients regardless of the key
|
||||
// type being used by the active CA. This is fine and allowed in TLS1.2 and
|
||||
// signing EC CSRs with an RSA key is supported by all current CA providers so
|
||||
// it's OK. IFF we ever need to support a CA provider that refuses to sign a
|
||||
// CSR with a different signature algorithm, or if we have compatibility
|
||||
// issues with external PKI systems that require EC certs be signed with ECDSA
|
||||
// from the CA (this was required in TLS1.1 but not in 1.2) then we can
|
||||
// instead intelligently pick the key type we generate here based on the key
|
||||
// type of the active signing CA. We already have that loaded since we need
|
||||
// the trust domain.
|
||||
pk, pkPEM, err := connect.GeneratePrivateKey()
|
||||
if err != nil {
|
||||
return result, err
|
||||
|
|
|
@ -341,11 +341,14 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
|||
// cluster. A minute is more than enough for typical DC clock drift.
|
||||
effectiveNow := time.Now().Add(-1 * time.Minute)
|
||||
template := x509.Certificate{
|
||||
SerialNumber: sn,
|
||||
Subject: pkix.Name{CommonName: subject},
|
||||
URIs: csr.URIs,
|
||||
Signature: csr.Signature,
|
||||
SignatureAlgorithm: csr.SignatureAlgorithm,
|
||||
SerialNumber: sn,
|
||||
Subject: pkix.Name{CommonName: subject},
|
||||
URIs: csr.URIs,
|
||||
Signature: csr.Signature,
|
||||
// We use the correct signature algorithm for the CA key we are signing with
|
||||
// regardless of the algorithm used to sign the CSR signature above since
|
||||
// the leaf might use a different key type.
|
||||
SignatureAlgorithm: connect.SigAlgoForKey(signer),
|
||||
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
|
||||
PublicKey: csr.PublicKey,
|
||||
BasicConstraintsValid: true,
|
||||
|
@ -413,7 +416,7 @@ func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
subjectKeyId, err := connect.KeyId(csr.PublicKey)
|
||||
subjectKeyID, err := connect.KeyId(csr.PublicKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -436,7 +439,7 @@ func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
|||
Subject: csr.Subject,
|
||||
URIs: csr.URIs,
|
||||
Signature: csr.Signature,
|
||||
SignatureAlgorithm: csr.SignatureAlgorithm,
|
||||
SignatureAlgorithm: connect.SigAlgoForKey(signer),
|
||||
PublicKeyAlgorithm: csr.PublicKeyAlgorithm,
|
||||
PublicKey: csr.PublicKey,
|
||||
BasicConstraintsValid: true,
|
||||
|
@ -447,7 +450,7 @@ func (c *ConsulProvider) SignIntermediate(csr *x509.CertificateRequest) (string,
|
|||
MaxPathLenZero: true,
|
||||
NotAfter: effectiveNow.AddDate(1, 0, 0),
|
||||
NotBefore: effectiveNow,
|
||||
SubjectKeyId: subjectKeyId,
|
||||
SubjectKeyId: subjectKeyID,
|
||||
}
|
||||
|
||||
// Create the certificate, PEM encode it and return that value.
|
||||
|
|
|
@ -62,7 +62,7 @@ func newMockDelegate(t *testing.T, conf *structs.CAConfiguration) *consulCAMockD
|
|||
|
||||
func testConsulCAConfig() *structs.CAConfiguration {
|
||||
return &structs.CAConfiguration{
|
||||
ClusterID: "asdf",
|
||||
ClusterID: connect.TestClusterID,
|
||||
Provider: "consul",
|
||||
Config: map[string]interface{}{
|
||||
// Tests duration parsing after msgpack type mangling during raft apply.
|
||||
|
@ -128,120 +128,138 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
|||
func TestConsulCAProvider_SignLeaf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
require := require.New(t)
|
||||
conf := testConsulCAConfig()
|
||||
conf.Config["LeafCertTTL"] = "1h"
|
||||
delegate := newMockDelegate(t, conf)
|
||||
for _, tc := range KeyTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
conf := testConsulCAConfig()
|
||||
conf.Config["LeafCertTTL"] = "1h"
|
||||
conf.Config["PrivateKeyType"] = tc.KeyType
|
||||
conf.Config["PrivateKeyBits"] = tc.KeyBits
|
||||
delegate := newMockDelegate(t, conf)
|
||||
|
||||
provider := &ConsulProvider{Delegate: delegate}
|
||||
require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
|
||||
require.NoError(provider.GenerateRoot())
|
||||
provider := &ConsulProvider{Delegate: delegate}
|
||||
require.NoError(provider.Configure(conf.ClusterID, true, conf.Config))
|
||||
require.NoError(provider.GenerateRoot())
|
||||
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "dc1",
|
||||
Service: "foo",
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "dc1",
|
||||
Service: "foo",
|
||||
}
|
||||
|
||||
// Generate a leaf cert for the service.
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.Equal(parsed.Subject.CommonName, "foo")
|
||||
require.Equal(uint64(2), parsed.SerialNumber.Uint64())
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
}
|
||||
|
||||
// Generate a new cert for another service and make sure
|
||||
// the serial number is incremented.
|
||||
spiffeService.Service = "bar"
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.Equal(parsed.Subject.CommonName, "bar")
|
||||
require.Equal(parsed.SerialNumber.Uint64(), uint64(2))
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
require.True(time.Until(parsed.NotAfter) < 3*24*time.Hour)
|
||||
require.True(parsed.NotBefore.Before(time.Now()))
|
||||
}
|
||||
|
||||
spiffeAgent := &connect.SpiffeIDAgent{
|
||||
Host: "node1",
|
||||
Datacenter: "dc1",
|
||||
Agent: "uuid",
|
||||
}
|
||||
// Generate a leaf cert for an agent.
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeAgent)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(spiffeAgent.URI(), parsed.URIs[0])
|
||||
require.Equal("uuid", parsed.Subject.CommonName)
|
||||
require.Equal(uint64(2), parsed.SerialNumber.Uint64())
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Generate a leaf cert for the service.
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.Equal(parsed.Subject.CommonName, "foo")
|
||||
require.Equal(uint64(2), parsed.SerialNumber.Uint64())
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
}
|
||||
|
||||
// Generate a new cert for another service and make sure
|
||||
// the serial number is incremented.
|
||||
spiffeService.Service = "bar"
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.Equal(parsed.Subject.CommonName, "bar")
|
||||
require.Equal(parsed.SerialNumber.Uint64(), uint64(2))
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
require.True(time.Until(parsed.NotAfter) < 3*24*time.Hour)
|
||||
require.True(parsed.NotBefore.Before(time.Now()))
|
||||
}
|
||||
|
||||
spiffeAgent := &connect.SpiffeIDAgent{
|
||||
Host: "node1",
|
||||
Datacenter: "dc1",
|
||||
Agent: "uuid",
|
||||
}
|
||||
// Generate a leaf cert for an agent.
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeAgent)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(spiffeAgent.URI(), parsed.URIs[0])
|
||||
require.Equal("uuid", parsed.Subject.CommonName)
|
||||
require.Equal(uint64(2), parsed.SerialNumber.Uint64())
|
||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestConsulCAProvider_CrossSignCA(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
conf1 := testConsulCAConfig()
|
||||
delegate1 := newMockDelegate(t, conf1)
|
||||
provider1 := &ConsulProvider{Delegate: delegate1}
|
||||
require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
|
||||
require.NoError(provider1.GenerateRoot())
|
||||
tests := CASigningKeyTypeCases()
|
||||
|
||||
conf2 := testConsulCAConfig()
|
||||
conf2.CreateIndex = 10
|
||||
delegate2 := newMockDelegate(t, conf2)
|
||||
provider2 := &ConsulProvider{Delegate: delegate2}
|
||||
require.NoError(provider2.Configure(conf2.ClusterID, true, conf2.Config))
|
||||
require.NoError(provider2.GenerateRoot())
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testCrossSignProviders(t, provider1, provider2)
|
||||
conf1 := testConsulCAConfig()
|
||||
delegate1 := newMockDelegate(t, conf1)
|
||||
provider1 := &ConsulProvider{Delegate: delegate1}
|
||||
conf1.Config["PrivateKeyType"] = tc.SigningKeyType
|
||||
conf1.Config["PrivateKeyBits"] = tc.SigningKeyBits
|
||||
require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
|
||||
require.NoError(provider1.GenerateRoot())
|
||||
|
||||
conf2 := testConsulCAConfig()
|
||||
conf2.CreateIndex = 10
|
||||
delegate2 := newMockDelegate(t, conf2)
|
||||
provider2 := &ConsulProvider{Delegate: delegate2}
|
||||
conf2.Config["PrivateKeyType"] = tc.CSRKeyType
|
||||
conf2.Config["PrivateKeyBits"] = tc.CSRKeyBits
|
||||
require.NoError(provider2.Configure(conf2.ClusterID, true, conf2.Config))
|
||||
require.NoError(provider2.GenerateRoot())
|
||||
|
||||
testCrossSignProviders(t, provider1, provider2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) {
|
||||
|
@ -328,21 +346,34 @@ func testCrossSignProviders(t *testing.T, provider1, provider2 Provider) {
|
|||
|
||||
func TestConsulProvider_SignIntermediate(t *testing.T) {
|
||||
t.Parallel()
|
||||
require := require.New(t)
|
||||
|
||||
conf1 := testConsulCAConfig()
|
||||
delegate1 := newMockDelegate(t, conf1)
|
||||
provider1 := &ConsulProvider{Delegate: delegate1}
|
||||
require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
|
||||
require.NoError(provider1.GenerateRoot())
|
||||
tests := CASigningKeyTypeCases()
|
||||
|
||||
conf2 := testConsulCAConfig()
|
||||
conf2.CreateIndex = 10
|
||||
delegate2 := newMockDelegate(t, conf2)
|
||||
provider2 := &ConsulProvider{Delegate: delegate2}
|
||||
require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config))
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
conf1 := testConsulCAConfig()
|
||||
delegate1 := newMockDelegate(t, conf1)
|
||||
provider1 := &ConsulProvider{Delegate: delegate1}
|
||||
conf1.Config["PrivateKeyType"] = tc.SigningKeyType
|
||||
conf1.Config["PrivateKeyBits"] = tc.SigningKeyBits
|
||||
require.NoError(provider1.Configure(conf1.ClusterID, true, conf1.Config))
|
||||
require.NoError(provider1.GenerateRoot())
|
||||
|
||||
conf2 := testConsulCAConfig()
|
||||
conf2.CreateIndex = 10
|
||||
delegate2 := newMockDelegate(t, conf2)
|
||||
provider2 := &ConsulProvider{Delegate: delegate2}
|
||||
conf2.Config["PrivateKeyType"] = tc.CSRKeyType
|
||||
conf2.Config["PrivateKeyBits"] = tc.CSRKeyBits
|
||||
require.NoError(provider2.Configure(conf2.ClusterID, false, conf2.Config))
|
||||
|
||||
testSignIntermediateCrossDC(t, provider1, provider2)
|
||||
})
|
||||
}
|
||||
|
||||
testSignIntermediateCrossDC(t, provider1, provider2)
|
||||
}
|
||||
|
||||
func testSignIntermediateCrossDC(t *testing.T, provider1, provider2 Provider) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package ca
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
@ -83,6 +84,22 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func assertCorrectKeyType(t *testing.T, want, certPEM string) {
|
||||
t.Helper()
|
||||
|
||||
cert, err := connect.ParseCert(certPEM)
|
||||
require.NoError(t, err)
|
||||
|
||||
switch want {
|
||||
case "ec":
|
||||
require.Equal(t, x509.ECDSA, cert.PublicKeyAlgorithm)
|
||||
case "rsa":
|
||||
require.Equal(t, x509.RSA, cert.PublicKeyAlgorithm)
|
||||
default:
|
||||
t.Fatal("test doesn't support key type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -90,61 +107,82 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
require := require.New(t)
|
||||
provider, testVault := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
})
|
||||
defer testVault.Stop()
|
||||
for _, tc := range KeyTestCases {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
provider, testVault := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
"PrivateKeyType": tc.KeyType,
|
||||
"PrivateKeyBits": tc.KeyBits,
|
||||
})
|
||||
defer testVault.Stop()
|
||||
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "dc1",
|
||||
Service: "foo",
|
||||
}
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "dc1",
|
||||
Service: "foo",
|
||||
}
|
||||
|
||||
// Generate a leaf cert for the service.
|
||||
var firstSerial uint64
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
rootPEM, err := provider.ActiveRoot()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.KeyType, rootPEM)
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
intPEM, err := provider.ActiveIntermediate()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.KeyType, intPEM)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
// Generate a leaf cert for the service.
|
||||
var firstSerial uint64
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
firstSerial = parsed.SerialNumber.Uint64()
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
}
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
// Generate a new cert for another service and make sure
|
||||
// the serial number is unique.
|
||||
spiffeService.Service = "bar"
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
firstSerial = parsed.SerialNumber.Uint64()
|
||||
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
now := time.Now()
|
||||
require.True(parsed.NotAfter.Sub(now) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(now))
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
// Make sure we can validate the cert as expected.
|
||||
require.NoError(connect.ValidateLeaf(rootPEM, cert, []string{intPEM}))
|
||||
}
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.NotEqual(firstSerial, parsed.SerialNumber.Uint64())
|
||||
// Generate a new cert for another service and make sure
|
||||
// the serial number is unique.
|
||||
spiffeService.Service = "bar"
|
||||
{
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
require.True(time.Until(parsed.NotAfter) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(time.Now()))
|
||||
csr, err := connect.ParseCSR(raw)
|
||||
require.NoError(err)
|
||||
|
||||
cert, err := provider.Sign(csr)
|
||||
require.NoError(err)
|
||||
|
||||
parsed, err := connect.ParseCert(cert)
|
||||
require.NoError(err)
|
||||
require.Equal(parsed.URIs[0], spiffeService.URI())
|
||||
require.NotEqual(firstSerial, parsed.SerialNumber.Uint64())
|
||||
|
||||
// Ensure the cert is valid now and expires within the correct limit.
|
||||
require.True(time.Until(parsed.NotAfter) < time.Hour)
|
||||
require.True(parsed.NotBefore.Before(time.Now()))
|
||||
|
||||
// Make sure we can validate the cert as expected.
|
||||
require.NoError(connect.ValidateLeaf(rootPEM, cert, []string{intPEM}))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,13 +193,54 @@ func TestVaultCAProvider_CrossSignCA(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
provider1, testVault1 := testVaultProvider(t)
|
||||
defer testVault1.Stop()
|
||||
tests := CASigningKeyTypeCases()
|
||||
|
||||
provider2, testVault2 := testVaultProvider(t)
|
||||
defer testVault2.Stop()
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
testCrossSignProviders(t, provider1, provider2)
|
||||
if tc.SigningKeyType != tc.CSRKeyType {
|
||||
// See https://github.com/hashicorp/vault/issues/7709
|
||||
t.Skip("Vault doesn't support cross-signing different key types yet.")
|
||||
}
|
||||
provider1, testVault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
"PrivateKeyType": tc.SigningKeyType,
|
||||
"PrivateKeyBits": tc.SigningKeyBits,
|
||||
})
|
||||
defer testVault1.Stop()
|
||||
|
||||
{
|
||||
rootPEM, err := provider1.ActiveRoot()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.SigningKeyType, rootPEM)
|
||||
|
||||
intPEM, err := provider1.ActiveIntermediate()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.SigningKeyType, intPEM)
|
||||
}
|
||||
|
||||
provider2, testVault2 := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
"PrivateKeyType": tc.CSRKeyType,
|
||||
"PrivateKeyBits": tc.CSRKeyBits,
|
||||
})
|
||||
defer testVault2.Stop()
|
||||
|
||||
{
|
||||
rootPEM, err := provider2.ActiveRoot()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.CSRKeyType, rootPEM)
|
||||
|
||||
intPEM, err := provider2.ActiveIntermediate()
|
||||
require.NoError(err)
|
||||
assertCorrectKeyType(t, tc.CSRKeyType, intPEM)
|
||||
}
|
||||
|
||||
testCrossSignProviders(t, provider1, provider2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultProvider_SignIntermediate(t *testing.T) {
|
||||
|
@ -171,13 +250,28 @@ func TestVaultProvider_SignIntermediate(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
provider1, testVault1 := testVaultProvider(t)
|
||||
defer testVault1.Stop()
|
||||
tests := CASigningKeyTypeCases()
|
||||
|
||||
provider2, testVault2 := testVaultProviderWithConfig(t, false, nil)
|
||||
defer testVault2.Stop()
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(tc.Desc, func(t *testing.T) {
|
||||
provider1, testVault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
"PrivateKeyType": tc.SigningKeyType,
|
||||
"PrivateKeyBits": tc.SigningKeyBits,
|
||||
})
|
||||
defer testVault1.Stop()
|
||||
|
||||
testSignIntermediateCrossDC(t, provider1, provider2)
|
||||
provider2, testVault2 := testVaultProviderWithConfig(t, false, map[string]interface{}{
|
||||
"LeafCertTTL": "1h",
|
||||
"PrivateKeyType": tc.CSRKeyType,
|
||||
"PrivateKeyBits": tc.CSRKeyBits,
|
||||
})
|
||||
defer testVault2.Stop()
|
||||
|
||||
testSignIntermediateCrossDC(t, provider1, provider2)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVaultProvider_SignIntermediateConsul(t *testing.T) {
|
||||
|
@ -241,7 +335,7 @@ func testVaultProviderWithConfig(t *testing.T, isRoot bool, rawConf map[string]i
|
|||
|
||||
provider := &VaultProvider{}
|
||||
|
||||
if err := provider.Configure("asdf", isRoot, conf); err != nil {
|
||||
if err := provider.Configure(connect.TestClusterID, isRoot, conf); err != nil {
|
||||
testVault.Stop()
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
package ca
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/agent/connect"
|
||||
)
|
||||
|
||||
// KeyTestCases is a list of the important CA key types that we should test
|
||||
// against when signing. For now leaf keys are always EC P256 but CA can be EC
|
||||
// (any NIST curve) or RSA (2048, 4096). Providers must be able to complete all
|
||||
// signing operations with both types that includes:
|
||||
// - Sign must be able to sign EC P256 leaf with all these types of CA key
|
||||
// - CrossSignCA must be able to sign all these types of new CA key with all
|
||||
// these types of old CA key.
|
||||
// - SignIntermediate muse bt able to sign all the types of secondary
|
||||
// intermediate CA key with all these types of primary CA key
|
||||
var KeyTestCases = []struct {
|
||||
Desc string
|
||||
KeyType string
|
||||
KeyBits int
|
||||
}{
|
||||
{
|
||||
Desc: "Default Key Type (EC 256)",
|
||||
KeyType: connect.DefaultPrivateKeyType,
|
||||
KeyBits: connect.DefaultPrivateKeyBits,
|
||||
},
|
||||
{
|
||||
Desc: "RSA 2048",
|
||||
KeyType: "rsa",
|
||||
KeyBits: 2048,
|
||||
},
|
||||
}
|
||||
|
||||
// CASigningKeyTypes is a struct with params for tests that sign one CA CSR with
|
||||
// another CA key.
|
||||
type CASigningKeyTypes struct {
|
||||
Desc string
|
||||
SigningKeyType string
|
||||
SigningKeyBits int
|
||||
CSRKeyType string
|
||||
CSRKeyBits int
|
||||
}
|
||||
|
||||
// CASigningKeyTypeCases returns the cross-product of the important supported CA
|
||||
// key types for generating table tests for CA signing tests (CrossSignCA and
|
||||
// SignIntermediate).
|
||||
func CASigningKeyTypeCases() []CASigningKeyTypes {
|
||||
cases := make([]CASigningKeyTypes, 0, len(KeyTestCases)*len(KeyTestCases))
|
||||
for _, outer := range KeyTestCases {
|
||||
for _, inner := range KeyTestCases {
|
||||
cases = append(cases, CASigningKeyTypes{
|
||||
Desc: fmt.Sprintf("%s-%d signing %s-%d", outer.KeyType, outer.KeyBits,
|
||||
inner.KeyType, inner.KeyBits),
|
||||
SigningKeyType: outer.KeyType,
|
||||
SigningKeyBits: outer.KeyBits,
|
||||
CSRKeyType: inner.KeyType,
|
||||
CSRKeyBits: inner.KeyBits,
|
||||
})
|
||||
}
|
||||
}
|
||||
return cases
|
||||
}
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
|
@ -11,12 +12,41 @@ import (
|
|||
"net/url"
|
||||
)
|
||||
|
||||
// SigAlgoForKey returns the preferred x509.SignatureAlgorithm for a given key
|
||||
// based on it's type. If the key type is not supported we return
|
||||
// ECDSAWithSHA256 on the basis that it will fail anyway and we've already type
|
||||
// checked keys by the time we call this in general.
|
||||
func SigAlgoForKey(key crypto.Signer) x509.SignatureAlgorithm {
|
||||
if _, ok := key.(*rsa.PrivateKey); ok {
|
||||
return x509.SHA256WithRSA
|
||||
}
|
||||
// We default to ECDSA but don't bother detecting invalid key types as we do
|
||||
// that in lots of other places and it will fail anyway if we try to sign with
|
||||
// an incompatible type.
|
||||
return x509.ECDSAWithSHA256
|
||||
}
|
||||
|
||||
// SigAlgoForKeyType returns the preferred x509.SignatureAlgorithm for a given
|
||||
// key type string from configuration or an existing cert. If the key type is
|
||||
// not supported we return ECDSAWithSHA256 on the basis that it will fail anyway
|
||||
// and we've already type checked config by the time we call this in general.
|
||||
func SigAlgoForKeyType(keyType string) x509.SignatureAlgorithm {
|
||||
switch keyType {
|
||||
case "rsa":
|
||||
return x509.SHA256WithRSA
|
||||
case "ec":
|
||||
fallthrough
|
||||
default:
|
||||
return x509.ECDSAWithSHA256
|
||||
}
|
||||
}
|
||||
|
||||
// CreateCSR returns a CSR to sign the given service along with the PEM-encoded
|
||||
// private key for this certificate.
|
||||
func CreateCSR(uri CertURI, privateKey crypto.Signer, extensions ...pkix.Extension) (string, error) {
|
||||
template := &x509.CertificateRequest{
|
||||
URIs: []*url.URL{uri.URI()},
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
SignatureAlgorithm: SigAlgoForKey(privateKey),
|
||||
ExtraExtensions: extensions,
|
||||
}
|
||||
|
||||
|
|
|
@ -121,7 +121,8 @@ func TestValidateBadConfigs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Tests the ability of a CA to sign a CSR using a different key type. If the key types differ, the test should fail.
|
||||
// Tests the ability of a CA to sign a CSR using a different key type. This is
|
||||
// allowed by TLS 1.2 and should succeed in all combinations.
|
||||
func TestSignatureMismatches(t *testing.T) {
|
||||
t.Parallel()
|
||||
r := require.New(t)
|
||||
|
@ -135,15 +136,11 @@ func TestSignatureMismatches(t *testing.T) {
|
|||
r.Equal(p1.keyType, ca.PrivateKeyType)
|
||||
r.Equal(p1.keyBits, ca.PrivateKeyBits)
|
||||
certPEM, keyPEM, err := testLeaf(t, "foobar.service.consul", ca, p2.keyType, p2.keyBits)
|
||||
if p1.keyType == p2.keyType {
|
||||
r.NoError(err)
|
||||
_, err := ParseCert(certPEM)
|
||||
r.NoError(err)
|
||||
_, err = ParseSigner(keyPEM)
|
||||
r.NoError(err)
|
||||
} else {
|
||||
r.Error(err)
|
||||
}
|
||||
r.NoError(err)
|
||||
_, err = ParseCert(certPEM)
|
||||
r.NoError(err)
|
||||
_, err = ParseSigner(keyPEM)
|
||||
r.NoError(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -206,3 +206,16 @@ func IsHexString(input []byte) bool {
|
|||
_, err := hex.DecodeString(s)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// KeyInfoFromCert returns the key type and key bit length for the key used by
|
||||
// the certificate.
|
||||
func KeyInfoFromCert(cert *x509.Certificate) (keyType string, keyBits int, err error) {
|
||||
switch k := cert.PublicKey.(type) {
|
||||
case *ecdsa.PublicKey:
|
||||
return "ec", k.Curve.Params().BitSize, nil
|
||||
case *rsa.PublicKey:
|
||||
return "rsa", k.N.BitLen(), nil
|
||||
default:
|
||||
return "", 0, fmt.Errorf("unsupported key type")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,35 @@ const TestClusterID = "11111111-2222-3333-4444-555555555555"
|
|||
// unique names for the CA certs.
|
||||
var testCACounter uint64
|
||||
|
||||
// ValidateLeaf is a convenience helper that returns an error if the certificate
|
||||
// provided in leadPEM does not validate against the CAs provided. If there is
|
||||
// an intermediate CA then it's cert must be in caPEMs as well as the root.
|
||||
func ValidateLeaf(caPEM string, leafPEM string, intermediatePEMs []string) error {
|
||||
roots := x509.NewCertPool()
|
||||
ok := roots.AppendCertsFromPEM([]byte(caPEM))
|
||||
if !ok {
|
||||
return fmt.Errorf("Failed to add root CA")
|
||||
}
|
||||
|
||||
intermediates := x509.NewCertPool()
|
||||
for idx, ca := range intermediatePEMs {
|
||||
ok := intermediates.AppendCertsFromPEM([]byte(ca))
|
||||
if !ok {
|
||||
return fmt.Errorf("Failed to add intermediate CA at index %d to pool", idx)
|
||||
}
|
||||
}
|
||||
|
||||
leaf, err := ParseCert(leafPEM)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = leaf.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
Intermediates: intermediates,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int) *structs.CARoot {
|
||||
var result structs.CARoot
|
||||
result.Active = true
|
||||
|
@ -36,8 +65,6 @@ func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int) *struc
|
|||
signer, keyPEM := testPrivateKey(t, keyType, keyBits)
|
||||
result.SigningKey = keyPEM
|
||||
result.SigningKeyID = EncodeSigningKeyID(testKeyID(t, signer.Public()))
|
||||
result.PrivateKeyType = keyType
|
||||
result.PrivateKeyBits = keyBits
|
||||
|
||||
// The serial number for the cert
|
||||
sn, err := testSerialNumber()
|
||||
|
@ -83,6 +110,8 @@ func testCA(t testing.T, xc *structs.CARoot, keyType string, keyBits int) *struc
|
|||
result.SerialNumber = uint64(sn.Int64())
|
||||
result.NotBefore = template.NotBefore.UTC()
|
||||
result.NotAfter = template.NotAfter.UTC()
|
||||
result.PrivateKeyType = keyType
|
||||
result.PrivateKeyBits = keyBits
|
||||
|
||||
// If there is a prior CA to cross-sign with, then we need to create that
|
||||
// and set it as the signing cert.
|
||||
|
@ -174,9 +203,9 @@ func testLeaf(t testing.T, service string, root *structs.CARoot, keyType string,
|
|||
return "", "", fmt.Errorf("failed to generate private key: %s", err)
|
||||
}
|
||||
|
||||
sigAlgo := x509.ECDSAWithSHA256
|
||||
if keyType == "rsa" {
|
||||
sigAlgo = x509.SHA256WithRSA
|
||||
rootKeyType, _, err := KeyInfoFromCert(caCert)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("error getting CA key type: %s", err)
|
||||
}
|
||||
|
||||
// Cert template for generation
|
||||
|
@ -184,7 +213,7 @@ func testLeaf(t testing.T, service string, root *structs.CARoot, keyType string,
|
|||
SerialNumber: sn,
|
||||
Subject: pkix.Name{CommonName: service},
|
||||
URIs: []*url.URL{spiffeId.URI()},
|
||||
SignatureAlgorithm: sigAlgo,
|
||||
SignatureAlgorithm: SigAlgoForKeyType(rootKeyType),
|
||||
BasicConstraintsValid: true,
|
||||
KeyUsage: x509.KeyUsageDataEncipherment |
|
||||
x509.KeyUsageKeyAgreement |
|
||||
|
@ -218,7 +247,12 @@ func testLeaf(t testing.T, service string, root *structs.CARoot, keyType string,
|
|||
// TestLeaf returns a valid leaf certificate and it's private key for the named
|
||||
// service with the given CA Root.
|
||||
func TestLeaf(t testing.T, service string, root *structs.CARoot) (string, string) {
|
||||
certPEM, keyPEM, err := testLeaf(t, service, root, root.PrivateKeyType, root.PrivateKeyBits)
|
||||
// Currently we only support EC leaf keys and certs even if the CA is using
|
||||
// RSA. We might allow Leafs to follow the signing CA key type later if we
|
||||
// need to for compatibility sake but this is allowed by TLS 1.2 and works with
|
||||
// both openssl verify (which we use as a sanity check in our tests of this
|
||||
// package) and Go's TLS verification.
|
||||
certPEM, keyPEM, err := testLeaf(t, service, root, DefaultPrivateKeyType, DefaultPrivateKeyBits)
|
||||
if err != nil {
|
||||
t.Fatalf(err.Error())
|
||||
}
|
||||
|
@ -325,8 +359,6 @@ func testCAConfigSet(t testing.T, a TestAgentRPC,
|
|||
"PrivateKey": ca.SigningKey,
|
||||
"RootCert": ca.RootCert,
|
||||
"RotationPeriod": 180 * 24 * time.Hour,
|
||||
"PrivateKeyType": ca.PrivateKeyType,
|
||||
"PrivateKeyBits": ca.PrivateKeyBits,
|
||||
},
|
||||
}
|
||||
args := &structs.CARequest{
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// hasOpenSSL is used to determine if the openssl CLI exists for unit tests.
|
||||
|
@ -35,7 +36,7 @@ func testCAAndLeaf(t *testing.T, keyType string, keyBits int) {
|
|||
return
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
|
||||
// Create the certs
|
||||
ca := TestCAWithKeyType(t, nil, keyType, keyBits)
|
||||
|
@ -43,12 +44,12 @@ func testCAAndLeaf(t *testing.T, keyType string, keyBits int) {
|
|||
|
||||
// Create a temporary directory for storing the certs
|
||||
td, err := ioutil.TempDir("", "consul")
|
||||
assert.Nil(err)
|
||||
require.NoError(err)
|
||||
defer os.RemoveAll(td)
|
||||
|
||||
// Write the cert
|
||||
assert.Nil(ioutil.WriteFile(filepath.Join(td, "ca.pem"), []byte(ca.RootCert), 0644))
|
||||
assert.Nil(ioutil.WriteFile(filepath.Join(td, "leaf.pem"), []byte(leaf), 0644))
|
||||
require.NoError(ioutil.WriteFile(filepath.Join(td, "ca.pem"), []byte(ca.RootCert), 0644))
|
||||
require.NoError(ioutil.WriteFile(filepath.Join(td, "leaf.pem"), []byte(leaf[:]), 0644))
|
||||
|
||||
// Use OpenSSL to verify so we have an external, known-working process
|
||||
// that can verify this outside of our own implementations.
|
||||
|
@ -56,8 +57,11 @@ func testCAAndLeaf(t *testing.T, keyType string, keyBits int) {
|
|||
"openssl", "verify", "-verbose", "-CAfile", "ca.pem", "leaf.pem")
|
||||
cmd.Dir = td
|
||||
output, err := cmd.Output()
|
||||
t.Log(string(output))
|
||||
assert.Nil(err)
|
||||
t.Log("STDOUT:", string(output))
|
||||
if ee, ok := err.(*exec.ExitError); ok {
|
||||
t.Log("STDERR:", string(ee.Stderr))
|
||||
}
|
||||
require.NoError(err)
|
||||
}
|
||||
|
||||
// Test cross-signing.
|
||||
|
|
|
@ -297,56 +297,73 @@ func TestConnectCAConfig_TriggerRotation(t *testing.T) {
|
|||
func TestConnectCASign(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
dir1, s1 := testServer(t)
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
defer codec.Close()
|
||||
|
||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
// Generate a CSR and request signing
|
||||
spiffeId := connect.TestSpiffeIDService(t, "web")
|
||||
csr, _ := connect.TestCSR(t, spiffeId)
|
||||
args := &structs.CASignRequest{
|
||||
Datacenter: "dc1",
|
||||
CSR: csr,
|
||||
}
|
||||
var reply structs.IssuedCert
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", args, &reply))
|
||||
|
||||
// Generate a second CSR and request signing
|
||||
spiffeId2 := connect.TestSpiffeIDService(t, "web2")
|
||||
csr, _ = connect.TestCSR(t, spiffeId2)
|
||||
args = &structs.CASignRequest{
|
||||
Datacenter: "dc1",
|
||||
CSR: csr,
|
||||
tests := []struct {
|
||||
caKeyType string
|
||||
caKeyBits int
|
||||
}{
|
||||
{
|
||||
caKeyType: connect.DefaultPrivateKeyType,
|
||||
caKeyBits: connect.DefaultPrivateKeyBits,
|
||||
},
|
||||
{
|
||||
// Ensure that an RSA Keyed CA can sign EC leaves and they validate.
|
||||
caKeyType: "rsa",
|
||||
caKeyBits: 2048,
|
||||
},
|
||||
}
|
||||
|
||||
var reply2 structs.IssuedCert
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", args, &reply2))
|
||||
require.True(reply2.ModifyIndex > reply.ModifyIndex)
|
||||
for _, tt := range tests {
|
||||
t.Run(fmt.Sprintf("%s-%d", tt.caKeyType, tt.caKeyBits), func(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
require := require.New(t)
|
||||
dir1, s1 := testServerWithConfig(t, func(cfg *Config) {
|
||||
cfg.CAConfig.Config["PrivateKeyType"] = tt.caKeyType
|
||||
cfg.CAConfig.Config["PrivateKeyBits"] = tt.caKeyBits
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
codec := rpcClient(t, s1)
|
||||
defer codec.Close()
|
||||
|
||||
// Get the current CA
|
||||
state := s1.fsm.State()
|
||||
_, ca, err := state.CARootActive(nil)
|
||||
require.NoError(err)
|
||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
||||
|
||||
// Verify that the cert is signed by the CA
|
||||
roots := x509.NewCertPool()
|
||||
assert.True(roots.AppendCertsFromPEM([]byte(ca.RootCert)))
|
||||
leaf, err := connect.ParseCert(reply.CertPEM)
|
||||
require.NoError(err)
|
||||
_, err = leaf.Verify(x509.VerifyOptions{
|
||||
Roots: roots,
|
||||
})
|
||||
require.NoError(err)
|
||||
// Generate a CSR and request signing
|
||||
spiffeId := connect.TestSpiffeIDService(t, "web")
|
||||
|
||||
// Verify other fields
|
||||
assert.Equal("web", reply.Service)
|
||||
assert.Equal(spiffeId.URI().String(), reply.ServiceURI)
|
||||
// TestCSR will always generate a CSR with an EC key currently.
|
||||
csr, _ := connect.TestCSR(t, spiffeId)
|
||||
args := &structs.CASignRequest{
|
||||
Datacenter: "dc1",
|
||||
CSR: csr,
|
||||
}
|
||||
var reply structs.IssuedCert
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", args, &reply))
|
||||
|
||||
// Generate a second CSR and request signing
|
||||
spiffeId2 := connect.TestSpiffeIDService(t, "web2")
|
||||
csr, _ = connect.TestCSR(t, spiffeId2)
|
||||
args = &structs.CASignRequest{
|
||||
Datacenter: "dc1",
|
||||
CSR: csr,
|
||||
}
|
||||
|
||||
var reply2 structs.IssuedCert
|
||||
require.NoError(msgpackrpc.CallWithCodec(codec, "ConnectCA.Sign", args, &reply2))
|
||||
require.True(reply2.ModifyIndex > reply.ModifyIndex)
|
||||
|
||||
// Get the current CA
|
||||
state := s1.fsm.State()
|
||||
_, ca, err := state.CARootActive(nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Verify that the cert is signed by the CA
|
||||
require.NoError(connect.ValidateLeaf(ca.RootCert, reply.CertPEM, nil))
|
||||
|
||||
// Verify other fields
|
||||
assert.Equal("web", reply.Service)
|
||||
assert.Equal(spiffeId.URI().String(), reply.ServiceURI)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Bench how long Signing RPC takes. This was used to ballpark reasonable
|
||||
|
|
|
@ -81,6 +81,10 @@ func parseCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error)
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("error parsing root cert: %v", err)
|
||||
}
|
||||
keyType, keyBits, err := connect.KeyInfoFromCert(rootCert)
|
||||
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)),
|
||||
|
@ -90,6 +94,8 @@ func parseCARoot(pemValue, provider, clusterID string) (*structs.CARoot, error)
|
|||
NotBefore: rootCert.NotBefore,
|
||||
NotAfter: rootCert.NotAfter,
|
||||
RootCert: pemValue,
|
||||
PrivateKeyType: keyType,
|
||||
PrivateKeyBits: keyBits,
|
||||
Active: true,
|
||||
}, nil
|
||||
}
|
||||
|
@ -224,13 +230,6 @@ func (s *Server) initializeRootCA(provider ca.Provider, conf *structs.CAConfigur
|
|||
return fmt.Errorf("error getting intermediate cert: %v", err)
|
||||
}
|
||||
|
||||
commonConfig, err := conf.GetCommonConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rootCA.PrivateKeyType = commonConfig.PrivateKeyType
|
||||
rootCA.PrivateKeyBits = commonConfig.PrivateKeyBits
|
||||
|
||||
// Check if the CA root is already initialized and exit if it is,
|
||||
// adding on any existing intermediate certs since they aren't directly
|
||||
// tied to the provider.
|
||||
|
|
|
@ -2,7 +2,10 @@ package consul
|
|||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -24,109 +27,118 @@ import (
|
|||
func TestLeader_SecondaryCA_Initialize(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
masterToken := "8a85f086-dd95-4178-b128-e10902767c5c"
|
||||
|
||||
// Initialize primary as the primary DC
|
||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "primary"
|
||||
c.ACLDatacenter = "primary"
|
||||
c.Build = "1.6.0"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = masterToken
|
||||
c.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
||||
s1.tokens.UpdateAgentToken(masterToken, token.TokenSourceConfig)
|
||||
|
||||
testrpc.WaitForLeader(t, s1.RPC, "primary")
|
||||
|
||||
// secondary as a secondary DC
|
||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "secondary"
|
||||
c.ACLDatacenter = "primary"
|
||||
c.Build = "1.6.0"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLDefaultPolicy = "deny"
|
||||
c.ACLTokenReplication = true
|
||||
})
|
||||
defer os.RemoveAll(dir2)
|
||||
defer s2.Shutdown()
|
||||
|
||||
s2.tokens.UpdateAgentToken(masterToken, token.TokenSourceConfig)
|
||||
s2.tokens.UpdateReplicationToken(masterToken, token.TokenSourceConfig)
|
||||
|
||||
testrpc.WaitForLeader(t, s2.RPC, "secondary")
|
||||
|
||||
// Create the WAN link
|
||||
joinWAN(t, s2, s1)
|
||||
|
||||
waitForNewACLs(t, s1)
|
||||
waitForNewACLs(t, s2)
|
||||
|
||||
// Ensure s2 is authoritative.
|
||||
waitForNewACLReplication(t, s2, structs.ACLReplicateTokens, 1, 1, 0)
|
||||
|
||||
// Wait until the providers are fully bootstrapped.
|
||||
var (
|
||||
caRoot *structs.CARoot
|
||||
secondaryProvider ca.Provider
|
||||
intermediatePEM string
|
||||
err error
|
||||
)
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
_, caRoot = s1.getCAProvider()
|
||||
secondaryProvider, _ = s2.getCAProvider()
|
||||
intermediatePEM, err = secondaryProvider.ActiveIntermediate()
|
||||
require.NoError(r, err)
|
||||
|
||||
// Verify the root lists are equal in each DC's state store.
|
||||
state1 := s1.fsm.State()
|
||||
_, roots1, err := state1.CARoots(nil)
|
||||
require.NoError(r, err)
|
||||
|
||||
state2 := s2.fsm.State()
|
||||
_, roots2, err := state2.CARoots(nil)
|
||||
require.NoError(r, err)
|
||||
require.Len(r, roots1, 1)
|
||||
require.Len(r, roots1, 1)
|
||||
require.Equal(r, roots1[0].ID, roots2[0].ID)
|
||||
require.Equal(r, roots1[0].RootCert, roots2[0].RootCert)
|
||||
require.Empty(r, roots1[0].IntermediateCerts)
|
||||
require.NotEmpty(r, roots2[0].IntermediateCerts)
|
||||
})
|
||||
|
||||
// Have secondary sign a leaf cert and make sure the chain is correct.
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "primary",
|
||||
Service: "foo",
|
||||
tests := []struct {
|
||||
keyType string
|
||||
keyBits int
|
||||
}{
|
||||
{connect.DefaultPrivateKeyType, connect.DefaultPrivateKeyBits},
|
||||
{"rsa", 2048},
|
||||
}
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
leafCsr, err := connect.ParseCSR(raw)
|
||||
require.NoError(t, err)
|
||||
for _, tc := range tests {
|
||||
tc := tc
|
||||
t.Run(fmt.Sprintf("%s-%d", tc.keyType, tc.keyBits), func(t *testing.T) {
|
||||
masterToken := "8a85f086-dd95-4178-b128-e10902767c5c"
|
||||
|
||||
leafPEM, err := secondaryProvider.Sign(leafCsr)
|
||||
require.NoError(t, err)
|
||||
// Initialize primary as the primary DC
|
||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "primary"
|
||||
c.ACLDatacenter = "primary"
|
||||
c.Build = "1.6.0"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = masterToken
|
||||
c.ACLDefaultPolicy = "deny"
|
||||
c.CAConfig.Config["PrivateKeyType"] = tc.keyType
|
||||
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
||||
cert, err := connect.ParseCert(leafPEM)
|
||||
require.NoError(t, err)
|
||||
s1.tokens.UpdateAgentToken(masterToken, token.TokenSourceConfig)
|
||||
|
||||
// Check that the leaf signed by the new cert can be verified using the
|
||||
// returned cert chain (signed intermediate + remote root).
|
||||
intermediatePool := x509.NewCertPool()
|
||||
intermediatePool.AppendCertsFromPEM([]byte(intermediatePEM))
|
||||
rootPool := x509.NewCertPool()
|
||||
rootPool.AppendCertsFromPEM([]byte(caRoot.RootCert))
|
||||
testrpc.WaitForLeader(t, s1.RPC, "primary")
|
||||
|
||||
_, err = cert.Verify(x509.VerifyOptions{
|
||||
Intermediates: intermediatePool,
|
||||
Roots: rootPool,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// secondary as a secondary DC
|
||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.Datacenter = "secondary"
|
||||
c.ACLDatacenter = "primary"
|
||||
c.Build = "1.6.0"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLDefaultPolicy = "deny"
|
||||
c.ACLTokenReplication = true
|
||||
c.CAConfig.Config["PrivateKeyType"] = tc.keyType
|
||||
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits
|
||||
})
|
||||
defer os.RemoveAll(dir2)
|
||||
defer s2.Shutdown()
|
||||
|
||||
s2.tokens.UpdateAgentToken(masterToken, token.TokenSourceConfig)
|
||||
s2.tokens.UpdateReplicationToken(masterToken, token.TokenSourceConfig)
|
||||
|
||||
testrpc.WaitForLeader(t, s2.RPC, "secondary")
|
||||
|
||||
// Create the WAN link
|
||||
joinWAN(t, s2, s1)
|
||||
|
||||
waitForNewACLs(t, s1)
|
||||
waitForNewACLs(t, s2)
|
||||
|
||||
// Ensure s2 is authoritative.
|
||||
waitForNewACLReplication(t, s2, structs.ACLReplicateTokens, 1, 1, 0)
|
||||
|
||||
// Wait until the providers are fully bootstrapped.
|
||||
var (
|
||||
caRoot *structs.CARoot
|
||||
secondaryProvider ca.Provider
|
||||
intermediatePEM string
|
||||
err error
|
||||
)
|
||||
retry.Run(t, func(r *retry.R) {
|
||||
_, caRoot = s1.getCAProvider()
|
||||
secondaryProvider, _ = s2.getCAProvider()
|
||||
intermediatePEM, err = secondaryProvider.ActiveIntermediate()
|
||||
require.NoError(r, err)
|
||||
|
||||
// Sanity check CA is using the correct key type
|
||||
require.Equal(r, tc.keyType, caRoot.PrivateKeyType)
|
||||
require.Equal(r, tc.keyBits, caRoot.PrivateKeyBits)
|
||||
|
||||
// Verify the root lists are equal in each DC's state store.
|
||||
state1 := s1.fsm.State()
|
||||
_, roots1, err := state1.CARoots(nil)
|
||||
require.NoError(r, err)
|
||||
|
||||
state2 := s2.fsm.State()
|
||||
_, roots2, err := state2.CARoots(nil)
|
||||
require.NoError(r, err)
|
||||
require.Len(r, roots1, 1)
|
||||
require.Len(r, roots2, 1)
|
||||
require.Equal(r, roots1[0].ID, roots2[0].ID)
|
||||
require.Equal(r, roots1[0].RootCert, roots2[0].RootCert)
|
||||
require.Empty(r, roots1[0].IntermediateCerts)
|
||||
require.NotEmpty(r, roots2[0].IntermediateCerts)
|
||||
})
|
||||
|
||||
// Have secondary sign a leaf cert and make sure the chain is correct.
|
||||
spiffeService := &connect.SpiffeIDService{
|
||||
Host: "node1",
|
||||
Namespace: "default",
|
||||
Datacenter: "primary",
|
||||
Service: "foo",
|
||||
}
|
||||
raw, _ := connect.TestCSR(t, spiffeService)
|
||||
|
||||
leafCsr, err := connect.ParseCSR(raw)
|
||||
require.NoError(t, err)
|
||||
|
||||
leafPEM, err := secondaryProvider.Sign(leafCsr)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Check that the leaf signed by the new cert can be verified using the
|
||||
// returned cert chain (signed intermediate + remote root).
|
||||
require.NoError(t, connect.ValidateLeaf(caRoot.RootCert, leafPEM, []string{intermediatePEM}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeader_SecondaryCA_IntermediateRefresh(t *testing.T) {
|
||||
|
@ -1202,44 +1214,83 @@ func TestLeader_PersistIntermediateCAs(t *testing.T) {
|
|||
|
||||
func TestLeader_ParseCARoot(t *testing.T) {
|
||||
type test struct {
|
||||
pem string
|
||||
expectedError bool
|
||||
name string
|
||||
pem string
|
||||
wantSerial uint64
|
||||
wantSigningKeyID string
|
||||
wantKeyType string
|
||||
wantKeyBits int
|
||||
wantErr bool
|
||||
}
|
||||
// Test certs 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{
|
||||
{"", true},
|
||||
{`-----BEGIN CERTIFICATE-----
|
||||
MIIDHDCCAsKgAwIBAgIQS+meruRVzrmVwEhXNrtk9jAKBggqhkjOPQQDAjCBuTEL
|
||||
MAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1TYW4gRnJhbmNpc2Nv
|
||||
MRowGAYDVQQJExExMDEgU2Vjb25kIFN0cmVldDEOMAwGA1UEERMFOTQxMDUxFzAV
|
||||
BgNVBAoTDkhhc2hpQ29ycCBJbmMuMUAwPgYDVQQDEzdDb25zdWwgQWdlbnQgQ0Eg
|
||||
MTkzNzYxNzQwMjcxNzUxOTkyMzAyMzE1NDkxNjUzODYyMzAwNzE3MB4XDTE5MDQx
|
||||
MjA5MTg0NVoXDTIwMDQxMTA5MTg0NVowHDEaMBgGA1UEAxMRY2xpZW50LmRjMS5j
|
||||
b25zdWwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS2UroGUh5k7eR//iPsn9ne
|
||||
CMCVsERnjqQnK6eDWnM5kTXgXcPPe5pcAS9xs0g8BZ+oVsJSc7sH6RYvX+gw6bCl
|
||||
o4IBRjCCAUIwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggr
|
||||
BgEFBQcDATAMBgNVHRMBAf8EAjAAMGgGA1UdDgRhBF84NDphNDplZjoxYTpjODo1
|
||||
MzoxMDo1YTpjNTplYTpjZTphYTowZDo2ZjpjOTozODozZDphZjo0NTphZTo5OTo4
|
||||
YzpiYjoyNzpiYzpiMzpmYTpmMDozMToxNDo4ZTozNDBqBgNVHSMEYzBhgF8yYTox
|
||||
MjpjYTo0Mzo0NzowODpiZjoxYTo0Yjo4MTpkNDo2MzowNTo1ODowZToxYzo3Zjoy
|
||||
NTo0ZjozNDpmNDozYjpmYzo5YTpkNzo4Mjo2YjpkYzpmODo3YjphMTo5ZDAtBgNV
|
||||
HREEJjAkghFjbGllbnQuZGMxLmNvbnN1bIIJbG9jYWxob3N0hwR/AAABMAoGCCqG
|
||||
SM49BAMCA0gAMEUCIHcLS74KSQ7RA+edwOprmkPTh1nolwXz9/y9CJ5nMVqEAiEA
|
||||
h1IHCbxWsUT3AiARwj5/D/CUppy6BHIFkvcpOCQoVyo=
|
||||
-----END CERTIFICATE-----`, false},
|
||||
{"no cert", "", 0, "", "", 0, true},
|
||||
{
|
||||
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: "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: "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,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
root, err := parseCARoot(test.pem, "consul", "cluster")
|
||||
if err == nil && test.expectedError {
|
||||
require.Error(t, err)
|
||||
}
|
||||
if test.pem != "" {
|
||||
rootCert, err := connect.ParseCert(test.pem)
|
||||
require.NoError(t, err)
|
||||
|
||||
// just to make sure these two are not the same
|
||||
require.NotEqual(t, rootCert.AuthorityKeyId, rootCert.SubjectKeyId)
|
||||
|
||||
require.Equal(t, connect.EncodeSigningKeyID(rootCert.SubjectKeyId), root.SigningKeyID)
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
root, err := parseCARoot(tt.pem, "consul", "cluster")
|
||||
if tt.wantErr {
|
||||
require.Error(err)
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
require.Equal(tt.wantSerial, root.SerialNumber)
|
||||
require.Equal(strings.ToLower(tt.wantSigningKeyID), root.SigningKeyID)
|
||||
require.Equal(tt.wantKeyType, root.PrivateKeyType)
|
||||
require.Equal(tt.wantKeyBits, root.PrivateKeyBits)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func readTestData(t *testing.T, name string) string {
|
||||
t.Helper()
|
||||
path := filepath.Join("testdata", name)
|
||||
bs, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
t.Fatalf("failed reading fixture file %s: %s", name, err)
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIB3DCCAYKgAwIBAgIIc8ST19qlIDUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ
|
||||
VGVzdCBDQSAxMB4XDTE5MTAxNzExNDYyOVoXDTI5MTAxNzExNDYyOVowFDESMBAG
|
||||
A1UEAxMJVGVzdCBDQSAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErA61DUlq
|
||||
qDnXAcHIHVJKBUtyDYoQmZB1T1H7NHTn4XezkF23RjL9Ha8DghMR/bwz7YhZ1Tv6
|
||||
UnYSq5r28P6b06OBvTCBujAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
|
||||
/zApBgNVHQ4EIgQgl00XgWT4tK8F6Gx5xUA7Dj6LwK44UVSKLwXb4+jkJOwwKwYD
|
||||
VR0jBCQwIoAgl00XgWT4tK8F6Gx5xUA7Dj6LwK44UVSKLwXb4+jkJOwwPwYDVR0R
|
||||
BDgwNoY0c3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1NTU1
|
||||
NTU1LmNvbnN1bDAKBggqhkjOPQQDAgNIADBFAiEA/x2MeYU5vCk2hwP7zlrv7bx3
|
||||
9zx5YSbn04sgP6sNK30CIEPfjxDGy6K2dPDckATboYkZVQ4CJpPd6WrgwQaHpWC9
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,14 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIICGTCCAZ+gAwIBAgIIKLuaeLzq/R0wCgYIKoZIzj0EAwMwFDESMBAGA1UEAxMJ
|
||||
VGVzdCBDQSAxMB4XDTE5MTAxNzExNTUxOFoXDTI5MTAxNzExNTUxOFowFDESMBAG
|
||||
A1UEAxMJVGVzdCBDQSAxMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEFWtdx2o3c9qv
|
||||
ka8vxPO2Iv9NAbiwh3cl1a90miRzhQMP7s6wycXfl1xKE02PRxiLQtuukKwE6ohv
|
||||
Ha5h4kkqGB+YdOT+18JS+ixJwmmSZL5pkAh6SMEGby3wf5sap5F/o4G9MIG6MA4G
|
||||
A1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MCkGA1UdDgQiBCALoIib3JUx
|
||||
US491PlC0GqgYkaC0nwi5ympquiljM/HQjArBgNVHSMEJDAigCALoIib3JUxUS49
|
||||
1PlC0GqgYkaC0nwi5ympquiljM/HQjA/BgNVHREEODA2hjRzcGlmZmU6Ly8xMTEx
|
||||
MTExMS0yMjIyLTMzMzMtNDQ0NC01NTU1NTU1NTU1NTUuY29uc3VsMAoGCCqGSM49
|
||||
BAMDA2gAMGUCMBT0orKHSATvulb6nRxVHq3OWOfmVgHu8VUCq9yuyAu1AAy/przY
|
||||
/U0ury3g8T4jhwIxAIoCqYwWSJMFb13DZAR3XY+aFssVP5+vzhlaulqtg+YqjpKP
|
||||
KzuCBpS3yUyAwWDphg==
|
||||
-----END CERTIFICATE-----
|
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIIFaDCCA1CgAwIBAgIIR/rYTE2KZtMwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UE
|
||||
AxMJVGVzdCBDQSAxMB4XDTE5MTAxNzExNTMxNVoXDTI5MTAxNzExNTMxNVowFDES
|
||||
MBAGA1UEAxMJVGVzdCBDQSAxMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC
|
||||
AgEAlIsTpCearMRx0fW2BKmGXHUYCf1ATALcRn31VQCuzszyt77RRbQn+v/XB+Q5
|
||||
Td+/o3ZkpKvCTAF31oZI0ihXEEq7o13pERGPZdD//jNvgePIoauzxjkWfEX5bQGx
|
||||
PxY3mGWakmiunQHm3ls8bm+ZYBfDM8rJTaKFBUCaNe6xi5znQ953+YELV0YHpSXm
|
||||
H0lP93/RVeua9094+YNadUfB1nOWvlayn/YX3oXPAwQsoT1DlqRlGFDAp3MPFNkC
|
||||
RvymdigxAf1HrsyZ+MlD99Htgx2YkmHRXidRW9OzMmVSTe5gT6AOCvC4Zd5udH2H
|
||||
bH+DUNuyJr2YC0VB7RBWbqCvpmxItLaDoSHLndRjP0wY9chMDqrKKRIzupeaYfVK
|
||||
isFdSwj39EnNQEfm7ZJT/8aRNtF8pVFYHO0fZ+2eObpteWyQj0iOJjHuRpUWI2LT
|
||||
ZMEPkj0N2I1tAvg1g/RYasXKwme+L7ejxzyYnC8kqDf4/C4tNaDURXP5fWn8aVsI
|
||||
Sp8gMZHxXD8e8FAMPuDQi4y0FZu9bY9mJ71OSqcR5u0DHKzaCfAwr+qheFxCCly0
|
||||
UnP7FAOSg96mRYAzgYGTFM5/HSmj0vvnsfAAebuiloE4IKfeBzp9kPuauEbcGbTa
|
||||
4bBbaoamqdEUT3bFqRXWCXCQ6kT1xos+YcfjBkpfmdOrrdUCAwEAAaOBvTCBujAO
|
||||
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zApBgNVHQ4EIgQgkvrMl1ce
|
||||
MYSiM92baqh8/L7ilMqsszMXOTu4Z5vcwQgwKwYDVR0jBCQwIoAgkvrMl1ceMYSi
|
||||
M92baqh8/L7ilMqsszMXOTu4Z5vcwQgwPwYDVR0RBDgwNoY0c3BpZmZlOi8vMTEx
|
||||
MTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1NTU1NTU1LmNvbnN1bDANBgkqhkiG
|
||||
9w0BAQsFAAOCAgEAaGorq69i6S6RIsOXx6zou2gr2ez6siVFkxoJEXx8lZKS8UXs
|
||||
mmAxJXXKePv921zFeUB5jcdxIaBlxyEHiJGr9KUDYWnkAY0g8343JueY8epPOGpb
|
||||
u1QoTpCRSJBdOyakK3ZhYqRa28G0fP0eQQ+mF54X2YA4jtg7pb30wahQd9U0M2ey
|
||||
A6VLw4xdmqC7KOfJSvQcCZJPi5Wkqv/pf55WmS28zhxrwMC9U4vaKVRsGKJi/p/X
|
||||
WUmyPpOUM41nEylcpFvs/Xx8eKUSlWRaMTWYHUKTdaXKYL/1op2PhuyoYkH384aj
|
||||
P7RzLkC1fRC8MPXo9L1YSoK7vxL5K/zLkJFb3KopDzujGx1cZbgO8QREe16bVN4b
|
||||
gFLwIzW+VzywmtoSur5V9ythVfvRT1XesmsK/G2ySxWvipYIDYYJrwgKR5b0ikfz
|
||||
RPw2YW6oqaMmZ9Uehxym8RDWqyyFPg9S0C73MTK7FitIROLW88hWKSpDDhFck/32
|
||||
FVaRL8cC0KVlMCFByL/o6u0AsRNCOux1q3BJEdmAh7VI84+SPgztHFkptR4VnlHZ
|
||||
kKTj2Mj/OylHHwhe6AU9pbtAGM6DtcqSjmd4wrkRX8WJDd/F3RlYZ8WhOToOj9gP
|
||||
ra4mUhGz/OlDg6vN9TSeVlb5Ap7c38KoCmmt2n+F/KUpe6V4L1QA5yfz0S8=
|
||||
-----END CERTIFICATE-----
|
|
@ -102,8 +102,14 @@ type CARoot struct {
|
|||
// active root.
|
||||
RotatedOutAt time.Time `json:"-"`
|
||||
|
||||
// Type of private key used to create the CA cert.
|
||||
// PrivateKeyType is the type of the private key used to sign certificates. It
|
||||
// may be "rsa" or "ec". This is provided as a convenience to avoid parsing
|
||||
// the public key to from the certificate to infer the type.
|
||||
PrivateKeyType string
|
||||
|
||||
// PrivateKeyBits is the length of the private key used to sign certificates.
|
||||
// This is provided as a convenience to avoid parsing the public key from the
|
||||
// certificate to infer the type.
|
||||
PrivateKeyBits int
|
||||
|
||||
RaftIndex
|
||||
|
@ -282,7 +288,18 @@ type CommonCAProviderConfig struct {
|
|||
// is used. This is ignored if CSRMaxPerSecond is non-zero.
|
||||
CSRMaxConcurrent int
|
||||
|
||||
// PrivateKeyType specifies which type of key the CA should generate. It only
|
||||
// applies when the provider is generating its own key and is ignored if the
|
||||
// provider already has a key or an external key is provided. Supported values
|
||||
// are "ec" or "rsa". "ec" is the default and will generate a NIST P-256
|
||||
// Elliptic key.
|
||||
PrivateKeyType string
|
||||
|
||||
// PrivateKeyBits specifies the number of bits the CA's private key should
|
||||
// use. For RSA, supported values are 2048 and 4096. For EC, supported values
|
||||
// are 224, 256, 384 and 521 and correspond to the NIST P-* curve of the same
|
||||
// name. As with PrivateKeyType this is only relevant whan the provier is
|
||||
// generating new CA keys (root or intermediate).
|
||||
PrivateKeyBits int
|
||||
}
|
||||
|
||||
|
@ -302,14 +319,14 @@ func (c CommonCAProviderConfig) Validate() error {
|
|||
switch c.PrivateKeyType {
|
||||
case "ec":
|
||||
if c.PrivateKeyBits != 224 && c.PrivateKeyBits != 256 && c.PrivateKeyBits != 384 && c.PrivateKeyBits != 521 {
|
||||
return fmt.Errorf("ECDSA key length must be one of (224, 256, 384, 521) bits")
|
||||
return fmt.Errorf("EC key length must be one of (224, 256, 384, 521) bits")
|
||||
}
|
||||
case "rsa":
|
||||
if c.PrivateKeyBits != 2048 && c.PrivateKeyBits != 4096 {
|
||||
return fmt.Errorf("RSA key length must be 2048 or 4096 bits")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("private key type must be either 'ecdsa' or 'rsa'")
|
||||
return fmt.Errorf("private key type must be either 'ec' or 'rsa'")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
//
|
||||
// You can verify a given leaf with a given root using:
|
||||
//
|
||||
// $ openssl verify -verbose -CAfile ca2-ca.cert.pem ca1-svc-db.cert.pem
|
||||
// $ openssl verify -verbose -CAfile ca1-ca.cert.pem ca1-svc-db.cert.pem
|
||||
//
|
||||
// Note that to verify via the cross-signed intermediate, openssl requires it to
|
||||
// be bundled with the _root_ CA bundle and will ignore the cert if it's passed
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
bind_addr = "0.0.0.0"
|
||||
advertise_addr = "{{ GetInterfaceIP \"eth0\" }}"
|
|
@ -0,0 +1,7 @@
|
|||
connect {
|
||||
enabled = true
|
||||
ca_config {
|
||||
private_key_type = "rsa"
|
||||
private_key_bits = 2048
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
snapshot_envoy_admin localhost:19000 s1 primary || true
|
||||
snapshot_envoy_admin localhost:19001 s2 secondary || true
|
|
@ -0,0 +1,17 @@
|
|||
services {
|
||||
name = "s1"
|
||||
port = 8080
|
||||
connect {
|
||||
sidecar_service {
|
||||
proxy {
|
||||
upstreams = [
|
||||
{
|
||||
destination_name = "s2"
|
||||
datacenter = "secondary"
|
||||
local_bind_port = 5000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# We don't want an s2 service in the primary dc
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eEuo pipefail
|
||||
|
||||
gen_envoy_bootstrap s1 19000 primary
|
||||
retry_default docker_consul primary curl -s "http://localhost:8500/v1/catalog/service/consul?dc=secondary" >/dev/null
|
|
@ -0,0 +1,43 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "s1 proxy is running correct version" {
|
||||
assert_envoy_version 19000
|
||||
}
|
||||
|
||||
@test "s1 proxy admin is up on :19000" {
|
||||
retry_default curl -f -s localhost:19000/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s1 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21000 s1
|
||||
}
|
||||
|
||||
@test "s1 upstream should have healthy endpoints for s2" {
|
||||
assert_upstream_has_endpoints_in_status 127.0.0.1:19000 s2.default.secondary HEALTHY 1
|
||||
}
|
||||
|
||||
@test "s1 upstream should be able to connect to s2" {
|
||||
run retry_default curl -s -f -d hello localhost:5000
|
||||
[ "$status" -eq 0 ]
|
||||
[ "$output" = "hello" ]
|
||||
}
|
||||
|
||||
@test "s1 upstream made 1 connection" {
|
||||
assert_envoy_metric_at_least 127.0.0.1:19000 "cluster.s2.default.secondary.*cx_total" 1
|
||||
}
|
||||
|
||||
@test "ca key should be RSA" {
|
||||
run retry_default curl -f -s 127.0.0.1:8500/v1/connect/ca/roots
|
||||
|
||||
echo "$status"
|
||||
echo "OUTPUT: $output"
|
||||
|
||||
[ "$status" -eq 0 ]
|
||||
|
||||
KEY_TYPE=$(echo "$output" | jq -r '.Roots[0].PrivateKeyType')
|
||||
echo "KEY_TYPE: $KEY_TYPE"
|
||||
|
||||
[ "$KEY_TYPE" == "rsa" ]
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
retry_join_wan = ["consul-primary"]
|
|
@ -0,0 +1 @@
|
|||
# we don't want an s1 service in the secondary dc
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -eEuo pipefail
|
||||
|
||||
gen_envoy_bootstrap s2 19001 secondary
|
||||
retry_default docker_consul secondary curl -s "http://localhost:8500/v1/catalog/service/consul?dc=primary" >/dev/null
|
|
@ -0,0 +1,19 @@
|
|||
#!/usr/bin/env bats
|
||||
|
||||
load helpers
|
||||
|
||||
@test "s2 proxy is running correct version" {
|
||||
assert_envoy_version 19001
|
||||
}
|
||||
|
||||
@test "s2 proxy admin is up on :19001" {
|
||||
retry_default curl -f -s localhost:19001/stats -o /dev/null
|
||||
}
|
||||
|
||||
@test "s2 proxy listener should be up and have right cert" {
|
||||
assert_proxy_presents_cert_uri localhost:21000 s2 secondary
|
||||
}
|
||||
|
||||
@test "s2 proxy should be healthy" {
|
||||
assert_service_has_healthy_instances s2 1 secondary
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
|
||||
export REQUIRED_SERVICES="s1 s1-sidecar-proxy s2-secondary s2-sidecar-proxy-secondary"
|
||||
export REQUIRE_SECONDARY=1
|
|
@ -17,7 +17,7 @@ FILTER_TESTS=${FILTER_TESTS:-}
|
|||
STOP_ON_FAIL=${STOP_ON_FAIL:-}
|
||||
|
||||
# ENVOY_VERSIONS is the list of envoy versions to run each test against
|
||||
ENVOY_VERSIONS=${ENVOY_VERSIONS:-"1.10.0 1.9.1 1.8.0 1.11.1"}
|
||||
ENVOY_VERSIONS=${ENVOY_VERSIONS:-"1.10.0 1.9.1 1.8.0 1.11.2"}
|
||||
|
||||
if [ ! -z "$DEBUG" ] ; then
|
||||
set -x
|
||||
|
|
|
@ -135,8 +135,8 @@ $ curl \
|
|||
"RootCert": "-----BEGIN CERTIFICATE-----\nMIICmDCCAj6gAwIBAgIBBzAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtDb25zdWwg\nQ0EgNzAeFw0xODA1MjExNjMzMjhaFw0yODA1MTgxNjMzMjhaMBYxFDASBgNVBAMT\nC0NvbnN1bCBDQSA3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAER0qlxjnRcMEr\niSGlH7G7dYU7lzBEmLUSMZkyBbClmyV8+e8WANemjn+PLnCr40If9cmpr7RnC9Qk\nGTaLnLiF16OCAXswggF3MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/\nMGgGA1UdDgRhBF8xZjo5MTpjYTo0MTo4ZjphYzo2NzpiZjo1OTpjMjpmYTo0ZTo3\nNTo1YzpkODpmMDo1NTpkZTpiZTo3NTpiODozMzozMTpkNToyNDpiMDowNDpiMzpl\nODo5Nzo1Yjo3ZTBqBgNVHSMEYzBhgF8xZjo5MTpjYTo0MTo4ZjphYzo2NzpiZjo1\nOTpjMjpmYTo0ZTo3NTo1YzpkODpmMDo1NTpkZTpiZTo3NTpiODozMzozMTpkNToy\nNDpiMDowNDpiMzplODo5Nzo1Yjo3ZTA/BgNVHREEODA2hjRzcGlmZmU6Ly8xMjRk\nZjVhMC05ODIwLTc2YzMtOWFhOS02ZjYyMTY0YmExYzIuY29uc3VsMD0GA1UdHgEB\n/wQzMDGgLzAtgisxMjRkZjVhMC05ODIwLTc2YzMtOWFhOS02ZjYyMTY0YmExYzIu\nY29uc3VsMAoGCCqGSM49BAMCA0gAMEUCIQDzkkI7R+0U12a+zq2EQhP/n2mHmta+\nfs2hBxWIELGwTAIgLdO7RRw+z9nnxCIA6kNl//mIQb+PGItespiHZKAz74Q=\n-----END CERTIFICATE-----\n",
|
||||
"IntermediateCerts": null,
|
||||
"Active": true,
|
||||
"PrivateKeyType": "",
|
||||
"PrivateKeyBits": 0,
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
"CreateIndex": 8,
|
||||
"ModifyIndex": 8
|
||||
}
|
||||
|
|
|
@ -56,8 +56,8 @@ $ curl \
|
|||
"RootCert": "-----BEGIN CERTIFICATE-----\nMIICmDCCAj6gAwIBAgIBBzAKBggqhkjOPQQDAjAWMRQwEgYDVQQDEwtDb25zdWwg\nQ0EgNzAeFw0xODA1MjUyMTM5MjNaFw0yODA1MjIyMTM5MjNaMBYxFDASBgNVBAMT\nC0NvbnN1bCBDQSA3MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq4S32Pu0/VL4\nG75gvdyQuAhqMZFsfBRwD3pgvblgZMeJc9KDosxnPR+W34NXtMD/860NNVJIILln\n9lLhIjWPQqOCAXswggF3MA4GA1UdDwEB/wQEAwIBhjAPBgNVHRMBAf8EBTADAQH/\nMGgGA1UdDgRhBF8yZDowOTo1ZDo4NDpiOTo4OTo0YjpkZDplMzo4ODpiYjo5Yzpl\nMjpiMjo2OTo4MToxZjo0YjphNjpmZDo0ZDpkZjplZTo3NDo2MzpmMzo3NDo1NTpj\nYTpiMDpiNTo2NTBqBgNVHSMEYzBhgF8yZDowOTo1ZDo4NDpiOTo4OTo0YjpkZDpl\nMzo4ODpiYjo5YzplMjpiMjo2OTo4MToxZjo0YjphNjpmZDo0ZDpkZjplZTo3NDo2\nMzpmMzo3NDo1NTpjYTpiMDpiNTo2NTA/BgNVHREEODA2hjRzcGlmZmU6Ly83ZjQy\nZjQ5Ni1mYmM3LTg2OTItMDVlZC0zMzRhYTUzNDBjMWUuY29uc3VsMD0GA1UdHgEB\n/wQzMDGgLzAtgis3ZjQyZjQ5Ni1mYmM3LTg2OTItMDVlZC0zMzRhYTUzNDBjMWUu\nY29uc3VsMAoGCCqGSM49BAMCA0gAMEUCIBBBDOWXWApx4S6bHJ49AW87Nw8uQ/gJ\nJ6lvm3HzEQw2AiEA4PVqWt+z8fsQht0cACM42kghL97SgDSf8rgCqfLYMng=\n-----END CERTIFICATE-----\n",
|
||||
"IntermediateCerts": null,
|
||||
"Active": true,
|
||||
"PrivateKeyType": "",
|
||||
"PrivateKeyBits": 0,
|
||||
"PrivateKeyType": "ec",
|
||||
"PrivateKeyBits": 256,
|
||||
"CreateIndex": 8,
|
||||
"ModifyIndex": 8
|
||||
}
|
||||
|
|
|
@ -914,19 +914,18 @@ default will automatically work with some tooling.
|
|||
|
||||
<p>There are also a number of common configuration options supported by all providers:</p>
|
||||
|
||||
* <a name="ca_leaf_cert_ttl"></a><a href="#ca_leaf_cert_ttl">`leaf_cert_ttl`</a> The upper bound on the
|
||||
lease duration of a leaf certificate issued for a service. In most
|
||||
cases a new leaf certificate will be requested by a proxy before this
|
||||
limit is reached. This is also the effective limit on how long a
|
||||
server outage can last (with no leader) before network connections
|
||||
will start being rejected, and as a result the defaults is `72h` to
|
||||
last through a weekend without intervention. This value cannot be
|
||||
lower than 1 hour or higher than 1 year.
|
||||
|
||||
This value is also used when rotating out old root certificates from
|
||||
the cluster. When a root certificate has been inactive (rotated out)
|
||||
for more than twice the *current* `leaf_cert_ttl`, it will be removed
|
||||
from the trusted list.
|
||||
* <a name="ca_csr_max_concurrent"></a><a
|
||||
href="#ca_csr_max_concurrent">`csr_max_concurrent`</a> Sets a limit
|
||||
on how many Certificate Signing Requests will be processed
|
||||
concurrently. Defaults to 0 (disabled). This is useful when you have
|
||||
more than one or two cores available to the server. For example on an
|
||||
8 core server, setting this to 1 will ensure that even during a CA
|
||||
rotation no more than one server core on the leader will be consumed
|
||||
at a time with generating new certificates. Setting this is
|
||||
recommended _instead_ of `csr_max_per_second` where you know there are
|
||||
multiple cores available since it is simpler to reason about limiting
|
||||
CSR resources this way without artificially slowing down rotations.
|
||||
Added in 1.4.1.
|
||||
|
||||
* <a name="ca_csr_max_per_second"></a><a
|
||||
href="#ca_csr_max_per_second">`csr_max_per_second`</a> Sets a rate
|
||||
|
@ -941,18 +940,58 @@ default will automatically work with some tooling.
|
|||
`csr_max_concurrent` instead if servers have more than one core.
|
||||
Setting this to zero disables rate limiting. Added in 1.4.1.
|
||||
|
||||
* <a name="ca_csr_max_concurrent"></a><a
|
||||
href="#ca_csr_max_concurrent">`csr_max_concurrent`</a> Sets a limit
|
||||
on how many Certificate Signing Requests will be processed
|
||||
concurrently. Defaults to 0 (disabled). This is useful when you have
|
||||
more than one or two cores available to the server. For example on an
|
||||
8 core server, setting this to 1 will ensure that even during a CA
|
||||
rotation no more than one server core on the leader will be consumed
|
||||
at a time with generating new certificates. Setting this is
|
||||
recommended _instead_ of `csr_max_per_second` where you know there are
|
||||
multiple cores available since it is simpler to reason about limiting
|
||||
CSR resources this way without artificially slowing down rotations.
|
||||
Added in 1.4.1.
|
||||
* <a name="ca_leaf_cert_ttl"></a><a href="#ca_leaf_cert_ttl">`leaf_cert_ttl`</a> The upper bound on the
|
||||
lease duration of a leaf certificate issued for a service. In most
|
||||
cases a new leaf certificate will be requested by a proxy before this
|
||||
limit is reached. This is also the effective limit on how long a
|
||||
server outage can last (with no leader) before network connections
|
||||
will start being rejected, and as a result the defaults is `72h` to
|
||||
last through a weekend without intervention. This value cannot be
|
||||
lower than 1 hour or higher than 1 year.
|
||||
|
||||
This value is also used when rotating out old root certificates from
|
||||
the cluster. When a root certificate has been inactive (rotated out)
|
||||
for more than twice the *current* `leaf_cert_ttl`, it will be removed
|
||||
from the trusted list.
|
||||
|
||||
* <a name="ca_private_key_type"></a><a
|
||||
href="#ca_private_key_type">`private_key_type`</a> The type of key to
|
||||
generate for this CA. This is only used when the provider is
|
||||
generating a new key. If `private_key` is set for the Consul provider,
|
||||
or existing root or intermediate PKI paths given for Vault then this
|
||||
will be ignored. Currently supported options are `ec` or `rsa`.
|
||||
Default is `ec`.
|
||||
|
||||
It is required that all servers in a Datacenter have
|
||||
the same config for the CA. It is recommended that servers in
|
||||
different Datacenters have the same CA config for key type and size
|
||||
although the built-in CA and Vault provider will both allow mixed CA
|
||||
key types.
|
||||
|
||||
Some CA providers (currently Vault) will not allow cross-signing a
|
||||
new CA certificate with a different key type. This means that if you
|
||||
migrate from an RSA-keyed Vault CA to an EC-keyed CA from any
|
||||
provider, you may have to proceed without cross-signing which risks
|
||||
temporary connection issues for workloads during the new certificate
|
||||
rollout. We highly recommend testing this outside of production to
|
||||
understand the impact and suggest sticking to same key type where
|
||||
possible.
|
||||
|
||||
Note that this only affects _CA_ keys generated by the provider.
|
||||
Leaf certificate keys are always EC 256 regardless of the CA
|
||||
configuration.
|
||||
|
||||
* <a name="ca_private_key_bits"></a><a
|
||||
href="#ca_private_key_bits">`private_key_bits`</a> The length of key
|
||||
to generate for this CA. This is only used when the provider is
|
||||
generating a new key. If `private_key` is set for the Consul provider,
|
||||
or existing root or intermediate PKI paths given for Vault then this
|
||||
will be ignored.
|
||||
|
||||
Currently supported values are:
|
||||
- `private_key_type = ec` (default): `224, 256, 384, 521`
|
||||
corresponding to the NIST P-* curves of the same name.
|
||||
- `private_key_type = rsa`: `2048, 4096`
|
||||
|
||||
* <a name="datacenter"></a><a href="#datacenter">`datacenter`</a> Equivalent to the
|
||||
[`-datacenter` command-line flag](#_datacenter).
|
||||
|
|
Loading…
Reference in New Issue