From 1c88479c3476a53f293e78cd46c2cdc4fa834eb5 Mon Sep 17 00:00:00 2001 From: Alex Chvatal Date: Fri, 16 Aug 2024 16:55:15 -0400 Subject: [PATCH] update the operator master deployment to support workload identity This causes the spec for the operator master deployment to mount the service account token as a volume, and maps the path to the environment variable expected by Azure to support workload identities --- pkg/operator/deploy/deploy.go | 13 ++- pkg/operator/deploy/deploy_test.go | 82 +++++++++++++++++++ .../master/deployment.yaml.tmpl | 23 +++++- 3 files changed, 115 insertions(+), 3 deletions(-) diff --git a/pkg/operator/deploy/deploy.go b/pkg/operator/deploy/deploy.go index 6c16d372ad3..ce0b555803f 100644 --- a/pkg/operator/deploy/deploy.go +++ b/pkg/operator/deploy/deploy.go @@ -106,6 +106,8 @@ type deploymentData struct { Version string IsLocalDevelopment bool SupportsPodSecurityAdmission bool + UsesWorkloadIdentity bool + TokenVolumeMountPath string } func templateManifests(data deploymentData) ([][]byte, error) { @@ -160,12 +162,19 @@ func (o *operator) createDeploymentData(ctx context.Context) (deploymentData, er return deploymentData{}, err } - return deploymentData{ + data := deploymentData{ IsLocalDevelopment: o.env.IsLocalDevelopmentMode(), Image: image, SupportsPodSecurityAdmission: usePodSecurityAdmission, Version: version, - }, nil + } + + if o.oc.UsesWorkloadIdentity() { + data.UsesWorkloadIdentity = o.oc.UsesWorkloadIdentity() + data.TokenVolumeMountPath = pkgoperator.OperatorTokenFile + } + + return data, nil } func (o *operator) createObjects(ctx context.Context) ([]kruntime.Object, error) { diff --git a/pkg/operator/deploy/deploy_test.go b/pkg/operator/deploy/deploy_test.go index 5960c1292a0..a75a5c87140 100644 --- a/pkg/operator/deploy/deploy_test.go +++ b/pkg/operator/deploy/deploy_test.go @@ -15,8 +15,10 @@ import ( appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kruntime "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes/fake" ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/yaml" "github.com/Azure/ARO-RP/pkg/api" pkgoperator "github.com/Azure/ARO-RP/pkg/operator" @@ -203,6 +205,39 @@ func TestCreateDeploymentData(t *testing.T) { SupportsPodSecurityAdmission: true, }, }, + { + name: "workload identity detected", + mock: func(env *mock_env.MockInterface, oc *api.OpenShiftCluster) { + env.EXPECT(). + AROOperatorImage(). + Return(operatorImageWithTag) + // set so that UsesWorkloadIdentity() returns true + oc.Properties.PlatformWorkloadIdentityProfile = &api.PlatformWorkloadIdentityProfile{} + }, + clusterVersion: "4.10.0", + expected: deploymentData{ + Image: operatorImageWithTag, + Version: operatorImageTag, + UsesWorkloadIdentity: true, + TokenVolumeMountPath: pkgoperator.OperatorTokenFile, + }, + }, + { + name: "service principal detected", + mock: func(env *mock_env.MockInterface, oc *api.OpenShiftCluster) { + env.EXPECT(). + AROOperatorImage(). + Return(operatorImageWithTag) + // set so that UsesWorkloadIdentity() returns false + oc.Properties.ServicePrincipalProfile = &api.ServicePrincipalProfile{} + }, + clusterVersion: "4.10.0", + expected: deploymentData{ + Image: operatorImageWithTag, + Version: operatorImageTag, + UsesWorkloadIdentity: false, + }, + }, } { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() @@ -646,3 +681,50 @@ func TestGenerateOperatorIdentitySecret(t *testing.T) { }) } } + +func TestTemplateManifests(t *testing.T) { + tests := []struct { + Name string + DeploymentData deploymentData + ExpectError bool + }{ + { + Name: "service principal data", + DeploymentData: deploymentData{ + Image: "someImage", + Version: "someVersion", + IsLocalDevelopment: false, + SupportsPodSecurityAdmission: false, + UsesWorkloadIdentity: false, + }, + }, + { + Name: "workload identity data", + DeploymentData: deploymentData{ + Image: "someImage", + Version: "someVersion", + IsLocalDevelopment: false, + SupportsPodSecurityAdmission: false, + UsesWorkloadIdentity: true, + }, + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + actualBytes, err := templateManifests(test.DeploymentData) + if test.ExpectError == (err == nil) { + t.Errorf("templateManifests() %s: ExpectError: %t, actual error: %s\n", test.Name, test.ExpectError, err) + } + + for _, fileBytes := range actualBytes { + var resource *kruntime.Object + err := yaml.Unmarshal(fileBytes, resource) + + if test.ExpectError == (err == nil) { + t.Errorf("templateManifests() %s: ExpectError: %t, actual error: %s\n", test.Name, test.ExpectError, err) + } + } + }) + } +} diff --git a/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl b/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl index a425c35f422..d5767f797c0 100644 --- a/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl +++ b/pkg/operator/deploy/staticresources/master/deployment.yaml.tmpl @@ -34,11 +34,16 @@ spec: secretKeyRef: name: azure-cloud-credentials key: azure_client_id + {{ if .UsesWorkloadIdentity }} + - name: AZURE_FEDERATED_TOKEN_FILE + value: "{{ .TokenVolumeMountPath }}" + {{ else }} - name: AZURE_CLIENT_SECRET valueFrom: secretKeyRef: name: azure-cloud-credentials key: azure_client_secret + {{ end }} - name: AZURE_TENANT_ID valueFrom: secretKeyRef: @@ -63,6 +68,12 @@ spec: - ALL runAsNonRoot: true {{ end }} + {{ if .UsesWorkloadIdentity }} + volumeMounts: + - mountPath: "{{ .TokenVolumeMountPath }}" + name: bound-sa-token + readOnly: true + {{ end }} nodeSelector: node-role.kubernetes.io/master: "" {{ if .SupportsPodSecurityAdmission }} @@ -79,4 +90,14 @@ spec: operator: Exists - effect: NoSchedule operator: Exists - + {{ if .UsesWorkloadIdentity }} + volumes: + - name: bound-sa-token + projected: + defaultMode: 420 + sources: + - serviceAccountToken: + audience: openshift + expirationSeconds: 3600 + path: token + {{ end }}