diff --git a/README.md b/README.md index dc424cf..9945906 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,10 @@ Note: GKE or Anthos natively support injecting workload identity for pods. This # The value must be one of 'gcloud'(default) or 'direct'. # Refer to the next section for 'direct' injection mode cloud.google.com/injection-mode: "gcloud" + + # optional: Defaults to value inside `service-account-email` + # + cloud.google.com/project: "12345" ``` 4. All new pods launched using the Kubernetes `ServiceAccount` will be mutated so that they can impersonate the GCP service account. Below is an example pod spec with the environment variables and volume fields mutated by the webhook. @@ -62,13 +66,13 @@ Note: GKE or Anthos natively support injecting workload identity for pods. This metadata: name: app-x-pod namespace: service-a - annotations: - # optional: A comma-separated list of initContainers and container names - # to skip adding volumeMounts and environment variables - cloud.google.com/skip-containers: "init-first,sidecar" - # optional: Defaults to 86400, or value specified in ServiceAccount - # annotation as shown in previous step, for expirationSeconds if not set - cloud.google.com/token-expiration: "86400" + annotations: + # optional: A comma-separated list of initContainers and container names + # to skip adding volumeMounts and environment variables + cloud.google.com/skip-containers: "init-first,sidecar" + # optional: Defaults to 86400, or value specified in ServiceAccount + # annotation as shown in previous step, for expirationSeconds if not set + cloud.google.com/token-expiration: "86400" spec: serviceAccountName: app-x initContainers: @@ -167,27 +171,27 @@ To use direct injection mode: metadata: name: app-x-pod namespace: service-a - annotations: - # optional: A comma-separated list of initContainers and container names - # to skip adding volumeMounts and environment variables - cloud.google.com/skip-containers: "init-first,sidecar" - # - # The Generated External Credentials Json is added as an annotation, and mounted into the container filesystem via the DownwardAPI Volume - # - cloud.google.com/external-credentials-json: |- - { - "type": "external_account", - "audience": "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/on-prem-kubernetes/providers/this-cluster", - "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", - "token_url": "https://sts.googleapis.com/v1/token", - "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/app-x@project.iam.gserviceaccount.com:generateAccessToken", - "credential_source": { - "file": "/var/run/secrets/sts.googleapis.com/serviceaccount/token", - "format": { - "type": "text" + annotations: + # optional: A comma-separated list of initContainers and container names + # to skip adding volumeMounts and environment variables + cloud.google.com/skip-containers: "init-first,sidecar" + # + # The Generated External Credentials Json is added as an annotation, and mounted into the container filesystem via the DownwardAPI Volume + # + cloud.google.com/external-credentials-json: |- + { + "type": "external_account", + "audience": "//iam.googleapis.com/projects/123456789/locations/global/workloadIdentityPools/on-prem-kubernetes/providers/this-cluster", + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/app-x@project.iam.gserviceaccount.com:generateAccessToken", + "credential_source": { + "file": "/var/run/secrets/sts.googleapis.com/serviceaccount/token", + "format": { + "type": "text" + } } } - } spec: serviceAccountName: app-x initContainers: diff --git a/charts/gcp-workload-identity-federation-webhook/templates/deployment.yaml b/charts/gcp-workload-identity-federation-webhook/templates/deployment.yaml index 43fe745..a3820f1 100644 --- a/charts/gcp-workload-identity-federation-webhook/templates/deployment.yaml +++ b/charts/gcp-workload-identity-federation-webhook/templates/deployment.yaml @@ -40,6 +40,8 @@ spec: affinity: {{- toYaml .Values.controllerManager.affinity | nindent 8 }} {{- end }} + imagePullSecrets: + {{- toYaml .Values.controllerManager.imagePullSecrets | nindent 8 }} containers: - args: - --health-probe-bind-address=:8081 @@ -54,6 +56,7 @@ spec: value: {{ .Values.kubernetesClusterDomain }} image: {{ .Values.controllerManager.manager.image.repository }}:{{ .Values.controllerManager.manager.image.tag | default (printf "v%v" .Chart.AppVersion) }} + imagePullPolicy: {{ .Values.controllerManager.manager.image.pullPolicy }} livenessProbe: httpGet: path: /healthz @@ -89,6 +92,7 @@ spec: value: {{ .Values.kubernetesClusterDomain }} image: {{ .Values.controllerManager.kubeRbacProxy.image.repository }}:{{ .Values.controllerManager.kubeRbacProxy.image.tag | default .Chart.AppVersion }} + imagePullPolicy: {{ .Values.controllerManager.kubeRbacProxy.image.pullPolicy }} name: kube-rbac-proxy ports: - containerPort: 8443 diff --git a/charts/gcp-workload-identity-federation-webhook/values.yaml b/charts/gcp-workload-identity-federation-webhook/values.yaml index 570599c..b29440b 100644 --- a/charts/gcp-workload-identity-federation-webhook/values.yaml +++ b/charts/gcp-workload-identity-federation-webhook/values.yaml @@ -11,6 +11,7 @@ controllerManager: kubeRbacProxy: image: + pullPolicy: IfNotPresent repository: gcr.io/kubebuilder/kube-rbac-proxy tag: v0.11.0 resources: @@ -21,8 +22,11 @@ controllerManager: cpu: 5m memory: 64Mi + imagePullSecrets: [] + manager: image: + pullPolicy: IfNotPresent repository: ghcr.io/pfnet-research/gcp-workload-identity-federation-webhook # default tag is v{{.Chart.AppVersion}} # tag: latest diff --git a/webhooks/annotations.go b/webhooks/annotations.go index 21f510a..2cf0ab9 100644 --- a/webhooks/annotations.go +++ b/webhooks/annotations.go @@ -43,4 +43,10 @@ const ( // // Set to 'direct' or 'gcloud' to determine credential injection mode. Defaults to 'gcloud'. InjectionModeAnnotation = "injection-mode" + + // + // Annotations for ServiceAccount + // + // Override GCP Project normally parsed from ServiceAccount. + ProjectAnnotation = "project" ) diff --git a/webhooks/external_account_config.go b/webhooks/external_account_config.go index 9b31bdc..e452e56 100644 --- a/webhooks/external_account_config.go +++ b/webhooks/external_account_config.go @@ -31,7 +31,7 @@ type ExternalAccountCredentials struct { TokenInfoURL string `json:"token_info_url,omitempty"` // ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only // required for workload identity pools when APIs to be accessed have not integrated with UberMint. - ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"` + ServiceAccountImpersonationURL string `json:"service_account_impersonation_url,omitempty"` // ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation // token will be valid for. ServiceAccountImpersonationLifetimeSeconds int `json:"service_account_impersonation_lifetime_seconds,omitempty"` @@ -63,9 +63,10 @@ func NewExternalAccountCredentials(aud, gsaEmail string) *ExternalAccountCredent File: filepath.Join(K8sSATokenMountPath, K8sSATokenName), Format: CredentialFormat{Type: "text"}, }, - ServiceAccountImpersonationURL: fmt.Sprintf("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken", gsaEmail), } - + if gsaEmail != "" { + creds.ServiceAccountImpersonationURL = fmt.Sprintf("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/%s:generateAccessToken", gsaEmail) + } return creds } diff --git a/webhooks/identityconfig.go b/webhooks/identityconfig.go index 7914546..239d57b 100644 --- a/webhooks/identityconfig.go +++ b/webhooks/identityconfig.go @@ -16,8 +16,9 @@ var ( ) type GCPWorkloadIdentityConfig struct { + Project *string WorkloadIdentityProvider *string - ServiceAccountEmail *string + ServiceAccountEmail string RunAsUser *int64 InjectionMode InjectionMode @@ -44,12 +45,15 @@ func NewGCPWorkloadIdentityConfig( } if v, ok := sa.Annotations[filepath.Join(annotationDomain, ServiceAccountEmailAnnotation)]; ok { - cfg.ServiceAccountEmail = &v + cfg.ServiceAccountEmail = v } if v, ok := sa.Annotations[filepath.Join(annotationDomain, AudienceAnnotation)]; ok { cfg.Audience = &v } + if v, ok := sa.Annotations[filepath.Join(annotationDomain, ProjectAnnotation)]; ok { + cfg.Project = &v + } if v, ok := sa.Annotations[filepath.Join(annotationDomain, TokenExpirationAnnotation)]; ok { seconds, err := strconv.ParseInt(v, 10, 64) @@ -80,14 +84,10 @@ func NewGCPWorkloadIdentityConfig( cfg.InjectionMode = UndefinedMode } - if cfg.WorkloadIdentityProvider == nil && cfg.ServiceAccountEmail == nil { + if cfg.WorkloadIdentityProvider == nil { return nil, nil } - if cfg.WorkloadIdentityProvider == nil || cfg.ServiceAccountEmail == nil { - return nil, fmt.Errorf("%s, %s must set at a time", filepath.Join(annotationDomain, WorkloadIdentityProviderAnnotation), filepath.Join(annotationDomain, TokenExpirationAnnotation)) - } - if !workloadIdentityProviderRegex.Match([]byte(*cfg.WorkloadIdentityProvider)) { return nil, fmt.Errorf("%s must be form of %s", filepath.Join(annotationDomain, WorkloadIdentityProviderAnnotation), workloadIdentityProviderFmt) } diff --git a/webhooks/identityconfig_test.go b/webhooks/identityconfig_test.go index 22ec4bd..bd483b1 100644 --- a/webhooks/identityconfig_test.go +++ b/webhooks/identityconfig_test.go @@ -24,7 +24,7 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { Annotations: map[string]string{}, }, } - idConfig, err := NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err := NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(err).NotTo(HaveOccurred()) Expect(idConfig).To(BeNil()) }) @@ -39,11 +39,11 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err := NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err := NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(err).NotTo(HaveOccurred()) Expect(idConfig).To(BeEquivalentTo(&GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadProvider, - ServiceAccountEmail: &saEmail, + ServiceAccountEmail: saEmail, Audience: nil, TokenExpirationSeconds: nil, })) @@ -61,11 +61,11 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err := NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err := NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(err).NotTo(HaveOccurred()) Expect(idConfig).To(BeEquivalentTo(&GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadProvider, - ServiceAccountEmail: &saEmail, + ServiceAccountEmail: saEmail, Audience: &audience, TokenExpirationSeconds: &tokenExpiration, })) @@ -84,11 +84,11 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err := NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err := NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(err).NotTo(HaveOccurred()) Expect(idConfig).To(BeEquivalentTo(&GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadProvider, - ServiceAccountEmail: &saEmail, + ServiceAccountEmail: saEmail, Audience: &audience, TokenExpirationSeconds: &tokenExpiration, InjectionMode: DirectMode, @@ -108,11 +108,11 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err := NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err := NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(err).NotTo(HaveOccurred()) Expect(idConfig).To(BeEquivalentTo(&GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadProvider, - ServiceAccountEmail: &saEmail, + ServiceAccountEmail: saEmail, Audience: &audience, TokenExpirationSeconds: &tokenExpiration, InjectionMode: GCloudMode, @@ -134,21 +134,9 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err = NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) - Expect(idConfig).To(BeNil()) - Expect(err).To(MatchError(ContainSubstring("must set at a time"))) - - By("without workload-identity-provider annotation") - sa = corev1.ServiceAccount{ - ObjectMeta: metav1.ObjectMeta{ - Annotations: map[string]string{ - saEmailAnnotation: saEmail, - }, - }, - } - idConfig, err = NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) - Expect(idConfig).To(BeNil()) - Expect(err).To(MatchError(ContainSubstring("must set at a time"))) + idConfig, err = NewGCPWorkloadIdentityConfig(annotationDomain, sa) + Expect(idConfig).NotTo(BeNil()) + Expect(err).To(BeNil()) }) }) When("ServiceAccount with malformed workload-identity-provider annotation", func() { @@ -161,7 +149,7 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err = NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err = NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(idConfig).To(BeNil()) Expect(err).To(MatchError(ContainSubstring("must be form of"))) }) @@ -177,7 +165,7 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err = NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err = NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(idConfig).To(BeNil()) Expect(err).To(MatchError(ContainSubstring("must be positive integer string"))) }) @@ -193,7 +181,7 @@ var _ = Describe("NewGCPWorkloadIdentityConfig", func() { }, }, } - idConfig, err = NewGCPWorkloadIdentityConfig(annotaitonDomain, sa) + idConfig, err = NewGCPWorkloadIdentityConfig(annotationDomain, sa) Expect(idConfig).To(BeNil()) Expect(err).To(MatchError(ContainSubstring("mode must be"))) }) diff --git a/webhooks/mutatepod.go b/webhooks/mutatepod.go index 0d08a31..f713b75 100644 --- a/webhooks/mutatepod.go +++ b/webhooks/mutatepod.go @@ -42,12 +42,12 @@ func (m *GCPWorkloadIdentityMutator) mutatePod(pod *corev1.Pod, idConfig GCPWork pod.Annotations = map[string]string{} } pod.Annotations[filepath.Join(m.AnnotationDomain, WorkloadIdentityProviderAnnotation)] = *idConfig.WorkloadIdentityProvider - pod.Annotations[filepath.Join(m.AnnotationDomain, ServiceAccountEmailAnnotation)] = *idConfig.ServiceAccountEmail + pod.Annotations[filepath.Join(m.AnnotationDomain, ServiceAccountEmailAnnotation)] = idConfig.ServiceAccountEmail pod.Annotations[filepath.Join(m.AnnotationDomain, AudienceAnnotation)] = audience pod.Annotations[filepath.Join(m.AnnotationDomain, TokenExpirationAnnotation)] = fmt.Sprint(expirationSeconds) if idConfig.InjectionMode == DirectMode { // Add annotation - credBody, err := buildExternalCredentialsJson(*idConfig.WorkloadIdentityProvider, *idConfig.ServiceAccountEmail) + credBody, err := buildExternalCredentialsJson(*idConfig.WorkloadIdentityProvider, idConfig.ServiceAccountEmail) if err != nil { return err } @@ -57,10 +57,11 @@ func (m *GCPWorkloadIdentityMutator) mutatePod(pod *corev1.Pod, idConfig GCPWork // // calculate project from service account // - matches := projectRegex.FindStringSubmatch(*idConfig.ServiceAccountEmail) project := "" - if len(matches) >= 2 { - project = matches[1] // the group 0 is thw whole match + if idConfig.Project != nil { + project = *idConfig.Project + } else if matches := projectRegex.FindStringSubmatch(idConfig.ServiceAccountEmail); len(matches) >= 2 { + project = matches[1] // the group 0 is the whole match } // @@ -75,7 +76,7 @@ func (m *GCPWorkloadIdentityMutator) mutatePod(pod *corev1.Pod, idConfig GCPWork // if idConfig.InjectionMode == GCloudMode || idConfig.InjectionMode == UndefinedMode { pod.Spec.InitContainers = prependOrReplaceContainer(pod.Spec.InitContainers, gcloudSetupContainer( - *idConfig.WorkloadIdentityProvider, *idConfig.ServiceAccountEmail, project, m.GcloudImage, idConfig.RunAsUser, m.SetupContainerResources, + *idConfig.WorkloadIdentityProvider, idConfig.ServiceAccountEmail, project, m.GcloudImage, idConfig.RunAsUser, m.SetupContainerResources, )) } diff --git a/webhooks/mutatepod_parts.go b/webhooks/mutatepod_parts.go index 81d605e..9885221 100644 --- a/webhooks/mutatepod_parts.go +++ b/webhooks/mutatepod_parts.go @@ -3,6 +3,7 @@ package webhooks import ( "fmt" "path/filepath" + "strings" "github.com/MakeNowJust/heredoc" corev1 "k8s.io/api/core/v1" @@ -100,6 +101,33 @@ func gcloudSetupContainer( if runAsUser != nil { securityContext.RunAsUser = runAsUser } + env := []corev1.EnvVar{{ + Name: "GCP_WORKLOAD_IDENTITY_PROVIDER", + Value: workloadIdProvider, + }, { + Name: "CLOUDSDK_CONFIG", + Value: GCloudConfigMountPath, + }} + + if saEmail != "" { + env = append(env, corev1.EnvVar{ + Name: "GCP_SERVICE_ACCOUNT", + Value: saEmail, + }) + } + env = append(env, projectEnvVar(project)...) + + createCredsArgs := []string{ + "$(GCP_WORKLOAD_IDENTITY_PROVIDER)", + fmt.Sprintf("--output-file=$(CLOUDSDK_CONFIG)/%s", ExternalCredConfigFilename), + fmt.Sprintf("--credential-source-file=%s", filepath.Join(K8sSATokenMountPath, K8sSATokenName)), + } + loginArgs := []string{ + fmt.Sprintf("--cred-file=$(CLOUDSDK_CONFIG)/%s", ExternalCredConfigFilename), + } + if saEmail != "" { + createCredsArgs = append(createCredsArgs, "--service-account=$(GCP_SERVICE_ACCOUNT)") + } c := corev1.Container{ Name: GCloudSetupInitContainerName, @@ -108,27 +136,16 @@ func gcloudSetupContainer( "sh", "-c", heredoc.Docf(` gcloud iam workload-identity-pools create-cred-config \ - $(GCP_WORKLOAD_IDENTITY_PROVIDER) \ - --service-account=$(GCP_SERVICE_ACCOUNT) \ - --output-file=$(CLOUDSDK_CONFIG)/%s \ - --credential-source-file=%s - gcloud auth login --cred-file=$(CLOUDSDK_CONFIG)/%s - `, ExternalCredConfigFilename, - filepath.Join(K8sSATokenMountPath, K8sSATokenName), - ExternalCredConfigFilename, + %s + gcloud auth login \ + %s + `, + strings.Join(createCredsArgs, " \\\n "), + strings.Join(loginArgs, " \\\n "), ), }, - VolumeMounts: volumeMountsToAddOrReplace(GCloudMode), - Env: []corev1.EnvVar{{ - Name: "GCP_WORKLOAD_IDENTITY_PROVIDER", - Value: workloadIdProvider, - }, { - Name: "GCP_SERVICE_ACCOUNT", - Value: saEmail, - }, { - Name: "CLOUDSDK_CONFIG", - Value: GCloudConfigMountPath, - }, projectEnvVar(project)}, + VolumeMounts: volumeMountsToAddOrReplace(GCloudMode), + Env: env, SecurityContext: securityContext, } if resources != nil { @@ -191,19 +208,25 @@ func envVarsToAddOrReplace(mode InjectionMode) []corev1.EnvVar { } func envVarsToAddIfNotPresent(region, project string) []corev1.EnvVar { - return []corev1.EnvVar{cloudSDKComputeRegionEnvVar(region), projectEnvVar(project)} + return append(cloudSDKComputeRegionEnvVar(region), projectEnvVar(project)...) } -func cloudSDKComputeRegionEnvVar(region string) corev1.EnvVar { - return corev1.EnvVar{ - Name: "CLOUDSDK_COMPUTE_REGION", - Value: region, +func cloudSDKComputeRegionEnvVar(region string) (ret []corev1.EnvVar) { + if region != "" { + ret = append(ret, corev1.EnvVar{ + Name: "CLOUDSDK_COMPUTE_REGION", + Value: region, + }) } + return } -func projectEnvVar(project string) corev1.EnvVar { - return corev1.EnvVar{ - Name: "CLOUDSDK_CORE_PROJECT", - Value: project, +func projectEnvVar(project string) (ret []corev1.EnvVar) { + if project != "" { + ret = append(ret, corev1.EnvVar{ + Name: "CLOUDSDK_CORE_PROJECT", + Value: project, + }) } + return } diff --git a/webhooks/mutatepod_parts_test.go b/webhooks/mutatepod_parts_test.go index 3594ea3..4238696 100644 --- a/webhooks/mutatepod_parts_test.go +++ b/webhooks/mutatepod_parts_test.go @@ -24,10 +24,11 @@ func TestGcloudSetupContainer(t *testing.T) { "sh", "-c", `gcloud iam workload-identity-pools create-cred-config \ $(GCP_WORKLOAD_IDENTITY_PROVIDER) \ - --service-account=$(GCP_SERVICE_ACCOUNT) \ --output-file=$(CLOUDSDK_CONFIG)/federation.json \ - --credential-source-file=/var/run/secrets/sts.googleapis.com/serviceaccount/token -gcloud auth login --cred-file=$(CLOUDSDK_CONFIG)/federation.json + --credential-source-file=/var/run/secrets/sts.googleapis.com/serviceaccount/token \ + --service-account=$(GCP_SERVICE_ACCOUNT) +gcloud auth login \ + --cred-file=$(CLOUDSDK_CONFIG)/federation.json `, }, VolumeMounts: []corev1.VolumeMount{ @@ -46,14 +47,14 @@ gcloud auth login --cred-file=$(CLOUDSDK_CONFIG)/federation.json Name: "GCP_WORKLOAD_IDENTITY_PROVIDER", Value: workloadIdProvider, }, - { - Name: "GCP_SERVICE_ACCOUNT", - Value: saEmail, - }, { Name: "CLOUDSDK_CONFIG", Value: "/var/run/secrets/gcloud/config", }, + { + Name: "GCP_SERVICE_ACCOUNT", + Value: saEmail, + }, { Name: "CLOUDSDK_CORE_PROJECT", Value: project, diff --git a/webhooks/mutatepod_test.go b/webhooks/mutatepod_test.go index 07e5a04..1dc2db6 100644 --- a/webhooks/mutatepod_test.go +++ b/webhooks/mutatepod_test.go @@ -2,6 +2,7 @@ package webhooks import ( "fmt" + "github.com/google/go-cmp/cmp" "path/filepath" . "github.com/onsi/ginkgo/v2" @@ -19,7 +20,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { project := "demo" BeforeEach(func() { m = &GCPWorkloadIdentityMutator{ - AnnotationDomain: annotaitonDomain, + AnnotationDomain: annotationDomain, DefaultAudience: AudienceDefault, DefaultTokenExpiration: DefaultTokenExpirationDefault, MinTokenExpration: MinTokenExprationDefault, @@ -37,7 +38,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { It("should raise error", func() { idConfig := GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadIdentityProviderFmt, - ServiceAccountEmail: ptr.To(fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project)), + ServiceAccountEmail: fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project), Audience: ptr.To("my-audience"), TokenExpirationSeconds: ptr.To[int64](10000), RunAsUser: ptr.To[int64](1000), @@ -58,7 +59,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { It("should replace reqiured fields and override configurations", func() { idConfig := GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadIdentityProviderFmt, - ServiceAccountEmail: ptr.To(fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project)), + ServiceAccountEmail: fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project), Audience: ptr.To("my-audience"), TokenExpirationSeconds: ptr.To[int64](10000), RunAsUser: ptr.To[int64](1000), @@ -126,18 +127,19 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { Name: "GOOGLE_APPLICATION_CREDENTIALS", Value: filepath.Join(GCloudConfigMountPath, ExternalCredConfigFilename), }, - cloudSDKComputeRegionEnvVar("not-to-be-replaced"), - { - Name: "CLOUDSDK_CONFIG", - Value: GCloudConfigMountPath, - }, - projectEnvVar(project), } + expectedEnvVars = append(expectedEnvVars, cloudSDKComputeRegionEnvVar("not-to-be-replaced")...) + expectedEnvVars = append(expectedEnvVars, corev1.EnvVar{ + Name: "CLOUDSDK_CONFIG", + Value: GCloudConfigMountPath, + }) + expectedEnvVars = append(expectedEnvVars, projectEnvVar(project)...) + expected := &corev1.Pod{ ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ idProviderAnnotation: workloadIdentityProviderFmt, - saEmailAnnotation: *idConfig.ServiceAccountEmail, + saEmailAnnotation: idConfig.ServiceAccountEmail, audienceAnnotation: "my-audience", tokenExpirationAnnotation: "3601", }, @@ -146,7 +148,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { InitContainers: []corev1.Container{ gcloudSetupContainer( *idConfig.WorkloadIdentityProvider, - *idConfig.ServiceAccountEmail, + idConfig.ServiceAccountEmail, project, m.GcloudImage, idConfig.RunAsUser, @@ -171,14 +173,15 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { // Expect(pod.Spec.InitContainers[0]).To(BeEquivalentTo(expected.Spec.InitContainers[0])) // Expect(pod.Spec.InitContainers[1]).To(BeEquivalentTo(expected.Spec.InitContainers[1])) // Expect(pod.Spec.Containers).To(BeEquivalentTo(expected.Spec.Containers)) - Expect(pod).To(BeEquivalentTo(expected)) + // Expect(pod).To(BeEquivalentTo(expected)) + Expect(cmp.Diff(pod, expected)).To(BeEmpty()) }) }) When("passed Pod doesn't have no conflicted and no override fields", func() { It("should mutate required fields", func() { idConfig := GCPWorkloadIdentityConfig{ WorkloadIdentityProvider: &workloadIdentityProviderFmt, - ServiceAccountEmail: ptr.To(fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project)), + ServiceAccountEmail: fmt.Sprintf("sa@%s.iam.gserviceaccount.com", project), } pod := &corev1.Pod{ Spec: corev1.PodSpec{ @@ -200,7 +203,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { ObjectMeta: metav1.ObjectMeta{ Annotations: map[string]string{ idProviderAnnotation: workloadIdentityProviderFmt, - saEmailAnnotation: *idConfig.ServiceAccountEmail, + saEmailAnnotation: idConfig.ServiceAccountEmail, audienceAnnotation: m.DefaultAudience, tokenExpirationAnnotation: fmt.Sprint(int64(m.DefaultTokenExpiration.Seconds())), }, @@ -209,7 +212,7 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { InitContainers: []corev1.Container{ gcloudSetupContainer( *idConfig.WorkloadIdentityProvider, - *idConfig.ServiceAccountEmail, + idConfig.ServiceAccountEmail, project, m.GcloudImage, idConfig.RunAsUser, @@ -239,7 +242,8 @@ var _ = Describe("GCPWorkloadIdentityMutator.mutatePod", func() { // Expect(pod.Spec.Volumes).To(BeEquivalentTo(expected.Spec.Volumes)) // Expect(pod.Spec.InitContainers).To(BeEquivalentTo(expected.Spec.InitContainers)) // Expect(pod.Spec.Containers).To(BeEquivalentTo(expected.Spec.Containers)) - Expect(pod).To(BeEquivalentTo(expected)) + // Expect(pod).To(BeEquivalentTo(expected)) + Expect(cmp.Diff(pod, expected)).To(BeEmpty()) }) }) }) diff --git a/webhooks/webhook_suite_test.go b/webhooks/webhook_suite_test.go index 7bf0833..829b4eb 100644 --- a/webhooks/webhook_suite_test.go +++ b/webhooks/webhook_suite_test.go @@ -26,14 +26,14 @@ import ( ) var ( - annotaitonDomain = AnnotationDomainDefault - idProviderAnnotation = filepath.Join(annotaitonDomain, WorkloadIdentityProviderAnnotation) - saEmailAnnotation = filepath.Join(annotaitonDomain, ServiceAccountEmailAnnotation) - audienceAnnotation = filepath.Join(annotaitonDomain, AudienceAnnotation) - tokenExpirationAnnotation = filepath.Join(annotaitonDomain, TokenExpirationAnnotation) - runAsUserAnnotation = filepath.Join(annotaitonDomain, RunAsUserAnnotation) - injectionModeAnnotation = filepath.Join(annotaitonDomain, InjectionModeAnnotation) - externalConfigAnnotation = filepath.Join(annotaitonDomain, ExternalCredentialsJsonAnnotation) + annotationDomain = AnnotationDomainDefault + idProviderAnnotation = filepath.Join(annotationDomain, WorkloadIdentityProviderAnnotation) + saEmailAnnotation = filepath.Join(annotationDomain, ServiceAccountEmailAnnotation) + audienceAnnotation = filepath.Join(annotationDomain, AudienceAnnotation) + tokenExpirationAnnotation = filepath.Join(annotationDomain, TokenExpirationAnnotation) + runAsUserAnnotation = filepath.Join(annotationDomain, RunAsUserAnnotation) + injectionModeAnnotation = filepath.Join(annotationDomain, InjectionModeAnnotation) + externalConfigAnnotation = filepath.Join(annotationDomain, ExternalCredentialsJsonAnnotation) setupContainerResources = &corev1.ResourceRequirements{ Requests: corev1.ResourceList{ corev1.ResourceCPU: resource.MustParse("100m"),