Skip to content

Commit

Permalink
feat: kubernetes_namespace omittable on token create for single names…
Browse files Browse the repository at this point in the history
…pace Vault role (openbao#27)
  • Loading branch information
thyton committed Apr 7, 2023
1 parent 9b6ea73 commit 1340266
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 9 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## Unreleased

### Features:

* allow omitting `kubernetes_namespace` on token create for single namespace Vault roles [GH-27](https://github.com/hashicorp/vault-plugin-secrets-kubernetes/pull/27)

## 0.4.0 (March 30, 2023)

### Features:
Expand Down
96 changes: 96 additions & 0 deletions integrationtest/creds_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,3 +463,99 @@ func TestCreds_generated_role_rules(t *testing.T) {
testClusterRoleType(t, client, path, roleConfig, expectedRoleResponse)
})
}

// Test kubernetes_namespace handling
func TestCreds_kubernetes_namespace(t *testing.T) {
// Pick up VAULT_ADDR and VAULT_TOKEN from env vars
client, err := api.NewClient(nil)
if err != nil {
t.Fatal(err)
}

path, umount := mountHelper(t, client)
defer umount()
client, delNamespace := namespaceHelper(t, client)
defer delNamespace()

// create default config
_, err = client.Logical().Write(path+"/config", map[string]interface{}{})
require.NoError(t, err)

type testCase struct {
roleConfig map[string]interface{}
credsConfig map[string]interface{}
expectedCredsCreateErrIsNil bool
}

tests := map[string]testCase{
"allowed_kubernetes_namespaces to * and kubernetes_namespace to test": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"*"},
"service_account_name": "sample-app",
},
credsConfig: map[string]interface{}{
"kubernetes_namespace": "test",
},
expectedCredsCreateErrIsNil: true,
},
"allowed_kubernetes_namespaces to a single namespace, allowed_kubernetes_namespace_selector to empty," +
" and kubernetes_namespace omitted": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"test"},
"service_account_name": "sample-app",
},
credsConfig: nil,
expectedCredsCreateErrIsNil: true,
},
"allowed_kubernetes_namespaces to * and kubernetes_namespace omitted": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"*"},
"service_account_name": "sample-app",
},
credsConfig: nil,
expectedCredsCreateErrIsNil: false,
},
"allowed_kubernetes_namespaces to a single namespace, allowed_kubernetes_namespace_selector to nonempty," +
" and kubernetes_namespace omitted": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"test"},
"allowed_kubernetes_namespace_selector": `{"matchExpressions": [{"key": "target", "operator": "In", "values": ["integration-test"]}, {"key": "nonexistantlabel", "operator": "DoesNotExist", "values": []}]}`,
"service_account_name": "sample-app",
},
credsConfig: nil,
expectedCredsCreateErrIsNil: false,
},
"allowed_kubernetes_namespaces to empty, allowed_kubernetes_namespace_selector to nonempty," +
"kubernetes_namespace omitted": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespace_selector": `{"matchExpressions": [{"key": "target", "operator": "In", "values": ["integration-test"]}, {"key": "nonexistantlabel", "operator": "DoesNotExist", "values": []}]}`,
"service_account_name": "sample-app",
},
credsConfig: nil,
expectedCredsCreateErrIsNil: false,
},
"allowed_kubernetes_namespaces to more than one specified, kubernetes_namespace omitted": {
roleConfig: map[string]interface{}{
"allowed_kubernetes_namespaces": []string{"test", "foo"},
"service_account_name": "sample-app",
},
credsConfig: nil,
expectedCredsCreateErrIsNil: false,
},
}
i := 0
for n, tc := range tests {
t.Run(n, func(t *testing.T) {
roleName := fmt.Sprintf("testrole-%d", i)
_, err = client.Logical().Write(path+"/roles/"+roleName, tc.roleConfig)
require.NoError(t, err)

creds, err := client.Logical().Write(path+"/creds/"+roleName, tc.credsConfig)
assert.Equal(t, tc.expectedCredsCreateErrIsNil, err == nil)
if tc.expectedCredsCreateErrIsNil {
require.NotNil(t, creds)
}
})
i = i + 1
}
}
4 changes: 2 additions & 2 deletions integrationtest/wal_rollback_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func TestCreds_wal_rollback(t *testing.T) {
// The backend's WAL min age is 10 seconds for tests. After that the k8s
// objects should be cleaned up.
t.Log("Checking hanging objects have been cleaned up")
checkObjects(t, roleConfig, false, false, 1*time.Minute)
checkObjects(t, roleConfig, false, false, 3*time.Minute)
})

