From 27227c0fd2abf3671afa5d5049a35ba37ef4c3f5 Mon Sep 17 00:00:00 2001 From: FFMMM Date: Tue, 2 Nov 2021 11:02:10 -0700 Subject: [PATCH] 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 * Apply suggestions from code review Co-authored-by: Chris S. Kim * add changelog, pr feedback Signed-off-by: FFMMM * Update .changelog/11428.txt, more docs Co-authored-by: Daniel Nephin * Update website/content/docs/agent/options.mdx Co-authored-by: Kyle Havlovitz Co-authored-by: Chris S. Kim Co-authored-by: Daniel Nephin Co-authored-by: Kyle Havlovitz --- .changelog/11428.txt | 3 + agent/config/builder.go | 1 + agent/config/runtime.go | 1 + agent/config/runtime_test.go | 73 ++++++++++++++++++- agent/config/testdata/full-config.hcl | 1 + agent/config/testdata/full-config.json | 1 + agent/connect/ca/provider_consul.go | 8 +- agent/connect/ca/provider_consul_config.go | 1 + agent/connect/ca/provider_consul_test.go | 27 ++++++- agent/connect/ca/provider_test.go | 1 + agent/connect/ca/provider_vault.go | 6 +- agent/connect/ca/provider_vault_test.go | 50 ++++++++++--- agent/connect/generate_test.go | 1 + agent/connect_ca_endpoint.go | 2 +- agent/consul/config.go | 1 + agent/structs/connect_ca.go | 10 ++- agent/structs/connect_ca_test.go | 29 ++++++++ api/connect_ca.go | 1 + api/connect_ca_test.go | 3 + sdk/testutil/server.go | 1 + website/content/docs/agent/options.mdx | 12 +++ .../http_api_connect_ca_common_options.mdx | 10 +++ 22 files changed, 220 insertions(+), 23 deletions(-) create mode 100644 .changelog/11428.txt diff --git a/.changelog/11428.txt b/.changelog/11428.txt new file mode 100644 index 000000000..99b90b2b3 --- /dev/null +++ b/.changelog/11428.txt @@ -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. +``` diff --git a/agent/config/builder.go b/agent/config/builder.go index 9b5f5fd97..46605d50b 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -724,6 +724,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { "csr_max_concurrent": "CSRMaxConcurrent", "private_key_type": "PrivateKeyType", "private_key_bits": "PrivateKeyBits", + "root_cert_ttl": "RootCertTTL", }) } diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 2a78f73f5..aae4f67b5 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -1623,6 +1623,7 @@ func (c *RuntimeConfig) ConnectCAConfiguration() (*structs.CAConfiguration, erro Config: map[string]interface{}{ "LeafCertTTL": structs.DefaultLeafCertTTL, "IntermediateCertTTL": structs.DefaultIntermediateCertTTL, + "RootCertTTL": structs.DefaultRootCertTTL, }, } diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 5d33f1376..3bd4257bd 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -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{ desc: "Connect AWS CA provider configuration", args: []string{ @@ -5472,6 +5531,7 @@ func TestLoad_FullConfig(t *testing.T) { ConnectCAConfig: map[string]interface{}{ "IntermediateCertTTL": "8760h", "LeafCertTTL": "1h", + "RootCertTTL": "96360h", "CSRMaxPerSecond": float64(100), "CSRMaxConcurrent": float64(2), }, @@ -6651,7 +6711,8 @@ func TestConnectCAConfiguration(t *testing.T) { Provider: "consul", Config: map[string]interface{}{ "LeafCertTTL": "72h", - "IntermediateCertTTL": "8760h", // 365 * 24h + "IntermediateCertTTL": "8760h", // 365 * 24h + "RootCertTTL": "87600h", // 365 * 10 * 24h }, }, }, @@ -6667,7 +6728,8 @@ func TestConnectCAConfiguration(t *testing.T) { ClusterID: "adfe7697-09b4-413a-ac0a-fa81ed3a3001", Config: map[string]interface{}{ "LeafCertTTL": "72h", - "IntermediateCertTTL": "8760h", // 365 * 24h + "IntermediateCertTTL": "8760h", // 365 * 24h + "RootCertTTL": "87600h", // 365 * 10 * 24h "cluster_id": "adfe7697-09b4-413a-ac0a-fa81ed3a3001", }, }, @@ -6690,7 +6752,8 @@ func TestConnectCAConfiguration(t *testing.T) { Provider: "vault", Config: map[string]interface{}{ "LeafCertTTL": "72h", - "IntermediateCertTTL": "8760h", // 365 * 24h + "IntermediateCertTTL": "8760h", // 365 * 24h + "RootCertTTL": "87600h", // 365 * 10 * 24h }, }, }, @@ -6698,7 +6761,8 @@ func TestConnectCAConfiguration(t *testing.T) { config: RuntimeConfig{ ConnectEnabled: true, ConnectCAConfig: map[string]interface{}{ - "foo": "bar", + "foo": "bar", + "RootCertTTL": "8761h", // 365 * 24h + 1 }, }, expected: &structs.CAConfiguration{ @@ -6706,6 +6770,7 @@ func TestConnectCAConfiguration(t *testing.T) { Config: map[string]interface{}{ "LeafCertTTL": "72h", "IntermediateCertTTL": "8760h", // 365 * 24h + "RootCertTTL": "8761h", // 365 * 24h + 1 "foo": "bar", }, }, diff --git a/agent/config/testdata/full-config.hcl b/agent/config/testdata/full-config.hcl index acdd740c6..f21e26f0f 100644 --- a/agent/config/testdata/full-config.hcl +++ b/agent/config/testdata/full-config.hcl @@ -200,6 +200,7 @@ connect { ca_config { intermediate_cert_ttl = "8760h" leaf_cert_ttl = "1h" + root_cert_ttl = "96360h" # hack float since json parses numbers as float and we have to # assert against the same thing csr_max_per_second = 100.0 diff --git a/agent/config/testdata/full-config.json b/agent/config/testdata/full-config.json index 711c0a1fa..200731915 100644 --- a/agent/config/testdata/full-config.json +++ b/agent/config/testdata/full-config.json @@ -200,6 +200,7 @@ "connect": { "ca_provider": "consul", "ca_config": { + "root_cert_ttl": "96360h", "intermediate_cert_ttl": "8760h", "leaf_cert_ttl": "1h", "csr_max_per_second": 100, diff --git a/agent/connect/ca/provider_consul.go b/agent/connect/ca/provider_consul.go index 1ac316b53..21f860f43 100644 --- a/agent/connect/ca/provider_consul.go +++ b/agent/connect/ca/provider_consul.go @@ -96,7 +96,7 @@ func (c *ConsulProvider) Configure(cfg ProviderConfig) error { 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 { _, providerState, err = c.Delegate.State().CAProviderState(oldID) if err != nil { @@ -194,7 +194,7 @@ func (c *ConsulProvider) GenerateRoot() error { 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 { 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 -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() _, config, err := stateStore.CAConfig(nil) if err != nil { @@ -652,7 +652,7 @@ func (c *ConsulProvider) generateCA(privateKey string, sn uint64) (string, error x509.KeyUsageCRLSign | x509.KeyUsageDigitalSignature, IsCA: true, - NotAfter: time.Now().AddDate(10, 0, 0), + NotAfter: time.Now().Add(rootCertTTL), NotBefore: time.Now(), AuthorityKeyId: keyId, SubjectKeyId: keyId, diff --git a/agent/connect/ca/provider_consul_config.go b/agent/connect/ca/provider_consul_config.go index ee0b551df..eb04ce9a7 100644 --- a/agent/connect/ca/provider_consul_config.go +++ b/agent/connect/ca/provider_consul_config.go @@ -52,5 +52,6 @@ func defaultCommonConfig() structs.CommonCAProviderConfig { IntermediateCertTTL: 24 * 365 * time.Hour, PrivateKeyType: connect.DefaultPrivateKeyType, PrivateKeyBits: connect.DefaultPrivateKeyBits, + RootCertTTL: 10 * 24 * 365 * time.Hour, } } diff --git a/agent/connect/ca/provider_consul_test.go b/agent/connect/ca/provider_consul_test.go index 8eef80a4f..f4e7c7923 100644 --- a/agent/connect/ca/provider_consul_test.go +++ b/agent/connect/ca/provider_consul_test.go @@ -43,8 +43,9 @@ func testConsulCAConfig() *structs.CAConfiguration { Provider: "consul", Config: map[string]interface{}{ // Tests duration parsing after msgpack type mangling during raft apply. - "LeafCertTTL": []uint8("72h"), - "IntermediateCertTTL": []uint8("288h"), + "LeafCertTTL": []byte("72h"), + "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)) requireNotEncoded(t, parsed.SubjectKeyId) 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) { @@ -95,7 +104,7 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) { // Make sure setting a custom private key/root cert works. require := require.New(t) - rootCA := connect.TestCA(t, nil) + rootCA := connect.TestCAWithTTL(t, nil, 5*time.Hour) conf := testConsulCAConfig() conf.Config = map[string]interface{}{ "PrivateKey": rootCA.SigningKey, @@ -110,6 +119,18 @@ func TestConsulCAProvider_Bootstrap_WithCert(t *testing.T) { root, err := provider.ActiveRoot() require.NoError(err) 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) { diff --git a/agent/connect/ca/provider_test.go b/agent/connect/ca/provider_test.go index 0a7dd6c3f..1bc1c8971 100644 --- a/agent/connect/ca/provider_test.go +++ b/agent/connect/ca/provider_test.go @@ -34,6 +34,7 @@ func TestStructs_CAConfiguration_MsgpackEncodeDecode(t *testing.T) { CSRMaxConcurrent: 55, PrivateKeyType: "rsa", PrivateKeyBits: 4096, + RootCertTTL: 10 * 24 * 365 * time.Hour, } cases := map[string]testcase{ diff --git a/agent/connect/ca/provider_vault.go b/agent/connect/ca/provider_vault.go index fab911042..872ca2e5e 100644 --- a/agent/connect/ca/provider_vault.go +++ b/agent/connect/ca/provider_vault.go @@ -170,7 +170,11 @@ func (v *VaultProvider) GenerateRoot() error { Type: "pki", Description: "root CA backend for Consul Connect", 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(), }, }) diff --git a/agent/connect/ca/provider_vault_test.go b/agent/connect/ca/provider_vault_test.go index 5987ea71f..a670effd2 100644 --- a/agent/connect/ca/provider_vault_test.go +++ b/agent/connect/ca/provider_vault_test.go @@ -89,28 +89,51 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) { SkipIfVaultNotPresent(t) - provider, testVault := testVaultProvider(t) - defer testVault.Stop() - client := testVault.client + providerWDefaultRootCertTtl, testvault1 := testVaultProviderWithConfig(t, true, map[string]interface{}{ + "LeafCertTTL": "1h", + }) + 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) cases := []struct { - certFunc func() (string, error) - backendPath string + certFunc func() (string, error) + backendPath string + rootCaCreation bool + provider *VaultProvider + client *vaultapi.Client + expectedRootCertTTL string }{ { - certFunc: provider.ActiveRoot, - backendPath: "pki-root/", + certFunc: providerWDefaultRootCertTtl.ActiveRoot, + backendPath: "pki-root/", + rootCaCreation: true, + client: client1, + provider: providerWDefaultRootCertTtl, + expectedRootCertTTL: structs.DefaultRootCertTTL, }, { - certFunc: provider.ActiveIntermediate, - backendPath: "pki-intermediate/", + certFunc: providerCustomRootCertTtl.ActiveIntermediate, + backendPath: "pki-intermediate/", + rootCaCreation: false, + provider: providerCustomRootCertTtl, + client: client2, + expectedRootCertTTL: "8761h", }, } // Verify the root and intermediate certs match the ones in the vault backends for _, tc := range cases { + provider := tc.provider + client := tc.client cert, err := tc.certFunc() require.NoError(err) req := client.NewRequest("GET", "/v1/"+tc.backendPath+"ca/pem") @@ -126,6 +149,15 @@ func TestVaultCAProvider_Bootstrap(t *testing.T) { require.True(parsed.IsCA) require.Len(parsed.URIs, 1) 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") + } } } diff --git a/agent/connect/generate_test.go b/agent/connect/generate_test.go index 4b7146d4d..bd328f69f 100644 --- a/agent/connect/generate_test.go +++ b/agent/connect/generate_test.go @@ -40,6 +40,7 @@ func makeConfig(kc KeyConfig) structs.CommonCAProviderConfig { return structs.CommonCAProviderConfig{ LeafCertTTL: 3 * 24 * time.Hour, IntermediateCertTTL: 365 * 24 * time.Hour, + RootCertTTL: 10 * 365 * 24 * time.Hour, PrivateKeyType: kc.keyType, PrivateKeyBits: kc.keyBits, } diff --git a/agent/connect_ca_endpoint.go b/agent/connect_ca_endpoint.go index 383757e85..2c6a1dfab 100644 --- a/agent/connect_ca_endpoint.go +++ b/agent/connect_ca_endpoint.go @@ -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) { // Method is tested in ConnectCAConfiguration var args structs.DCSpecificRequest diff --git a/agent/consul/config.go b/agent/consul/config.go index fac025fc1..86c87f5d7 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -494,6 +494,7 @@ func DefaultConfig() *Config { Config: map[string]interface{}{ "LeafCertTTL": structs.DefaultLeafCertTTL, "IntermediateCertTTL": structs.DefaultIntermediateCertTTL, + "RootCertTTL": structs.DefaultRootCertTTL, }, }, diff --git a/agent/structs/connect_ca.go b/agent/structs/connect_ca.go index ed54f7939..8c2186498 100644 --- a/agent/structs/connect_ca.go +++ b/agent/structs/connect_ca.go @@ -12,7 +12,8 @@ import ( const ( 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. @@ -326,6 +327,7 @@ func (c *CAConfiguration) GetCommonConfig() (*CommonCAProviderConfig, error) { type CommonCAProviderConfig struct { LeafCertTTL time.Duration IntermediateCertTTL time.Duration + RootCertTTL time.Duration SkipValidate bool @@ -380,6 +382,12 @@ func (c CommonCAProviderConfig) Validate() error { 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 { return fmt.Errorf("leaf cert TTL must be greater or equal than %s", MinLeafCertTTL) } diff --git a/agent/structs/connect_ca_test.go b/agent/structs/connect_ca_test.go index c47caa330..1609b0756 100644 --- a/agent/structs/connect_ca_test.go +++ b/agent/structs/connect_ca_test.go @@ -80,6 +80,7 @@ func TestCAProviderConfig_Validate(t *testing.T) { cfg: &CommonCAProviderConfig{ LeafCertTTL: 2 * time.Hour, IntermediateCertTTL: 4 * time.Hour, + RootCertTTL: 5 * time.Hour, }, wantErr: true, 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{ LeafCertTTL: 5 * time.Hour, IntermediateCertTTL: 15*time.Hour - 1, + RootCertTTL: 15 * time.Hour, }, wantErr: true, 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{ LeafCertTTL: 1 * time.Hour, IntermediateCertTTL: 4 * time.Hour, + RootCertTTL: 5 * time.Hour, }, wantErr: true, wantMsg: "private key type must be either 'ec' or 'rsa'", @@ -107,6 +110,7 @@ func TestCAProviderConfig_Validate(t *testing.T) { cfg: &CommonCAProviderConfig{ LeafCertTTL: 1 * time.Hour, IntermediateCertTTL: 4 * time.Hour, + RootCertTTL: 5 * time.Hour, PrivateKeyType: "ec", }, wantErr: true, @@ -117,11 +121,36 @@ func TestCAProviderConfig_Validate(t *testing.T) { cfg: &CommonCAProviderConfig{ LeafCertTTL: 1 * time.Hour, IntermediateCertTTL: 4 * time.Hour, + RootCertTTL: 5 * time.Hour, PrivateKeyType: "ec", PrivateKeyBits: 256, }, 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 { t.Run(tt.name, func(t *testing.T) { diff --git a/api/connect_ca.go b/api/connect_ca.go index 4ab2fb710..69c652dac 100644 --- a/api/connect_ca.go +++ b/api/connect_ca.go @@ -38,6 +38,7 @@ type CAConfig struct { // CommonCAProviderConfig is the common options available to all CA providers. type CommonCAProviderConfig struct { LeafCertTTL time.Duration + RootCertTTL time.Duration SkipValidate bool CSRMaxPerSecond float32 CSRMaxConcurrent int diff --git a/api/connect_ca_test.go b/api/connect_ca_test.go index f956b0f4c..67d986ef9 100644 --- a/api/connect_ca_test.go +++ b/api/connect_ca_test.go @@ -66,6 +66,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) { IntermediateCertTTL: 365 * 24 * 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 // retry @@ -84,6 +85,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) { // Change a config value and update conf.Config["PrivateKey"] = "" 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 // we can read it again. @@ -95,6 +97,7 @@ func TestAPI_ConnectCAConfig_get_set(t *testing.T) { updated, _, err := connect.CAGetConfig(nil) r.Check(err) expected.IntermediateCertTTL = 300 * 24 * time.Hour + expected.RootCertTTL = 11 * 365 * 24 * time.Hour parsed, err = ParseConsulCAConfig(updated.Config) r.Check(err) require.Equal(r, expected, parsed) diff --git a/sdk/testutil/server.go b/sdk/testutil/server.go index ba38c8fed..1ecde56a1 100644 --- a/sdk/testutil/server.go +++ b/sdk/testutil/server.go @@ -224,6 +224,7 @@ type TestServer struct { // callback function to modify the configuration. If there is an error // configuring or starting the server, the server will NOT be running when the // 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) { path, err := exec.LookPath("consul") if err != nil || path == "" { diff --git a/website/content/docs/agent/options.mdx b/website/content/docs/agent/options.mdx index 570d7dcd1..1eaf0a637 100644 --- a/website/content/docs/agent/options.mdx +++ b/website/content/docs/agent/options.mdx @@ -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 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 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 diff --git a/website/content/partials/http_api_connect_ca_common_options.mdx b/website/content/partials/http_api_connect_ca_common_options.mdx index d64d6ee47..c467bc548 100644 --- a/website/content/partials/http_api_connect_ca_common_options.mdx +++ b/website/content/partials/http_api_connect_ca_common_options.mdx @@ -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 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 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