Merge pull request #4400 from hashicorp/leaf-cert-ttl

Add configurable leaf cert TTL to Connect CA
pull/4469/head
Kyle Havlovitz 2018-07-25 17:53:25 -07:00 committed by GitHub
commit ed87949385
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 176 additions and 44 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

@ -2602,7 +2602,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": {
@ -3073,7 +3074,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 {
@ -3687,6 +3689,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,
} }
@ -31,6 +33,10 @@ func ParseConsulCAConfig(raw map[string]interface{}) (*structs.ConsulCAProviderC
return nil, fmt.Errorf("must provide a private key when providing a root cert") return nil, fmt.Errorf("must provide a private key when providing a root cert")
} }
if err := config.CommonCAProviderConfig.Validate(); err != nil {
return nil, err
}
return &config, nil return &config, nil
} }
@ -75,3 +81,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

@ -172,7 +172,7 @@ func (v *VaultProvider) GenerateIntermediate() (string, error) {
"allow_any_name": true, "allow_any_name": true,
"allowed_uri_sans": "spiffe://*", "allowed_uri_sans": "spiffe://*",
"key_type": "any", "key_type": "any",
"max_ttl": "72h", "max_ttl": v.config.LeafCertTTL.String(),
"require_cn": false, "require_cn": false,
}) })
if err != nil { if err != nil {
@ -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": v.config.LeafCertTTL.String(),
}) })
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,
} }
@ -318,5 +321,9 @@ func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderCon
config.IntermediatePKIPath += "/" config.IntermediatePKIPath += "/"
} }
if err := config.CommonCAProviderConfig.Validate(); err != nil {
return nil, err
}
return &config, nil return &config, nil
} }

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
@ -142,7 +154,7 @@ func TestVaultCAProvider_SignLeaf(t *testing.T) {
require.NotEqual(firstSerial, parsed.SerialNumber.Uint64()) require.NotEqual(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) require.True(parsed.NotAfter.Sub(time.Now()) < time.Hour)
require.True(parsed.NotBefore.Before(time.Now())) require.True(parsed.NotBefore.Before(time.Now()))
} }
} }

View File

