diff --git a/packages/apps/mariadb/Makefile b/packages/apps/mariadb/Makefile index eb12cf44a..58eb92744 100644 --- a/packages/apps/mariadb/Makefile +++ b/packages/apps/mariadb/Makefile @@ -11,6 +11,10 @@ update: hack/update-versions.sh make generate +.PHONY: test +test: + helm unittest . + image: docker buildx build images/mariadb-backup \ $(call image-tags,mariadb-backup,$(MARIADB_BACKUP_TAG)) \ diff --git a/packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml b/packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml index e3fd5c32c..62ed50b93 100644 --- a/packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml +++ b/packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml @@ -18,16 +18,49 @@ spec: spec: serviceAccountName: {{ .Release.Name }}-cleanup restartPolicy: Never + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: + type: RuntimeDefault containers: - name: cleanup image: docker.io/clastix/kubectl:v1.32 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: ["ALL"] + env: + - name: HOME + value: /tmp + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi command: - /bin/sh - -c - | echo "Deleting orphaned PVCs for {{ .Release.Name }}..." - kubectl delete pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} || true + kubectl delete pvc -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} --ignore-not-found \ + || echo "WARNING: PVC cleanup failed for {{ .Release.Name }}; orphaned PVCs may remain" >&2 echo "PVC cleanup complete." + echo "Deleting orphaned operator-generated Secrets for {{ .Release.Name }}..." + kubectl delete secret -n {{ .Release.Namespace }} \ + {{ .Release.Name }}-metrics-password {{ .Release.Name }}-repl-password --ignore-not-found \ + || echo "WARNING: Secret cleanup failed for {{ .Release.Name }}; orphaned Secrets may remain" >&2 + echo "Secret cleanup complete." + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} --- apiVersion: v1 kind: ServiceAccount @@ -51,9 +84,18 @@ metadata: "helm.sh/hook-weight": "5" "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded rules: + # PVCs are deleted by label selector, which requires list. - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "delete"] + # Operator-generated Secrets are deleted by name, so scope the rule to + # exactly those two resources (least privilege — no namespace-wide secret access). + - apiGroups: [""] + resources: ["secrets"] + resourceNames: + - {{ .Release.Name }}-metrics-password + - {{ .Release.Name }}-repl-password + verbs: ["get", "delete"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/packages/apps/mariadb/tests/cleanup_hook_test.yaml b/packages/apps/mariadb/tests/cleanup_hook_test.yaml new file mode 100644 index 000000000..d12918700 --- /dev/null +++ b/packages/apps/mariadb/tests/cleanup_hook_test.yaml @@ -0,0 +1,52 @@ +suite: post-delete cleanup hook + +# Locks in the post-delete cleanup behaviour. The mariadb-operator creates +# -metrics-password and -repl-password Secrets as a side +# effect of metrics/replication being enabled. These Secrets are not +# Helm-owned and survive `helm uninstall`, so the post-delete hook must +# sweep them (issue #3056) alongside the orphaned PVCs. The hook Role also +# needs RBAC on secrets to do so. + +templates: + - templates/hooks/cleanup-pvc.yaml + +release: + name: mariadb-test + namespace: tenant-test + +tests: + - it: "cleanup Job deletes the operator-generated password Secrets by name" + documentSelector: + path: kind + value: Job + asserts: + - matchRegex: + path: spec.template.spec.containers[0].command[2] + pattern: "mariadb-test-metrics-password mariadb-test-repl-password --ignore-not-found" + + - it: "cleanup Role grants get/list/delete on persistentvolumeclaims" + documentSelector: + path: kind + value: Role + asserts: + - contains: + path: rules + content: + apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "delete"] + + - it: "cleanup Role scopes secret access to the two named secrets only (least privilege)" + documentSelector: + path: kind + value: Role + asserts: + - contains: + path: rules + content: + apiGroups: [""] + resources: ["secrets"] + resourceNames: + - mariadb-test-metrics-password + - mariadb-test-repl-password + verbs: ["get", "delete"]