connect/ca: update Consul provider to use new cross-sign CSR method

This commit is contained in:
Kyle Havlovitz 2018-06-15 17:59:08 -07:00 committed by Jack Pearkes
parent 675555c4ff
commit cea94d0bcf
10 changed files with 127 additions and 66 deletions

View File

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

View File

@ -113,7 +113,6 @@ func DevSource() Source {
connect = { connect = {
enabled = true enabled = true
ca_provider = "consul"
} }
performance = { performance = {
raft_multiplier = 1 raft_multiplier = 1

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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