@ -67,6 +67,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.
{ {
@ -88,7 +89,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

@ -32,10 +32,6 @@ var (
// caRootPruneInterval is how often we check for stale CARoots to remove. // caRootPruneInterval is how often we check for stale CARoots to remove.
caRootPruneInterval = time.Hour caRootPruneInterval = time.Hour
// caRootExpireDuration is the duration after which an inactive root is considered
// "expired". Currently this is based on the default leaf cert TTL of 3 days.
caRootExpireDuration = 7 * 24 * time.Hour
// minAutopilotVersion is the minimum Consul version in which Autopilot features // minAutopilotVersion is the minimum Consul version in which Autopilot features
// are supported. // are supported.
minAutopilotVersion = version.Must(version.NewVersion("0.8.0")) minAutopilotVersion = version.Must(version.NewVersion("0.8.0"))
@ -601,14 +597,25 @@ func (s *Server) pruneCARoots() error {
return nil return nil
} }
idx, roots, err := s.fsm.State().CARoots(nil) state := s.fsm.State()
idx, roots, err := state.CARoots(nil)
if err != nil {
return err
}
_, caConf, err := state.CAConfig()
if err != nil {
return err
}
common, err := caConf.GetCommonConfig()
if err != nil { if err != nil {
return err return err
} }
var newRoots structs.CARoots var newRoots structs.CARoots
for _, r := range roots { for _, r := range roots {
if !r.Active && !r.RotatedOutAt.IsZero() && time.Now().Sub(r.RotatedOutAt) > caRootExpireDuration { if !r.Active && !r.RotatedOutAt.IsZero() && time.Now().Sub(r.RotatedOutAt) > common.LeafCertTTL*2 {
s.logger.Printf("[INFO] connect: pruning old unused root CA (ID: %s)", r.ID) s.logger.Printf("[INFO] connect: pruning old unused root CA (ID: %s)", r.ID)
continue continue
} }

View File

@ -1008,7 +1008,6 @@ func TestLeader_ACL_Initialization(t *testing.T) {
func TestLeader_CARootPruning(t *testing.T) { func TestLeader_CARootPruning(t *testing.T) {
t.Parallel() t.Parallel()
caRootExpireDuration = 500 * time.Millisecond
caRootPruneInterval = 200 * time.Millisecond caRootPruneInterval = 200 * time.Millisecond
require := require.New(t) require := require.New(t)
@ -1036,9 +1035,11 @@ func TestLeader_CARootPruning(t *testing.T) {
newConfig := &structs.CAConfiguration{ newConfig := &structs.CAConfiguration{
Provider: "consul", Provider: "consul",
Config: map[string]interface{}{ Config: map[string]interface{}{
"LeafCertTTL": 500 * time.Millisecond,
"PrivateKey": newKey, "PrivateKey": newKey,
"RootCert": "", "RootCert": "",
"RotationPeriod": 90 * 24 * time.Hour, "RotationPeriod": 90 * 24 * time.Hour,
"SkipValidate": true,
}, },
} }
{ {
@ -1056,7 +1057,7 @@ func TestLeader_CARootPruning(t *testing.T) {
require.NoError(err) require.NoError(err)
require.Len(roots, 2) require.Len(roots, 2)
time.Sleep(caRootExpireDuration * 2) time.Sleep(2 * time.Second)
// Now the old root should be pruned. // Now the old root should be pruned.
_, roots, err = s1.fsm.State().CARoots(nil) _, roots, err = s1.fsm.State().CARoots(nil)

View File

@ -1,7 +1,10 @@
package structs package structs
import ( import (
"fmt"
"time" "time"
"github.com/mitchellh/mapstructure"
) )
// IndexedCARoots is the list of currently trusted CA Roots. // IndexedCARoots is the list of currently trusted CA Roots.
@ -192,7 +195,54 @@ type CAConfiguration struct {
RaftIndex RaftIndex
} }
func (c *CAConfiguration) GetCommonConfig() (*CommonCAProviderConfig, error) {
if c == nil {
return nil, fmt.Errorf("config map was nil")
}
var config CommonCAProviderConfig
decodeConf := &mapstructure.DecoderConfig{
DecodeHook: mapstructure.StringToTimeDurationHookFunc(),
Result: &config,
}
decoder, err := mapstructure.NewDecoder(decodeConf)
if err != nil {
return nil, err
}
if err := decoder.Decode(c.Config); err != nil {
return nil, fmt.Errorf("error decoding config: %s", err)
}
return &config, nil
}
type CommonCAProviderConfig struct {
LeafCertTTL time.Duration
SkipValidate bool
}
func (c CommonCAProviderConfig) Validate() error {
if c.SkipValidate {
return nil
}
if c.LeafCertTTL < time.Hour {
return fmt.Errorf("leaf cert TTL must be greater than 1h")
}
if c.LeafCertTTL > 365*24*time.Hour {
return fmt.Errorf("leaf cert TTL must be less than 1 year")
}
return nil
}
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 +258,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

@ -63,6 +63,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

View File

@ -91,8 +91,7 @@ $ curl \
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"PrivateKey": null, "LeafCertTTL": "72h",
"RootCert": null,
"RotationPeriod": "2160h" "RotationPeriod": "2160h"
}, },
"CreateIndex": 5, "CreateIndex": 5,
@ -133,8 +132,10 @@ providers, see [Provider Config](/docs/connect/ca.html).
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"LeafCertTTL": "72h",
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----...", "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----...",
"RootCert": "-----BEGIN CERTIFICATE-----...", "RootCert": "-----BEGIN CERTIFICATE-----...",
"RotationPeriod": "2160h"
} }
} }
``` ```

View File

@ -728,6 +728,21 @@ Consul will not enable TLS for the HTTP API unless the `https` port has been ass
`write` access to this backend, as well as permission to mount the backend at this path if it is not `write` access to this backend, as well as permission to mount the backend at this path if it is not
already mounted. already mounted.
#### Common CA Config Options
<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="connect_proxy"></a><a href="#connect_proxy">`proxy`</a> This object allows setting options for the Connect proxies. The following sub-keys are available: * <a name="connect_proxy"></a><a href="#connect_proxy">`proxy`</a> This object allows setting options for the Connect proxies. The following sub-keys are available:
* <a name="connect_proxy_allow_managed_registration"></a><a href="#connect_proxy_allow_managed_registration">`allow_managed_api_registration`</a> Allows managed proxies to be configured with services that are registered via the Agent HTTP API. Enabling this would allow anyone with permission to register a service to define a command to execute for the proxy. By default, this is false to protect against arbitrary process execution. * <a name="connect_proxy_allow_managed_registration"></a><a href="#connect_proxy_allow_managed_registration">`allow_managed_api_registration`</a> Allows managed proxies to be configured with services that are registered via the Agent HTTP API. Enabling this would allow anyone with permission to register a service to define a command to execute for the proxy. By default, this is false to protect against arbitrary process execution.

View File

@ -88,6 +88,7 @@ $ curl http://localhost:8500/v1/connect/ca/configuration
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"LeafCertTTL": "72h",
"RotationPeriod": "2160h" "RotationPeriod": "2160h"
}, },
"CreateIndex": 5, "CreateIndex": 5,

View File

@ -53,6 +53,9 @@ is used if configuring in an agent configuration file.
bootstrap with the ".consul" TLD. The cluster identifier can be found bootstrap with the ".consul" TLD. The cluster identifier can be found
using the [CA List Roots endpoint](/api/connect/ca.html#list-ca-root-certificates). using the [CA List Roots endpoint](/api/connect/ca.html#list-ca-root-certificates).
There are also [common CA configuration options](/docs/agent/options.html#common-ca-config-options)
that are supported by all CA providers.
## Specifying a Custom Private Key and Root Certificate ## Specifying a Custom Private Key and Root Certificate
By default, a root certificate and private key will be automatically By default, a root certificate and private key will be automatically
@ -69,6 +72,7 @@ $ curl localhost:8500/v1/connect/ca/configuration
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"LeafCertTTL": "72h",
"RotationPeriod": "2160h" "RotationPeriod": "2160h"
}, },
"CreateIndex": 5, "CreateIndex": 5,
@ -99,6 +103,7 @@ $ jq -n --arg key "$(cat root.key)" --arg cert "$(cat root.crt)" '
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"LeafCertTTL": "72h",
"PrivateKey": $key, "PrivateKey": $key,
"RootCert": $cert, "RootCert": $cert,
"RotationPeriod": "2160h" "RotationPeriod": "2160h"
@ -113,6 +118,7 @@ $ cat ca_config.json
{ {
"Provider": "consul", "Provider": "consul",
"Config": { "Config": {
"LeafCertTTL": "72h",
"PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEArqiy1c3pbT3cSkjdEM1APALUareU...", "PrivateKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEArqiy1c3pbT3cSkjdEM1APALUareU...",
"RootCert": "-----BEGIN CERTIFICATE-----\nMIIDijCCAnKgAwIBAgIJAOFZ66em1qC7MA0GCSqGSIb3...", "RootCert": "-----BEGIN CERTIFICATE-----\nMIIDijCCAnKgAwIBAgIJAOFZ66em1qC7MA0GCSqGSIb3...",
"RotationPeriod": "2160h" "RotationPeriod": "2160h"