add root_cert_ttl option for consul connect, vault ca providers (#11428)
* add root_cert_ttl option for consul connect, vault ca providers Signed-off-by: FFMMM <FFMMM@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Chris S. Kim <ckim@hashicorp.com> * add changelog, pr feedback Signed-off-by: FFMMM <FFMMM@users.noreply.github.com> * Update .changelog/11428.txt, more docs Co-authored-by: Daniel Nephin <dnephin@hashicorp.com> * Update website/content/docs/agent/options.mdx Co-authored-by: Kyle Havlovitz <kylehav@gmail.com> Co-authored-by: Chris S. Kim <ckim@hashicorp.com> Co-authored-by: Daniel Nephin <dnephin@hashicorp.com> Co-authored-by: Kyle Havlovitz <kylehav@gmail.com>
This commit is contained in:
parent
0ec2a804df
commit
27227c0fd2
|
@ -0,0 +1,3 @@
|
||||||
|
```release-note:feature
|
||||||
|
ca: Add a configurable TTL for Connect CA root certificates. The configuration is supported by the Vault and Consul providers.
|
||||||
|
```
|
|
@ -724,6 +724,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
"csr_max_concurrent": "CSRMaxConcurrent",
|
"csr_max_concurrent": "CSRMaxConcurrent",
|
||||||
"private_key_type": "PrivateKeyType",
|
"private_key_type": "PrivateKeyType",
|
||||||
"private_key_bits": "PrivateKeyBits",
|
"private_key_bits": "PrivateKeyBits",
|
||||||
|
"root_cert_ttl": "RootCertTTL",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1623,6 +1623,7 @@ func (c *RuntimeConfig) ConnectCAConfiguration() (*structs.CAConfiguration, erro
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": structs.DefaultLeafCertTTL,
|
"LeafCertTTL": structs.DefaultLeafCertTTL,
|
||||||
"IntermediateCertTTL": structs.DefaultIntermediateCertTTL,
|
"IntermediateCertTTL": structs.DefaultIntermediateCertTTL,
|
||||||
|
"RootCertTTL": structs.DefaultRootCertTTL,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3162,6 +3162,65 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
run(t, testCase{
|
||||||
|
desc: "test connect vault provider configuration with root cert ttl",
|
||||||
|
args: []string{
|
||||||
|
`-data-dir=` + dataDir,
|
||||||
|
},
|
||||||
|
json: []string{`{
|
||||||
|
"connect": {
|
||||||
|
"enabled": true,
|
||||||
|
"ca_provider": "vault",
|
||||||
|
"ca_config": {
|
||||||
|
"ca_file": "/capath/ca.pem",
|
||||||
|
"ca_path": "/capath/",
|
||||||
|
"cert_file": "/certpath/cert.pem",
|
||||||
|
"key_file": "/certpath/key.pem",
|
||||||
|
"tls_server_name": "server.name",
|
||||||
|
"tls_skip_verify": true,
|
||||||
|
"token": "abc",
|
||||||
|
"root_pki_path": "consul-vault",
|
||||||
|
"root_cert_ttl": "96360h",
|
||||||
|
"intermediate_pki_path": "connect-intermediate"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`},
|
||||||
|
hcl: []string{`
|
||||||
|
connect {
|
||||||
|
enabled = true
|
||||||
|
ca_provider = "vault"
|
||||||
|
ca_config {
|
||||||
|
ca_file = "/capath/ca.pem"
|
||||||
|
ca_path = "/capath/"
|
||||||
|
cert_file = "/certpath/cert.pem"
|
||||||
|
key_file = "/certpath/key.pem"
|
||||||
|
tls_server_name = "server.name"
|
||||||
|
tls_skip_verify = true
|
||||||
|
root_pki_path = "consul-vault"
|
||||||
|
token = "abc"
|
||||||
|
intermediate_pki_path = "connect-intermediate"
|
||||||
|
root_cert_ttl = "96360h"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`},
|
||||||
|
expected: func(rt *RuntimeConfig) {
|
||||||
|
rt.DataDir = dataDir
|
||||||
|
rt.ConnectEnabled = true
|
||||||
|
rt.ConnectCAProvider = "vault"
|
||||||
|
rt.ConnectCAConfig = map[string]interface{}{
|
||||||
|
"CAFile": "/capath/ca.pem",
|
||||||
|
"CAPath": "/capath/",
|
||||||
|
"CertFile": "/certpath/cert.pem",
|
||||||
|
"KeyFile": "/certpath/key.pem",
|
||||||
|
"TLSServerName": "server.name",
|
||||||
|
"TLSSkipVerify": true,
|
||||||
|
"Token": "abc",
|
||||||
|
"RootPKIPath": "consul-vault",
|
||||||
|
"RootCertTTL": "96360h",
|
||||||
|
"IntermediatePKIPath": "connect-intermediate",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
run(t, testCase{
|
run(t, testCase{
|
||||||
desc: "Connect AWS CA provider configuration",
|
desc: "Connect AWS CA provider configuration",
|
||||||
args: []string{
|
args: []string{
|
||||||
|
@ -5472,6 +5531,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
ConnectCAConfig: map[string]interface{}{
|
ConnectCAConfig: map[string]interface{}{
|
||||||
"IntermediateCertTTL": "8760h",
|
"IntermediateCertTTL": "8760h",
|
||||||
"LeafCertTTL": "1h",
|
"LeafCertTTL": "1h",
|
||||||
|
"RootCertTTL": "96360h",
|
||||||
"CSRMaxPerSecond": float64(100),
|
"CSRMaxPerSecond": float64(100),
|
||||||
"CSRMaxConcurrent": float64(2),
|
"CSRMaxConcurrent": float64(2),
|
||||||
},
|
},
|
||||||
|
@ -6652,6 +6712,7 @@ func TestConnectCAConfiguration(t *testing.T) {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": "72h",
|
"LeafCertTTL": "72h",
|
||||||
"IntermediateCertTTL": "8760h", // 365 * 24h
|
"IntermediateCertTTL": "8760h", // 365 * 24h
|
||||||
|
"RootCertTTL": "87600h", // 365 * 10 * 24h
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -6668,6 +6729,7 @@ func TestConnectCAConfiguration(t *testing.T) {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": "72h",
|
"LeafCertTTL": "72h",
|
||||||
"IntermediateCertTTL": "8760h", // 365 * 24h
|
"IntermediateCertTTL": "8760h", // 365 * 24h
|
||||||
|
"RootCertTTL": "87600h", // 365 * 10 * 24h
|
||||||
"cluster_id": "adfe7697-09b4-413a-ac0a-fa81ed3a3001",
|
"cluster_id": "adfe7697-09b4-413a-ac0a-fa81ed3a3001",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -6691,6 +6753,7 @@ func TestConnectCAConfiguration(t *testing.T) {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": "72h",
|
"LeafCertTTL": "72h",
|
||||||
"IntermediateCertTTL": "8760h", // 365 * 24h
|
"IntermediateCertTTL": "8760h", // 365 * 24h
|
||||||
|
"RootCertTTL": "87600h", // 365 * 10 * 24h
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -6699,6 +6762,7 @@ func TestConnectCAConfiguration(t *testing.T) {
|
||||||
ConnectEnabled: true,
|
ConnectEnabled: true,
|
||||||
ConnectCAConfig: map[string]interface{}{
|
ConnectCAConfig: map[string]interface{}{
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
|
"RootCertTTL": "8761h", // 365 * 24h + 1
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
expected: &structs.CAConfiguration{
|
expected: &structs.CAConfiguration{
|
||||||
|
@ -6706,6 +6770,7 @@ func TestConnectCAConfiguration(t *testing.T) {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": "72h",
|
"LeafCertTTL": "72h",
|
||||||
"IntermediateCertTTL": "8760h", // 365 * 24h
|
"IntermediateCertTTL": "8760h", // 365 * 24h
|
||||||
|
"RootCertTTL": "8761h", // 365 * 24h + 1
|
||||||
"foo": "bar",
|
"foo": "bar",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -200,6 +200,7 @@ connect {
|
||||||
ca_config {
|
ca_config {
|
||||||
intermediate_cert_ttl = "8760h"
|
intermediate_cert_ttl = "8760h"
|
||||||
leaf_cert_ttl = "1h"
|
leaf_cert_ttl = "1h"
|
||||||
|
root_cert_ttl = "96360h"
|
||||||
# hack float since json parses numbers as float and we have to
|
# hack float since json parses numbers as float and we have to
|
||||||
# assert against the same thing
|
# assert against the same thing
|
||||||
csr_max_per_second = 100.0
|
csr_max_per_second = 100.0
|
||||||
|
|
|
@ -200,6 +200,7 @@
|
||||||
"connect": {
|
"connect": {
|
||||||
"ca_provider": "consul",
|
"ca_provider": "consul",
|
||||||
"ca_config": {
|
"ca_config": {
|
||||||
|
"root_cert_ttl": "96360h",
|
||||||
"intermediate_cert_ttl": "8760h",
|
"intermediate_cert_ttl": "8760h",
|
||||||
"leaf_cert_ttl": "1h",
|
"leaf_cert_ttl": "1h",
|
||||||
"csr_max_per_second": 100,
|
"csr_max_per_second": 100,
|
||||||
|
|
|
@ -96,7 +96,7 @@ func (c *ConsulProvider) Configure(cfg ProviderConfig) error {
|
||||||
fmt.Sprintf("%s,%s", config.PrivateKey, config.RootCert),
|
fmt.Sprintf("%s,%s", config.PrivateKey, config.RootCert),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there any entries with old ID schemes.
|
// Check if there are any entries with old ID schemes.
|
||||||
for _, oldID := range oldIDs {
|
for _, oldID := range oldIDs {
|
||||||
_, providerState, err = c.Delegate.State().CAProviderState(oldID)
|
_, providerState, err = c.Delegate.State().CAProviderState(oldID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -194,7 +194,7 @@ func (c *ConsulProvider) GenerateRoot() error {
|
||||||
return fmt.Errorf("error computing next serial number: %v", err)
|
return fmt.Errorf("error computing next serial number: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ca, err := c.generateCA(newState.PrivateKey, nextSerial)
|
ca, err := c.generateCA(newState.PrivateKey, nextSerial, c.config.RootCertTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("error generating CA: %v", err)
|
return fmt.Errorf("error generating CA: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -616,7 +616,7 @@ func (c *ConsulProvider) incrementAndGetNextSerialNumber() (uint64, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateCA makes a new root CA using the current private key
|
// generateCA makes a new root CA using the current private key
|
||||||
func (c *ConsulProvider) generateCA(privateKey string, sn uint64) (string, error) {
|
func (c *ConsulProvider) generateCA(privateKey string, sn uint64, rootCertTTL time.Duration) (string, error) {
|
||||||
stateStore := c.Delegate.State()
|
stateStore := c.Delegate.State()
|
||||||
_, config, err := stateStore.CAConfig(nil)
|
_, config, err := stateStore.CAConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -652,7 +652,7 @@ func (c *ConsulProvider) generateCA(privateKey string, sn uint64) (string, error
|
||||||
x509.KeyUsageCRLSign |
|
x509.KeyUsageCRLSign |
|
||||||
x509.KeyUsageDigitalSignature,
|
x509.KeyUsageDigitalSignature,
|
||||||
IsCA: true,
|
IsCA: true,
|
||||||
NotAfter: time.Now().AddDate(10, 0, 0),
|
NotAfter: time.Now().Add(rootCertTTL),
|
||||||
NotBefore: time.Now(),
|
NotBefore: time.Now(),
|
||||||
AuthorityKeyId: keyId,
|
AuthorityKeyId: keyId,
|
||||||
SubjectKeyId: keyId,
|
SubjectKeyId: keyId,
|
||||||
|
|
|
@ -52,5 +52,6 @@ func defaultCommonConfig() structs.CommonCAProviderConfig {
|
||||||
IntermediateCertTTL: 24 * 365 * time.Hour,
|
IntermediateCertTTL: 24 * 365 * time.Hour,
|
||||||
PrivateKeyType: connect.DefaultPrivateKeyType,
|
PrivateKeyType: connect.DefaultPrivateKeyType,
|
||||||
PrivateKeyBits: connect.DefaultPrivateKeyBits,
|
PrivateKeyBits: connect.DefaultPrivateKeyBits,
|
||||||
|
RootCertTTL: 10 * 24 * 365 * time.Hour,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,9 @@ func testConsulCAConfig() *structs.CAConfiguration {
|
||||||
Provider: "consul",
|
Provider: "consul",
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
// Tests duration parsing after msgpack type mangling during raft apply.
|
// Tests duration parsing after msgpack type mangling during raft apply.
|
||||||
"LeafCertTTL": []uint8("72h"),
|
"LeafCertTTL": []byte("72h"),
|
||||||
"IntermediateCertTTL": []uint8("288h"),
|
"IntermediateCertTTL": []byte("288h"),
|
||||||
|
"RootCertTTL": []byte("87600h"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,6 +89,14 @@ func TestConsulCAProvider_Bootstrap(t *testing.T) {
|
||||||
require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", conf.ClusterID))
|
require.Equal(parsed.URIs[0].String(), fmt.Sprintf("spiffe://%s.consul", conf.ClusterID))
|
||||||
requireNotEncoded(t, parsed.SubjectKeyId)
|
requireNotEncoded(t, parsed.SubjectKeyId)
|
||||||
requireNotEncoded(t, parsed.AuthorityKeyId)
|
requireNotEncoded(t, parsed.AuthorityKeyId)
|
||||||
|
|
||||||
|
// test that the root cert ttl is the same as the expected value
|
||||||
|
// notice that we allow a margin of "error" of 10 minutes between the
|
||||||
|
// generateCA() creation and this check
|
||||||
|
defaultRootCertTTL, err := time.ParseDuration(structs.DefaultRootCertTTL)
|
||||||
|
require.NoError(err)
|
||||||
|
expectedNotAfter := time.Now().Add(defaultRootCertTTL).UTC()
|
||||||
|
require.WithinDuration(expectedNotAfter, parsed.NotAfter, 10*time.Minute, "expected parsed cert ttl to be the same as the value configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
||||||
|
@ -95,7 +104,7 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
||||||
|
|
||||||
// Make sure setting a custom private key/root cert works.
|
// Make sure setting a custom private key/root cert works.
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
rootCA := connect.TestCA(t, nil)
|
rootCA := connect.TestCAWithTTL(t, nil, 5*time.Hour)
|
||||||
conf := testConsulCAConfig()
|
conf := testConsulCAConfig()
|
||||||
conf.Config = map[string]interface{}{
|
conf.Config = map[string]interface{}{
|
||||||
"PrivateKey": rootCA.SigningKey,
|
"PrivateKey": rootCA.SigningKey,
|
||||||
|
@ -110,6 +119,18 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) {
|
||||||
root, err := provider.ActiveRoot()
|
root, err := provider.ActiveRoot()
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Equal(root, rootCA.RootCert)
|
require.Equal(root, rootCA.RootCert)
|
||||||
|
|
||||||
|
// Should be a valid cert
|
||||||
|
parsed, err := connect.ParseCert(root)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
// test that the default root cert ttl was not applied to the provided cert
|
||||||
|
defaultRootCertTTL, err := time.ParseDuration(structs.DefaultRootCertTTL)
|
||||||
|
require.NoError(err)
|
||||||
|
defaultNotAfter := time.Now().Add(defaultRootCertTTL).UTC()
|
||||||
|
// we can't compare given the "delta" between the time the cert is generated
|
||||||
|
// and when we start the test; so just look at the years for now, given different years
|
||||||
|
require.NotEqualf(defaultNotAfter.Year(), parsed.NotAfter.Year(), "parsed cert ttl expected to be different from default root cert ttl")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConsulCAProvider_SignLeaf(t *testing.T) {
|
func TestConsulCAProvider_SignLeaf(t *testing.T) {
|
||||||
|
|
|
@ -34,6 +34,7 @@ func TestStructs_CAConfiguration_MsgpackEncodeDecode(t *testing.T) {
|
||||||
CSRMaxConcurrent: 55,
|
CSRMaxConcurrent: 55,
|
||||||
PrivateKeyType: "rsa",
|
PrivateKeyType: "rsa",
|
||||||
PrivateKeyBits: 4096,
|
PrivateKeyBits: 4096,
|
||||||
|
RootCertTTL: 10 * 24 * 365 * time.Hour,
|
||||||
}
|
}
|
||||||
|
|
||||||
cases := map[string]testcase{
|
cases := map[string]testcase{
|
||||||
|
|
|
@ -170,7 +170,11 @@ func (v *VaultProvider) GenerateRoot() error {
|
||||||
Type: "pki",
|
Type: "pki",
|
||||||
Description: "root CA backend for Consul Connect",
|
Description: "root CA backend for Consul Connect",
|
||||||
Config: vaultapi.MountConfigInput{
|
Config: vaultapi.MountConfigInput{
|
||||||
MaxLeaseTTL: "8760h",
|
// the max lease ttl denotes the maximum ttl that secrets are created from the engine
|
||||||
|
// the default lease ttl is the kind of ttl that will *reliably* set the ttl to v.config.RootCertTTL
|
||||||
|
// https://www.vaultproject.io/docs/secrets/pki#configure-a-ca-certificate
|
||||||
|
MaxLeaseTTL: v.config.RootCertTTL.String(),
|
||||||
|
DefaultLeaseTTL: v.config.RootCertTTL.String(),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -89,28 +89,51 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) {
|
||||||
|
|
||||||
SkipIfVaultNotPresent(t)
|
SkipIfVaultNotPresent(t)
|
||||||
|
|
||||||
provider, testVault := testVaultProvider(t)
|
providerWDefaultRootCertTtl, testvault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||||
defer testVault.Stop()
|
"LeafCertTTL": "1h",
|
||||||
client := testVault.client
|
})
|
||||||
|
defer testvault1.Stop()
|
||||||
|
client1 := testvault1.client
|
||||||
|
|
||||||
|
providerCustomRootCertTtl, testvault2 := testVaultProviderWithConfig(t, true, map[string]interface{}{
|
||||||
|
"LeafCertTTL": "1h",
|
||||||
|
"RootCertTTL": "8761h",
|
||||||
|
})
|
||||||
|
defer testvault2.Stop()
|
||||||
|
client2 := testvault2.client
|
||||||
|
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
|
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
certFunc func() (string, error)
|
certFunc func() (string, error)
|
||||||
backendPath string
|
backendPath string
|
||||||
|
rootCaCreation bool
|
||||||
|
provider *VaultProvider
|
||||||
|
client *vaultapi.Client
|
||||||
|
expectedRootCertTTL string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
certFunc: provider.ActiveRoot,
|
certFunc: providerWDefaultRootCertTtl.ActiveRoot,
|
||||||
backendPath: "pki-root/",
|
backendPath: "pki-root/",
|
||||||
|
rootCaCreation: true,
|
||||||
|
client: client1,
|
||||||
|
provider: providerWDefaultRootCertTtl,
|
||||||
|
expectedRootCertTTL: structs.DefaultRootCertTTL,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
certFunc: provider.ActiveIntermediate,
|
certFunc: providerCustomRootCertTtl.ActiveIntermediate,
|
||||||
backendPath: "pki-intermediate/",
|
backendPath: "pki-intermediate/",
|
||||||
|
rootCaCreation: false,
|
||||||
|
provider: providerCustomRootCertTtl,
|
||||||
|
client: client2,
|
||||||
|
expectedRootCertTTL: "8761h",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the root and intermediate certs match the ones in the vault backends
|
// Verify the root and intermediate certs match the ones in the vault backends
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
|
provider := tc.provider
|
||||||
|
client := tc.client
|
||||||
cert, err := tc.certFunc()
|
cert, err := tc.certFunc()
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem")
|
req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem")
|
||||||
|
@ -126,6 +149,15 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) {
|
||||||
require.True(parsed.IsCA)
|
require.True(parsed.IsCA)
|
||||||
require.Len(parsed.URIs, 1)
|
require.Len(parsed.URIs, 1)
|
||||||
require.Equal(fmt.Sprintf("spiffe://%s.consul", provider.clusterID), parsed.URIs[0].String())
|
require.Equal(fmt.Sprintf("spiffe://%s.consul", provider.clusterID), parsed.URIs[0].String())
|
||||||
|
|
||||||
|
// test that the root cert ttl as applied
|
||||||
|
if tc.rootCaCreation {
|
||||||
|
rootCertTTL, err := time.ParseDuration(tc.expectedRootCertTTL)
|
||||||
|
require.NoError(err)
|
||||||
|
expectedNotAfter := time.Now().Add(rootCertTTL).UTC()
|
||||||
|
|
||||||
|
require.WithinDuration(expectedNotAfter, parsed.NotAfter, 10*time.Minute, "expected parsed cert ttl to be the same as the value configured")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,7 @@ func makeConfig(kc KeyConfig) structs.CommonCAProviderConfig {
|
||||||
return structs.CommonCAProviderConfig{
|
return structs.CommonCAProviderConfig{
|
||||||
LeafCertTTL: 3 * 24 * time.Hour,
|
LeafCertTTL: 3 * 24 * time.Hour,
|
||||||
IntermediateCertTTL: 365 * 24 * time.Hour,
|
IntermediateCertTTL: 365 * 24 * time.Hour,
|
||||||
|
RootCertTTL: 10 * 365 * 24 * time.Hour,
|
||||||
PrivateKeyType: kc.keyType,
|
PrivateKeyType: kc.keyType,
|
||||||
PrivateKeyBits: kc.keyBits,
|
PrivateKeyBits: kc.keyBits,
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ func (s *HTTPHandlers) ConnectCAConfiguration(resp http.ResponseWriter, req *htt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GEt /v1/connect/ca/configuration
|
// GET /v1/connect/ca/configuration
|
||||||
func (s *HTTPHandlers) ConnectCAConfigurationGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
func (s *HTTPHandlers) ConnectCAConfigurationGet(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
||||||
// Method is tested in ConnectCAConfiguration
|
// Method is tested in ConnectCAConfiguration
|
||||||
var args structs.DCSpecificRequest
|
var args structs.DCSpecificRequest
|
||||||
|
|
|
@ -494,6 +494,7 @@ func DefaultConfig() *Config {
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"LeafCertTTL": structs.DefaultLeafCertTTL,
|
"LeafCertTTL": structs.DefaultLeafCertTTL,
|
||||||
"IntermediateCertTTL": structs.DefaultIntermediateCertTTL,
|
"IntermediateCertTTL": structs.DefaultIntermediateCertTTL,
|
||||||
|
"RootCertTTL": structs.DefaultRootCertTTL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,8 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
DefaultLeafCertTTL = "72h"
|
DefaultLeafCertTTL = "72h"
|
||||||
DefaultIntermediateCertTTL = "8760h" // 365 * 24h
|
DefaultIntermediateCertTTL = "8760h" // ~ 1 year = 365 * 24h
|
||||||
|
DefaultRootCertTTL = "87600h" // ~ 10 years = 365 * 24h * 10
|
||||||
)
|
)
|
||||||
|
|
||||||
// IndexedCARoots is the list of currently trusted CA Roots.
|
// IndexedCARoots is the list of currently trusted CA Roots.
|
||||||
|
@ -326,6 +327,7 @@ func (c *CAConfiguration) GetCommonConfig() (*CommonCAProviderConfig, error) {
|
||||||
type CommonCAProviderConfig struct {
|
type CommonCAProviderConfig struct {
|
||||||
LeafCertTTL time.Duration
|
LeafCertTTL time.Duration
|
||||||
IntermediateCertTTL time.Duration
|
IntermediateCertTTL time.Duration
|
||||||
|
RootCertTTL time.Duration
|
||||||
|
|
||||||
SkipValidate bool
|
SkipValidate bool
|
||||||
|
|
||||||
|
@ -380,6 +382,12 @@ func (c CommonCAProviderConfig) Validate() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// it's sufficient to check that the root cert ttl >= intermediate cert ttl
|
||||||
|
// since intermediate cert ttl >= 3* leaf cert ttl; so root cert ttl >= 3 * leaf cert ttl > leaf cert ttl
|
||||||
|
if c.RootCertTTL < c.IntermediateCertTTL {
|
||||||
|
return fmt.Errorf("root cert TTL is set and is not greater than intermediate cert ttl. root cert ttl: %s, intermediate cert ttl: %s", c.RootCertTTL, c.IntermediateCertTTL)
|
||||||
|
}
|
||||||
|
|
||||||
if c.LeafCertTTL < MinLeafCertTTL {
|
if c.LeafCertTTL < MinLeafCertTTL {
|
||||||
return fmt.Errorf("leaf cert TTL must be greater or equal than %s", MinLeafCertTTL)
|
return fmt.Errorf("leaf cert TTL must be greater or equal than %s", MinLeafCertTTL)
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ func TestCAProviderConfig_Validate(t *testing.T) {
|
||||||
cfg: &CommonCAProviderConfig{
|
cfg: &CommonCAProviderConfig{
|
||||||
LeafCertTTL: 2 * time.Hour,
|
LeafCertTTL: 2 * time.Hour,
|
||||||
IntermediateCertTTL: 4 * time.Hour,
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 5 * time.Hour,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantMsg: "Intermediate Cert TTL must be greater or equal than 3 * LeafCertTTL (>=6h0m0s).",
|
wantMsg: "Intermediate Cert TTL must be greater or equal than 3 * LeafCertTTL (>=6h0m0s).",
|
||||||
|
@ -89,6 +90,7 @@ func TestCAProviderConfig_Validate(t *testing.T) {
|
||||||
cfg: &CommonCAProviderConfig{
|
cfg: &CommonCAProviderConfig{
|
||||||
LeafCertTTL: 5 * time.Hour,
|
LeafCertTTL: 5 * time.Hour,
|
||||||
IntermediateCertTTL: 15*time.Hour - 1,
|
IntermediateCertTTL: 15*time.Hour - 1,
|
||||||
|
RootCertTTL: 15 * time.Hour,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantMsg: "Intermediate Cert TTL must be greater or equal than 3 * LeafCertTTL (>=15h0m0s).",
|
wantMsg: "Intermediate Cert TTL must be greater or equal than 3 * LeafCertTTL (>=15h0m0s).",
|
||||||
|
@ -98,6 +100,7 @@ func TestCAProviderConfig_Validate(t *testing.T) {
|
||||||
cfg: &CommonCAProviderConfig{
|
cfg: &CommonCAProviderConfig{
|
||||||
LeafCertTTL: 1 * time.Hour,
|
LeafCertTTL: 1 * time.Hour,
|
||||||
IntermediateCertTTL: 4 * time.Hour,
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 5 * time.Hour,
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
wantMsg: "private key type must be either 'ec' or 'rsa'",
|
wantMsg: "private key type must be either 'ec' or 'rsa'",
|
||||||
|
@ -107,6 +110,7 @@ func TestCAProviderConfig_Validate(t *testing.T) {
|
||||||
cfg: &CommonCAProviderConfig{
|
cfg: &CommonCAProviderConfig{
|
||||||
LeafCertTTL: 1 * time.Hour,
|
LeafCertTTL: 1 * time.Hour,
|
||||||
IntermediateCertTTL: 4 * time.Hour,
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 5 * time.Hour,
|
||||||
PrivateKeyType: "ec",
|
PrivateKeyType: "ec",
|
||||||
},
|
},
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
|
@ -117,11 +121,36 @@ func TestCAProviderConfig_Validate(t *testing.T) {
|
||||||
cfg: &CommonCAProviderConfig{
|
cfg: &CommonCAProviderConfig{
|
||||||
LeafCertTTL: 1 * time.Hour,
|
LeafCertTTL: 1 * time.Hour,
|
||||||
IntermediateCertTTL: 4 * time.Hour,
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 5 * time.Hour,
|
||||||
PrivateKeyType: "ec",
|
PrivateKeyType: "ec",
|
||||||
PrivateKeyBits: 256,
|
PrivateKeyBits: 256,
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "good root cert/ intermediate TTLs",
|
||||||
|
cfg: &CommonCAProviderConfig{
|
||||||
|
LeafCertTTL: 1 * time.Hour,
|
||||||
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 5 * time.Hour,
|
||||||
|
PrivateKeyType: "ec",
|
||||||
|
PrivateKeyBits: 256,
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
wantMsg: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bad root cert/ intermediate TTLs",
|
||||||
|
cfg: &CommonCAProviderConfig{
|
||||||
|
LeafCertTTL: 1 * time.Hour,
|
||||||
|
IntermediateCertTTL: 4 * time.Hour,
|
||||||
|
RootCertTTL: 3 * time.Hour,
|
||||||
|
PrivateKeyType: "ec",
|
||||||
|
PrivateKeyBits: 256,
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
wantMsg: "root cert TTL is set and is not greater than intermediate cert ttl. root cert ttl: 3h0m0s, intermediate cert ttl: 4h0m0s",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
|
|
@ -38,6 +38,7 @@ type CAConfig struct {
|
||||||
// CommonCAProviderConfig is the common options available to all CA providers.
|
// CommonCAProviderConfig is the common options available to all CA providers.
|
||||||
type CommonCAProviderConfig struct {
|
type CommonCAProviderConfig struct {
|
||||||
LeafCertTTL time.Duration
|
LeafCertTTL time.Duration
|
||||||
|
RootCertTTL time.Duration
|
||||||
SkipValidate bool
|
SkipValidate bool
|
||||||
CSRMaxPerSecond float32
|
CSRMaxPerSecond float32
|
||||||
CSRMaxConcurrent int
|
CSRMaxConcurrent int
|
||||||
|
|
|
@ -66,6 +66,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) {
|
||||||
IntermediateCertTTL: 365 * 24 * time.Hour,
|
IntermediateCertTTL: 365 * 24 * time.Hour,
|
||||||
}
|
}
|
||||||
expected.LeafCertTTL = 72 * time.Hour
|
expected.LeafCertTTL = 72 * time.Hour
|
||||||
|
expected.RootCertTTL = 10 * 365 * 24 * 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
|
||||||
|
@ -84,6 +85,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) {
|
||||||
// Change a config value and update
|
// Change a config value and update
|
||||||
conf.Config["PrivateKey"] = ""
|
conf.Config["PrivateKey"] = ""
|
||||||
conf.Config["IntermediateCertTTL"] = 300 * 24 * time.Hour
|
conf.Config["IntermediateCertTTL"] = 300 * 24 * time.Hour
|
||||||
|
conf.Config["RootCertTTL"] = 11 * 365 * 24 * time.Hour
|
||||||
|
|
||||||
// Pass through some state as if the provider stored it so we can make sure
|
// Pass through some state as if the provider stored it so we can make sure
|
||||||
// we can read it again.
|
// we can read it again.
|
||||||
|
@ -95,6 +97,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) {
|
||||||
updated, _, err := connect.CAGetConfig(nil)
|
updated, _, err := connect.CAGetConfig(nil)
|
||||||
r.Check(err)
|
r.Check(err)
|
||||||
expected.IntermediateCertTTL = 300 * 24 * time.Hour
|
expected.IntermediateCertTTL = 300 * 24 * time.Hour
|
||||||
|
expected.RootCertTTL = 11 * 365 * 24 * time.Hour
|
||||||
parsed, err = ParseConsulCAConfig(updated.Config)
|
parsed, err = ParseConsulCAConfig(updated.Config)
|
||||||
r.Check(err)
|
r.Check(err)
|
||||||
require.Equal(r, expected, parsed)
|
require.Equal(r, expected, parsed)
|
||||||
|
|
|
@ -224,6 +224,7 @@ type TestServer struct {
|
||||||
// callback function to modify the configuration. If there is an error
|
// callback function to modify the configuration. If there is an error
|
||||||
// configuring or starting the server, the server will NOT be running when the
|
// configuring or starting the server, the server will NOT be running when the
|
||||||
// function returns (thus you do not need to stop it).
|
// function returns (thus you do not need to stop it).
|
||||||
|
// This function will call the `consul` binary in GOPATH.
|
||||||
func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, error) {
|
func NewTestServerConfigT(t TestingTB, cb ServerConfigCallback) (*TestServer, error) {
|
||||||
path, err := exec.LookPath("consul")
|
path, err := exec.LookPath("consul")
|
||||||
if err != nil || path == "" {
|
if err != nil || path == "" {
|
||||||
|
|
|
@ -1267,6 +1267,18 @@ bind_addr = "{{ GetPrivateInterfaces | include \"network\" \"10.0.0.0/8\" | attr
|
||||||
for more than twice the _current_ `leaf_cert_ttl`, it will be removed
|
for more than twice the _current_ `leaf_cert_ttl`, it will be removed
|
||||||
from the trusted list.
|
from the trusted list.
|
||||||
|
|
||||||
|
- `root_cert_ttl` ((#ca_root_cert_ttl)) The time to live (TTL) for a root certificate.
|
||||||
|
Defaults to 10 years as `87600h`. This value, if provided, needs to be higher than the
|
||||||
|
intermediate certificate TTL.
|
||||||
|
|
||||||
|
This setting currently applies only to the consul connect and Vault CA providers. It is
|
||||||
|
ignored for the AWS acm pca provider. The value for root certificates issued by the AWS
|
||||||
|
CA provider is 5 years and not configurable at this time.
|
||||||
|
|
||||||
|
For the Vault provider, this value is only used if the backend is not initialized at first.
|
||||||
|
|
||||||
|
This value is also applied on the `ca set-config` command.
|
||||||
|
|
||||||
- `private_key_type` ((#ca_private_key_type)) The type of key to generate
|
- `private_key_type` ((#ca_private_key_type)) The type of key to generate
|
||||||
for this CA. This is only used when the provider is generating a new key. If
|
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
|
`private_key` is set for the Consul provider, or existing root or intermediate
|
||||||
|
|
|
@ -35,6 +35,16 @@ The following configuration options are supported by all CA providers:
|
||||||
for more than twice the _current_ `leaf_cert_ttl`, it will be removed
|
for more than twice the _current_ `leaf_cert_ttl`, it will be removed
|
||||||
from the trusted list.
|
from the trusted list.
|
||||||
|
|
||||||
|
- `RootCertTTL` / `root_cert_ttl` (`duration: "87600h"`) The time to live (TTL) for a root certificate.
|
||||||
|
Defaults to 10 years as `87600h`. This value, if provided, needs to be higher than the
|
||||||
|
intermediate certificate TTL.
|
||||||
|
|
||||||
|
This setting currently applies only to the consul connect and Vault CA providers. It is
|
||||||
|
ignored for the AWS acm pca provider. The value for root certificates issued by the AWS
|
||||||
|
CA provider is 5 years and not configurable at this time.
|
||||||
|
|
||||||
|
For the Vault provider, this value is only used if the backend is not initialized at first.
|
||||||
|
|
||||||
- `PrivateKeyType` / `private_key_type` (`string: "ec"`) - The type of key to generate
|
- `PrivateKeyType` / `private_key_type` (`string: "ec"`) - The type of key to generate
|
||||||
for this CA. This is only used when the provider is generating a new key. If
|
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
|
`private_key` is set for the Consul provider, or existing root or intermediate
|
||||||
|
|
Loading…
Reference in New Issue