From f7406aed009b04d138f13eb8def51c84b38955ab Mon Sep 17 00:00:00 2001 From: Thy Ton Date: Mon, 8 Jan 2024 00:43:37 -0800 Subject: [PATCH] modify integ test --- integrationtest/integration_test.go | 51 ++++++++++++++++++----------- path_login.go | 43 ++++++++++++++++-------- path_login_test.go | 16 ++++++--- service_account_getter.go | 22 ++++++++----- 4 files changed, 86 insertions(+), 46 deletions(-) diff --git a/integrationtest/integration_test.go b/integrationtest/integration_test.go index b8fd8237..d4249a3c 100644 --- a/integrationtest/integration_test.go +++ b/integrationtest/integration_test.go @@ -243,16 +243,16 @@ func TestFailWithBadTokenReviewerJwt(t *testing.T) { } } -func TestAuthAliasCustomMetadataAssignment(t *testing.T) { +func TestAuthAliasMetadataAssignment(t *testing.T) { // annotate the service account - expCustomMetadata := map[string]string{ + expMetadata := map[string]string{ "foo": "bar", "bar": "baz", } annotationPrefix := "auth-metadata.vault.hashicorp.com/" annotations := map[string]string{} - for k, v := range expCustomMetadata { + for k, v := range expMetadata { annotations[annotationPrefix+k] = v } annotateServiceAccount(t, "vault", annotations) @@ -274,27 +274,38 @@ func TestAuthAliasCustomMetadataAssignment(t *testing.T) { t.Fatalf("Expected successful entity-alias list but got: %v", err) } - customMetadataMatches := 0 - for k, v := range secret.Data { - if k != "key_info" { - continue - } + v, ok := secret.Data["keys"] + if !ok { + t.Fatal("Expected entity-alias LIST response to have \"keys\"") + } - keyInfo := v.(map[string]interface{}) - for _, info := range keyInfo { - infoMap := info.(map[string]interface{}) - customMetadata := infoMap["custom_metadata"].(map[string]string) - for expK, expV := range expCustomMetadata { - if realK, ok := customMetadata[expK]; ok && realK == expV { - customMetadataMatches += 1 - } - } + keys := v.([]interface{}) + if len(keys) == 0 { + t.Fatal("Expected entity-alias LIST response to have non-empty \"keys\"") + } + + // query the entity alias and match up its custom metadata + secret, err = client.Logical().Read(fmt.Sprintf("identity/entity-alias/id/%s", keys[0])) + if err != nil { + t.Fatalf("Expected successful entity-alias GET request but got: %v", err) + } + + v, ok = secret.Data["metadata"] + if !ok { + t.Fatal("Expected entity-alias GET response to have \"metadata\"") + } + + metadataMatches := 0 + metadata := v.(map[string]interface{}) + for expK, expV := range expMetadata { + if realK, ok := metadata[expK]; ok && realK.(string) == expV { + metadataMatches += 1 } } - if len(expCustomMetadata) != customMetadataMatches { - t.Fatalf("Expected %d matching key value entries from alias custom metadata %#v but got: %d", - len(expCustomMetadata), secret.Data, customMetadataMatches) + if len(expMetadata) != metadataMatches { + t.Fatalf("Expected %d matching key value entries from alias metadata %#v but got: %d", + len(expMetadata), secret.Data, metadataMatches) } } diff --git a/path_login.go b/path_login.go index 37cdb660..f40eb174 100644 --- a/path_login.go +++ b/path_login.go @@ -20,6 +20,20 @@ import ( josejwt "gopkg.in/square/go-jose.v2/jwt" ) +const ( + metadataKeySaUid = "service_account_uid" + metadataKeySaName = "service_account_name" + metadataKeySaNamespace = "service_account_namespace" + metadataKeySaSecretName = "service_account_secret_name" +) + +var aliasMetadataDisallowedKeys = map[string]struct{}{ + metadataKeySaUid: {}, + metadataKeySaName: {}, + metadataKeySaNamespace: {}, + metadataKeySaSecretName: {}, +} + // defaultJWTIssuer is used to verify the iss header on the JWT if the config doesn't specify an issuer. var defaultJWTIssuer = "kubernetes/serviceaccount" @@ -151,7 +165,7 @@ func (b *kubeAuthBackend) pathLogin(ctx context.Context, req *logical.Request, d return nil, logical.ErrPermissionDenied } - annotations, err := b.serviceAccountGetterFactory(config).annotations(ctx, client, sa.namespace(), sa.name()) + annotations, err := b.serviceAccountGetterFactory(config).annotations(ctx, client, jwtStr, sa.namespace(), sa.name()) if err != nil { b.Logger().Debug("failed to get service account annotations", "err", err) return nil, err @@ -161,26 +175,27 @@ func (b *kubeAuthBackend) pathLogin(ctx context.Context, req *logical.Request, d if err != nil { return nil, err } + + metadata := annotations + metadata[metadataKeySaUid] = uid + metadata[metadataKeySaName] = sa.name() + metadata[metadataKeySaNamespace] = sa.namespace() + metadata[metadataKeySaSecretName] = sa.SecretName + auth := &logical.Auth{ Alias: &logical.Alias{ - Name: aliasName, - Metadata: map[string]string{ - "service_account_uid": uid, - "service_account_name": sa.name(), - "service_account_namespace": sa.namespace(), - "service_account_secret_name": sa.SecretName, - }, - CustomMetadata: annotations, + Name: aliasName, + Metadata: metadata, }, InternalData: map[string]interface{}{ "role": roleName, }, Metadata: map[string]string{ - "service_account_uid": uid, - "service_account_name": sa.name(), - "service_account_namespace": sa.namespace(), - "service_account_secret_name": sa.SecretName, - "role": roleName, + metadataKeySaUid: uid, + metadataKeySaName: sa.name(), + metadataKeySaNamespace: sa.namespace(), + metadataKeySaSecretName: sa.SecretName, + "role": roleName, }, DisplayName: fmt.Sprintf("%s-%s", sa.namespace(), sa.name()), } diff --git a/path_login_test.go b/path_login_test.go index a3e07b8c..28f1bc29 100644 --- a/path_login_test.go +++ b/path_login_test.go @@ -47,7 +47,7 @@ var ( testNamespace = "default" testName = "vault-auth" testUID = "d77f89bc-9055-11e7-a068-0800276d99bf" - testMetadataAnnotations = map[string]string{"key": "value", "foo": "bar"} + testMetadataAnnotations = map[string]string{"baz": "qux", "foo": "bar"} testMockTokenReviewFactory = mockTokenReviewFactory(testName, testNamespace, testUID) testMockNamespaceValidateFactory = mockNamespaceValidateFactory( map[string]string{"key": "value", "other": "label"}) @@ -846,7 +846,7 @@ func TestLoginSvcAcctNamespaceSelector(t *testing.T) { } } -func TestLoginEntityAliasCustomMetadataAssignment(t *testing.T) { +func TestLoginEntityAliasMetadataAssignment(t *testing.T) { b, storage := setupBackend(t, defaultTestBackendConfig()) data := map[string]interface{}{ @@ -869,8 +869,16 @@ func TestLoginEntityAliasCustomMetadataAssignment(t *testing.T) { t.Fatalf("err:%s resp:%#v\n", err, resp) } - if !reflect.DeepEqual(resp.Auth.Alias.CustomMetadata, testMetadataAnnotations) { - t.Fatalf("expected %#v, got %#v", testMetadataAnnotations, resp.Auth.Alias.CustomMetadata) + for expK, expV := range testMetadataAnnotations { + v, ok := resp.Auth.Alias.Metadata[expK] + + if !ok { + t.Fatalf("expected key %q not found", expK) + } + + if v != expV { + t.Fatalf("expected value %q got %q for key %q", expV, v, expK) + } } } diff --git a/service_account_getter.go b/service_account_getter.go index d7c7411b..9885bd93 100644 --- a/service_account_getter.go +++ b/service_account_getter.go @@ -3,7 +3,6 @@ package kubeauth import ( "context" "encoding/json" - "errors" "fmt" "io" "net/http" @@ -19,7 +18,7 @@ const annotationKeyPrefix = "auth-metadata.vault.hashicorp.com/" // serviceAccountGetter defines a namespace validator interface type serviceAccountGetter interface { - annotations(context.Context, *http.Client, string, string) (map[string]string, error) + annotations(context.Context, *http.Client, string, string, string) (map[string]string, error) } type serviceAccountGetterFactory func(*kubeConfig) serviceAccountGetter @@ -35,7 +34,7 @@ func newServiceAccountGetterWrapper(config *kubeConfig) serviceAccountGetter { } } -func (w *serviceAccountGetterWrapper) annotations(ctx context.Context, client *http.Client, namespace, serviceAccount string) (map[string]string, error) { +func (w *serviceAccountGetterWrapper) annotations(ctx context.Context, client *http.Client, jwtStr, namespace, serviceAccount string) (map[string]string, error) { url := fmt.Sprintf("%s/api/v1/namespaces/%s/serviceaccounts/%s", strings.TrimSuffix(w.config.Host, "/"), namespace, serviceAccount) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) @@ -43,11 +42,13 @@ func (w *serviceAccountGetterWrapper) annotations(ctx context.Context, client *h return nil, err } - // Use the configured TokenReviewer JWT as the bearer - if w.config.TokenReviewerJWT == "" { - return nil, errors.New("service account lookup failed: TokenReviewer JWT needs to be configured to retrieve service accounts") + // If we have a configured TokenReviewer JWT use it as the bearer, otherwise + // try to use the passed in JWT. + bearer := fmt.Sprintf("Bearer %s", jwtStr) + if len(w.config.TokenReviewerJWT) > 0 { + bearer = fmt.Sprintf("Bearer %s", w.config.TokenReviewerJWT) } - setRequestHeader(req, fmt.Sprintf("Bearer %s", w.config.TokenReviewerJWT)) + setRequestHeader(req, bearer) resp, err := client.Do(req) if err != nil { @@ -78,6 +79,11 @@ func (w *serviceAccountGetterWrapper) annotations(ctx context.Context, client *h for k, v := range sa.Annotations { if strings.HasPrefix(k, annotationKeyPrefix) { newK := strings.TrimPrefix(k, annotationKeyPrefix) + + if _, ok := aliasMetadataDisallowedKeys[newK]; ok { + return nil, fmt.Errorf("key %q is reserved for internal use", newK) + } + annotations[newK] = v } } @@ -96,7 +102,7 @@ func mockServiceAccountGetterFactory(meta metav1.ObjectMeta) serviceAccountGette } } -func (v *mockServiceAccountGetter) annotations(context.Context, *http.Client, string, string) (map[string]string, error) { +func (v *mockServiceAccountGetter) annotations(context.Context, *http.Client, string, string, string) (map[string]string, error) { annotations := map[string]string{} for k, v := range v.meta.Annotations { if strings.HasPrefix(k, annotationKeyPrefix) {