fix(mariadb): clean up orphaned operator-generated secrets (#3056) - #3071
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses an issue where operator-generated secrets for MariaDB applications persist after a Helm uninstall. By enhancing the existing post-delete cleanup hook and updating the associated RBAC roles, the system now ensures these orphaned resources are properly garbage-collected. Additionally, new unit tests have been introduced to maintain this behavior and ensure future reliability. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request adds a test target to the MariaDB package Makefile, introduces cleanup of operator-generated secrets to the post-delete cleanup hook, and adds corresponding unit tests. The review feedback suggests restricting the RBAC permissions granted to the cleanup hook by using resourceNames and limiting the verbs to get and delete for secrets to adhere to the principle of least privilege, and updating the unit tests accordingly.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| - apiGroups: [""] | ||
| resources: ["persistentvolumeclaims"] | ||
| resources: ["persistentvolumeclaims", "secrets"] | ||
| verbs: ["get", "list", "delete"] |
There was a problem hiding this comment.
Granting namespace-wide get, list, and delete permissions on all secrets is overly permissive and violates the principle of least privilege. Since the cleanup job only deletes two specific operator-generated secrets by name, we should restrict the RBAC role using resourceNames and limit the verbs to get and delete (removing list).
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
- apiGroups: [""]
resources: ["secrets"]
resourceNames:
- {{ .Release.Name }}-metrics-password
- {{ .Release.Name }}-repl-password
verbs: ["get", "delete"]References
- Flag overly broad RBAC permissions. Granting wildcard or full namespace-wide 'get', 'list', and 'delete' access to all secrets is overly permissive. It should be restricted to specific resource names using 'resourceNames' and only the necessary verbs ('get', 'delete'). (link)
| - contains: | ||
| path: rules | ||
| content: | ||
| apiGroups: [""] | ||
| resources: ["persistentvolumeclaims", "secrets"] | ||
| verbs: ["get", "list", "delete"] |
There was a problem hiding this comment.
Update the unit test assertions to match the restricted RBAC rules for secrets.
- contains:
path: rules
content:
apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "delete"]
- contains:
path: rules
content:
apiGroups: [""]
resources: ["secrets"]
resourceNames:
- mariadb-test-metrics-password
- mariadb-test-repl-password
verbs: ["get", "delete"]|
Caution Review failedAn error occurred during the review process. Please try again later. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Caution Review failedAn error occurred during the review process. Please try again later. 📝 WalkthroughWalkthroughThe MariaDB cleanup hook now deletes the generated metrics-password and repl-password Secrets alongside orphaned PVCs. The chart also adds RBAC for those Secrets, plus helm-unittest coverage and a Makefile test target. ChangesMariaDB cleanup and validation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
354ed21 to
b7c06bc
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml`:
- Around line 42-50: The cleanup hook in the PVC/Secret deletion script is
masking real failures because the `kubectl delete` commands are followed by `||
echo ...`, which still allows the hook to succeed even when RBAC or API errors
occur. Update the cleanup logic in the hook script so `cleanup-pvc.yaml` still
warns on missing resources via `--ignore-not-found` but only treats actual
delete failures as failures; keep the warning messages, and make sure the Job
exits non-zero when PVC or operator-generated Secret deletion fails in the
cleanup block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 34b5fe76-e4e9-412c-8b7e-26746448744c
📒 Files selected for processing (3)
packages/apps/mariadb/Makefilepackages/apps/mariadb/templates/hooks/cleanup-pvc.yamlpackages/apps/mariadb/tests/cleanup_hook_test.yaml
The mariadb-operator creates <release>-metrics-password and <release>-repl-password Secrets as a side effect of metrics/replication being enabled. These are not Helm-owned and survive `helm uninstall`, leaving orphaned Secrets in the tenant namespace after the app CR is deleted. Extend the existing post-delete cleanup hook to sweep them: grant the cleanup Role RBAC on secrets and delete both password Secrets by name. Add a helm-unittest suite locking in the behaviour (and a `test` Makefile target so the package's tests run in CI). Signed-off-by: Алексей Артамонов <aleksei.artamonov@aenix.io>
b7c06bc to
7ef2b2f
Compare
IvanHunters
left a comment
There was a problem hiding this comment.
Verdict
NOT LGTM
The hook closes two of (likely) three operator-generated Secrets, and the soft-fail pattern hides the failure mode the hook is meant to eliminate — fix both before merging rather than as follow-ups.
Findings
[MAJOR] packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml:42-56 — Incomplete fix: <release>-metrics-config likely orphaned by the same mechanism.
Reading mariadb-operator upstream, <release>-metrics-config is constructed via the same SecretReconciler codepath (api/v1alpha1/mariadb_keys.go: "%s-metrics-config", callsite internal/controller/mariadb_controller_metrics.go:reconcileExporterConfig) as <release>-metrics-password. Upstream sets Owner: mariadb for all three (password, repl-password, metrics-config), yet #3056 empirically observed the OwnerReference path failing for the password Secrets — there is no plausible reason metrics-config would behave differently in a production cluster. The original repro grep -E 'metrics-password|repl-password' was scoped narrowly and would have missed it. Re-run the create→delete leak sweep on a live cluster with kubectl get secret -n <ns> (no grep) and, if <release>-metrics-config survives, extend resourceNames in the Role and the kubectl delete secret line in the hook before this lands. Tightening RBAC and the cleanup Job together is much easier than bolting it on in a second PR after the partial fix is already in customer clusters.
[MAJOR] packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml:50-56 — Soft-fail makes the post-delete hook lie about success.
Both kubectl delete secret invocations end in || echo "WARNING: ... " >&2, and echo always exits 0. Combined with helm.sh/hook-delete-policy: hook-succeeded, the hook Job reports success and is immediately garbage-collected even when the cleanup itself failed (apiserver outage mid-uninstall, RBAC denial on a Secret name not in resourceNames, network blip). The orphan Secrets the PR is supposed to prevent then survive, but with the operator now believing the cleanup ran cleanly — the failure mode is silent. The pre-existing || true on PVCs has the same problem, but extending the pattern to Secrets compounds it. Either drop the suppression and let the Job fail (Helm surfaces post-delete failures, an operator at least gets a signal), or replace it with kubectl get secret <name> -o name && kubectl delete secret <name> per-Secret so "not found" is the only swallowed case.
Operational risks
- The Role pins
resourceNamesto<release>-metrics-password/<release>-repl-password, matching the post-UPGRADE_25.10.0mariadb-operator naming. The vendored operator is currentlyappVersion: 25.10.2. A future bump that returns to (or introduces) different naming silently neutralises the cleanup: the Role no longer permits access to the renamed Secret, the soft-fail above swallows the RBAC error, and orphans return without any signal. Either pre-populateresourceNameswith both naming schemes the operator has shipped, or move this check into thepackages/system/mariadb-operatorbump checklist explicitly.
Recommended follow-ups
- Apply the same audit to sibling operator-backed app charts (
packages/apps/postgres,packages/apps/kafka,packages/apps/redis, …) where operators likely generate similar side-effect Secrets that survivehelm uninstall. - If a future maintainer concludes the soft-fail behaviour is correct after all, leave a one-line comment on the
|| echo WARNINGredirect so the next reader does not "improve" it by removing the suppression.
The post-delete cleanup hook used `kubectl delete ... --ignore-not-found || echo WARNING`, which swallowed genuine RBAC/API failures and let the Job succeed with orphaned password Secrets still present. Track delete failures and propagate them via a non-zero exit, while --ignore-not-found still tolerates already-absent resources. Add a helm-unittest assertion locking in the non-zero exit-on-failure behaviour. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/apps/mariadb/tests/cleanup_hook_test.yaml`:
- Around line 51-64: The RBAC test in cleanup_hook_test.yaml is too permissive
because the current `contains` assertion on `rules` can still pass if another
broader `secrets` rule is present. Update the check around the `Role` assertion
to either require an exact match for the rendered `rules` entry or add a
negative assertion ensuring no additional `secrets` rules exist beyond the one
with `resourceNames` for `mariadb-test-metrics-password` and
`mariadb-test-repl-password`.
- Around line 27-37: The cleanup test only verifies the final exit in the Job
command, so it can miss regressions where the failure flag is never set. Update
the cleanup hook assertions in cleanup_hook_test.yaml to also match the PVC and
Secret delete failure branches in the Job command generated by the cleanup
logic, using the same command path currently checked, so both delete paths are
asserted to set cleanup_failed before the final exit.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: d0efd265-30f3-41ea-844d-fb7b8034d8cf
📒 Files selected for processing (3)
packages/apps/mariadb/Makefilepackages/apps/mariadb/templates/hooks/cleanup-pvc.yamlpackages/apps/mariadb/tests/cleanup_hook_test.yaml
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/apps/mariadb/templates/hooks/cleanup-pvc.yaml
…le set Strengthen the cleanup hook suite per review: - assert the PVC and Secret delete branches each set cleanup_failed=1, not just the final exit, so a regression in either branch is caught; - pin the Role to exactly two rules so a broader unscoped secrets rule can't slip past the subset `contains` check. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Алексей Артамонов <alexeyartamonov1987@gmail.com>
IvanHunters
left a comment
There was a problem hiding this comment.
Verification of the prior reviewer's blocker
Claim under test
mariadb-operator generates
<release>-metrics-config(ConfigMap/Secret with exporter'smy.cnf) as a side-effect ofmetrics.enabled=true, this PR misses cleaning it up, so it ALSO surviveshelm uninstall.
Result: claim is false. The Secret exists, but it is owner-referenced to the MariaDB CR and garbage-collected automatically.
Evidence (upstream mariadb-operator on main, paths and code excerpts)
1. The third resource does exist. internal/controller/mariadb_controller_metrics.go reconcileMetrics() calls four sub-reconciles in order: reconcileMetricsPassword, reconcileAuth, reconcileExporterConfig, reconcileExporterDeployment, reconcileExporterService, reconcileServiceMonitor. The third one creates a Secret holding the exporter's [client] user=… password=… (and TLS paths if enabled).
2. The naming pattern is <mariadb-name>-metrics-config (key exporter.cnf), from api/v1alpha1/mariadb_keys.go:
func (m *MariaDB) MetricsConfigSecretKeyRef() GeneratedSecretKeyRef {
return GeneratedSecretKeyRef{
SecretKeySelector: SecretKeySelector{
LocalObjectReference: LocalObjectReference{
Name: fmt.Sprintf("%s-metrics-config", m.Name),
},
Key: "exporter.cnf",
},
Generate: true,
}
}3. Critical: the code path that creates -metrics-config DOES set a controller reference back to the MariaDB CR. Two cooperating files:
internal/controller/mariadb_controller_metrics.go::reconcileExporterConfig:
secretReq := secret.SecretRequest{
Owner: mariadb,
Metadata: ...,
Key: ...,
Data: map[string][]byte{ secretKeyRef.Key: buf.Bytes() },
}
return r.SecretReconciler.Reconcile(ctx, &secretReq)pkg/controller/secret/controller.go::Reconcile:
secret, err := r.Builder.BuildSecret(secretOpts, req.Owner)pkg/builder/secret_builder.go::BuildSecret:
if owner != nil {
if err := controllerutil.SetControllerReference(owner, secret, b.scheme); err != nil {
return nil, fmt.Errorf("error setting controller reference to Secret: %v", err)
}
}So -metrics-config is created with metadata.ownerReferences[].controller=true pointing at the MariaDB CR. When helm uninstall deletes the MariaDB CR (which IS Helm-owned via the templates/mariadb.yaml template), Kubernetes garbage collection cascades to -metrics-config. No orphan, no work for the cleanup hook.
4. Compare to the path that DOES orphan (the two Secrets the PR targets): Both -metrics-password and -repl-password go through SecretReconciler.ReconcilePassword, which contains this anti-ownerRef shortcut:
var owner = req.Owner
if req.Generate {
owner = nil
}
secret, err := r.Builder.BuildSecret(opts, owner)Both Secrets are created with Generate: true, so the operator deliberately strips the owner before BuildSecret. These are the real orphans, and the PR sweeps exactly them.
Adjacent operator-generated artifacts probed
| Resource | How produced in the cozystack chart | Owner reference? | Orphan risk? |
|---|---|---|---|
<release>-credentials (root, user passwords) |
Helm template (templates/secret.yaml) |
Helm-managed | None |
<release>-metrics-config |
Operator via SecretReconciler.Reconcile, Owner=mariadb |
Yes, MariaDB CR | None, k8s GC |
<release>-metrics-password |
Operator via ReconcilePassword, Generate=true so owner nil-ed |
No | YES, covered by PR |
<release>-repl-password |
Operator via ReconcilePassword, Generate=true |
No | YES, covered by PR |
| Internal root password Secret | Operator via Reconcile() with Owner=mariadb |
Yes | None, k8s GC |
<release>-backup |
Helm template (templates/backup-secret.yaml) |
Helm-managed | None |
ServiceMonitor for metrics |
Operator-generated, owner reference to MariaDB CR | Yes | None, k8s GC |
The set {-metrics-password, -repl-password} is exhaustive given the chart's value flags.
Verdict
LGTM. The PR addresses the complete orphan set (-metrics-password, -repl-password); the -metrics-config Secret is owner-referenced to the MariaDB CR upstream and is cleaned up by Kubernetes garbage collection, so it does not need to be in the hook.
Backport of #3344 onto release-1.5. The bot's cherry-pick imported hack/e2e-chainsaw/mariadb/chainsaw-test.yaml whole, because release-1.5 has no such file: the app suite here is still BATS, and the Chainsaw port landed with #2826 on main only. That file is dropped and its widening applied to the gate that actually runs on this branch instead. That gate needed the change more than the chainsaw one did. In hack/e2e-apps/mariadb.bats both tests wait 80s for an endpoint address to appear, and an address appears only once a replica has cleared its startup then readiness probe. This commit raises the startup budget to 310s, so an 80s ceiling would fail runs the probe was still willing to wait for -- importing the chainsaw file and leaving the BATS suite alone would have turned the fix into an e2e failure. Both waits go to 600s, matching the 10m the upstream chainsaw assert uses and for the same reason. packages/apps/mariadb/Makefile gains the `test:` target. It is absent on release-1.5 -- it arrived with #3071, which was not backported -- and hack/helm-unit-tests.sh discovers suites by probing for that target, so without it the backported tests/startup_probe_test.yaml would never be executed by `make unit-tests`. Verified locally: 4 assertions in tests/startup_probe_test.yaml pass. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> (cherry picked from commit a37715062e5da24e54956f075dd2d6417c4dff2e)
What this PR does
After deleting a
MariaDBapp CR with metrics and/or replication enabled, twoSecrets created by the mariadb-operator were left orphaned in the tenant
namespace:
<release>-metrics-password<release>-repl-passwordThese are generated by the operator (
k8s.mariadb.com/v1alpha1) as a sideeffect of
metrics.enabled/replication.enabled, so they are notHelm-owned and survive
helm uninstall. The existing post-delete hook(
templates/hooks/cleanup-pvc.yaml) only swept PVCs, leaving the Secrets behind.This PR extends that hook to also clean them up:
RoleRBAC onsecrets;--ignore-not-found;helm-unittestsuite locking in the behaviour, plus atestMakefiletarget so the package's tests actually run in CI (
hack/helm-unit-tests.shonly runs packages that define a
testtarget).Fixes #3056.
Release note
Summary by CodeRabbit
testtarget for the MariaDB chart.