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:
Paul Banks 2019-11-01 13:20:26 +00:00 committed by GitHub
parent e0e3ec1f32
commit 5f405c3277
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 994 additions and 425 deletions

View File

@ -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

View File

@ -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.

View File

@ -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) {

View File

@ -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)
}

View File

@ -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
}

View File

@ -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,
}

View File

@ -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)
})
}
}

View File

@ -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")
}
}

View File

@ -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{

View File

@ -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.

View File

@ -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

View File

@ -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.

View File

@ -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)
}

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIB3DCCAYKgAwIBAgIIc8ST19qlIDUwCgYIKoZIzj0EAwIwFDESMBAGA1UEAxMJ
VGVzdCBDQSAxMB4XDTE5MTAxNzExNDYyOVoXDTI5MTAxNzExNDYyOVowFDESMBAG
A1UEAxMJVGVzdCBDQSAxMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAErA61DUlq
qDnXAcHIHVJKBUtyDYoQmZB1T1H7NHTn4XezkF23RjL9Ha8DghMR/bwz7YhZ1Tv6
UnYSq5r28P6b06OBvTCBujAOBgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB
/zApBgNVHQ4EIgQgl00XgWT4tK8F6Gx5xUA7Dj6LwK44UVSKLwXb4+jkJOwwKwYD
VR0jBCQwIoAgl00XgWT4tK8F6Gx5xUA7Dj6LwK44UVSKLwXb4+jkJOwwPwYDVR0R
BDgwNoY0c3BpZmZlOi8vMTExMTExMTEtMjIyMi0zMzMzLTQ0NDQtNTU1NTU1NTU1
NTU1LmNvbnN1bDAKBggqhkjOPQQDAgNIADBFAiEA/x2MeYU5vCk2hwP7zlrv7bx3
9zx5YSbn04sgP6sNK30CIEPfjxDGy6K2dPDckATboYkZVQ4CJpPd6WrgwQaHpWC9
-----END CERTIFICATE-----

View File

@ -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-----

View File

@ -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-----

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,2 @@
bind_addr = "0.0.0.0"
advertise_addr = "{{ GetInterfaceIP \"eth0\" }}"

View File

@ -0,0 +1,7 @@
connect {
enabled = true
ca_config {
private_key_type = "rsa"
private_key_bits = 2048
}
}

View File

@ -0,0 +1,4 @@
#!/bin/bash
snapshot_envoy_admin localhost:19000 s1 primary || true
snapshot_envoy_admin localhost:19001 s2 secondary || true

View File

@ -0,0 +1,17 @@
services {
name = "s1"
port = 8080
connect {
sidecar_service {
proxy {
upstreams = [
{
destination_name = "s2"
datacenter = "secondary"
local_bind_port = 5000
}
]
}
}
}
}

View File

@ -0,0 +1 @@
# We don't want an s2 service in the primary dc

View File

@ -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

View File

@ -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" ]
}

View File

@ -0,0 +1 @@
retry_join_wan = ["consul-primary"]

View File

@ -0,0 +1 @@
# we don't want an s1 service in the secondary dc

View File

@ -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

View File

@ -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
}

View File

@ -0,0 +1,4 @@
#!/bin/bash
export REQUIRED_SERVICES="s1 s1-sidecar-proxy s2-secondary s2-sidecar-proxy-secondary"
export REQUIRE_SECONDARY=1

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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).