connect/ca: add configurable leaf cert TTL

This commit is contained in:
Kyle Havlovitz 2018-07-16 02:46:10 -07:00
parent 915f930ce3
commit d6ca015a42
No known key found for this signature in database
GPG Key ID: 8A5E6B173056AD6C
12 changed files with 81 additions and 32 deletions

View File

@ -544,6 +544,9 @@ func (b *Builder) Build() (rt RuntimeConfig, err error) {
"token": "Token", "token": "Token",
"root_pki_path": "RootPKIPath", "root_pki_path": "RootPKIPath",
"intermediate_pki_path": "IntermediatePKIPath", "intermediate_pki_path": "IntermediatePKIPath",
// Common CA config
"leaf_cert_ttl": "LeafCertTTL",
}) })
} }

View File

@ -2545,7 +2545,8 @@ func TestFullConfig(t *testing.T) {
"connect": { "connect": {
"ca_provider": "consul", "ca_provider": "consul",
"ca_config": { "ca_config": {
"RotationPeriod": "90h" "RotationPeriod": "90h",
"LeafCertTTL": "1h"
}, },
"enabled": true, "enabled": true,
"proxy_defaults": { "proxy_defaults": {
@ -3006,7 +3007,8 @@ func TestFullConfig(t *testing.T) {
connect { connect {
ca_provider = "consul" ca_provider = "consul"
ca_config { ca_config {
"RotationPeriod" = "90h" rotation_period = "90h"
leaf_cert_ttl = "1h"
} }
enabled = true enabled = true
proxy_defaults { proxy_defaults {
@ -3610,6 +3612,7 @@ func TestFullConfig(t *testing.T) {
ConnectCAProvider: "consul", ConnectCAProvider: "consul",
ConnectCAConfig: map[string]interface{}{ ConnectCAConfig: map[string]interface{}{
"RotationPeriod": "90h", "RotationPeriod": "90h",
"LeafCertTTL": "1h",
}, },
ConnectProxyAllowManagedRoot: false, ConnectProxyAllowManagedRoot: false,
ConnectProxyAllowManagedAPIRegistration: false, ConnectProxyAllowManagedAPIRegistration: false,

View File

@ -214,8 +214,7 @@ func (c *ConsulProvider) Sign(csr *x509.CertificateRequest) (string, error) {
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageClientAuth,
x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageServerAuth,
}, },
// todo(kyhavlov): add a way to set the cert lifetime here from the CA config NotAfter: effectiveNow.Add(c.config.LeafCertTTL),
NotAfter: effectiveNow.Add(3 * 24 * time.Hour),
NotBefore: effectiveNow, NotBefore: effectiveNow,
AuthorityKeyId: keyId, AuthorityKeyId: keyId,
SubjectKeyId: keyId, SubjectKeyId: keyId,

View File

@ -10,10 +10,12 @@ import (
) )
func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderConfig, error) { func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderConfig, error) {
var config structs.ConsulCAProviderConfig config := structs.ConsulCAProviderConfig{
CommonCAProviderConfig: defaultCommonConfig(),
}
decodeConf := &mapstructure.DecoderConfig{ decodeConf := &mapstructure.DecoderConfig{
DecodeHook: ParseDurationFunc(), DecodeHook: ParseDurationFunc(),
ErrorUnused: true,
Result: &config, Result: &config,
WeaklyTypedInput: true, WeaklyTypedInput: true,
} }
@ -75,3 +77,9 @@ func Uint8ToString(bs []uint8) string {
} }
return string(b) return string(b)
} }
func defaultCommonConfig() structs.CommonCAProviderConfig {
return structs.CommonCAProviderConfig{
LeafCertTTL: 3 * 24 * time.Hour,
}
}

View File

@ -117,12 +117,13 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
func TestConsulCAProvider_SignLeaf(t *testing.T) { func TestConsulCAProvider_SignLeaf(t *testing.T) {
t.Parallel() t.Parallel()
assert := assert.New(t) require := require.New(t)
conf := testConsulCAConfig() conf := testConsulCAConfig()
conf.Config["LeafCertTTL"] = "1h"
delegate := newMockDelegate(t, conf) delegate := newMockDelegate(t, conf)
provider, err := NewConsulProvider(conf.Config, delegate) provider, err := NewConsulProvider(conf.Config, delegate)
assert.NoError(err) require.NoError(err)
spiffeService := &connect.SpiffeIDService{ spiffeService := &connect.SpiffeIDService{
Host: "node1", Host: "node1",
@ -136,20 +137,21 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {
raw, _ := connect.TestCSR(t, spiffeService) raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw) csr, err := connect.ParseCSR(raw)
assert.NoError(err) require.NoError(err)
cert, err := provider.Sign(csr) cert, err := provider.Sign(csr)
assert.NoError(err) require.NoError(err)
parsed, err := connect.ParseCert(cert) parsed, err := connect.ParseCert(cert)
assert.NoError(err) require.NoError(err)
assert.Equal(parsed.URIs[0], spiffeService.URI()) require.Equal(parsed.URIs[0], spiffeService.URI())
assert.Equal(parsed.Subject.CommonName, "foo") require.Equal(parsed.Subject.CommonName, "foo")
assert.Equal(uint64(2), parsed.SerialNumber.Uint64()) require.Equal(uint64(2), parsed.SerialNumber.Uint64())
// Ensure the cert is valid now and expires within the correct limit. // Ensure the cert is valid now and expires within the correct limit.
assert.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour) now := time.Now()
assert.True(parsed.NotBefore.Before(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 // Generate a new cert for another service and make sure
@ -159,20 +161,20 @@ func TestConsulCAProvider_SignLeaf(t *testing.T) {
raw, _ := connect.TestCSR(t, spiffeService) raw, _ := connect.TestCSR(t, spiffeService)
csr, err := connect.ParseCSR(raw) csr, err := connect.ParseCSR(raw)
assert.NoError(err) require.NoError(err)
cert, err := provider.Sign(csr) cert, err := provider.Sign(csr)
assert.NoError(err) require.NoError(err)
parsed, err := connect.ParseCert(cert) parsed, err := connect.ParseCert(cert)
assert.NoError(err) require.NoError(err)
assert.Equal(parsed.URIs[0], spiffeService.URI()) require.Equal(parsed.URIs[0], spiffeService.URI())
assert.Equal(parsed.Subject.CommonName, "bar") require.Equal(parsed.Subject.CommonName, "bar")
assert.Equal(parsed.SerialNumber.Uint64(), uint64(2)) require.Equal(parsed.SerialNumber.Uint64(), uint64(2))
// Ensure the cert is valid now and expires within the correct limit. // Ensure the cert is valid now and expires within the correct limit.
assert.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour) require.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour)
assert.True(parsed.NotBefore.Before(time.Now())) require.True(parsed.NotBefore.Before(time.Now()))
} }
} }

View File

@ -227,6 +227,7 @@ func (v *VaultProvider) Sign(csr *x509.CertificateRequest) (string, error) {
// Use the leaf cert role to sign a new cert for this CSR. // Use the leaf cert role to sign a new cert for this CSR.
response, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"sign/"+VaultCALeafCertRole, map[string]interface{}{ response, err := v.client.Logical().Write(v.config.IntermediatePKIPath+"sign/"+VaultCALeafCertRole, map[string]interface{}{
"csr": pemBuf.String(), "csr": pemBuf.String(),
"ttl": fmt.Sprintf("%.0fh", v.config.LeafCertTTL.Hours()),
}) })
if err != nil { if err != nil {
return "", fmt.Errorf("error issuing cert: %v", err) return "", fmt.Errorf("error issuing cert: %v", err)
@ -283,10 +284,12 @@ func (v *VaultProvider) Cleanup() error {
} }
func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderConfig, error) { func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderConfig, error) {
var config structs.VaultCAProviderConfig config := structs.VaultCAProviderConfig{
CommonCAProviderConfig: defaultCommonConfig(),
}
decodeConf := &mapstructure.DecoderConfig{ decodeConf := &mapstructure.DecoderConfig{
ErrorUnused: true, DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Result: &config, Result: &config,
WeaklyTypedInput: true, WeaklyTypedInput: true,
} }

View File

@ -16,6 +16,10 @@ import (
) )
func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) { func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener) {
return testVaultClusterWithConfig(t, nil)
}
func testVaultClusterWithConfig(t *testing.T, rawConf map[string]interface{}) (*VaultProvider, *vault.Core, net.Listener) {
if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil { if err := vault.AddTestLogicalBackend("pki", pki.Factory); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -23,12 +27,17 @@ func testVaultCluster(t *testing.T) (*VaultProvider, *vault.Core, net.Listener)
ln, addr := vaulthttp.TestServer(t, core) ln, addr := vaulthttp.TestServer(t, core)
provider, err := NewVaultProvider(map[string]interface{}{ conf := map[string]interface{}{
"Address": addr, "Address": addr,
"Token": token, "Token": token,
"RootPKIPath": "pki-root/", "RootPKIPath": "pki-root/",
"IntermediatePKIPath": "pki-intermediate/", "IntermediatePKIPath": "pki-intermediate/",
}, "asdf") }
for k, v := range rawConf {
conf[k] = v
}
provider, err := NewVaultProvider(conf, "asdf")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -87,7 +96,9 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
t.Parallel() t.Parallel()
require := require.New(t) require := require.New(t)
provider, core, listener := testVaultCluster(t) provider, core, listener := testVaultClusterWithConfig(t, map[string]interface{}{
"LeafCertTTL": "1h",
})
defer core.Shutdown() defer core.Shutdown()
defer listener.Close() defer listener.Close()
client, err := vaultapi.NewClient(&vaultapi.Config{ client, err := vaultapi.NewClient(&vaultapi.Config{
@ -120,8 +131,9 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
firstSerial = parsed.SerialNumber.Uint64() firstSerial = parsed.SerialNumber.Uint64()
// Ensure the cert is valid now and expires within the correct limit. // Ensure the cert is valid now and expires within the correct limit.
require.True(parsed.NotAfter.Sub(time.Now()) < 3*24*time.Hour) now := time.Now()
require.True(parsed.NotBefore.Before(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 // Generate a new cert for another service and make sure

View File

@ -68,6 +68,7 @@ func TestConnectCAConfig(t *testing.T) {
expected := &structs.ConsulCAProviderConfig{ expected := &structs.ConsulCAProviderConfig{
RotationPeriod: 90 * 24 * time.Hour, RotationPeriod: 90 * 24 * time.Hour,
} }
expected.LeafCertTTL = 72 * time.Hour
// Get the initial config. // Get the initial config.
{ {
@ -89,7 +90,8 @@ func TestConnectCAConfig(t *testing.T) {
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"RotationPeriod": 3600000000000 "LeafCertTTL": "72h",
"RotationPeriod": "1h"
} }
}`)) }`))
req, _ := http.NewRequest("PUT", "/v1/connect/ca/configuration", body) req, _ := http.NewRequest("PUT", "/v1/connect/ca/configuration", body)

View File

@ -438,6 +438,7 @@ func DefaultConfig() *Config {
Provider: "consul", Provider: "consul",
Config: map[string]interface{}{ Config: map[string]interface{}{
"RotationPeriod": "2160h", "RotationPeriod": "2160h",
"LeafCertTTL": "72h",
}, },
}, },

View File

@ -192,7 +192,13 @@ type CAConfiguration struct {
RaftIndex RaftIndex
} }
type CommonCAProviderConfig struct {
LeafCertTTL time.Duration
}
type ConsulCAProviderConfig struct { type ConsulCAProviderConfig struct {
CommonCAProviderConfig `mapstructure:",squash"`
PrivateKey string PrivateKey string
RootCert string RootCert string
RotationPeriod time.Duration RotationPeriod time.Duration
@ -208,6 +214,8 @@ type CAConsulProviderState struct {
} }
type VaultCAProviderConfig struct { type VaultCAProviderConfig struct {
CommonCAProviderConfig `mapstructure:",squash"`
Address string Address string
Token string Token string
RootPKIPath string RootPKIPath string

View File

@ -21,8 +21,15 @@ type CAConfig struct {
ModifyIndex uint64 ModifyIndex uint64
} }
// CommonCAProviderConfig is the common options available to all CA providers.
type CommonCAProviderConfig struct {
LeafCertTTL time.Duration
}
// ConsulCAProviderConfig is the config for the built-in Consul CA provider. // ConsulCAProviderConfig is the config for the built-in Consul CA provider.
type ConsulCAProviderConfig struct { type ConsulCAProviderConfig struct {
CommonCAProviderConfig `mapstructure:",squash"`
PrivateKey string PrivateKey string
RootCert string RootCert string
RotationPeriod time.Duration RotationPeriod time.Duration

View File

@ -64,6 +64,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) {
expected := &ConsulCAProviderConfig{ expected := &ConsulCAProviderConfig{
RotationPeriod: 90 * 24 * time.Hour, RotationPeriod: 90 * 24 * time.Hour,
} }
expected.LeafCertTTL = 72 * time.Hour
// This fails occasionally if server doesn't have time to bootstrap CA so // This fails occasionally if server doesn't have time to bootstrap CA so
// retry // retry