Skip to content

Commit

Permalink
modify integ test
Browse files Browse the repository at this point in the history
  • Loading branch information
thyton committed Jan 8, 2024
1 parent aebf670 commit f7406ae
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 46 deletions.
51 changes: 31 additions & 20 deletions integrationtest/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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)
}
}

Expand Down
43 changes: 29 additions & 14 deletions path_login.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand All @@ -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()),
}
Expand Down
16 changes: 12 additions & 4 deletions path_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
Expand Down Expand Up @@ -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{}{
Expand All @@ -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)
}
}
}

Expand Down
22 changes: 14 additions & 8 deletions service_account_getter.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package kubeauth
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
Expand All @@ -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
Expand All @@ -35,19 +34,21 @@ 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)
if err != nil {
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 {
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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) {
Expand Down

0 comments on commit f7406ae

Please sign in to comment.