From 7e4116438ffb7b97c94739eb55ee001e106e74c0 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Mon, 22 Jun 2026 11:51:09 +0200 Subject: [PATCH 1/6] fix: replace autodelete ClusterRoleBinding+default SA with scoped RoleBinding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The autodelete CronJob was created without a dedicated ServiceAccount, falling back to `default`. The operator then bound `default` to the cluster-wide `azimuth-caas-operator:edit` ClusterRole, which granted every workload sharing that ServiceAccount in the namespace the ability to manage CaaS resources. Changes in create_scheduled_delete_job: - Create a dedicated ServiceAccount `autodelete-` in the tenant namespace with an ownerReference on the Cluster object so it is garbage-collected automatically on cluster deletion. - Replace the ClusterRoleBinding (cluster-scoped, `default` SA) with a namespaced RoleBinding targeting the new SA. A RoleBinding that references a ClusterRole scopes its permissions to the namespace. - Inject serviceAccountName into the CronJob pod spec. - Add pod-level securityContext (runAsNonRoot, runAsUser 65534, seccompProfile RuntimeDefault) and container-level securityContext (allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop ALL). - Reorder resource creation: ConfigMap → ServiceAccount → RoleBinding → CronJob, so the CronJob always starts with its identity and permissions in place. --- azimuth_caas_operator/utils/cluster.py | 72 +++++++++++++++++++------- 1 file changed, 52 insertions(+), 20 deletions(-) diff --git a/azimuth_caas_operator/utils/cluster.py b/azimuth_caas_operator/utils/cluster.py index b0eaa982..be1ed9a2 100644 --- a/azimuth_caas_operator/utils/cluster.py +++ b/azimuth_caas_operator/utils/cluster.py @@ -171,6 +171,36 @@ async def create_scheduled_delete_job(client, name, namespace, uid, lifetime_hou default_field_manager="autodelete", default_namespace="{namespace}") cluster_resource = client.api("{registry.API_VERSION}").resource("cluster") cluster_resource.delete("{name}", propagation_policy="Foreground") +""" + sa_yaml = f"""apiVersion: v1 +kind: ServiceAccount +metadata: + name: autodelete-{name} + namespace: {namespace} + ownerReferences: + - apiVersion: {registry.API_VERSION} + kind: Cluster + name: "{name}" + uid: "{uid}" +""" + role_binding_yaml = f"""apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: autodelete-{name} + namespace: {namespace} + ownerReferences: + - apiVersion: {registry.API_VERSION} + kind: Cluster + name: "{name}" + uid: "{uid}" +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: azimuth-caas-operator:edit +subjects: +- kind: ServiceAccount + name: autodelete-{name} + namespace: {namespace} """ job_yaml = f"""apiVersion: batch/v1 kind: CronJob @@ -187,6 +217,13 @@ async def create_scheduled_delete_job(client, name, namespace, uid, lifetime_hou spec: template: spec: + serviceAccountName: autodelete-{name} + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: + type: RuntimeDefault containers: - name: delete image: "{image}" @@ -194,6 +231,11 @@ async def create_scheduled_delete_job(client, name, namespace, uid, lifetime_hou args: - "-c" - "python3 /delete.py" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] volumeMounts: - name: python-delete mountPath: /delete.py @@ -209,32 +251,22 @@ async def create_scheduled_delete_job(client, name, namespace, uid, lifetime_hou configmap_resource = await client.api("v1").resource("ConfigMap") await configmap_resource.create(configmap_data, namespace=namespace) - job_data = yaml.safe_load(job_yaml) - job_resource = await client.api("batch/v1").resource("CronJob") - await job_resource.create(job_data, namespace=namespace) + sa_data = yaml.safe_load(sa_yaml) + sa_resource = await client.api("v1").resource("ServiceAccount") + await sa_resource.create_or_patch(f"autodelete-{name}", sa_data, namespace=namespace) - # ensure above cron job can delete the cluster - role_binding_yaml = f"""apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: azimuth-caas-operator-edit-{namespace} -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: azimuth-caas-operator:edit -subjects: -- kind: ServiceAccount - name: default - namespace: {namespace} -""" role_binding_data = yaml.safe_load(role_binding_yaml) role_binding_resource = await client.api("rbac.authorization.k8s.io/v1").resource( - "ClusterRoleBinding" + "RoleBinding" ) - # TODO(johngarbutt): really just need to ensure its present await role_binding_resource.create_or_patch( - f"azimuth-caas-operator-edit-{namespace}", + f"autodelete-{name}", role_binding_data, namespace=namespace, ) + + job_data = yaml.safe_load(job_yaml) + job_resource = await client.api("batch/v1").resource("CronJob") + await job_resource.create(job_data, namespace=namespace) + LOG.info(f"Scheduled cluster auto delete for cluster: {name} in: {namespace}") From 5c95705a9a5dd1cca46661344c34703c1ac2e175 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Mon, 22 Jun 2026 11:58:57 +0200 Subject: [PATCH 2/6] ci: fix tox syntax check Signed-off-by: Mathieu Grzybek --- azimuth_caas_operator/utils/cluster.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/azimuth_caas_operator/utils/cluster.py b/azimuth_caas_operator/utils/cluster.py index be1ed9a2..1cc709c5 100644 --- a/azimuth_caas_operator/utils/cluster.py +++ b/azimuth_caas_operator/utils/cluster.py @@ -253,7 +253,9 @@ async def create_scheduled_delete_job(client, name, namespace, uid, lifetime_hou sa_data = yaml.safe_load(sa_yaml) sa_resource = await client.api("v1").resource("ServiceAccount") - await sa_resource.create_or_patch(f"autodelete-{name}", sa_data, namespace=namespace) + await sa_resource.create_or_patch( + f"autodelete-{name}", sa_data, namespace=namespace + ) role_binding_data = yaml.safe_load(role_binding_yaml) role_binding_resource = await client.api("rbac.authorization.k8s.io/v1").resource( From 09455793ede6a4106a88e1830f5e5b41e1fac1b6 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Mon, 22 Jun 2026 12:16:34 +0200 Subject: [PATCH 3/6] fix: add CRUD verbs Signed-off-by: Mathieu Grzybek --- charts/operator/templates/clusterrole-operator.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/charts/operator/templates/clusterrole-operator.yaml b/charts/operator/templates/clusterrole-operator.yaml index c6af05f9..c8265501 100644 --- a/charts/operator/templates/clusterrole-operator.yaml +++ b/charts/operator/templates/clusterrole-operator.yaml @@ -51,9 +51,8 @@ rules: verbs: ["*"] - apiGroups: ["rbac.authorization.k8s.io"] resources: - - clusterrolebindings - rolebindings - verbs: ["*"] + verbs: ["get", "create", "patch", "delete"] # Required so that the CaaS operator can grant permissions on leases for the TF plugin - apiGroups: [coordination.k8s.io] resources: [leases] From da3b4224d31420c337377162fdcd556590411a66 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Tue, 30 Jun 2026 11:00:03 +0200 Subject: [PATCH 4/6] fix: bump chart version to 0.1.1 --- charts/operator/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/operator/Chart.yaml b/charts/operator/Chart.yaml index 1cdbe9cc..614b75a7 100644 --- a/charts/operator/Chart.yaml +++ b/charts/operator/Chart.yaml @@ -3,7 +3,7 @@ name: azimuth-caas-operator description: Helm chart for deploying the Azimuth Cluster-as-a-Service operator. type: application # The version and appVersion are set by the CI script -version: 0.1.0 +version: 0.1.1 appVersion: "main" dependencies: From 6511362f6e0002063d881bade9c15d38c56c93c9 Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Tue, 30 Jun 2026 11:25:45 +0200 Subject: [PATCH 5/6] fix: set maintainers --- charts/operator/Chart.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/charts/operator/Chart.yaml b/charts/operator/Chart.yaml index 614b75a7..29ee20b4 100644 --- a/charts/operator/Chart.yaml +++ b/charts/operator/Chart.yaml @@ -6,6 +6,10 @@ type: application version: 0.1.1 appVersion: "main" +maintainers: + - name: azimuth-cloud + url: https://github.com/azimuth-cloud + dependencies: - name: ara version: ">=0-0" From a030ec88dfddcd300e77182a15938fb8b88f64cc Mon Sep 17 00:00:00 2001 From: Mathieu Grzybek Date: Tue, 30 Jun 2026 11:29:14 +0200 Subject: [PATCH 6/6] fix: update snapshots --- .../__snapshot__/snapshot_test.yaml.snap | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/charts/operator/tests/__snapshot__/snapshot_test.yaml.snap b/charts/operator/tests/__snapshot__/snapshot_test.yaml.snap index 7f35c704..79021fb1 100644 --- a/charts/operator/tests/__snapshot__/snapshot_test.yaml.snap +++ b/charts/operator/tests/__snapshot__/snapshot_test.yaml.snap @@ -121,7 +121,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 rbac.authorization.k8s.io/aggregate-to-admin: "true" rbac.authorization.k8s.io/aggregate-to-edit: "true" rbac.authorization.k8s.io/aggregate-to-view: "true" @@ -142,7 +142,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator:controller rules: - apiGroups: @@ -213,10 +213,12 @@ templated manifests should match snapshot: - apiGroups: - rbac.authorization.k8s.io resources: - - clusterrolebindings - rolebindings verbs: - - '*' + - get + - create + - patch + - delete - apiGroups: - coordination.k8s.io resources: @@ -234,7 +236,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator:tfstate rules: - apiGroups: @@ -263,7 +265,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 rbac.authorization.k8s.io/aggregate-to-view: "true" name: release-name-azimuth-caas-operator:view rules: @@ -284,7 +286,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator roleRef: apiGroup: rbac.authorization.k8s.io @@ -303,7 +305,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator spec: replicas: 1 @@ -369,7 +371,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator-extravars stringData: extravars: | @@ -383,7 +385,7 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator spec: ports: @@ -404,5 +406,5 @@ templated manifests should match snapshot: app.kubernetes.io/managed-by: Helm app.kubernetes.io/name: azimuth-caas-operator app.kubernetes.io/version: main - helm.sh/chart: azimuth-caas-operator-0.1.0 + helm.sh/chart: azimuth-caas-operator-0.1.1 name: release-name-azimuth-caas-operator