t.Run("kubernetes_role_name", func(t *testing.T) {
Expand Down Expand Up @@ -183,7 +183,7 @@ func TestCreds_wal_rollback(t *testing.T) {
// The backend's WAL min age is 10 seconds for tests. After that the k8s
// objects should be cleaned up.
t.Log("Checking hanging objects have been cleaned up")
checkObjects(t, roleConfig, true, false, 1*time.Minute)
checkObjects(t, roleConfig, true, false, 3*time.Minute)
})
}

Expand Down
23 changes: 16 additions & 7 deletions path_creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,10 +105,9 @@ func (b *backend) pathCredentialsRead(ctx context.Context, req *logical.Request,
RoleName: roleName,
}
requestNamespace, ok := d.GetOk("kubernetes_namespace")
if !ok {
return logical.ErrorResponse("'kubernetes_namespace' is required"), nil
if ok {
request.Namespace = requestNamespace.(string)
}
request.Namespace = requestNamespace.(string)

request.ClusterRoleBinding = d.Get("cluster_role_binding").(bool)

Expand All @@ -123,7 +122,7 @@ func (b *backend) pathCredentialsRead(ctx context.Context, req *logical.Request,
}

// Validate the request
isValidNs, err := b.isValidKubernetesNamespace(ctx, req, request.Namespace, roleEntry)
isValidNs, err := b.isValidKubernetesNamespace(ctx, req, request, roleEntry)
if err != nil {
return nil, fmt.Errorf("error verifying namespace: %w", err)
}
Expand All @@ -137,8 +136,18 @@ func (b *backend) pathCredentialsRead(ctx context.Context, req *logical.Request,
return b.createCreds(ctx, req, roleEntry, request)
}

func (b *backend) isValidKubernetesNamespace(ctx context.Context, req *logical.Request, namespace string, role *roleEntry) (bool, error) {
if strutil.StrListContains(role.K8sNamespaces, "*") || strutil.StrListContains(role.K8sNamespaces, namespace) {
func (b *backend) isValidKubernetesNamespace(ctx context.Context, req *logical.Request, request *credsRequest, role *roleEntry) (bool, error) {
if request.Namespace == "" {
if role.HasSingleK8sNamespace() {
// Assign the single namespace to the creds request namespace
request.Namespace = role.K8sNamespaces[0]
return true, nil
}

return false, fmt.Errorf("'kubernetes_namespace' is required unless the Vault role has a single namespace specified")
}

if strutil.StrListContains(role.K8sNamespaces, "*") || strutil.StrListContains(role.K8sNamespaces, request.Namespace) {
return true, nil
}

Expand All @@ -154,7 +163,7 @@ func (b *backend) isValidKubernetesNamespace(ctx context.Context, req *logical.R
if err != nil {
return false, err
}
nsLabels, err := client.getNamespaceLabelSet(ctx, namespace)
nsLabels, err := client.getNamespaceLabelSet(ctx, request.Namespace)
if err != nil {
return false, err
}
Expand Down
7 changes: 7 additions & 0 deletions path_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ type roleEntry struct {
ExtraAnnotations map[string]string `json:"extra_annotations" mapstructure:"extra_annotations"`
}

// HasSingleK8sNamespace returns true if the role has a single namespace specified
// and the label selector for Kubernetes namespaces is empty
func (r *roleEntry) HasSingleK8sNamespace() bool {
return r.K8sNamespaceSelector == "" &&
len(r.K8sNamespaces) == 1 && r.K8sNamespaces[0] != "" && r.K8sNamespaces[0] != "*"
}

func (r *roleEntry) toResponseData() (map[string]interface{}, error) {
respData := map[string]interface{}{}
if err := mapstructure.Decode(r, &respData); err != nil {
Expand Down

0 comments on commit 1340266

Please sign in to comment.