| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962 |
- /*
- Copyright © The ESO Authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- https://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package secretmanager
- import (
- "context"
- "encoding/json"
- "testing"
- "github.com/stretchr/testify/assert"
- "golang.org/x/oauth2/google/externalaccount"
- authv1 "k8s.io/api/authentication/v1"
- corev1 "k8s.io/api/core/v1"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/types"
- "sigs.k8s.io/controller-runtime/pkg/client"
- clientfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
- esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
- esmeta "github.com/external-secrets/external-secrets/apis/meta/v1"
- )
- type workloadIdentityFederationTest struct {
- name string
- wifConfig *esv1.GCPWorkloadIdentityFederation
- kubeObjects []client.Object
- genSAToken func(context.Context, []string, string, string) (*authv1.TokenRequest, error)
- expectError string
- expectTokenSource bool
- expectImpersonationURL string
- }
- const (
- testConfigMapName = "external-account-config"
- testConfigMapKey = "config.json"
- testServiceAccount = "test-sa"
- testAudience = "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/test-pool/providers/test-provider"
- testServiceAccountImpersonationURL = "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test@test.iam.gserviceaccount.com:generateAccessToken"
- testGCPServiceAccountEmail = "test@test.iam.gserviceaccount.com"
- testSAToken = "test-sa-token"
- testAwsRegion = "us-west-2"
- // below values taken from https://docs.aws.amazon.com/sdkref/latest/guide/feature-static-credentials.html
- testAwsAccessKey = "AKIAIOSFODNN7EXAMPLE"
- testAwsSecretKey = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
- // below value taken from https://docs.aws.amazon.com/STS/latest/APIReference/API_GetSessionToken.html
- testAwsSessionToken = "AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/LTo6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3zrkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtpZ3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE"
- testAwsTokenFQDNURL = "http://metadata.google.internal/latest/meta-data/iam/security-credentials"
- testAwsRegionFQDNURL = "http://metadata.google.internal/latest/meta-data/placement/availability-zone"
- testAwsSessionTokenFQDNURL = "http://metadata.google.internal/latest/api/token"
- testAwsTokenIPV4URL = "http://169.254.169.254/latest/meta-data/iam/security-credentials"
- testAwsRegionIPv4URL = "http://169.254.169.254/latest/meta-data/placement/availability-zone"
- testAwsSessionTokenIPv4URL = "http://169.254.169.254/latest/api/token"
- testAwsTokenIPV6URL = "http://[fd00:ec2::254]/latest/meta-data/iam/security-credentials"
- testAwsRegionIPv6URL = "http://[fd00:ec2::254]/latest/meta-data/placement/availability-zone"
- testAwsSessionTokenIPv6URL = "http://[fd00:ec2::254]/latest/api/token"
- )
- var (
- testNamespace = "external-secrets-tests"
- )
- func createValidK8sExternalAccountConfig(audience string) string {
- config := map[string]any{
- "type": externalAccountCredentialType,
- "audience": audience,
- "subject_token_type": workloadIdentitySubjectTokenType,
- "token_url": workloadIdentityTokenURL,
- "credential_source": map[string]any{
- "file": "/var/run/secrets/oidc_token",
- },
- "token_info_url": workloadIdentityTokenInfoURL,
- }
- data, _ := json.Marshal(config)
- return string(data)
- }
- func createValidAWSExternalAccountConfig(audience string) string {
- config := map[string]any{
- "type": externalAccountCredentialType,
- "audience": audience,
- "subject_token_type": workloadIdentitySubjectTokenType,
- "token_url": workloadIdentityTokenURL,
- "service_account_impersonation_url": testServiceAccountImpersonationURL,
- "credential_source": map[string]any{
- "environment_id": "aws1",
- "url": testAwsTokenIPV4URL,
- "region_url": testAwsRegionIPv4URL,
- "imdsv2_session_token_url": testAwsSessionTokenIPv4URL,
- },
- }
- data, _ := json.Marshal(config)
- return string(data)
- }
- func createInvalidTypeExternalAccountConfig() string {
- config := map[string]any{
- "type": "service_account",
- "audience": testAudience,
- }
- data, _ := json.Marshal(config)
- return string(data)
- }
- func createInvalidK8sExternalAccountConfigWithUnallowedTokenFilePath(audience string) string {
- config := map[string]any{
- "type": externalAccountCredentialType,
- "audience": audience,
- "subject_token_type": workloadIdentitySubjectTokenType,
- "token_url": workloadIdentityTokenURL,
- "credential_source": map[string]any{
- "file": autoMountedServiceAccountTokenPath,
- },
- "token_info_url": workloadIdentityTokenInfoURL,
- }
- data, _ := json.Marshal(config)
- return string(data)
- }
- func createInvalidK8sExternalAccountConfigWithUnallowedTokenURL(audience string) string {
- config := map[string]any{
- "type": externalAccountCredentialType,
- "audience": audience,
- "subject_token_type": workloadIdentitySubjectTokenType,
- "token_url": "https://example.com",
- "credential_source": map[string]any{
- "file": "/var/run/secrets/oidc_token",
- },
- "token_info_url": workloadIdentityTokenInfoURL,
- }
- data, _ := json.Marshal(config)
- return string(data)
- }
- func defaultSATokenGenerator(ctx context.Context, idPool []string, namespace, name string) (*authv1.TokenRequest, error) {
- return &authv1.TokenRequest{
- Status: authv1.TokenRequestStatus{
- Token: testSAToken,
- },
- }, nil
- }
- func TestWorkloadIdentityFederation(t *testing.T) {
- tests := []*workloadIdentityFederationTest{
- {
- name: "workload identity federation config is empty",
- wifConfig: nil,
- },
- {
- name: "invalid workload identity federation config without audience",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- ServiceAccountRef: &esmeta.ServiceAccountSelector{
- Name: testServiceAccount,
- Namespace: &testNamespace,
- Audiences: []string{testAudience},
- },
- },
- expectError: `invalid workloadIdentityFederation config: audience must be provided, when serviceAccountRef or awsSecurityCredentials is provided`,
- },
- {
- name: "successful kubernetes service account token federation",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidK8sExternalAccountConfig(testAudience),
- },
- },
- },
- expectTokenSource: true,
- },
- {
- name: "cred configmap configured non-existent key",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Namespace: testNamespace,
- Key: testConfigMapKey,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- "incorrect": createValidK8sExternalAccountConfig(testAudience),
- },
- },
- },
- expectError: `missing key "config.json" in configmap "external-account-config"`,
- },
- {
- name: "cred configmap configured with wrong key name",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: "wrongKey",
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidK8sExternalAccountConfig(testAudience),
- },
- },
- },
- expectError: `missing key "wrongKey" in configmap "external-account-config"`,
- },
- {
- name: "invalid cred config - invalid tokenURL",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Namespace: testNamespace,
- Key: testConfigMapKey,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createInvalidK8sExternalAccountConfigWithUnallowedTokenURL(testAudience),
- },
- },
- },
- expectError: "invalid external_account config\ntoken_url \"https://example.com\" must match https://sts.googleapis.com/v1/token",
- },
- {
- name: "successful AWS federation with security credentials",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidAWSExternalAccountConfig(testAudience),
- },
- },
- &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- Data: map[string][]byte{
- awsAccessKeyIDKeyName: []byte(testAwsAccessKey),
- awsSecretAccessKeyKeyName: []byte(testAwsSecretKey),
- awsSessionTokenKeyName: []byte(testAwsSessionToken),
- },
- },
- },
- expectTokenSource: true,
- },
- {
- name: "external account creds configmap not present",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{},
- expectError: `failed to fetch external acccount credentials configmap "external-secrets-tests/external-account-config": configmaps "external-account-config" not found`,
- },
- {
- name: "creds configmap has invalid type",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createInvalidTypeExternalAccountConfig(),
- },
- },
- },
- expectError: `invalid credentials: 'type' field is "service_account" (expected "external_account")`,
- },
- {
- name: "creds configmap has non-json data",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: "invalid-json",
- },
- },
- },
- expectError: "failed to unmarshal external acccount config in \"external-account-config\": invalid character 'i' looking for beginning of value",
- },
- {
- name: "successful with service account reference",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- ServiceAccountRef: &esmeta.ServiceAccountSelector{
- Name: testServiceAccount,
- Namespace: &testNamespace,
- Audiences: []string{testAudience},
- },
- Audience: testAudience,
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createInvalidK8sExternalAccountConfigWithUnallowedTokenFilePath(testAudience),
- },
- },
- &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: testServiceAccount,
- Namespace: testNamespace,
- },
- },
- },
- genSAToken: func(c context.Context, s1 []string, s2, s3 string) (*authv1.TokenRequest, error) {
- return &authv1.TokenRequest{
- Status: authv1.TokenRequestStatus{
- Token: testSAToken,
- },
- }, nil
- },
- expectTokenSource: true,
- },
- {
- name: "fail on missing service account",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- ServiceAccountRef: &esmeta.ServiceAccountSelector{
- Name: testServiceAccount,
- Namespace: &testNamespace,
- Audiences: []string{testAudience},
- },
- Audience: testAudience,
- },
- expectError: "failed to fetch serviceaccount \"external-secrets-tests/test-sa\": serviceaccounts \"test-sa\" not found",
- },
- {
- name: "successful kubernetes service account token federation with GCP service account impersonation",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- Audience: testAudience,
- ServiceAccountRef: &esmeta.ServiceAccountSelector{
- Name: testServiceAccount,
- Namespace: &testNamespace,
- Audiences: []string{testAudience},
- },
- },
- kubeObjects: []client.Object{
- &corev1.ServiceAccount{
- ObjectMeta: metav1.ObjectMeta{
- Name: testServiceAccount,
- Namespace: testNamespace,
- Annotations: map[string]string{
- gcpSAAnnotation: testGCPServiceAccountEmail,
- },
- },
- },
- },
- genSAToken: func(c context.Context, s1 []string, s2, s3 string) (*authv1.TokenRequest, error) {
- return &authv1.TokenRequest{
- Status: authv1.TokenRequestStatus{
- Token: testSAToken,
- },
- }, nil
- },
- expectImpersonationURL: testServiceAccountImpersonationURL,
- expectTokenSource: true,
- },
- {
- name: "valid AWS credentials secret",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- AwsSecurityCredentials: &esv1.AwsCredentialsConfig{
- Region: testAwsRegion,
- AwsCredentialsSecretRef: &esv1.SecretReference{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- },
- Audience: testAudience,
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidAWSExternalAccountConfig(testAudience),
- },
- },
- &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- Data: map[string][]byte{
- awsAccessKeyIDKeyName: []byte(testAwsAccessKey),
- awsSecretAccessKeyKeyName: []byte(testAwsSecretKey),
- },
- },
- },
- },
- {
- name: "non-existent AWS credentials secret",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- AwsSecurityCredentials: &esv1.AwsCredentialsConfig{
- Region: testAwsRegion,
- AwsCredentialsSecretRef: &esv1.SecretReference{
- Name: "non-existent-aws-creds",
- Namespace: testNamespace,
- },
- },
- Audience: testAudience,
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidAWSExternalAccountConfig(testAudience),
- },
- },
- },
- expectError: `failed to fetch AwsSecurityCredentials secret "external-secrets-tests/non-existent-aws-creds": secrets "non-existent-aws-creds" not found`,
- },
- {
- name: "invalid AWS credentials - aws_access_key_id not provided",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- AwsSecurityCredentials: &esv1.AwsCredentialsConfig{
- Region: testAwsRegion,
- AwsCredentialsSecretRef: &esv1.SecretReference{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- },
- Audience: testAudience,
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidAWSExternalAccountConfig(testAudience),
- },
- },
- &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- Data: map[string][]byte{
- awsSecretAccessKeyKeyName: []byte(testAwsSecretKey),
- },
- },
- },
- expectError: "aws_access_key_id and aws_secret_access_key keys must be present in AwsSecurityCredentials secret",
- },
- {
- name: "credConfig is empty",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: nil,
- },
- expectError: "invalid workloadIdentityFederation config: exactly one of credConfig, awsSecurityCredentials or serviceAccountRef must be provided",
- },
- {
- name: "both credential_source in credConfig and AwsCredentialsConfig are set",
- wifConfig: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Key: testConfigMapKey,
- Namespace: testNamespace,
- },
- AwsSecurityCredentials: &esv1.AwsCredentialsConfig{
- Region: testAwsRegion,
- AwsCredentialsSecretRef: &esv1.SecretReference{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: createValidAWSExternalAccountConfig(testAudience),
- },
- },
- &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- Data: map[string][]byte{
- awsAccessKeyIDKeyName: []byte(testAwsAccessKey),
- awsSecretAccessKeyKeyName: []byte(testAwsSecretKey),
- awsSessionTokenKeyName: []byte(testAwsSessionToken),
- },
- },
- },
- expectError: "invalid workloadIdentityFederation config: exactly one of credConfig, awsSecurityCredentials or serviceAccountRef must be provided",
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- fakeClient := clientfake.NewClientBuilder().WithObjects(tc.kubeObjects...).Build()
- fakeSATG := &fakeSATokenGen{
- GenerateFunc: tc.genSAToken,
- }
- if tc.genSAToken == nil {
- fakeSATG.GenerateFunc = defaultSATokenGenerator
- }
- wif := &workloadIdentityFederation{
- kubeClient: fakeClient,
- saTokenGenerator: fakeSATG,
- config: tc.wifConfig,
- isClusterKind: true,
- namespace: testNamespace,
- }
- if tc.expectImpersonationURL != "" {
- cfg, cfgErr := wif.generateExternalAccountConfig(context.Background(), nil)
- assert.NoError(t, cfgErr)
- assert.Equal(t, tc.expectImpersonationURL, cfg.ServiceAccountImpersonationURL)
- }
- ts, err := wif.TokenSource(context.Background())
- if tc.expectError != "" {
- assert.Error(t, err)
- assert.Equal(t, tc.expectError, err.Error())
- assert.Nil(t, ts)
- } else {
- assert.NoError(t, err)
- if tc.expectTokenSource {
- assert.NotNil(t, ts)
- }
- }
- })
- }
- }
- func TestValidateCredConfig(t *testing.T) {
- tests := []struct {
- name string
- config *externalaccount.Config
- wif *esv1.GCPWorkloadIdentityFederation
- expectError string
- }{
- {
- name: "valid kubernetes provider config",
- config: &externalaccount.Config{
- Audience: testAudience,
- SubjectTokenType: workloadIdentitySubjectTokenType,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- File: autoMountedServiceAccountTokenPath,
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "",
- },
- {
- name: "valid AWS provider config with IPv6",
- config: &externalaccount.Config{
- Audience: testAudience,
- SubjectTokenType: workloadIdentitySubjectTokenType,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- EnvironmentID: "aws1",
- URL: testAwsTokenIPV6URL,
- RegionURL: testAwsRegionIPv6URL,
- IMDSv2SessionTokenURL: testAwsSessionTokenIPv6URL,
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "",
- },
- {
- name: "valid AWS provider config with FQDN",
- config: &externalaccount.Config{
- Audience: testAudience,
- SubjectTokenType: workloadIdentitySubjectTokenType,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- EnvironmentID: "aws1",
- URL: testAwsTokenFQDNURL,
- RegionURL: testAwsRegionFQDNURL,
- IMDSv2SessionTokenURL: testAwsSessionTokenFQDNURL,
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "",
- },
- {
- name: "invalid service account impersonation URL",
- config: &externalaccount.Config{
- Audience: testAudience,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: "https://invalid-url.com",
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "invalid external_account config\nservice_account_impersonation_url \"https://invalid-url.com\" does not have expected value",
- },
- {
- name: "invalid token URL",
- config: &externalaccount.Config{
- Audience: testAudience,
- TokenURL: "https://invalid-token-url.com",
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "invalid external_account config\ntoken_url \"https://invalid-token-url.com\" must match https://sts.googleapis.com/v1/token",
- },
- {
- name: "executable is configured",
- config: &externalaccount.Config{
- Audience: testAudience,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- Executable: &externalaccount.ExecutableConfig{
- Command: "/usr/local/bin/token-issuer",
- },
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "invalid external_account config\ncredential_source.executable.command is not allowed\none of credential_source.file, credential_source.url, credential_source.aws.url or credential_source_environment_id should be provided",
- },
- {
- name: "invalid config - empty audience",
- config: &externalaccount.Config{
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- File: "/var/run/secrets/token",
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- },
- expectError: "invalid external_account config\naudience is empty",
- },
- {
- name: "invalid config - invalid URL",
- config: &externalaccount.Config{
- Audience: testAudience,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- URL: "https://example.com",
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- ExternalTokenEndpoint: "https://mismatch.com",
- },
- expectError: "invalid external_account config\ncredential_source.url \"https://example.com\" does not match with the configured https://mismatch.com externalTokenEndpoint",
- },
- {
- name: "invalid config - invalid AWS config",
- config: &externalaccount.Config{
- Audience: testAudience,
- TokenURL: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- CredentialSource: &externalaccount.CredentialSource{
- EnvironmentID: "sample",
- URL: "https://aws-token.com",
- RegionURL: "https://region.com",
- IMDSv2SessionTokenURL: "https://session-token.com",
- },
- },
- wif: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{Name: testConfigMapName},
- ExternalTokenEndpoint: "https://mismatch.com",
- },
- expectError: "invalid external_account config\ncredential_source.environment_id \"sample\" must start with aws\ncredential_source.aws.url \"https://aws-token.com\" does not have expected value\ncredential_source.aws.region_url \"https://region.com\" does not have expected value\ncredential_source.aws.imdsv2_session_token_url \"https://session-token.com\" does not have expected value",
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- err := validateExternalAccountConfig(tc.config, tc.wif)
- if tc.expectError != "" {
- assert.Error(t, err)
- assert.Equal(t, tc.expectError, err.Error())
- } else {
- assert.NoError(t, err)
- }
- })
- }
- }
- func TestK8sSATokenReader(t *testing.T) {
- r := &k8sSATokenReader{
- audience: testAudience,
- subjectTokenType: workloadIdentitySubjectTokenType,
- saTokenGenerator: &fakeSATokenGen{
- GenerateFunc: defaultSATokenGenerator,
- },
- saAudience: []string{testAudience},
- serviceAccount: types.NamespacedName{
- Name: testServiceAccount,
- Namespace: testNamespace,
- },
- }
- ctx := context.Background()
- // Test successful token generation
- token, err := r.SubjectToken(ctx, externalaccount.SupplierOptions{
- Audience: testAudience,
- SubjectTokenType: workloadIdentitySubjectTokenType,
- })
- assert.NoError(t, err)
- assert.Equal(t, testSAToken, token)
- // Test invalid audience
- _, err = r.SubjectToken(ctx, externalaccount.SupplierOptions{
- Audience: "invalid-audience",
- SubjectTokenType: workloadIdentitySubjectTokenType,
- })
- assert.Error(t, err)
- assert.Equal(
- t,
- `invalid subject token request, audience is invalid-audience(expected //iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/test-pool/providers/test-provider) and subject_token_type is urn:ietf:params:oauth:token-type:jwt(expected urn:ietf:params:oauth:token-type:jwt)`,
- err.Error(),
- )
- // Test invalid subject token type
- _, err = r.SubjectToken(ctx, externalaccount.SupplierOptions{
- Audience: testAudience,
- SubjectTokenType: "invalid-type",
- })
- assert.Error(t, err)
- assert.Equal(
- t,
- `invalid subject token request, audience is //iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/test-pool/providers/test-provider(expected //iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/test-pool/providers/test-provider) and subject_token_type is invalid-type(expected urn:ietf:params:oauth:token-type:jwt)`,
- err.Error(),
- )
- }
- func TestAWSSecurityCredentialsReader(t *testing.T) {
- r := &awsSecurityCredentialsReader{
- region: testAwsRegion,
- awsSecurityCredentials: &externalaccount.AwsSecurityCredentials{
- AccessKeyID: testAwsAccessKey,
- SecretAccessKey: testAwsSecretKey,
- SessionToken: testAwsSessionToken,
- },
- }
- ctx := context.Background()
- options := externalaccount.SupplierOptions{}
- // Test region retrieval
- region, err := r.AwsRegion(ctx, options)
- assert.NoError(t, err)
- assert.Equal(t, testAwsRegion, region)
- // Test credentials retrieval
- creds, err := r.AwsSecurityCredentials(ctx, options)
- assert.NoError(t, err)
- assert.Equal(t, testAwsAccessKey, creds.AccessKeyID)
- assert.Equal(t, testAwsSecretKey, creds.SecretAccessKey)
- assert.Equal(t, testAwsSessionToken, creds.SessionToken)
- }
- func TestReadCredConfig(t *testing.T) {
- tests := []struct {
- name string
- config *esv1.GCPWorkloadIdentityFederation
- kubeObjects []client.Object
- expectError string
- }{
- {
- name: "cred configmap has empty data",
- config: &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Namespace: testNamespace,
- Key: testConfigMapKey,
- },
- },
- kubeObjects: []client.Object{
- &corev1.ConfigMap{
- ObjectMeta: metav1.ObjectMeta{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- Data: map[string]string{
- testConfigMapKey: "",
- },
- },
- },
- expectError: `key "config.json" in configmap "external-account-config" has empty value`,
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- fakeClient := clientfake.NewClientBuilder().WithObjects(tc.kubeObjects...).Build()
- wif := &workloadIdentityFederation{
- kubeClient: fakeClient,
- saTokenGenerator: &fakeSATokenGen{GenerateFunc: defaultSATokenGenerator},
- config: tc.config,
- isClusterKind: false,
- namespace: testNamespace,
- }
- ctx := context.Background()
- _, err := wif.readCredConfig(ctx)
- if tc.expectError != "" {
- assert.Error(t, err)
- assert.Equal(t, tc.expectError, err.Error())
- } else {
- assert.NoError(t, err)
- }
- })
- }
- }
- func TestGenerateExternalAccountConfig(t *testing.T) {
- wif := &esv1.GCPWorkloadIdentityFederation{
- CredConfig: &esv1.ConfigMapReference{
- Name: testConfigMapName,
- Namespace: testNamespace,
- },
- AwsSecurityCredentials: &esv1.AwsCredentialsConfig{
- Region: testAwsRegion,
- AwsCredentialsSecretRef: &esv1.SecretReference{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- },
- Audience: testAudience,
- }
- kubeObjects := []client.Object{
- &corev1.Secret{
- ObjectMeta: metav1.ObjectMeta{
- Name: "aws-creds",
- Namespace: testNamespace,
- },
- Data: map[string][]byte{
- awsAccessKeyIDKeyName: []byte(testAwsAccessKey),
- awsSecretAccessKeyKeyName: []byte(testAwsSecretKey),
- awsSessionTokenKeyName: []byte(testAwsSessionToken),
- },
- },
- }
- fakeClient := clientfake.NewClientBuilder().WithObjects(kubeObjects...).Build()
- wifInstance := &workloadIdentityFederation{
- kubeClient: fakeClient,
- saTokenGenerator: &fakeSATokenGen{GenerateFunc: defaultSATokenGenerator},
- config: wif,
- isClusterKind: false,
- namespace: testNamespace,
- }
- ctx := context.Background()
- credFile := &credentialsFile{
- Type: externalAccountCredentialType,
- Audience: testAudience,
- SubjectTokenType: workloadIdentitySubjectTokenType,
- TokenURLExternal: workloadIdentityTokenURL,
- ServiceAccountImpersonationURL: testServiceAccountImpersonationURL,
- }
- config, err := wifInstance.generateExternalAccountConfig(ctx, credFile)
- assert.NoError(t, err)
- assert.NotNil(t, config)
- assert.NotNil(t, config.AwsSecurityCredentialsSupplier)
- assert.Equal(t, testAudience, config.Audience)
- }
|