From 23ef36b6602709fcad471f2ebc112abe0abca40a Mon Sep 17 00:00:00 2001 From: Mark Anderson Date: Mon, 4 Apr 2022 10:00:41 -0700 Subject: [PATCH] Support vault namespaces in connect CA Follow on to some missed items from #12655 From an internal ticket "Support standard "Vault namespace in the path" semantics for Connect Vault CA Provider" Vault allows the namespace to be specified as a prefix in the path of a PKI definition, but our usage of the Vault API includes calls that don't support a namespaced key. In particular the sys.* family of calls simply appends the key, instead of prefixing the namespace in front of the path. Unfortunately it is difficult to reliably parse a path with a namespace; only vault knows what namespaces are present, and the '/' separator can be inside a key name, as well as separating path elements. This is in use in the wild; for example 'dc1/intermediate-key' is a relatively common naming schema. Instead we add two new fields: RootPKINamespace and IntermediatePKINamespace, which are the absolute namespace paths 'prefixed' in front of the respective PKI Paths. Signed-off-by: Mark Anderson --- agent/config/builder.go | 22 ++++++----- agent/connect/ca/provider_vault.go | 46 +++++++++++++++++++++-- agent/structs/connect_ca.go | 12 +++--- website/content/docs/connect/ca/vault.mdx | 6 +++ 4 files changed, 67 insertions(+), 19 deletions(-) diff --git a/agent/config/builder.go b/agent/config/builder.go index 7ef6c4906d6ba..86c493230ef50 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -697,16 +697,18 @@ func (b *builder) build() (rt RuntimeConfig, err error) { "intermediate_cert_ttl": "IntermediateCertTTL", // Vault CA config - "address": "Address", - "token": "Token", - "root_pki_path": "RootPKIPath", - "intermediate_pki_path": "IntermediatePKIPath", - "ca_file": "CAFile", - "ca_path": "CAPath", - "cert_file": "CertFile", - "key_file": "KeyFile", - "tls_server_name": "TLSServerName", - "tls_skip_verify": "TLSSkipVerify", + "address": "Address", + "token": "Token", + "root_pki_path": "RootPKIPath", + "root_pki_namespace": "RootPKINamespace", + "intermediate_pki_path": "IntermediatePKIPath", + "intermediate_pki_namespace": "IntermediatePKINamespace", + "ca_file": "CAFile", + "ca_path": "CAPath", + "cert_file": "CertFile", + "key_file": "KeyFile", + "tls_server_name": "TLSServerName", + "tls_skip_verify": "TLSSkipVerify", // AWS CA config "existing_arn": "ExistingARN", diff --git a/agent/connect/ca/provider_vault.go b/agent/connect/ca/provider_vault.go index dd548b218c9c3..8baabc8db8b1a 100644 --- a/agent/connect/ca/provider_vault.go +++ b/agent/connect/ca/provider_vault.go @@ -285,7 +285,8 @@ func (v *VaultProvider) GenerateRoot() (RootResult, error) { rootPEM, err := v.getCA(v.config.RootPKIPath) switch err { case ErrBackendNotMounted: - err := v.client.Sys().Mount(v.config.RootPKIPath, &vaultapi.MountInput{ + + err := v.mountNamespaced(v.config.RootPKINamespace, v.config.RootPKIPath, &vaultapi.MountInput{ Type: "pki", Description: "root CA backend for Consul Connect", Config: vaultapi.MountConfigInput{ @@ -361,14 +362,13 @@ func (v *VaultProvider) setupIntermediatePKIPath() error { _, err := v.getCA(v.config.IntermediatePKIPath) if err != nil { if err == ErrBackendNotMounted { - err := v.client.Sys().Mount(v.config.IntermediatePKIPath, &vaultapi.MountInput{ + err := v.mountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath, &vaultapi.MountInput{ Type: "pki", Description: "intermediate CA backend for Consul Connect", Config: vaultapi.MountConfigInput{ MaxLeaseTTL: v.config.IntermediateCertTTL.String(), }, }) - if err != nil { return err } @@ -380,6 +380,7 @@ func (v *VaultProvider) setupIntermediatePKIPath() error { // Create the role for issuing leaf certs if it doesn't exist yet rolePath := v.config.IntermediatePKIPath + "roles/" + VaultCALeafCertRole role, err := v.client.Logical().Read(rolePath) + if err != nil { return err } @@ -392,6 +393,7 @@ func (v *VaultProvider) setupIntermediatePKIPath() error { "no_store": true, "require_cn": false, }) + if err != nil { return err } @@ -691,7 +693,7 @@ func (v *VaultProvider) Cleanup(providerTypeChange bool, otherConfig map[string] } } - err := v.client.Sys().Unmount(v.config.IntermediatePKIPath) + err := v.unmountNamespaced(v.config.IntermediatePKINamespace, v.config.IntermediatePKIPath) switch err { case ErrBackendNotMounted, ErrBackendNotInitialized: @@ -709,6 +711,42 @@ func (v *VaultProvider) Stop() { func (v *VaultProvider) PrimaryUsesIntermediate() {} +// We use raw path here +func (v *VaultProvider) mountNamespaced(namespace, path string, mountInfo *vaultapi.MountInput) error { + fullPath := makePathHelper(namespace, path) + + r := v.client.NewRequest("POST", fullPath) + if err := r.SetJSONBody(mountInfo); 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 { + fullPath := makePathHelper(namespace, path) + + r := v.client.NewRequest("DELETE", fullPath) + resp, err := v.client.RawRequest(r) + if resp != nil { + defer resp.Body.Close() + } + return err +} + +func makePathHelper(namespace, path string) string { + var fullPath string + if namespace != "" { + fullPath = fmt.Sprintf("/v1/%s/sys/mounts/%s", namespace, path) + } else { + fullPath = fmt.Sprintf("/v1/sys/mounts/%s", path) + } + return fullPath +} + func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderConfig, error) { config := structs.VaultCAProviderConfig{ CommonCAProviderConfig: defaultCommonConfig(), diff --git a/agent/structs/connect_ca.go b/agent/structs/connect_ca.go index 18210e5718e7a..58cda04f7ed73 100644 --- a/agent/structs/connect_ca.go +++ b/agent/structs/connect_ca.go @@ -517,11 +517,13 @@ type CAConsulProviderState struct { type VaultCAProviderConfig struct { CommonCAProviderConfig `mapstructure:",squash"` - Address string - Token string - RootPKIPath string - IntermediatePKIPath string - Namespace string + Address string + Token string + RootPKIPath string + RootPKINamespace string + IntermediatePKIPath string + IntermediatePKINamespace string + Namespace string CAFile string CAPath string diff --git a/website/content/docs/connect/ca/vault.mdx b/website/content/docs/connect/ca/vault.mdx index be0953364a85b..9ce2c40cf110b 100644 --- a/website/content/docs/connect/ca/vault.mdx +++ b/website/content/docs/connect/ca/vault.mdx @@ -136,6 +136,9 @@ The configuration options are listed below. must contain a valid chain, where each certificate is followed by the certificate that authorized it. +- `RootPKINamespace` / `root_pki_namespace` (`string: `) - The absolute namespace + that the RootPKIPath is in. + - `IntermediatePKIPath` / `intermediate_pki_path` (`string: `) - The path to a PKI secrets engine for the generated intermediate certificate. This certificate will be signed by the configured root PKI path. If this @@ -145,6 +148,9 @@ The configuration options are listed below. When WAN Federation is enabled, every secondary datacenter must specify a unique `intermediate_pki_path`. +- `IntermediatePKINamespace` / `intermedial_pki_namespace` (`string: `) - The absolute namespace + that the IntermediatePKIPath is in. + - `CAFile` / `ca_file` (`string: ""`) - Specifies an optional path to the CA certificate used for Vault communication. If unspecified, this will fallback to the default system CA bundle, which varies by OS and version.