From 47a476c04567d98f2ee4814799f7d96ec6e87601 Mon Sep 17 00:00:00 2001 From: Nan Yu Date: Thu, 7 Sep 2023 22:08:33 +0000 Subject: [PATCH] Inject GSA email to root-reconciler when using FWI (#856) It also updates the unit test to validate the injection. --- .../controllers/reposync_controller_test.go | 45 ++++++++++++++++++- .../controllers/rootsync_controller.go | 3 ++ .../controllers/rootsync_controller_test.go | 8 +++- 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/pkg/reconcilermanager/controllers/reposync_controller_test.go b/pkg/reconcilermanager/controllers/reposync_controller_test.go index a7d3ca5d33..ac3f8591c1 100644 --- a/pkg/reconcilermanager/controllers/reposync_controller_test.go +++ b/pkg/reconcilermanager/controllers/reposync_controller_test.go @@ -18,6 +18,7 @@ import ( "context" "encoding/json" "fmt" + "path/filepath" "reflect" "strconv" "strings" @@ -2800,6 +2801,32 @@ func TestMapObjectToRepoSync(t *testing.T) { } } +func validateContainerEnv(container, key, expectedValue string) validateFunc { + return func(deployment *appsv1.Deployment) error { + hasContainer := false + var envVars []corev1.EnvVar + for _, c := range deployment.Spec.Template.Spec.Containers { + if c.Name == container { + hasContainer = true + envVars = c.Env + } + } + if !hasContainer { + return fmt.Errorf("the container %q is not found in the deployment %q/%q", container, deployment.Namespace, deployment.Name) + } + + for _, env := range envVars { + if env.Name == key { + if env.Value == expectedValue { + return nil + } + return fmt.Errorf("the value for ENV %q in the %q container is expected to be %q, but got %q", key, container, expectedValue, env.Value) + } + } + return fmt.Errorf("the ENV %q is not found in the %q container", key, container) + } +} + func TestInjectFleetWorkloadIdentityCredentialsToRepoSync(t *testing.T) { // Mock out parseDeployment for testing. parseDeployment = parsedDeployment @@ -2807,6 +2834,7 @@ func TestInjectFleetWorkloadIdentityCredentialsToRepoSync(t *testing.T) { rs := repoSyncWithGit(reposyncNs, reposyncName, reposyncRef(gitRevision), reposyncBranch(branch), reposyncSecretType(configsync.AuthGCPServiceAccount), reposyncGCPSAEmail(gcpSAEmail)) reqNamespacedName := namespacedName(rs.Name, rs.Namespace) fakeClient, fakeDynamicClient, testReconciler := setupNSReconciler(t, rs, secretObj(t, reposyncSSHKey, configsync.AuthSSH, v1beta1.GitSource, core.Namespace(rs.Namespace))) + // The membership doesn't have WorkloadIdentityPool and IdentityProvider specified, so FWI creds won't be injected. testReconciler.membership = &hubv1.Membership{ Spec: hubv1.MembershipSpec{ Owner: hubv1.MembershipOwner{ @@ -2842,6 +2870,7 @@ func TestInjectFleetWorkloadIdentityCredentialsToRepoSync(t *testing.T) { workloadIdentityPool := "test-gke-dev.svc.id.goog" testReconciler.membership = &hubv1.Membership{ Spec: hubv1.MembershipSpec{ + // Configuring WorkloadIdentityPool and IdentityProvider to validate if FWI creds are injected. WorkloadIdentityPool: workloadIdentityPool, IdentityProvider: "https://container.googleapis.com/v1/projects/test-gke-dev/locations/us-central1-c/clusters/fleet-workload-identity-test-cluster", }, @@ -2865,7 +2894,11 @@ func TestInjectFleetWorkloadIdentityCredentialsToRepoSync(t *testing.T) { wantDeployments = map[core.ID]*appsv1.Deployment{core.IDOf(repoDeployment): repoDeployment} // compare Deployment. - if err := validateDeployments(wantDeployments, fakeDynamicClient); err != nil { + if err := validateDeployments(wantDeployments, fakeDynamicClient, + // Validate the credentials are injected in the askpass container + validateContainerEnv(reconcilermanager.GCENodeAskpassSidecar, gsaEmailEnvKey, gcpSAEmail), + validateContainerEnv(reconcilermanager.GCENodeAskpassSidecar, googleApplicationCredentialsEnvKey, filepath.Join(gcpKSATokenDir, googleApplicationCredentialsFile)), + ); err != nil { t.Errorf("Deployment validation failed. err: %v", err) } if t.Failed() { @@ -3808,8 +3841,10 @@ func validateClusterRoleBinding(want *rbacv1.ClusterRoleBinding, fakeClient *syn return nil } +type validateFunc func(*appsv1.Deployment) error + // validateDeployments validates that important fields in the `wants` deployments match those same fields in the current deployments found in the unstructured Map -func validateDeployments(wants map[core.ID]*appsv1.Deployment, fakeDynamicClient *syncerFake.DynamicClient) error { +func validateDeployments(wants map[core.ID]*appsv1.Deployment, fakeDynamicClient *syncerFake.DynamicClient, validations ...validateFunc) error { ctx := context.Background() for id, want := range wants { uObj, err := fakeDynamicClient.Resource(kinds.DeploymentResource()). @@ -3928,6 +3963,12 @@ func validateDeployments(wants map[core.ID]*appsv1.Deployment, fakeDynamicClient if diff := cmp.Diff(want.ResourceVersion, got.ResourceVersion); diff != "" { return errors.Errorf("Unexpected Deployment ResourceVersion found for %q. Diff (- want, + got): %v", id, diff) } + + for _, v := range validations { + if err := v(got); err != nil { + return err + } + } } return nil } diff --git a/pkg/reconcilermanager/controllers/rootsync_controller.go b/pkg/reconcilermanager/controllers/rootsync_controller.go index d910005ad2..9e6225fd1b 100644 --- a/pkg/reconcilermanager/controllers/rootsync_controller.go +++ b/pkg/reconcilermanager/controllers/rootsync_controller.go @@ -674,6 +674,9 @@ func (r *RootSyncReconciler) populateContainerEnvs(ctx context.Context, rs *v1be noSSLVerify: rs.Spec.Git.NoSSLVerify, caCertSecretRef: v1beta1.GetSecretName(rs.Spec.Git.CACertSecretRef), }) + if enableAskpassSidecar(rs.Spec.SourceType, rs.Spec.Git.Auth) { + result[reconcilermanager.GCENodeAskpassSidecar] = gceNodeAskPassSidecarEnvs(rs.Spec.GCPServiceAccountEmail) + } case v1beta1.OciSource: result[reconcilermanager.OciSync] = ociSyncEnvs(rs.Spec.Oci.Image, rs.Spec.Oci.Auth, v1beta1.GetPeriodSecs(rs.Spec.Oci.Period, configsync.DefaultReconcilerPollingPeriodSeconds)) case v1beta1.HelmSource: diff --git a/pkg/reconcilermanager/controllers/rootsync_controller_test.go b/pkg/reconcilermanager/controllers/rootsync_controller_test.go index 7b52bc7c8e..640ecf1e08 100644 --- a/pkg/reconcilermanager/controllers/rootsync_controller_test.go +++ b/pkg/reconcilermanager/controllers/rootsync_controller_test.go @@ -2297,6 +2297,7 @@ func TestInjectFleetWorkloadIdentityCredentialsToRootSync(t *testing.T) { rs := rootSyncWithGit(rootsyncName, rootsyncRef(gitRevision), rootsyncBranch(branch), rootsyncSecretType(configsync.AuthGCPServiceAccount), rootsyncGCPSAEmail(gcpSAEmail)) reqNamespacedName := namespacedName(rs.Name, rs.Namespace) fakeClient, fakeDynamicClient, testReconciler := setupRootReconciler(t, rs, secretObj(t, rootsyncSSHKey, configsync.AuthSSH, v1beta1.GitSource, core.Namespace(rs.Namespace))) + // The membership doesn't have WorkloadIdentityPool and IdentityProvider specified, so FWI creds won't be injected. testReconciler.membership = &hubv1.Membership{ Spec: hubv1.MembershipSpec{ Owner: hubv1.MembershipOwner{ @@ -2331,6 +2332,7 @@ func TestInjectFleetWorkloadIdentityCredentialsToRootSync(t *testing.T) { workloadIdentityPool := "test-gke-dev.svc.id.goog" testReconciler.membership = &hubv1.Membership{ Spec: hubv1.MembershipSpec{ + // Configuring WorkloadIdentityPool and IdentityProvider to validate if FWI creds are injected. WorkloadIdentityPool: workloadIdentityPool, IdentityProvider: "https://container.googleapis.com/v1/projects/test-gke-dev/locations/us-central1-c/clusters/fleet-workload-identity-test-cluster", }, @@ -2352,7 +2354,11 @@ func TestInjectFleetWorkloadIdentityCredentialsToRootSync(t *testing.T) { wantDeployments = map[core.ID]*appsv1.Deployment{core.IDOf(rootDeployment): rootDeployment} // compare Deployment. - if err := validateDeployments(wantDeployments, fakeDynamicClient); err != nil { + if err := validateDeployments(wantDeployments, fakeDynamicClient, + // Validate the credentials are injected in the askpass container + validateContainerEnv(reconcilermanager.GCENodeAskpassSidecar, gsaEmailEnvKey, gcpSAEmail), + validateContainerEnv(reconcilermanager.GCENodeAskpassSidecar, googleApplicationCredentialsEnvKey, filepath.Join(gcpKSATokenDir, googleApplicationCredentialsFile)), + ); err != nil { t.Errorf("Deployment validation failed. err: %v", err) } if t.Failed() {