connect/ca: update Consul provider to use new cross-sign CSR method
This commit is contained in:
parent
675555c4ff
commit
cea94d0bcf
|
@ -935,6 +935,7 @@ func (b *Builder) Validate(rt RuntimeConfig) error {
|
||||||
|
|
||||||
// Validate the given Connect CA provider config
|
// Validate the given Connect CA provider config
|
||||||
validCAProviders := map[string]bool{
|
validCAProviders := map[string]bool{
|
||||||
|
"": true,
|
||||||
structs.ConsulCAProvider: true,
|
structs.ConsulCAProvider: true,
|
||||||
structs.VaultCAProvider: true,
|
structs.VaultCAProvider: true,
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,7 +113,6 @@ func DevSource() Source {
|
||||||
|
|
||||||
connect = {
|
connect = {
|
||||||
enabled = true
|
enabled = true
|
||||||
ca_provider = "consul"
|
|
||||||
}
|
}
|
||||||
performance = {
|
performance = {
|
||||||
raft_multiplier = 1
|
raft_multiplier = 1
|
||||||
|
|
|
@ -2484,10 +2484,9 @@ func TestFullConfig(t *testing.T) {
|
||||||
"check_update_interval": "16507s",
|
"check_update_interval": "16507s",
|
||||||
"client_addr": "93.83.18.19",
|
"client_addr": "93.83.18.19",
|
||||||
"connect": {
|
"connect": {
|
||||||
"ca_provider": "b8j4ynx9",
|
"ca_provider": "consul",
|
||||||
"ca_config": {
|
"ca_config": {
|
||||||
"g4cvJyys": "IRLXE9Ds",
|
"RotationPeriod": "90h"
|
||||||
"hyMy9Oxn": "XeBp4Sis"
|
|
||||||
},
|
},
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"proxy_defaults": {
|
"proxy_defaults": {
|
||||||
|
@ -2946,10 +2945,9 @@ func TestFullConfig(t *testing.T) {
|
||||||
check_update_interval = "16507s"
|
check_update_interval = "16507s"
|
||||||
client_addr = "93.83.18.19"
|
client_addr = "93.83.18.19"
|
||||||
connect {
|
connect {
|
||||||
ca_provider = "b8j4ynx9"
|
ca_provider = "consul"
|
||||||
ca_config {
|
ca_config {
|
||||||
"g4cvJyys" = "IRLXE9Ds"
|
"RotationPeriod" = "90h"
|
||||||
"hyMy9Oxn" = "XeBp4Sis"
|
|
||||||
}
|
}
|
||||||
enabled = true
|
enabled = true
|
||||||
proxy_defaults {
|
proxy_defaults {
|
||||||
|
@ -3550,10 +3548,9 @@ func TestFullConfig(t *testing.T) {
|
||||||
ConnectEnabled: true,
|
ConnectEnabled: true,
|
||||||
ConnectProxyBindMinPort: 2000,
|
ConnectProxyBindMinPort: 2000,
|
||||||
ConnectProxyBindMaxPort: 3000,
|
ConnectProxyBindMaxPort: 3000,
|
||||||
ConnectCAProvider: "b8j4ynx9",
|
ConnectCAProvider: "consul",
|
||||||
ConnectCAConfig: map[string]interface{}{
|
ConnectCAConfig: map[string]interface{}{
|
||||||
"g4cvJyys": "IRLXE9Ds",
|
"RotationPeriod": "90h",
|
||||||
"hyMy9Oxn": "XeBp4Sis",
|
|
||||||
},
|
},
|
||||||
ConnectProxyAllowManagedRoot: false,
|
ConnectProxyAllowManagedRoot: false,
|
||||||
ConnectProxyAllowManagedAPIRegistration: false,
|
ConnectProxyAllowManagedAPIRegistration: false,
|
||||||
|
|
|
@ -242,8 +242,53 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
|
||||||
return buf.String(), nil
|
return buf.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCrossSigningCSR creates a CSR from our root CA certificate to be signed
|
||||||
|
// by another CA provider.
|
||||||
|
func (c *ConsulProvider) GetCrossSigningCSR() (*x509.CertificateRequest, error) {
|
||||||
|
c.RLock()
|
||||||
|
defer c.RUnlock()
|
||||||
|
|
||||||
|
// Get the provider state
|
||||||
|
state := c.delegate.State()
|
||||||
|
_, providerState, err := state.CAProviderState(c.id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privKey, err := connect.ParseSigner(providerState.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rootCA, err := connect.ParseCert(providerState.RootCert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the CSR
|
||||||
|
template := &x509.CertificateRequest{
|
||||||
|
DNSNames: rootCA.DNSNames,
|
||||||
|
EmailAddresses: rootCA.EmailAddresses,
|
||||||
|
IPAddresses: rootCA.IPAddresses,
|
||||||
|
URIs: rootCA.URIs,
|
||||||
|
SignatureAlgorithm: rootCA.SignatureAlgorithm,
|
||||||
|
Subject: rootCA.Subject,
|
||||||
|
Extensions: rootCA.Extensions,
|
||||||
|
ExtraExtensions: rootCA.ExtraExtensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
bs, err := x509.CreateCertificateRequest(rand.Reader, template, privKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
csr, err := x509.ParseCertificateRequest(bs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return csr, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CrossSignCA returns the given intermediate CA cert signed by the current active root.
|
// CrossSignCA returns the given intermediate CA cert signed by the current active root.
|
||||||
func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
func (c *ConsulProvider) CrossSignCA(csr *x509.CertificateRequest) (string, error) {
|
||||||
c.Lock()
|
c.Lock()
|
||||||
defer c.Unlock()
|
defer c.Unlock()
|
||||||
|
|
||||||
|
@ -264,7 +309,12 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
keyId, err := connect.KeyId(privKey.Public())
|
authKeyId, err := connect.KeyId(privKey.Public())
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
subjKeyId, err := connect.KeyId(csr.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -272,11 +322,26 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
// Create the cross-signing template from the existing root CA
|
// Create the cross-signing template from the existing root CA
|
||||||
serialNum := &big.Int{}
|
serialNum := &big.Int{}
|
||||||
serialNum.SetUint64(idx + 1)
|
serialNum.SetUint64(idx + 1)
|
||||||
template := *cert
|
template := &x509.Certificate{
|
||||||
template.SerialNumber = serialNum
|
SerialNumber: serialNum,
|
||||||
template.SignatureAlgorithm = rootCA.SignatureAlgorithm
|
SignatureAlgorithm: rootCA.SignatureAlgorithm,
|
||||||
template.SubjectKeyId = keyId
|
DNSNames: csr.DNSNames,
|
||||||
template.AuthorityKeyId = keyId
|
EmailAddresses: csr.EmailAddresses,
|
||||||
|
IPAddresses: csr.IPAddresses,
|
||||||
|
URIs: csr.URIs,
|
||||||
|
SubjectKeyId: subjKeyId,
|
||||||
|
AuthorityKeyId: authKeyId,
|
||||||
|
Subject: csr.Subject,
|
||||||
|
Extensions: csr.Extensions,
|
||||||
|
ExtraExtensions: csr.ExtraExtensions,
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
KeyUsage: x509.KeyUsageCertSign |
|
||||||
|
x509.KeyUsageCRLSign |
|
||||||
|
x509.KeyUsageDigitalSignature,
|
||||||
|
IsCA: true,
|
||||||
|
NotAfter: time.Now().Add(7 * 24 * time.Hour),
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
// Sign the certificate valid from 1 minute in the past, this helps it be
|
// Sign the certificate valid from 1 minute in the past, this helps it be
|
||||||
// accepted right away even when nodes are not in close time sync accross the
|
// accepted right away even when nodes are not in close time sync accross the
|
||||||
|
@ -290,7 +355,7 @@ func (c *ConsulProvider) CrossSignCA(cert *x509.Certificate) (string, error) {
|
||||||
template.NotAfter = effectiveNow.Add(7 * 24 * time.Hour)
|
template.NotAfter = effectiveNow.Add(7 * 24 * time.Hour)
|
||||||
|
|
||||||
bs, err := x509.CreateCertificate(
|
bs, err := x509.CreateCertificate(
|
||||||
rand.Reader, &template, rootCA, cert.PublicKey, privKey)
|
rand.Reader, template, rootCA, csr.PublicKey, privKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error generating CA certificate: %s", err)
|
return "", fmt.Errorf("error generating CA certificate: %s", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/consul/state"
|
"github.com/hashicorp/consul/agent/consul/state"
|
||||||
"github.com/hashicorp/consul/agent/structs"
|
"github.com/hashicorp/consul/agent/structs"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
type consulCAMockDelegate struct {
|
type consulCAMockDelegate struct {
|
||||||
|
@ -52,7 +53,7 @@ func newMockDelegate(t *testing.T, conf *structs.CAConfiguration) *consulCAMockD
|
||||||
if s == nil {
|
if s == nil {
|
||||||
t.Fatalf("missing state store")
|
t.Fatalf("missing state store")
|
||||||
}
|
}
|
||||||
if err := s.CASetConfig(0, conf); err != nil {
|
if err := s.CASetConfig(conf.RaftIndex.CreateIndex, conf); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,37 +178,44 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {
|
||||||
func TestConsulCAProvider_CrossSignCA(t *testing.T) {
|
func TestConsulCAProvider_CrossSignCA(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
assert := assert.New(t)
|
require := require.New(t)
|
||||||
conf := testConsulCAConfig()
|
|
||||||
delegate := newMockDelegate(t, conf)
|
|
||||||
provider, err := NewConsulProvider(conf.Config, delegate)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
// Make a new CA cert to get cross-signed.
|
conf1 := testConsulCAConfig()
|
||||||
rootCA := connect.TestCA(t, nil)
|
delegate1 := newMockDelegate(t, conf1)
|
||||||
rootPEM, err := provider.ActiveRoot()
|
provider1, err := NewConsulProvider(conf1.Config, delegate1)
|
||||||
assert.NoError(err)
|
|
||||||
root, err := connect.ParseCert(rootPEM)
|
conf2 := testConsulCAConfig()
|
||||||
assert.NoError(err)
|
conf2.CreateIndex = 10
|
||||||
|
delegate2 := newMockDelegate(t, conf2)
|
||||||
|
provider2, err := NewConsulProvider(conf2.Config, delegate2)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// Have provider2 generate a cross-signing CSR
|
||||||
|
csr, err := provider2.GetCrossSigningCSR()
|
||||||
|
require.NoError(err)
|
||||||
|
oldSubject := csr.Subject.CommonName
|
||||||
|
|
||||||
// Have the provider cross sign our new CA cert.
|
// Have the provider cross sign our new CA cert.
|
||||||
cert, err := connect.ParseCert(rootCA.RootCert)
|
xcPEM, err := provider1.CrossSignCA(csr)
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
oldSubject := cert.Subject.CommonName
|
|
||||||
xcPEM, err := provider.CrossSignCA(cert)
|
|
||||||
assert.NoError(err)
|
|
||||||
|
|
||||||
xc, err := connect.ParseCert(xcPEM)
|
xc, err := connect.ParseCert(xcPEM)
|
||||||
assert.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
// AuthorityKeyID and SubjectKeyID should be the signing root's.
|
rootPEM, err := provider1.ActiveRoot()
|
||||||
assert.Equal(root.AuthorityKeyId, xc.AuthorityKeyId)
|
require.NoError(err)
|
||||||
assert.Equal(root.SubjectKeyId, xc.SubjectKeyId)
|
root, err := connect.ParseCert(rootPEM)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// AuthorityKeyID should be the signing root's, SubjectKeyId should be different.
|
||||||
|
require.Equal(root.AuthorityKeyId, xc.AuthorityKeyId)
|
||||||
|
require.NotEqual(root.SubjectKeyId, xc.SubjectKeyId)
|
||||||
|
|
||||||
// Subject name should not have changed.
|
// Subject name should not have changed.
|
||||||
assert.NotEqual(root.Subject.CommonName, xc.Subject.CommonName)
|
require.NotEqual(root.Subject.CommonName, xc.Subject.CommonName)
|
||||||
assert.Equal(oldSubject, xc.Subject.CommonName)
|
require.Equal(oldSubject, xc.Subject.CommonName)
|
||||||
|
|
||||||
// Issuer should be the signing root.
|
// Issuer should be the signing root.
|
||||||
assert.Equal(root.Issuer.CommonName, xc.Issuer.CommonName)
|
require.Equal(root.Issuer.CommonName, xc.Issuer.CommonName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package connect
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/sha1"
|
"crypto/sha1"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -98,7 +97,6 @@ func ParseCSR(pemValue string) (*x509.CertificateRequest, error) {
|
||||||
func KeyId(raw interface{}) ([]byte, error) {
|
func KeyId(raw interface{}) ([]byte, error) {
|
||||||
switch raw.(type) {
|
switch raw.(type) {
|
||||||
case *ecdsa.PublicKey:
|
case *ecdsa.PublicKey:
|
||||||
case *rsa.PublicKey:
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("invalid key type: %T", raw)
|
return nil, fmt.Errorf("invalid key type: %T", raw)
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,16 +82,16 @@ func fixupConfig(conf *structs.CAConfiguration) {
|
||||||
for k, v := range conf.Config {
|
for k, v := range conf.Config {
|
||||||
if raw, ok := v.([]uint8); ok {
|
if raw, ok := v.([]uint8); ok {
|
||||||
conf.Config[k] = ca.Uint8ToString(raw)
|
conf.Config[k] = ca.Uint8ToString(raw)
|
||||||
}
|
|
||||||
switch conf.Provider {
|
switch conf.Provider {
|
||||||
case structs.ConsulCAProvider:
|
case structs.ConsulCAProvider:
|
||||||
if v, ok := conf.Config["PrivateKey"]; ok && v != "" {
|
if k == "PrivateKey" && ca.Uint8ToString(raw) != "" {
|
||||||
conf.Config["PrivateKey"] = "hidden"
|
conf.Config["PrivateKey"] = "hidden"
|
||||||
}
|
}
|
||||||
case structs.VaultCAProvider:
|
case structs.VaultCAProvider:
|
||||||
if v, ok := conf.Config["Token"]; ok && v != "" {
|
if k == "Token" && ca.Uint8ToString(raw) != "" {
|
||||||
conf.Config["Token"] = "hidden"
|
conf.Config["Token"] = "hidden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,22 +140,14 @@ func (s *ConnectCA) ConfigurationSet(
|
||||||
// to get the cross-signed intermediate
|
// to get the cross-signed intermediate
|
||||||
// 3. Get the active root for the new provider, append the intermediate from step 3
|
// 3. Get the active root for the new provider, append the intermediate from step 3
|
||||||
// to its list of intermediates
|
// to its list of intermediates
|
||||||
intermediatePEM, err := newProvider.GenerateIntermediate()
|
csr, err := newProvider.GetCrossSigningCSR()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
intermediateCA, err := connect.ParseCert(intermediatePEM)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Have the old provider cross-sign the new intermediate
|
// Have the old provider cross-sign the new intermediate
|
||||||
oldProvider, _ := s.srv.getCAProvider()
|
oldProvider, _ := s.srv.getCAProvider()
|
||||||
if oldProvider == nil {
|
if oldProvider == nil {
|
||||||
return fmt.Errorf("internal error: CA provider is nil")
|
return fmt.Errorf("internal error: CA provider is nil")
|
||||||
}
|
}
|
||||||
xcCert, err := oldProvider.CrossSignCA(intermediateCA)
|
xcCert, err := oldProvider.CrossSignCA(csr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,10 +210,10 @@ func TestConnectCAConfig_TriggerRotation(t *testing.T) {
|
||||||
oldRootCert := testParseCert(t, oldRoot.RootCert)
|
oldRootCert := testParseCert(t, oldRoot.RootCert)
|
||||||
newRootCert := testParseCert(t, r.RootCert)
|
newRootCert := testParseCert(t, r.RootCert)
|
||||||
|
|
||||||
// Should have the authority/subject key IDs and signature algo of the
|
// Should have the authority key ID and signature algo of the
|
||||||
// (old) signing CA.
|
// (old) signing CA.
|
||||||
assert.Equal(xc.AuthorityKeyId, oldRootCert.AuthorityKeyId)
|
assert.Equal(xc.AuthorityKeyId, oldRootCert.AuthorityKeyId)
|
||||||
assert.Equal(xc.SubjectKeyId, oldRootCert.SubjectKeyId)
|
assert.NotEqual(xc.SubjectKeyId, oldRootCert.SubjectKeyId)
|
||||||
assert.Equal(xc.SignatureAlgorithm, oldRootCert.SignatureAlgorithm)
|
assert.Equal(xc.SignatureAlgorithm, oldRootCert.SignatureAlgorithm)
|
||||||
|
|
||||||
// The common name and SAN should not have changed.
|
// The common name and SAN should not have changed.
|
||||||
|
|
|
@ -80,6 +80,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) {
|
||||||
verify.Values(r, "", parsed, expected)
|
verify.Values(r, "", parsed, expected)
|
||||||
|
|
||||||
// Change a config value and update
|
// Change a config value and update
|
||||||
|
conf.Config["PrivateKey"] = ""
|
||||||
conf.Config["RotationPeriod"] = 120 * 24 * time.Hour
|
conf.Config["RotationPeriod"] = 120 * 24 * time.Hour
|
||||||
_, err = connect.CASetConfig(conf, nil)
|
_, err = connect.CASetConfig(conf, nil)
|
||||||
r.Check(err)
|
r.Check(err)
|
||||||
|
|
Loading…
Reference in New Issue