Skip to content

Commit

Permalink
Support vault namespaces in connect CA
Browse files Browse the repository at this point in the history
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.

Signed-off-by: Mark Anderson <[email protected]>
  • Loading branch information
markan committed Apr 13, 2022
1 parent 99f373d commit 253a0eb
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 11 deletions.
66 changes: 55 additions & 11 deletions agent/connect/ca/provider_vault.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ type VaultProvider struct {
spiffeID *connect.SpiffeIDSigning
setupIntermediatePKIPathDone bool
logger hclog.Logger
namespace string
}

func NewVaultProvider(logger hclog.Logger) *VaultProvider {
Expand Down Expand Up @@ -117,6 +118,7 @@ func (v *VaultProvider) Configure(cfg ProviderConfig) error {
// client also makes sure the inputs are not empty strings so let's do the
// same.
if config.Namespace != "" {
v.namespace = config.Namespace
client.SetNamespace(config.Namespace)
}
v.config = config
Expand Down Expand Up @@ -284,7 +286,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.RootPKIPath, &vaultapi.MountInput{
Type: "pki",
Description: "root CA backend for Consul Connect",
Config: vaultapi.MountConfigInput{
Expand Down Expand Up @@ -360,14 +363,14 @@ 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{
Type: "pki",
Description: "intermediate CA backend for Consul Connect",
Config: vaultapi.MountConfigInput{
MaxLeaseTTL: v.config.IntermediateCertTTL.String(),
},
})

err := v.mountNamespaced(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
}
Expand All @@ -379,18 +382,21 @@ 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
}
if role == nil {
_, err := v.client.Logical().Write(rolePath, map[string]interface{}{
r, err := v.client.Logical().Write(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,
})
v.logger.Info("======= Role %v Error%v", r, err)

if err != nil {
return err
}
Expand Down Expand Up @@ -690,7 +696,7 @@ func (v *VaultProvider) Cleanup(providerTypeChange bool, otherConfig map[string]
}
}

err := v.client.Sys().Unmount(v.config.IntermediatePKIPath)
err := v.unmountNamespaced(v.config.IntermediatePKIPath)

switch err {
case ErrBackendNotMounted, ErrBackendNotInitialized:
Expand All @@ -708,6 +714,44 @@ func (v *VaultProvider) Stop() {

func (v *VaultProvider) PrimaryUsesIntermediate() {}

func (v *VaultProvider) mountNamespaced(path string, mountInfo *vaultapi.MountInput) error {
r := v.client.NewRequest("POST", namespacedSysMountPath(path))
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(path string) error {
r := v.client.NewRequest("DELETE", namespacedSysMountPath(path))
resp, err := v.client.RawRequest(r)
if resp != nil {
defer resp.Body.Close()
}
return err
}

func extractNamespaceFromObject(objectName string) (string, string) {
parts := strings.Split(strings.TrimSuffix(objectName, "/"), "/")

base := parts[len(parts)-1] + "/"

namespace := strings.Join(parts[0:len(parts)-1], "/")
return namespace, base
}

func namespacedSysMountPath(objectName string) string {
namespace, base := extractNamespaceFromObject(objectName)
if namespace == "" {
return fmt.Sprintf("/v1/sys/mounts/%s", base)
}
return fmt.Sprintf("/v1/%s/sys/mounts/%s", namespace, base)
}

func ParseVaultCAConfig(raw map[string]interface{}) (*structs.VaultCAProviderConfig, error) {
config := structs.VaultCAProviderConfig{
CommonCAProviderConfig: defaultCommonConfig(),
Expand Down
31 changes: 31 additions & 0 deletions agent/connect/ca/provider_vault_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,3 +852,34 @@ func vaultProviderConfig(t *testing.T, addr, token string, rawConf map[string]in

return cfg
}

func TestVaultProvider_namespacedSysMountPath(t *testing.T) {

testcases := map[string]struct {
path string
out string
}{
"simple path": {
path: "foo/",
out: "/v1/sys/mounts/foo/",
},
"simple namespace": {
path: "foo/bar/",
out: "/v1/foo/sys/mounts/bar/",
},
"two layer namespace": {
path: "foo/bar/baz/",
out: "/v1/foo/bar/sys/mounts/baz/",
},
}

for name, testcase := range testcases {
t.Run(name, func(t *testing.T) {
path := namespacedSysMountPath(testcase.path)

require.Equal(t, testcase.out, path)
})
}

return
}

0 comments on commit 253a0eb

Please sign in to comment.