Merge pull request #14516 from hashicorp/ca-ttl-fixes

Fix inconsistent TTL behavior in CA providers
This commit is contained in:
Kyle Havlovitz 2022-09-13 16:07:36 -07:00 committed by GitHub
commit 60cee76746
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 82 additions and 46 deletions

3
.changelog/14516.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ca: Fixed a bug with the Vault CA provider where the intermediate PKI mount and leaf cert role were not being updated when the CA configuration was changed.
```

View File

@ -66,11 +66,10 @@ type VaultProvider struct {
stopWatcher func() stopWatcher func()
isPrimary bool isPrimary bool
clusterID string clusterID string
spiffeID *connect.SpiffeIDSigning spiffeID *connect.SpiffeIDSigning
setupIntermediatePKIPathDone bool logger hclog.Logger
logger hclog.Logger
} }
func NewVaultProvider(logger hclog.Logger) *VaultProvider { func NewVaultProvider(logger hclog.Logger) *VaultProvider {
@ -174,6 +173,11 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
go v.renewToken(ctx, lifetimeWatcher) go v.renewToken(ctx, lifetimeWatcher)
} }
// Update the intermediate (managed) PKI mount and role
if err := v.setupIntermediatePKIPath(); err != nil {
return err
}
return nil return nil
} }
@ -363,8 +367,8 @@ func (v *VaultProvider) GenerateIntermediateCSR() (string, error) {
} }
func (v *VaultProvider) setupIntermediatePKIPath() error { func (v *VaultProvider) setupIntermediatePKIPath() error {
if v.setupIntermediatePKIPathDone { mountConfig := vaultapi.MountConfigInput{
return nil MaxLeaseTTL: v.config.IntermediateCertTTL.String(),
} }
_, err := v.getCA(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath) _, err := v.getCA(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath)
@ -373,9 +377,7 @@ func (v *VaultProvider) setupIntermediatePKIPath() error {
err := v.mountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath, &vaultapi.MountInput{ err := v.mountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath, &vaultapi.MountInput{
Type: "pki", Type: "pki",
Description: "intermediate CA backend for Consul Connect", Description: "intermediate CA backend for Consul Connect",
Config: vaultapi.MountConfigInput{ Config: mountConfig,
MaxLeaseTTL: v.config.IntermediateCertTTL.String(),
},
}) })
if err != nil { if err != nil {
return err return err
@ -383,39 +385,28 @@ func (v *VaultProvider) setupIntermediatePKIPath() error {
} else { } else {
return err return err
} }
} } else {
err := v.tuneMountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath, &mountConfig)
// Create the role for issuing leaf certs if it doesn't exist yet
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
role, err := v.readNamespaced(v.config.IntermediatePKINamespace, rolePath)
if err != nil {
return err
}
if role == nil {
_, err := v.writeNamespaced(v.config.IntermediatePKINamespace, rolePath, map[string]interface{}{
"allow_any_name": true,
"allowed_uri_sans": "spiffe://*",
"key_type": "any",
"max_ttl": v.config.LeafCertTTL.String(),
"no_store": true,
"require_cn": false,
})
if err != nil { if err != nil {
return err return err
} }
} }
v.setupIntermediatePKIPathDone = true
return nil // Create the role for issuing leaf certs if it doesn't exist yet
rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole
_, err = v.writeNamespaced(v.config.IntermediatePKINamespace, rolePath, map[string]interface{}{
"allow_any_name": true,
"allowed_uri_sans": "spiffe://*",
"key_type": "any",
"max_ttl": v.config.LeafCertTTL.String(),
"no_store": true,
"require_cn": false,
})
return err
} }
func (v *VaultProvider) generateIntermediateCSR() (string, error) { func (v *VaultProvider) generateIntermediateCSR() (string, error) {
err := v.setupIntermediatePKIPath()
if err != nil {
return "", err
}
// Generate a new intermediate CSR for the root to sign. // Generate a new intermediate CSR for the root to sign.
uid, err := connect.CompactUID() uid, err := connect.CompactUID()
if err != nil { if err != nil {
@ -465,10 +456,6 @@ func (v *VaultProvider) SetIntermediate(intermediatePEM, rootPEM string) error {
// ActiveIntermediate returns the current intermediate certificate. // ActiveIntermediate returns the current intermediate certificate.
func (v *VaultProvider) ActiveIntermediate() (string, error) { func (v *VaultProvider) ActiveIntermediate() (string, error) {
if err := v.setupIntermediatePKIPath(); err != nil {
return "", err
}
cert, err := v.getCA(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath) cert, err := v.getCA(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath)
// This error is expected when calling initializeSecondaryCA for the // This error is expected when calling initializeSecondaryCA for the
@ -737,6 +724,19 @@ func (v *VaultProvider) mountNamespaced(namespace, path string, mountInfo *vault
return err return err
} }
func (v *VaultProvider) tuneMountNamespaced(namespace, path string, mountConfig *vaultapi.MountConfigInput) error {
defer v.setNamespace(namespace)()
r := v.client.NewRequest("POST", fmt.Sprintf("/v1/sys/mounts/%s/tune", path))
if err := r.SetJSONBody(mountConfig); err != nil {
return err
}
resp, err := v.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
return err
}
func (v *VaultProvider) unmountNamespaced(namespace, path string) error { func (v *VaultProvider) unmountNamespaced(namespace, path string) error {
defer v.setNamespace(namespace)() defer v.setNamespace(namespace)()
r := v.client.NewRequest("DELETE", fmt.Sprintf("/v1/sys/mounts/%s", path)) r := v.client.NewRequest("DELETE", fmt.Sprintf("/v1/sys/mounts/%s", path))

View File

@ -19,6 +19,16 @@ import (
"github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/sdk/testutil/retry"
) )
const pkiTestPolicy = `
path "sys/mounts/*"
{
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}
path "pki-intermediate/*"
{
capabilities = ["create", "read", "update", "delete", "list", "sudo"]
}`
func TestVaultCAProvider_ParseVaultCAConfig(t *testing.T) { func TestVaultCAProvider_ParseVaultCAConfig(t *testing.T) {
cases := map[string]struct { cases := map[string]struct {
rawConfig map[string]interface{} rawConfig map[string]interface{}
@ -653,7 +663,7 @@ func TestVaultProvider_ConfigureWithAuthMethod(t *testing.T) {
authMethodType: "userpass", authMethodType: "userpass",
configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} { configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} {
_, err := vaultClient.Logical().Write("/auth/userpass/users/test", _, err := vaultClient.Logical().Write("/auth/userpass/users/test",
map[string]interface{}{"password": "foo", "policies": "admins"}) map[string]interface{}{"password": "foo", "policies": "pki"})
require.NoError(t, err) require.NoError(t, err)
return map[string]interface{}{ return map[string]interface{}{
"Type": "userpass", "Type": "userpass",
@ -667,7 +677,8 @@ func TestVaultProvider_ConfigureWithAuthMethod(t *testing.T) {
{ {
authMethodType: "approle", authMethodType: "approle",
configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} { configureAuthMethodFunc: func(t *testing.T, vaultClient *vaultapi.Client) map[string]interface{} {
_, err := vaultClient.Logical().Write("auth/approle/role/my-role", nil) _, err := vaultClient.Logical().Write("auth/approle/role/my-role",
map[string]interface{}{"token_policies": "pki"})
require.NoError(t, err) require.NoError(t, err)
resp, err := vaultClient.Logical().Read("auth/approle/role/my-role/role-id") resp, err := vaultClient.Logical().Read("auth/approle/role/my-role/role-id")
require.NoError(t, err) require.NoError(t, err)
@ -695,6 +706,9 @@ func TestVaultProvider_ConfigureWithAuthMethod(t *testing.T) {
err := testVault.Client().Sys().EnableAuthWithOptions(c.authMethodType, &vaultapi.EnableAuthOptions{Type: c.authMethodType}) err := testVault.Client().Sys().EnableAuthWithOptions(c.authMethodType, &vaultapi.EnableAuthOptions{Type: c.authMethodType})
require.NoError(t, err) require.NoError(t, err)
err = testVault.Client().Sys().PutPolicy("pki", pkiTestPolicy)
require.NoError(t, err)
authMethodConf := c.configureAuthMethodFunc(t, testVault.Client()) authMethodConf := c.configureAuthMethodFunc(t, testVault.Client())
conf := map[string]interface{}{ conf := map[string]interface{}{
@ -726,11 +740,18 @@ func TestVaultProvider_RotateAuthMethodToken(t *testing.T) {
testVault := NewTestVaultServer(t) testVault := NewTestVaultServer(t)
err := testVault.Client().Sys().EnableAuthWithOptions("approle", &vaultapi.EnableAuthOptions{Type: "approle"}) err := testVault.Client().Sys().PutPolicy("pki", pkiTestPolicy)
require.NoError(t, err)
err = testVault.Client().Sys().EnableAuthWithOptions("approle", &vaultapi.EnableAuthOptions{Type: "approle"})
require.NoError(t, err) require.NoError(t, err)
_, err = testVault.Client().Logical().Write("auth/approle/role/my-role", _, err = testVault.Client().Logical().Write("auth/approle/role/my-role",
map[string]interface{}{"token_ttl": "2s", "token_explicit_max_ttl": "2s"}) map[string]interface{}{
"token_ttl": "2s",
"token_explicit_max_ttl": "2s",
"token_policies": "pki",
})
require.NoError(t, err) require.NoError(t, err)
resp, err := testVault.Client().Logical().Read("auth/approle/role/my-role/role-id") resp, err := testVault.Client().Logical().Read("auth/approle/role/my-role/role-id")
require.NoError(t, err) require.NoError(t, err)

View File

@ -382,9 +382,12 @@ func (c *CAConfiguration) GetCommonConfig() (*CommonCAProviderConfig, error) {
} }
type CommonCAProviderConfig struct { type CommonCAProviderConfig struct {
LeafCertTTL time.Duration LeafCertTTL time.Duration
RootCertTTL time.Duration
// IntermediateCertTTL is only valid in the primary datacenter, and determines
// the duration that any signed intermediates are valid for.
IntermediateCertTTL time.Duration IntermediateCertTTL time.Duration
RootCertTTL time.Duration
SkipValidate bool SkipValidate bool

View File

@ -43,6 +43,15 @@ The following configuration options are supported by all CA providers:
For the Vault provider, this value is only used if the backend is not initialized at first. For the Vault provider, this value is only used if the backend is not initialized at first.
- `IntermediateCertTTL` / `intermediate_cert_ttl` (`duration: "8760h"`) The time to live (TTL) for
any intermediate certificates signed by root certificate of the primary datacenter. *This field is only
valid in the primary datacenter*.
Defaults to 1 year as `8760h`.
This setting applies to all Consul CA providers.
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