Skip to content

Commit

Permalink
Inject GSA email to root-reconciler when using FWI (#856) (#857)
Browse files Browse the repository at this point in the history
It also updates the unit test to validate the injection.
  • Loading branch information
nan-yu authored Sep 7, 2023
1 parent 025b19d commit 3d011de
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 3 deletions.
45 changes: 43 additions & 2 deletions pkg/reconcilermanager/controllers/reposync_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"context"
"encoding/json"
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
Expand Down Expand Up @@ -2800,13 +2801,40 @@ 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

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{
Expand Down Expand Up @@ -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",
},
Expand All @@ -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() {
Expand Down Expand Up @@ -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()).
Expand Down Expand Up @@ -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
}
Expand Down
3 changes: 3 additions & 0 deletions pkg/reconcilermanager/controllers/rootsync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down Expand Up @@ -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",
},
Expand All @@ -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() {
Expand Down

0 comments on commit 3d011de

Please sign in to comment.