From 84cce6b9a64a60dbdf39d0656b16faa941c1a31b Mon Sep 17 00:00:00 2001 From: Igal Tsoiref Date: Thu, 6 Jul 2023 18:06:47 +0300 Subject: [PATCH] CCO-363: Adding azure identidy webhook --- .../aws-pod-identity-webhook/deployment.yaml | 3 +- .../mutatingwebhook.yaml | 42 +- .../deployment.yaml | 98 ++++ .../mutatingwebhook.yaml | 32 ++ .../clusterrole.yaml | 0 .../clusterrolebinding.yaml | 0 .../poddisruptionbudget.yaml | 0 .../role.yaml | 0 .../rolebinding.yaml | 0 .../sa.yaml | 0 .../svc.yaml | 2 +- manifests/03-deployment.yaml | 2 + manifests/image-references | 4 + pkg/assets/v410_00_assets/bindata.go | 444 ++++++++++++------ .../awspodidentitywebhook_controller_test.go | 173 ------- pkg/operator/controller.go | 4 +- .../podidentity/awspodidentitywebhook.go | 34 ++ .../podidentity/azurepodidentitywebhook.go | 48 ++ .../podidentitywebhook_controller.go} | 113 +++-- .../podidentitywebhook_controller_test.go | 295 ++++++++++++ 20 files changed, 913 insertions(+), 381 deletions(-) create mode 100644 bindata/v4.1.0/azure-pod-identity-webhook/deployment.yaml create mode 100644 bindata/v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/clusterrole.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/clusterrolebinding.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/poddisruptionbudget.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/role.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/rolebinding.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/sa.yaml (100%) rename bindata/v4.1.0/{aws-pod-identity-webhook => common}/svc.yaml (94%) delete mode 100644 pkg/operator/awspodidentity/awspodidentitywebhook_controller_test.go create mode 100644 pkg/operator/podidentity/awspodidentitywebhook.go create mode 100644 pkg/operator/podidentity/azurepodidentitywebhook.go rename pkg/operator/{awspodidentity/awspodidentitywebhook_controller.go => podidentity/podidentitywebhook_controller.go} (76%) create mode 100644 pkg/operator/podidentity/podidentitywebhook_controller_test.go diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/deployment.yaml b/bindata/v4.1.0/aws-pod-identity-webhook/deployment.yaml index cfa8a76fe..6d84594bb 100644 --- a/bindata/v4.1.0/aws-pod-identity-webhook/deployment.yaml +++ b/bindata/v4.1.0/aws-pod-identity-webhook/deployment.yaml @@ -29,7 +29,7 @@ spec: - --tls-cert=/var/run/app/certs/tls.crt - --tls-key=/var/run/app/certs/tls.key - --namespace=openshift-cloud-credential-operator - - --port=6443 + - --port=9443 - --service-name=pod-identity-webhook - --annotation-prefix=eks.amazonaws.com # TODO: use openshift.io based prefix - --token-audience=sts.amazonaws.com @@ -66,4 +66,3 @@ spec: - name: webhook-certs secret: secretName: pod-identity-webhook - diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml b/bindata/v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml index 852e03ecd..6f4105c0f 100644 --- a/bindata/v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml +++ b/bindata/v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml @@ -5,24 +5,24 @@ metadata: annotations: service.beta.openshift.io/inject-cabundle: "true" webhooks: -- name: pod-identity-webhook.amazonaws.com - admissionReviewVersions: - - v1beta1 - failurePolicy: Ignore - sideEffects: None - clientConfig: - service: - name: pod-identity-webhook - namespace: openshift-cloud-credential-operator - path: "/mutate" - namespaceSelector: - matchExpressions: - - key: openshift.io/run-level - operator: NotIn - values: - - "0" - rules: - - operations: [ "CREATE" ] - apiGroups: [""] - apiVersions: ["v1"] - resources: ["pods"] + - name: pod-identity-webhook.aws.mutate.io + admissionReviewVersions: + - v1beta1 + failurePolicy: Ignore + sideEffects: None + clientConfig: + service: + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator + path: "/mutate" + namespaceSelector: + matchExpressions: + - key: openshift.io/run-level + operator: NotIn + values: + - "0" + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] diff --git a/bindata/v4.1.0/azure-pod-identity-webhook/deployment.yaml b/bindata/v4.1.0/azure-pod-identity-webhook/deployment.yaml new file mode 100644 index 000000000..d4420b3dd --- /dev/null +++ b/bindata/v4.1.0/azure-pod-identity-webhook/deployment.yaml @@ -0,0 +1,98 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + azure-workload-identity.io/system: "true" + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator +spec: + replicas: 2 + selector: + matchLabels: + app: pod-identity-webhook + template: + metadata: + annotations: + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + labels: + app: pod-identity-webhook + spec: + containers: + - args: + - --log-level=info + - --disable-cert-rotation=true + command: + - /usr/bin/azure-workload-identity-webhook + env: + - name: AZURE_TENANT_ID + valueFrom: + secretKeyRef: + name: azure-credentials + key: azure_tenant_id + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + image: ${IMAGE} + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: healthz + initialDelaySeconds: 15 + periodSeconds: 20 + name: pod-identity-webhook + resources: + requests: + cpu: 10m + memory: 10Mi + ports: + - containerPort: 6443 + name: webhook-server + protocol: TCP + - containerPort: 8095 + name: metrics + protocol: TCP + - containerPort: 9440 + name: healthz + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: healthz + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ "ALL" ] + volumeMounts: + - mountPath: /certs + name: webhook-certs + readOnly: true + nodeSelector: + node-role.kubernetes.io/master: "" + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 + priorityClassName: system-cluster-critical + serviceAccountName: pod-identity-webhook + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumes: + - name: webhook-certs + secret: + secretName: pod-identity-webhook diff --git a/bindata/v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml b/bindata/v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml new file mode 100644 index 000000000..ff13d40b0 --- /dev/null +++ b/bindata/v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml @@ -0,0 +1,32 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: pod-identity-webhook + annotations: + service.beta.openshift.io/inject-cabundle: "true" +webhooks: + - admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator + path: /mutate-v1-pod + failurePolicy: Fail + matchPolicy: Equivalent + name: pod-identity-webhook.azure.mutate.io + objectSelector: + matchLabels: + azure.workload.identity/use: "true" + reinvocationPolicy: IfNeeded + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + sideEffects: None diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/clusterrole.yaml b/bindata/v4.1.0/common/clusterrole.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/clusterrole.yaml rename to bindata/v4.1.0/common/clusterrole.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml b/bindata/v4.1.0/common/clusterrolebinding.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml rename to bindata/v4.1.0/common/clusterrolebinding.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml b/bindata/v4.1.0/common/poddisruptionbudget.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml rename to bindata/v4.1.0/common/poddisruptionbudget.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/role.yaml b/bindata/v4.1.0/common/role.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/role.yaml rename to bindata/v4.1.0/common/role.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/rolebinding.yaml b/bindata/v4.1.0/common/rolebinding.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/rolebinding.yaml rename to bindata/v4.1.0/common/rolebinding.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/sa.yaml b/bindata/v4.1.0/common/sa.yaml similarity index 100% rename from bindata/v4.1.0/aws-pod-identity-webhook/sa.yaml rename to bindata/v4.1.0/common/sa.yaml diff --git a/bindata/v4.1.0/aws-pod-identity-webhook/svc.yaml b/bindata/v4.1.0/common/svc.yaml similarity index 94% rename from bindata/v4.1.0/aws-pod-identity-webhook/svc.yaml rename to bindata/v4.1.0/common/svc.yaml index eff7ed7bb..a236303da 100644 --- a/bindata/v4.1.0/aws-pod-identity-webhook/svc.yaml +++ b/bindata/v4.1.0/common/svc.yaml @@ -11,6 +11,6 @@ metadata: spec: ports: - port: 443 - targetPort: 6443 + targetPort: 9443 selector: app: pod-identity-webhook diff --git a/manifests/03-deployment.yaml b/manifests/03-deployment.yaml index 555aa707c..8e7421f52 100644 --- a/manifests/03-deployment.yaml +++ b/manifests/03-deployment.yaml @@ -69,6 +69,8 @@ spec: env: - name: RELEASE_VERSION value: 0.0.1-snapshot + - name: AZURE_POD_IDENTITY_WEBHOOK_IMAGE + value: quay.io/openshift/azure-workload-identity-webhook:latest - name: AWS_POD_IDENTITY_WEBHOOK_IMAGE value: quay.io/openshift/aws-pod-identity-webhook:latest image: quay.io/openshift/origin-cloud-credential-operator:latest diff --git a/manifests/image-references b/manifests/image-references index 06c13c690..4e84426d1 100644 --- a/manifests/image-references +++ b/manifests/image-references @@ -10,6 +10,10 @@ spec: from: kind: DockerImage Name: quay.io/openshift/aws-pod-identity-webhook + - name: azure-workload-identity-webhook + from: + kind: DockerImage + Name: quay.io/openshift/azure-workload-identity-webhook - name: kube-rbac-proxy from: kind: DockerImage diff --git a/pkg/assets/v410_00_assets/bindata.go b/pkg/assets/v410_00_assets/bindata.go index d41d88f27..2012d3e68 100644 --- a/pkg/assets/v410_00_assets/bindata.go +++ b/pkg/assets/v410_00_assets/bindata.go @@ -1,14 +1,16 @@ // Code generated for package v410_00_assets by go-bindata DO NOT EDIT. (@generated) // sources: -// bindata/v4.1.0/aws-pod-identity-webhook/clusterrole.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml // bindata/v4.1.0/aws-pod-identity-webhook/deployment.yaml // bindata/v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/role.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/rolebinding.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/sa.yaml -// bindata/v4.1.0/aws-pod-identity-webhook/svc.yaml +// bindata/v4.1.0/azure-pod-identity-webhook/deployment.yaml +// bindata/v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml +// bindata/v4.1.0/common/clusterrole.yaml +// bindata/v4.1.0/common/clusterrolebinding.yaml +// bindata/v4.1.0/common/poddisruptionbudget.yaml +// bindata/v4.1.0/common/role.yaml +// bindata/v4.1.0/common/rolebinding.yaml +// bindata/v4.1.0/common/sa.yaml +// bindata/v4.1.0/common/svc.yaml package v410_00_assets import ( @@ -62,65 +64,6 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _v410AwsPodIdentityWebhookClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: pod-identity-webhook -rules: -- apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - get - - watch - - list -`) - -func v410AwsPodIdentityWebhookClusterroleYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookClusterroleYaml, nil -} - -func v410AwsPodIdentityWebhookClusterroleYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookClusterroleYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - -var _v410AwsPodIdentityWebhookClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: pod-identity-webhook -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: pod-identity-webhook -subjects: -- kind: ServiceAccount - name: pod-identity-webhook - namespace: openshift-cloud-credential-operator -`) - -func v410AwsPodIdentityWebhookClusterrolebindingYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookClusterrolebindingYaml, nil -} - -func v410AwsPodIdentityWebhookClusterrolebindingYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookClusterrolebindingYamlBytes() - if err != nil { - return nil, err - } - - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} - a := &asset{bytes: bytes, info: info} - return a, nil -} - var _v410AwsPodIdentityWebhookDeploymentYaml = []byte(`apiVersion: apps/v1 kind: Deployment metadata: @@ -152,7 +95,7 @@ spec: - --tls-cert=/var/run/app/certs/tls.crt - --tls-key=/var/run/app/certs/tls.key - --namespace=openshift-cloud-credential-operator - - --port=6443 + - --port=9443 - --service-name=pod-identity-webhook - --annotation-prefix=eks.amazonaws.com # TODO: use openshift.io based prefix - --token-audience=sts.amazonaws.com @@ -214,27 +157,27 @@ metadata: annotations: service.beta.openshift.io/inject-cabundle: "true" webhooks: -- name: pod-identity-webhook.amazonaws.com - admissionReviewVersions: - - v1beta1 - failurePolicy: Ignore - sideEffects: None - clientConfig: - service: - name: pod-identity-webhook - namespace: openshift-cloud-credential-operator - path: "/mutate" - namespaceSelector: - matchExpressions: - - key: openshift.io/run-level - operator: NotIn - values: - - "0" - rules: - - operations: [ "CREATE" ] - apiGroups: [""] - apiVersions: ["v1"] - resources: ["pods"] + - name: pod-identity-webhook.aws.mutate.io + admissionReviewVersions: + - v1beta1 + failurePolicy: Ignore + sideEffects: None + clientConfig: + service: + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator + path: "/mutate" + namespaceSelector: + matchExpressions: + - key: openshift.io/run-level + operator: NotIn + values: + - "0" + rules: + - operations: [ "CREATE" ] + apiGroups: [""] + apiVersions: ["v1"] + resources: ["pods"] `) func v410AwsPodIdentityWebhookMutatingwebhookYamlBytes() ([]byte, error) { @@ -252,7 +195,226 @@ func v410AwsPodIdentityWebhookMutatingwebhookYaml() (*asset, error) { return a, nil } -var _v410AwsPodIdentityWebhookPoddisruptionbudgetYaml = []byte(`apiVersion: policy/v1 +var _v410AzurePodIdentityWebhookDeploymentYaml = []byte(`apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + azure-workload-identity.io/system: "true" + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator +spec: + replicas: 2 + selector: + matchLabels: + app: pod-identity-webhook + template: + metadata: + annotations: + target.workload.openshift.io/management: '{"effect": "PreferredDuringScheduling"}' + labels: + app: pod-identity-webhook + spec: + containers: + - args: + - --log-level=info + - --disable-cert-rotation=true + command: + - /usr/bin/azure-workload-identity-webhook + env: + - name: AZURE_TENANT_ID + valueFrom: + secretKeyRef: + name: azure-credentials + key: azure_tenant_id + - name: POD_NAMESPACE + valueFrom: + fieldRef: + apiVersion: v1 + fieldPath: metadata.namespace + image: ${IMAGE} + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 6 + httpGet: + path: /healthz + port: healthz + initialDelaySeconds: 15 + periodSeconds: 20 + name: pod-identity-webhook + ports: + - containerPort: 6443 + name: webhook-server + protocol: TCP + - containerPort: 8095 + name: metrics + protocol: TCP + - containerPort: 9440 + name: healthz + protocol: TCP + readinessProbe: + httpGet: + path: /readyz + port: healthz + initialDelaySeconds: 5 + periodSeconds: 5 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: [ "ALL" ] + volumeMounts: + - mountPath: /certs + name: webhook-certs + readOnly: true + nodeSelector: + node-role.kubernetes.io/master: "" + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 120 + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 120 + priorityClassName: system-cluster-critical + serviceAccountName: pod-identity-webhook + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + volumes: + - name: webhook-certs + secret: + secretName: pod-identity-webhook +`) + +func v410AzurePodIdentityWebhookDeploymentYamlBytes() ([]byte, error) { + return _v410AzurePodIdentityWebhookDeploymentYaml, nil +} + +func v410AzurePodIdentityWebhookDeploymentYaml() (*asset, error) { + bytes, err := v410AzurePodIdentityWebhookDeploymentYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "v4.1.0/azure-pod-identity-webhook/deployment.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _v410AzurePodIdentityWebhookMutatingwebhookYaml = []byte(`apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: pod-identity-webhook + annotations: + service.beta.openshift.io/inject-cabundle: "true" +webhooks: + - admissionReviewVersions: + - v1 + - v1beta1 + clientConfig: + service: + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator + path: /mutate-v1-pod + failurePolicy: Fail + matchPolicy: Equivalent + name: pod-identity-webhook.azure.mutate.io + objectSelector: + matchLabels: + azure.workload.identity/use: "true" + reinvocationPolicy: IfNeeded + rules: + - apiGroups: + - "" + apiVersions: + - v1 + operations: + - CREATE + resources: + - pods + sideEffects: None +`) + +func v410AzurePodIdentityWebhookMutatingwebhookYamlBytes() ([]byte, error) { + return _v410AzurePodIdentityWebhookMutatingwebhookYaml, nil +} + +func v410AzurePodIdentityWebhookMutatingwebhookYaml() (*asset, error) { + bytes, err := v410AzurePodIdentityWebhookMutatingwebhookYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _v410CommonClusterroleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: pod-identity-webhook +rules: +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - watch + - list +`) + +func v410CommonClusterroleYamlBytes() ([]byte, error) { + return _v410CommonClusterroleYaml, nil +} + +func v410CommonClusterroleYaml() (*asset, error) { + bytes, err := v410CommonClusterroleYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "v4.1.0/common/clusterrole.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _v410CommonClusterrolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: pod-identity-webhook +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: pod-identity-webhook +subjects: +- kind: ServiceAccount + name: pod-identity-webhook + namespace: openshift-cloud-credential-operator +`) + +func v410CommonClusterrolebindingYamlBytes() ([]byte, error) { + return _v410CommonClusterrolebindingYaml, nil +} + +func v410CommonClusterrolebindingYaml() (*asset, error) { + bytes, err := v410CommonClusterrolebindingYamlBytes() + if err != nil { + return nil, err + } + + info := bindataFileInfo{name: "v4.1.0/common/clusterrolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + a := &asset{bytes: bytes, info: info} + return a, nil +} + +var _v410CommonPoddisruptionbudgetYaml = []byte(`apiVersion: policy/v1 kind: PodDisruptionBudget metadata: name: pod-identity-webhook @@ -264,22 +426,22 @@ spec: app: pod-identity-webhook `) -func v410AwsPodIdentityWebhookPoddisruptionbudgetYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookPoddisruptionbudgetYaml, nil +func v410CommonPoddisruptionbudgetYamlBytes() ([]byte, error) { + return _v410CommonPoddisruptionbudgetYaml, nil } -func v410AwsPodIdentityWebhookPoddisruptionbudgetYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookPoddisruptionbudgetYamlBytes() +func v410CommonPoddisruptionbudgetYaml() (*asset, error) { + bytes, err := v410CommonPoddisruptionbudgetYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "v4.1.0/common/poddisruptionbudget.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _v410AwsPodIdentityWebhookRoleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +var _v410CommonRoleYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: pod-identity-webhook @@ -303,22 +465,22 @@ rules: - "pod-identity-webhook" `) -func v410AwsPodIdentityWebhookRoleYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookRoleYaml, nil +func v410CommonRoleYamlBytes() ([]byte, error) { + return _v410CommonRoleYaml, nil } -func v410AwsPodIdentityWebhookRoleYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookRoleYamlBytes() +func v410CommonRoleYaml() (*asset, error) { + bytes, err := v410CommonRoleYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "v4.1.0/common/role.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _v410AwsPodIdentityWebhookRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 +var _v410CommonRolebindingYaml = []byte(`apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: pod-identity-webhook @@ -333,44 +495,44 @@ subjects: namespace: openshift-cloud-credential-operator `) -func v410AwsPodIdentityWebhookRolebindingYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookRolebindingYaml, nil +func v410CommonRolebindingYamlBytes() ([]byte, error) { + return _v410CommonRolebindingYaml, nil } -func v410AwsPodIdentityWebhookRolebindingYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookRolebindingYamlBytes() +func v410CommonRolebindingYaml() (*asset, error) { + bytes, err := v410CommonRolebindingYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "v4.1.0/common/rolebinding.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _v410AwsPodIdentityWebhookSaYaml = []byte(`apiVersion: v1 +var _v410CommonSaYaml = []byte(`apiVersion: v1 kind: ServiceAccount metadata: name: pod-identity-webhook namespace: openshift-cloud-credential-operator `) -func v410AwsPodIdentityWebhookSaYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookSaYaml, nil +func v410CommonSaYamlBytes() ([]byte, error) { + return _v410CommonSaYaml, nil } -func v410AwsPodIdentityWebhookSaYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookSaYamlBytes() +func v410CommonSaYaml() (*asset, error) { + bytes, err := v410CommonSaYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/sa.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "v4.1.0/common/sa.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } -var _v410AwsPodIdentityWebhookSvcYaml = []byte(`apiVersion: v1 +var _v410CommonSvcYaml = []byte(`apiVersion: v1 kind: Service metadata: name: pod-identity-webhook @@ -383,22 +545,22 @@ metadata: spec: ports: - port: 443 - targetPort: 6443 + targetPort: 9443 selector: app: pod-identity-webhook `) -func v410AwsPodIdentityWebhookSvcYamlBytes() ([]byte, error) { - return _v410AwsPodIdentityWebhookSvcYaml, nil +func v410CommonSvcYamlBytes() ([]byte, error) { + return _v410CommonSvcYaml, nil } -func v410AwsPodIdentityWebhookSvcYaml() (*asset, error) { - bytes, err := v410AwsPodIdentityWebhookSvcYamlBytes() +func v410CommonSvcYaml() (*asset, error) { + bytes, err := v410CommonSvcYamlBytes() if err != nil { return nil, err } - info := bindataFileInfo{name: "v4.1.0/aws-pod-identity-webhook/svc.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} + info := bindataFileInfo{name: "v4.1.0/common/svc.yaml", size: 0, mode: os.FileMode(0), modTime: time.Unix(0, 0)} a := &asset{bytes: bytes, info: info} return a, nil } @@ -455,15 +617,17 @@ func AssetNames() []string { // _bindata is a table, holding each asset generator, mapped to its name. var _bindata = map[string]func() (*asset, error){ - "v4.1.0/aws-pod-identity-webhook/clusterrole.yaml": v410AwsPodIdentityWebhookClusterroleYaml, - "v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml": v410AwsPodIdentityWebhookClusterrolebindingYaml, - "v4.1.0/aws-pod-identity-webhook/deployment.yaml": v410AwsPodIdentityWebhookDeploymentYaml, - "v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml": v410AwsPodIdentityWebhookMutatingwebhookYaml, - "v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml": v410AwsPodIdentityWebhookPoddisruptionbudgetYaml, - "v4.1.0/aws-pod-identity-webhook/role.yaml": v410AwsPodIdentityWebhookRoleYaml, - "v4.1.0/aws-pod-identity-webhook/rolebinding.yaml": v410AwsPodIdentityWebhookRolebindingYaml, - "v4.1.0/aws-pod-identity-webhook/sa.yaml": v410AwsPodIdentityWebhookSaYaml, - "v4.1.0/aws-pod-identity-webhook/svc.yaml": v410AwsPodIdentityWebhookSvcYaml, + "v4.1.0/aws-pod-identity-webhook/deployment.yaml": v410AwsPodIdentityWebhookDeploymentYaml, + "v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml": v410AwsPodIdentityWebhookMutatingwebhookYaml, + "v4.1.0/azure-pod-identity-webhook/deployment.yaml": v410AzurePodIdentityWebhookDeploymentYaml, + "v4.1.0/azure-pod-identity-webhook/mutatingwebhook.yaml": v410AzurePodIdentityWebhookMutatingwebhookYaml, + "v4.1.0/common/clusterrole.yaml": v410CommonClusterroleYaml, + "v4.1.0/common/clusterrolebinding.yaml": v410CommonClusterrolebindingYaml, + "v4.1.0/common/poddisruptionbudget.yaml": v410CommonPoddisruptionbudgetYaml, + "v4.1.0/common/role.yaml": v410CommonRoleYaml, + "v4.1.0/common/rolebinding.yaml": v410CommonRolebindingYaml, + "v4.1.0/common/sa.yaml": v410CommonSaYaml, + "v4.1.0/common/svc.yaml": v410CommonSvcYaml, } // AssetDir returns the file names below a certain @@ -511,15 +675,21 @@ type bintree struct { var _bintree = &bintree{nil, map[string]*bintree{ "v4.1.0": {nil, map[string]*bintree{ "aws-pod-identity-webhook": {nil, map[string]*bintree{ - "clusterrole.yaml": {v410AwsPodIdentityWebhookClusterroleYaml, map[string]*bintree{}}, - "clusterrolebinding.yaml": {v410AwsPodIdentityWebhookClusterrolebindingYaml, map[string]*bintree{}}, - "deployment.yaml": {v410AwsPodIdentityWebhookDeploymentYaml, map[string]*bintree{}}, - "mutatingwebhook.yaml": {v410AwsPodIdentityWebhookMutatingwebhookYaml, map[string]*bintree{}}, - "poddisruptionbudget.yaml": {v410AwsPodIdentityWebhookPoddisruptionbudgetYaml, map[string]*bintree{}}, - "role.yaml": {v410AwsPodIdentityWebhookRoleYaml, map[string]*bintree{}}, - "rolebinding.yaml": {v410AwsPodIdentityWebhookRolebindingYaml, map[string]*bintree{}}, - "sa.yaml": {v410AwsPodIdentityWebhookSaYaml, map[string]*bintree{}}, - "svc.yaml": {v410AwsPodIdentityWebhookSvcYaml, map[string]*bintree{}}, + "deployment.yaml": {v410AwsPodIdentityWebhookDeploymentYaml, map[string]*bintree{}}, + "mutatingwebhook.yaml": {v410AwsPodIdentityWebhookMutatingwebhookYaml, map[string]*bintree{}}, + }}, + "azure-pod-identity-webhook": {nil, map[string]*bintree{ + "deployment.yaml": {v410AzurePodIdentityWebhookDeploymentYaml, map[string]*bintree{}}, + "mutatingwebhook.yaml": {v410AzurePodIdentityWebhookMutatingwebhookYaml, map[string]*bintree{}}, + }}, + "common": {nil, map[string]*bintree{ + "clusterrole.yaml": {v410CommonClusterroleYaml, map[string]*bintree{}}, + "clusterrolebinding.yaml": {v410CommonClusterrolebindingYaml, map[string]*bintree{}}, + "poddisruptionbudget.yaml": {v410CommonPoddisruptionbudgetYaml, map[string]*bintree{}}, + "role.yaml": {v410CommonRoleYaml, map[string]*bintree{}}, + "rolebinding.yaml": {v410CommonRolebindingYaml, map[string]*bintree{}}, + "sa.yaml": {v410CommonSaYaml, map[string]*bintree{}}, + "svc.yaml": {v410CommonSvcYaml, map[string]*bintree{}}, }}, }}, }} diff --git a/pkg/operator/awspodidentity/awspodidentitywebhook_controller_test.go b/pkg/operator/awspodidentity/awspodidentitywebhook_controller_test.go deleted file mode 100644 index 8043ca64a..000000000 --- a/pkg/operator/awspodidentity/awspodidentitywebhook_controller_test.go +++ /dev/null @@ -1,173 +0,0 @@ -/* -Copyright 2018 The OpenShift 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 - - http://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 awspodidentity - -import ( - "context" - "testing" - - log "github.com/sirupsen/logrus" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/golang/mock/gomock" - appsv1 "k8s.io/api/apps/v1" - policyv1 "k8s.io/api/policy/v1" - v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes" - fakeclientgo "k8s.io/client-go/kubernetes/fake" - "k8s.io/client-go/kubernetes/scheme" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - configv1 "github.com/openshift/api/config/v1" - schemeutils "github.com/openshift/cloud-credential-operator/pkg/util" - "github.com/openshift/library-go/pkg/operator/events" - "github.com/openshift/library-go/pkg/operator/resource/resourceapply" -) - -func init() { - log.SetLevel(log.DebugLevel) -} - -func TestAWSPodIdentityWebhookController(t *testing.T) { - schemeutils.SetupScheme(scheme.Scheme) - - getDeployment := func(c kubernetes.Interface, name, namespace string) *appsv1.Deployment { - deployment, err := c.AppsV1().Deployments(namespace).Get(context.TODO(), name, v1.GetOptions{}) - if err == nil { - return deployment - } - return nil - } - - getPDB := func(c kubernetes.Interface, name, namespace string) *policyv1.PodDisruptionBudget { - pdb, err := c.PolicyV1().PodDisruptionBudgets(namespace).Get(context.TODO(), name, v1.GetOptions{}) - if err == nil { - return pdb - } - return nil - } - - tests := []struct { - name string - existing []runtime.Object - expectErr bool - expectedReplicas int32 - expectPDB bool - }{ - { - name: "Cluster infrastructure topology is SingleReplica", - existing: []runtime.Object{ - &configv1.Infrastructure{ - ObjectMeta: v1.ObjectMeta{ - Name: "cluster", - }, - Status: configv1.InfrastructureStatus{ - InfrastructureTopology: configv1.SingleReplicaTopologyMode, - }, - }}, - expectErr: false, - expectedReplicas: 1, - expectPDB: false, - }, - { - name: "Cluster infrastructure topology is HighlyAvailable", - existing: []runtime.Object{ - &configv1.Infrastructure{ - ObjectMeta: v1.ObjectMeta{ - Name: "cluster", - }, - Status: configv1.InfrastructureStatus{ - InfrastructureTopology: configv1.HighlyAvailableTopologyMode, - }, - }}, - expectErr: false, - expectedReplicas: 2, - expectPDB: true, - }, - { - name: "Cluster infrastructure object has no infrastructure topology set", - existing: []runtime.Object{ - &configv1.Infrastructure{ - ObjectMeta: v1.ObjectMeta{ - Name: "cluster", - }, - Status: configv1.InfrastructureStatus{}, - }}, - expectErr: false, - expectedReplicas: 2, - expectPDB: true, - }, - { - name: "Cluster infrastructure object doesn't exist", - expectErr: true, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - mockCtrl := gomock.NewController(t) - defer mockCtrl.Finish() - - logger := log.WithField("controller", "awspodidentitywebhookcontrollertest") - fakeClient := fake.NewClientBuilder().WithRuntimeObjects(test.existing...).Build() - fakeClientset := fakeclientgo.NewSimpleClientset() - r := &staticResourceReconciler{ - client: fakeClient, - clientset: fakeClientset, - logger: logger, - eventRecorder: events.NewInMemoryRecorder(""), - cache: resourceapply.NewResourceCache(), - conditions: []configv1.ClusterOperatorStatusCondition{}, - imagePullSpec: "testimagepullspec", - } - - _, err := r.Reconcile(context.TODO(), reconcile.Request{ - NamespacedName: types.NamespacedName{ - Name: "testName", - Namespace: "testNamespace", - }, - }) - - if err != nil && !test.expectErr { - require.NoError(t, err, "Unexpected error: %v", err) - } - if err == nil && test.expectErr { - t.Errorf("Expected error but got none") - } - - if !test.expectErr { - podIdentityWebhookDeployment := getDeployment(fakeClientset, "pod-identity-webhook", "openshift-cloud-credential-operator") - assert.NotNil(t, podIdentityWebhookDeployment, "did not find expected pod-identity-webhook Deployment") - - if test.expectedReplicas != 0 { - assert.Equal(t, *podIdentityWebhookDeployment.Spec.Replicas, test.expectedReplicas, "found unexpected pod-identity-webhook deployment replicas") - } - - podDisruptionBudget := getPDB(fakeClientset, "pod-identity-webhook", "openshift-cloud-credential-operator") - if test.expectPDB { - assert.NotNil(t, podDisruptionBudget, "did not find expected pod-identity-webhook PodDisruptionBudget") - } else { - assert.Nil(t, podDisruptionBudget, "found unexpected pod-identity-webhook PodDisruptionBudget") - } - } - }) - } -} diff --git a/pkg/operator/controller.go b/pkg/operator/controller.go index 41385ec3c..52089d7ea 100644 --- a/pkg/operator/controller.go +++ b/pkg/operator/controller.go @@ -23,13 +23,13 @@ import ( gcpactuator "github.com/openshift/cloud-credential-operator/pkg/gcp/actuator" "github.com/openshift/cloud-credential-operator/pkg/kubevirt" "github.com/openshift/cloud-credential-operator/pkg/openstack" - "github.com/openshift/cloud-credential-operator/pkg/operator/awspodidentity" "github.com/openshift/cloud-credential-operator/pkg/operator/cleanup" "github.com/openshift/cloud-credential-operator/pkg/operator/credentialsrequest" "github.com/openshift/cloud-credential-operator/pkg/operator/credentialsrequest/actuator" "github.com/openshift/cloud-credential-operator/pkg/operator/loglevel" "github.com/openshift/cloud-credential-operator/pkg/operator/metrics" "github.com/openshift/cloud-credential-operator/pkg/operator/platform" + "github.com/openshift/cloud-credential-operator/pkg/operator/podidentity" "github.com/openshift/cloud-credential-operator/pkg/operator/secretannotator" "github.com/openshift/cloud-credential-operator/pkg/operator/status" "github.com/openshift/cloud-credential-operator/pkg/operator/utils" @@ -51,7 +51,7 @@ const ( func init() { AddToManagerFuncs = append(AddToManagerFuncs, metrics.Add) AddToManagerFuncs = append(AddToManagerFuncs, secretannotator.Add) - AddToManagerFuncs = append(AddToManagerFuncs, awspodidentity.Add) + AddToManagerFuncs = append(AddToManagerFuncs, podidentity.Add) AddToManagerFuncs = append(AddToManagerFuncs, status.Add) AddToManagerFuncs = append(AddToManagerFuncs, loglevel.Add) AddToManagerFuncs = append(AddToManagerFuncs, cleanup.Add) diff --git a/pkg/operator/podidentity/awspodidentitywebhook.go b/pkg/operator/podidentity/awspodidentitywebhook.go new file mode 100644 index 000000000..7d103c52d --- /dev/null +++ b/pkg/operator/podidentity/awspodidentitywebhook.go @@ -0,0 +1,34 @@ +package podidentity + +import ( + "context" + "fmt" + "os" + + "k8s.io/client-go/kubernetes" +) + +const awsFolder = "v4.1.0/aws-pod-identity-webhook" + +type AwsPodIdentity struct { +} + +func (a AwsPodIdentity) ShouldBeDeployed(ctx context.Context, clientSet kubernetes.Interface, namespace string) (bool, error) { + return true, nil +} + +func (a AwsPodIdentity) Deployment() string { + return fmt.Sprintf("%s/deployment.yaml", awsFolder) +} + +func (a AwsPodIdentity) Webhook() string { + return fmt.Sprintf("%s/mutatingwebhook.yaml", awsFolder) +} + +func (a AwsPodIdentity) GetImagePullSpec() string { + return os.Getenv("AWS_POD_IDENTITY_WEBHOOK_IMAGE") +} + +func (a AwsPodIdentity) Name() string { + return "aws" +} diff --git a/pkg/operator/podidentity/azurepodidentitywebhook.go b/pkg/operator/podidentity/azurepodidentitywebhook.go new file mode 100644 index 000000000..a6ce93276 --- /dev/null +++ b/pkg/operator/podidentity/azurepodidentitywebhook.go @@ -0,0 +1,48 @@ +package podidentity + +import ( + "context" + "fmt" + "os" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" +) + +const ( + azureTenantIdKey = "azure_tenant_id" + azureFolder = "v4.1.0/azure-pod-identity-webhook" +) + +type AzurePodIdentity struct { +} + +func (a AzurePodIdentity) ShouldBeDeployed(ctx context.Context, clientSet kubernetes.Interface, namespace string) (bool, error) { + secret, err := clientSet.CoreV1().Secrets(namespace).Get(ctx, + "azure-credentials", metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + _, ok := secret.Data[azureTenantIdKey] + return ok, nil +} + +func (a AzurePodIdentity) Deployment() string { + return fmt.Sprintf("%s/deployment.yaml", azureFolder) +} + +func (a AzurePodIdentity) Webhook() string { + return fmt.Sprintf("%s/mutatingwebhook.yaml", azureFolder) +} + +func (a AzurePodIdentity) GetImagePullSpec() string { + return os.Getenv("AZURE_POD_IDENTITY_WEBHOOK_IMAGE") +} + +func (a AzurePodIdentity) Name() string { + return "azure" +} diff --git a/pkg/operator/awspodidentity/awspodidentitywebhook_controller.go b/pkg/operator/podidentity/podidentitywebhook_controller.go similarity index 76% rename from pkg/operator/awspodidentity/awspodidentitywebhook_controller.go rename to pkg/operator/podidentity/podidentitywebhook_controller.go index af11dcbd4..23d88e5ca 100644 --- a/pkg/operator/awspodidentity/awspodidentitywebhook_controller.go +++ b/pkg/operator/podidentity/podidentitywebhook_controller.go @@ -1,13 +1,12 @@ -package awspodidentity +package podidentity import ( "context" "fmt" - "os" + "strings" "time" log "github.com/sirupsen/logrus" - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/serializer" @@ -36,28 +35,24 @@ import ( ) const ( - controllerName = "awspodidentity" + controllerName = "pod-identity" deploymentName = "cloud-credential-operator" operatorNamespace = "openshift-cloud-credential-operator" retryInterval = 10 * time.Second reasonStaticResourceReconcileFailed = "StaticResourceReconcileFailed" + pdb = "v4.1.0/common/poddisruptionbudget.yaml" ) var ( defaultCodecs = serializer.NewCodecFactory(scheme.Scheme) defaultCodec = defaultCodecs.UniversalDeserializer() staticFiles = []string{ - "v4.1.0/aws-pod-identity-webhook/sa.yaml", - "v4.1.0/aws-pod-identity-webhook/clusterrole.yaml", - "v4.1.0/aws-pod-identity-webhook/role.yaml", - "v4.1.0/aws-pod-identity-webhook/clusterrolebinding.yaml", - "v4.1.0/aws-pod-identity-webhook/rolebinding.yaml", - "v4.1.0/aws-pod-identity-webhook/svc.yaml", - } - templateFiles = []string{ - "v4.1.0/aws-pod-identity-webhook/deployment.yaml", - "v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml", - "v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml", + "v4.1.0/common/sa.yaml", + "v4.1.0/common/clusterrole.yaml", + "v4.1.0/common/role.yaml", + "v4.1.0/common/clusterrolebinding.yaml", + "v4.1.0/common/rolebinding.yaml", + "v4.1.0/common/svc.yaml", } relatedObjects = []configv1.ObjectReference{ { @@ -106,13 +101,21 @@ var ( } ) -type awsPodIdentityController struct { +type PodIdentityManifestSource interface { + Deployment() string + GetImagePullSpec() string + Webhook() string + ShouldBeDeployed(ctx context.Context, clientSet kubernetes.Interface, namespace string) (bool, error) + Name() string +} + +type podIdentityController struct { reconciler *staticResourceReconciler cache cache.Cache logger log.FieldLogger } -func (c *awsPodIdentityController) Start(ctx context.Context) error { +func (c *podIdentityController) Start(ctx context.Context) error { retryTimer := time.NewTimer(retryInterval) for { err := c.reconciler.ReconcileResources(ctx) @@ -133,16 +136,24 @@ func Add(mgr, rootCredentialManager manager.Manager, kubeconfig string) error { if err != nil { return err } - // Only add controller when PlatformType is AWS - platformType := platform.GetType(infraStatus) - if platformType != configv1.AWSPlatformType { - return nil - } // Do not add controller when ControlPlaneTopology is External if infraStatus.ControlPlaneTopology == configv1.ExternalTopologyMode { return nil } - log.Info("setting up AWS pod identity controller") + + var podIdentityType PodIdentityManifestSource + platformType := platform.GetType(infraStatus) + switch platformType { + case configv1.AWSPlatformType: + podIdentityType = AwsPodIdentity{} + case configv1.AzurePlatformType: + podIdentityType = AzurePodIdentity{} + default: + log.WithField("controller", controllerName).Warn("Failed to get platform type") + return nil + } + ctx := context.TODO() + logger := log.WithFields(log.Fields{"platform": platformType, "controller": controllerName}) config := mgr.GetConfig() clientset, err := kubernetes.NewForConfig(config) @@ -150,27 +161,40 @@ func Add(mgr, rootCredentialManager manager.Manager, kubeconfig string) error { return err } + shouldBeDeployed, err := podIdentityType.ShouldBeDeployed(ctx, clientset, operatorNamespace) + if err != nil { + return err + } + if !shouldBeDeployed { + logger.Info("pod identity was not enabled, nothing to deploy") + return nil + } + + logger.Info("setting up pod identity controller") + controllerRef := &corev1.ObjectReference{ Kind: "deployment", Namespace: operatorNamespace, Name: deploymentName, } eventRecorder := events.NewKubeRecorder(clientset.CoreV1().Events(operatorNamespace), deploymentName, controllerRef) - logger := log.WithFields(log.Fields{"controller": controllerName}) - imagePullSpec := os.Getenv("AWS_POD_IDENTITY_WEBHOOK_IMAGE") + + imagePullSpec := podIdentityType.GetImagePullSpec() if len(imagePullSpec) == 0 { - logger.Warn("AWS_POD_IDENTITY_WEBHOOK_IMAGE is not set, AWS pod identity webhook will not be deployed") + logger.Warnf("%s_POD_IDENTITY_WEBHOOK_IMAGE is not set, pod identity webhook will not be deployed", + strings.ToUpper(string(platformType))) return nil } r := &staticResourceReconciler{ - client: mgr.GetClient(), - clientset: clientset, - logger: logger, - eventRecorder: eventRecorder, - imagePullSpec: imagePullSpec, - conditions: []configv1.ClusterOperatorStatusCondition{}, - cache: resourceapply.NewResourceCache(), + client: mgr.GetClient(), + clientset: clientset, + logger: logger, + eventRecorder: eventRecorder, + imagePullSpec: imagePullSpec, + conditions: []configv1.ClusterOperatorStatusCondition{}, + cache: resourceapply.NewResourceCache(), + podIdentityType: podIdentityType, } c, err := controller.New(controllerName, mgr, controller.Options{Reconciler: r}) @@ -193,11 +217,12 @@ func Add(mgr, rootCredentialManager manager.Manager, kubeconfig string) error { // Create a namespace local cache separate from the Manager cache // A namespace scoped cache can still handle cluster scoped resources - cache, err := cache.New(config, cache.Options{Namespaces: namespaces}) + controllerCache, err := cache.New(config, cache.Options{Namespaces: namespaces}) if err != nil { return err } - allFiles := append(staticFiles, templateFiles...) + + allFiles := append(staticFiles, []string{podIdentityType.Webhook(), podIdentityType.Deployment(), pdb}...) for _, file := range allFiles { objBytes := v410_00_assets.MustAsset(file) obj, _, err := defaultCodec.Decode(objBytes, nil, nil) @@ -209,7 +234,7 @@ func Add(mgr, rootCredentialManager manager.Manager, kubeconfig string) error { if !ok { return fmt.Errorf("failed to convert runtime.Object to client.Object") } - informer, err := cache.GetInformer(context.TODO(), co) + informer, err := controllerCache.GetInformer(ctx, co) if err != nil { return err } @@ -221,7 +246,7 @@ func Add(mgr, rootCredentialManager manager.Manager, kubeconfig string) error { } status.AddHandler(controllerName, r) - if err := mgr.Add(&awsPodIdentityController{reconciler: r, cache: cache, logger: logger}); err != nil { + if err := mgr.Add(&podIdentityController{reconciler: r, cache: controllerCache, logger: logger}); err != nil { return err } @@ -245,6 +270,7 @@ type staticResourceReconciler struct { imagePullSpec string conditions []configv1.ClusterOperatorStatusCondition cache resourceapply.ResourceCache + podIdentityType PodIdentityManifestSource } var _ reconcile.Reconciler = &staticResourceReconciler{} @@ -293,8 +319,7 @@ func (r *staticResourceReconciler) ReconcileResources(ctx context.Context) error r.logger.Infof("%s reconciled successfully", result.Type) } - // "v4.1.0/aws-pod-identity-webhook/deployment.yaml" - requestedDeployment := resourceread.ReadDeploymentV1OrDie(v410_00_assets.MustAsset("v4.1.0/aws-pod-identity-webhook/deployment.yaml")) + requestedDeployment := resourceread.ReadDeploymentV1OrDie(v410_00_assets.MustAsset(r.podIdentityType.Deployment())) if topology == configv1.SingleReplicaTopologyMode { // Set replicas=1 for deployment on single replica topology clusters requestedDeployment.Spec.Replicas = pointer.Int32(1) @@ -310,9 +335,8 @@ func (r *staticResourceReconciler) ReconcileResources(ctx context.Context) error r.logger.Infof("Deployment reconciled successfully") } - // "v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml" - requestedMutatingWebhookConfiguration := resourceread.ReadMutatingWebhookConfigurationV1OrDie(v410_00_assets.MustAsset("v4.1.0/aws-pod-identity-webhook/mutatingwebhook.yaml")) - _, modified, err = resourceapply.ApplyMutatingWebhookConfigurationImproved(context.TODO(), r.clientset.AdmissionregistrationV1(), r.eventRecorder, requestedMutatingWebhookConfiguration, r.cache) + requestedMutatingWebhookConfiguration := resourceread.ReadMutatingWebhookConfigurationV1OrDie(v410_00_assets.MustAsset(r.podIdentityType.Webhook())) + _, modified, err = resourceapply.ApplyMutatingWebhookConfigurationImproved(ctx, r.clientset.AdmissionregistrationV1(), r.eventRecorder, requestedMutatingWebhookConfiguration, r.cache) if err != nil { r.logger.WithError(err).Error("error applying MutatingWebhookConfiguration") return err @@ -325,9 +349,8 @@ func (r *staticResourceReconciler) ReconcileResources(ctx context.Context) error // Don't deploy the PDB to single replica topology clusters r.logger.Debugf("not deploying PodDisruptionBudget to single replica topology") } else { - // "v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml" - requestedPDB := resourceread.ReadPodDisruptionBudgetV1OrDie(v410_00_assets.MustAsset("v4.1.0/aws-pod-identity-webhook/poddisruptionbudget.yaml")) - _, modified, err = resourceapply.ApplyPodDisruptionBudget(context.TODO(), r.clientset.PolicyV1(), r.eventRecorder, requestedPDB) + requestedPDB := resourceread.ReadPodDisruptionBudgetV1OrDie(v410_00_assets.MustAsset(pdb)) + _, modified, err = resourceapply.ApplyPodDisruptionBudget(ctx, r.clientset.PolicyV1(), r.eventRecorder, requestedPDB) if err != nil { r.logger.WithError(err).Error("error applying PodDisruptionBudget") return err diff --git a/pkg/operator/podidentity/podidentitywebhook_controller_test.go b/pkg/operator/podidentity/podidentitywebhook_controller_test.go new file mode 100644 index 000000000..ba3d43a74 --- /dev/null +++ b/pkg/operator/podidentity/podidentitywebhook_controller_test.go @@ -0,0 +1,295 @@ +/* +Copyright 2018 The OpenShift 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 + + http://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 podidentity + +import ( + "context" + "fmt" + clientgotesting "k8s.io/client-go/testing" + "testing" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/golang/mock/gomock" + admissionv1 "k8s.io/api/admissionregistration/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + policyv1 "k8s.io/api/policy/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + fakeclientgo "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + configv1 "github.com/openshift/api/config/v1" + schemeutils "github.com/openshift/cloud-credential-operator/pkg/util" + "github.com/openshift/library-go/pkg/operator/events" + "github.com/openshift/library-go/pkg/operator/resource/resourceapply" +) + +func init() { + log.SetLevel(log.DebugLevel) +} + +func TestPodIdentityWebhookController(t *testing.T) { + schemeutils.SetupScheme(scheme.Scheme) + + getDeployment := func(c kubernetes.Interface, name, namespace string) (*appsv1.Deployment, error) { + return c.AppsV1().Deployments(namespace).Get(context.TODO(), name, v1.GetOptions{}) + } + + getPDB := func(c kubernetes.Interface, name, namespace string) (*policyv1.PodDisruptionBudget, error) { + return c.PolicyV1().PodDisruptionBudgets(namespace).Get(context.TODO(), name, v1.GetOptions{}) + } + + getWebhook := func(c kubernetes.Interface, name string) (*admissionv1.MutatingWebhookConfiguration, error) { + return c.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(context.TODO(), name, v1.GetOptions{}) + } + + t.Setenv("AWS_POD_IDENTITY_WEBHOOK_IMAGE", "aws_identity_image") + t.Setenv("AZURE_POD_IDENTITY_WEBHOOK_IMAGE", "azure_identity_image") + + tests := []struct { + name string + existing []runtime.Object + expectErr bool + expectedReplicas int32 + expectPDB bool + podIdentityType PodIdentityManifestSource + }{ + { + name: "Cluster infrastructure topology is SingleReplica", + existing: []runtime.Object{ + &configv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{ + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{ + InfrastructureTopology: configv1.SingleReplicaTopologyMode, + }, + }}, + expectErr: false, + expectedReplicas: 1, + expectPDB: false, + podIdentityType: AwsPodIdentity{}, + }, + { + name: "Cluster infrastructure topology is HighlyAvailable", + existing: []runtime.Object{ + &configv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{ + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{ + InfrastructureTopology: configv1.HighlyAvailableTopologyMode, + }, + }}, + expectErr: false, + expectedReplicas: 2, + expectPDB: true, + podIdentityType: AwsPodIdentity{}, + }, + { + name: "Cluster infrastructure object has no infrastructure topology set", + existing: []runtime.Object{ + &configv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{ + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{}, + }}, + expectErr: false, + expectedReplicas: 2, + expectPDB: true, + podIdentityType: AwsPodIdentity{}, + }, + { + name: "Cluster infrastructure object doesn't exist", + expectErr: true, + podIdentityType: AwsPodIdentity{}, + }, + { + name: "Azure platform", + existing: []runtime.Object{ + &configv1.Infrastructure{ + ObjectMeta: v1.ObjectMeta{ + Name: "cluster", + }, + Status: configv1.InfrastructureStatus{ + InfrastructureTopology: configv1.HighlyAvailableTopologyMode, + }, + }}, + expectErr: false, + expectedReplicas: 2, + expectPDB: true, + podIdentityType: AzurePodIdentity{}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + logger := log.WithField("controller", "podidentitywebhookcontrollertest") + fakeClient := fake.NewClientBuilder().WithRuntimeObjects(test.existing...).Build() + fakeClientset := fakeclientgo.NewSimpleClientset() + r := &staticResourceReconciler{ + client: fakeClient, + clientset: fakeClientset, + logger: logger, + eventRecorder: events.NewInMemoryRecorder(""), + cache: resourceapply.NewResourceCache(), + conditions: []configv1.ClusterOperatorStatusCondition{}, + imagePullSpec: test.podIdentityType.GetImagePullSpec(), + podIdentityType: test.podIdentityType, + } + + _, err := r.Reconcile(context.TODO(), reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: "testName", + Namespace: "testNamespace", + }, + }) + switch { + + } + + if err != nil && !test.expectErr { + require.NoError(t, err, "Unexpected error: %v", err) + } + if err == nil && test.expectErr { + t.Errorf("Expected error but got none") + } + + if !test.expectErr { + expectedImage := fmt.Sprintf("%s_identity_image", test.podIdentityType.Name()) + assert.Equal(t, r.imagePullSpec, expectedImage) + + podIdentityWebhookDeployment, err := getDeployment(fakeClientset, "pod-identity-webhook", "openshift-cloud-credential-operator") + assert.Nil(t, err) + assert.NotNil(t, podIdentityWebhookDeployment, "did not find expected pod-identity-webhook Deployment") + + if test.expectedReplicas != 0 { + assert.Equal(t, *podIdentityWebhookDeployment.Spec.Replicas, test.expectedReplicas, "found unexpected pod-identity-webhook deployment replicas") + } + + assert.Equal(t, podIdentityWebhookDeployment.Spec.Template.Spec.Containers[0].Image, expectedImage, "container image matches expected one") + + podDisruptionBudget, err := getPDB(fakeClientset, "pod-identity-webhook", "openshift-cloud-credential-operator") + if test.expectPDB { + assert.Nil(t, err) + assert.NotNil(t, podDisruptionBudget, "did not find expected pod-identity-webhook PodDisruptionBudget") + } else { + assert.Nil(t, podDisruptionBudget, "found unexpected pod-identity-webhook PodDisruptionBudget") + } + + webhook, err := getWebhook(fakeClientset, "pod-identity-webhook") + assert.NotNil(t, webhook, "did not find expected pod-identity-webhook webhook config") + assert.Contains(t, webhook.Webhooks[0].Name, test.podIdentityType.Name()) + } + }) + } +} + +func TestPodIdentityShouldDeploy(t *testing.T) { + schemeutils.SetupScheme(scheme.Scheme) + + azureCredsSecretName := "azure-credentials" + tests := []struct { + name string + existing []runtime.Object + expectedResult bool + expectErr bool + podIdentityType PodIdentityManifestSource + }{{ + name: "Azure secret with credentials exists", + existing: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: azureCredsSecretName, + Namespace: operatorNamespace, + }, + Data: map[string][]byte{ + azureTenantIdKey: {}, + }, + }}, + podIdentityType: AzurePodIdentity{}, + expectedResult: true, + expectErr: false, + }, + { + name: "Azure secret with credentials without tenant id", + existing: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: azureCredsSecretName, + Namespace: operatorNamespace, + }, + Data: map[string][]byte{}, + }}, + podIdentityType: AzurePodIdentity{}, + expectedResult: false, + expectErr: false, + }, + { + name: "Azure no secret with credentials", + existing: []runtime.Object{}, + podIdentityType: AzurePodIdentity{}, + expectedResult: false, + expectErr: false, + }, + { + name: "Azure fails to get secret and error is not NotFound", + existing: []runtime.Object{ + &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: azureCredsSecretName, + Namespace: operatorNamespace, + }, + Data: map[string][]byte{}, + }}, + podIdentityType: AzurePodIdentity{}, + expectedResult: false, + expectErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + fakeClientset := fakeclientgo.NewSimpleClientset(test.existing...) + if test.expectErr { + fakeClientset.PrependReactor("get", "secrets", func(action clientgotesting.Action) (handled bool, ret runtime.Object, err error) { + return true, &corev1.Secret{}, fmt.Errorf("error getting secret") + }) + } + + result, err := test.podIdentityType.ShouldBeDeployed(context.TODO(), fakeClientset, operatorNamespace) + if err != nil && !test.expectErr { + require.NoError(t, err, "Unexpected error: %v", err) + } + if err == nil && test.expectErr { + t.Errorf("Expected error but got none") + } + assert.Equal(t, result, test.expectedResult) + }) + } +}