Repository navigation
fix(bucket): clean up orphaned cert-manager ACME solver resources on delete (#3060) - #3076
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 cert-manager ACME solver resources remain in the cluster after a Bucket application is deleted. By implementing a post-delete cleanup hook, the system now ensures that all associated certificates, orders, challenges, and any leftover solver Ingresses, Services, or Pods are properly reaped, preventing resource leakage in the tenant namespace. 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
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdds a post-delete Helm hook for Bucket that deletes cert-manager ACME cleanup resources, plus a dedicated service account, RBAC, and Helm unittest coverage. Also adds a ChangesPost-delete ACME cleanup
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 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 |
There was a problem hiding this comment.
Code Review
This pull request introduces a post-delete Helm hook to clean up orphaned cert-manager ACME resources for the bucket application, along with corresponding unit tests and a Makefile test target. The reviewer feedback highlights a critical issue where a blanket sweep of all http01-solver resources in the namespace could disrupt active ACME challenges for other applications sharing the same namespace. To address this, the reviewer suggests removing the unsafe sweep, narrowing the RBAC permissions 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.
| kubectl delete certificate {{ .Release.Name }}-ui-tls -n {{ .Release.Namespace }} --ignore-not-found \ | ||
| || echo "WARNING: cert-manager cleanup failed for {{ .Release.Name }}; orphaned ACME resources may remain" >&2 | ||
| kubectl delete certificate,order,challenge -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} --ignore-not-found \ | ||
| || echo "WARNING: cert-manager cleanup failed for {{ .Release.Name }}; orphaned ACME resources may remain" >&2 | ||
| # Backstop sweep: if the challenge never completed there may be no | ||
| # live owner to cascade the deletion, so remove any leftover | ||
| # http01 solver Ingress/Service/Pod directly. This label is set by | ||
| # cert-manager on every solver object and is the reliable selector. | ||
| echo "Sweeping leftover ACME http01-solver objects..." | ||
| kubectl delete ingress,svc,pod -n {{ .Release.Namespace }} -l acme.cert-manager.io/http01-solver=true --ignore-not-found \ | ||
| || echo "WARNING: ACME solver sweep failed for {{ .Release.Name }}; orphaned solver objects may remain" >&2 | ||
| echo "ACME cleanup complete." |
There was a problem hiding this comment.
Sweeping all resources with the label acme.cert-manager.io/http01-solver=true in the namespace is highly dangerous. In Cozystack, multiple apps can share the same tenant namespace. Running a blanket delete on all solver resources will disrupt and fail active ACME challenges for other apps in the same namespace. Deleting the Certificate, Order, and Challenge resources is sufficient, as cert-manager will automatically cascade-delete their associated solver resources safely.
kubectl delete certificate {{ .Release.Name }}-ui-tls -n {{ .Release.Namespace }} --ignore-not-found \
|| echo "WARNING: cert-manager cleanup failed for {{ .Release.Name }}; orphaned ACME resources may remain" >&2
kubectl delete certificate,order,challenge -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }} --ignore-not-found \
|| echo "WARNING: cert-manager cleanup failed for {{ .Release.Name }}; orphaned ACME resources may remain" >&2
echo "ACME cleanup complete."| rules: | ||
| - apiGroups: ["cert-manager.io"] | ||
| resources: ["certificates"] | ||
| verbs: ["get", "list", "delete"] | ||
| - apiGroups: ["acme.cert-manager.io"] | ||
| resources: ["orders", "challenges"] | ||
| verbs: ["get", "list", "delete"] | ||
| - apiGroups: ["networking.k8s.io"] | ||
| resources: ["ingresses"] | ||
| verbs: ["get", "list", "delete"] | ||
| - apiGroups: [""] | ||
| resources: ["services", "pods"] | ||
| verbs: ["get", "list", "delete"] |
There was a problem hiding this comment.
Since the blanket sweep of ingresses, services, and pods is removed, we no longer need to grant delete (or any other) permissions on these core resources. Removing these rules adheres to the principle of least privilege and hardens the cleanup Job's RBAC footprint.
rules:
- apiGroups: ["cert-manager.io"]
resources: ["certificates"]
verbs: ["get", "list", "delete"]
- apiGroups: ["acme.cert-manager.io"]
resources: ["orders", "challenges"]
verbs: ["get", "list", "delete"]| - it: deletes cert-manager resources and sweeps http01-solver leftovers | ||
| documentIndex: 0 | ||
| asserts: | ||
| - matchRegex: | ||
| path: spec.template.spec.containers[0].command[2] | ||
| pattern: "kubectl delete certificate my-bucket-ui-tls -n tenant-root" | ||
| - matchRegex: | ||
| path: spec.template.spec.containers[0].command[2] | ||
| pattern: "kubectl delete certificate,order,challenge -n tenant-root -l app.kubernetes.io/instance=my-bucket" | ||
| - matchRegex: | ||
| path: spec.template.spec.containers[0].command[2] | ||
| pattern: "kubectl delete ingress,svc,pod -n tenant-root -l acme.cert-manager.io/http01-solver=true" |
There was a problem hiding this comment.
Update the test assertions to match the removal of the unsafe backstop sweep command.
- it: deletes cert-manager resources
documentIndex: 0
asserts:
- matchRegex:
path: spec.template.spec.containers[0].command[2]
pattern: "kubectl delete certificate my-bucket-ui-tls -n tenant-root"
- matchRegex:
path: spec.template.spec.containers[0].command[2]
pattern: "kubectl delete certificate,order,challenge -n tenant-root -l app.kubernetes.io/instance=my-bucket"| - it: grants RBAC on cert-manager, acme and networking/core resources | ||
| documentIndex: 2 | ||
| asserts: | ||
| - equal: | ||
| path: kind | ||
| value: Role | ||
| - contains: | ||
| path: rules | ||
| content: | ||
| apiGroups: ["cert-manager.io"] | ||
| resources: ["certificates"] | ||
| verbs: ["get", "list", "delete"] | ||
| - contains: | ||
| path: rules | ||
| content: | ||
| apiGroups: ["acme.cert-manager.io"] | ||
| resources: ["orders", "challenges"] | ||
| verbs: ["get", "list", "delete"] | ||
| - contains: | ||
| path: rules | ||
| content: | ||
| apiGroups: ["networking.k8s.io"] | ||
| resources: ["ingresses"] | ||
| verbs: ["get", "list", "delete"] | ||
| - contains: | ||
| path: rules | ||
| content: | ||
| apiGroups: [""] | ||
| resources: ["services", "pods"] | ||
| verbs: ["get", "list", "delete"] | ||
|
|
There was a problem hiding this comment.
Update the RBAC test assertions to match the simplified Role permissions.
- it: grants RBAC on cert-manager and acme resources
documentIndex: 2
asserts:
- equal:
path: kind
value: Role
- contains:
path: rules
content:
apiGroups: ["cert-manager.io"]
resources: ["certificates"]
verbs: ["get", "list", "delete"]
- contains:
path: rules
content:
apiGroups: ["acme.cert-manager.io"]
resources: ["orders", "challenges"]
verbs: ["get", "list", "delete"]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/bucket/templates/hooks/cleanup-acme.yaml`:
- Around line 47-50: The cleanup command in cleanup-acme.yaml is using the Helm
release label selector in kubectl delete, which won’t match cert-manager Order
and Challenge resources. Update the kubectl delete target in the cert-manager
cleanup block to select those resources by the certificate name using the
cert-manager.io/certificate label, keeping the existing delete of the
certificate itself in place. Refer to the cleanup shell block around the kubectl
delete certificate,order,challenge command and preserve the warning behavior.
🪄 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: 8586a518-5e87-4600-ac76-90de1e0b5e8a
📒 Files selected for processing (3)
packages/apps/bucket/Makefilepackages/apps/bucket/templates/hooks/cleanup-acme.yamlpackages/apps/bucket/tests/cleanup_acme_test.yaml
06d6106 to
5dbb352
Compare
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — adds a post-delete Job that removes the release-scoped cert-manager Certificate, letting cert-manager cascade-delete the orphaned ACME Order/Challenge/solver objects; deliberately avoids a namespace-wide solver sweep that would kill sibling apps' in-flight challenges.
Business context: deleting a bucket left orphaned cert-manager ACME solver resources behind (#3060).
Verified: the targeted Certificate name is correct — packages/system/bucket/templates/ingress.yaml sets tls.secretName=<bucketName>-ui-tls and bucketName == .Release.Name, so cert-manager's ingress-shim creates Certificate/<release>-ui-tls; deleting it cascades to CertificateRequest -> Order -> Challenge -> solver via ownerReferences. RBAC is scoped to cert-manager/acme resources only, hook ordering (SA -> Role/RoleBinding -> Job) is correct, and the no-blanket-sweep decision is sound and well-commented.
Non-blocking follow-up
- The secondary
kubectl delete certificate,order,challenge -l app.kubernetes.io/instance=<release>is effectively a no-op: cert-manager doesn't putapp.kubernetes.io/instanceonCertificate/Order/Challenge(Orders/Challenges carrycert-manager.io/certificate-name). Cleanup still works via the name-basedCertificatedelete + cascade, so it's harmless — but either switch the selector tocert-manager.io/certificate-name=<release>-ui-tlsor drop the redundant line so it doesn't imply coverage it doesn't provide.
…delete (#3060) cert-manager ACME http01-solver Ingress/Pod/Service objects were left orphaned after a Bucket app CR was deleted (a stuck challenge leaves them without a live owner to cascade-delete). Add a post-delete hook that deletes the release Certificate/Order/Challenge and sweeps leftover solver objects by the acme.cert-manager.io/http01-solver label. RBAC verified against the vendored cert-manager CRDs. The cleanup Job runs non-root with a read-only root fs and dropped capabilities; failures are logged but never block teardown. Signed-off-by: Алексей Артамонов <[email protected]>
5dbb352 to
4425d3a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
packages/apps/bucket/tests/cleanup_acme_test.yaml (2)
13-147: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winPin the total rendered document count.
These tests only verify documents
0..3. Iftemplates/hooks/cleanup-acme.yamlstarts emitting a fifth manifest, the suite still passes and an extra hook/RBAC object can slip in unnoticed. Add ahasDocuments: { count: 4 }guard for the whole template.Suggested addition
tests: + - it: renders only the expected cleanup hook objects + asserts: + - hasDocuments: + count: 4 + - it: renders a post-delete cleanup Job with the expected name and SA documentIndex: 0 asserts:🤖 Prompt for 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. In `@packages/apps/bucket/tests/cleanup_acme_test.yaml` around lines 13 - 147, The cleanup-acme test suite only checks documents 0 through 3, so extra rendered manifests could be added without failing. Update the cleanup-acme YAML test to assert the total rendered document count with a hasDocuments count of 4, so templates like cleanup-acme.yaml cannot emit an unexpected additional hook or RBAC object. Use the existing cleanup_acme_test.yaml document assertions as the anchor for where to add the count guard.
14-85: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAssert the hardening contract too.
The PR explicitly relies on this hook running with non-root, read-only, dropped-capability, and
RuntimeDefaultseccomp settings, but the suite never checks them. A future template change could remove those protections while all of these tests still pass.Suggested additions
- it: renders a post-delete cleanup Job with the expected name and SA documentIndex: 0 asserts: - equal: path: kind value: Job @@ - equal: path: spec.template.spec.restartPolicy value: Never + - equal: + path: spec.template.spec.securityContext.runAsNonRoot + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.readOnlyRootFilesystem + value: true + - equal: + path: spec.template.spec.containers[0].securityContext.allowPrivilegeEscalation + value: false + - equal: + path: spec.template.spec.containers[0].securityContext.capabilities.drop[0] + value: ALL + - equal: + path: spec.template.spec.containers[0].securityContext.seccompProfile.type + value: RuntimeDefault - equal: path: spec.template.metadata.labels["policy.cozystack.io/allow-to-apiserver"] value: "true"🤖 Prompt for 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. In `@packages/apps/bucket/tests/cleanup_acme_test.yaml` around lines 14 - 85, The cleanup Job test coverage is missing the security hardening assertions, so add checks in the bucket cleanup Helm test to verify the Job pod template preserves the non-root, read-only, dropped-capability, and RuntimeDefault seccomp settings. Update the existing cleanup Job assertions around spec.template.spec and the container securityContext in cleanup_acme_test.yaml so future changes to the cleanup template still enforce these protections.
🤖 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/bucket/templates/hooks/cleanup-acme.yaml`:
- Around line 12-88: The cleanup Job in cleanup-acme.yaml needs runtime bounds
and safer kubectl behavior because the current delete/list flow can hang on
finalizers and hide list failures. Update the Job spec to enforce an execution
timeout with activeDeadlineSeconds, and adjust the kubectl calls in the cleanup
command so deletions use --wait=false and all kubectl get lookups set a request
timeout instead of relying on default waits. Also remove the /dev/null
redirection from the Challenge and Ingress list commands so failures are
surfaced in the job logs, using the existing cleanup command block and kubectl
delete/get loops as the places to change.
---
Nitpick comments:
In `@packages/apps/bucket/tests/cleanup_acme_test.yaml`:
- Around line 13-147: The cleanup-acme test suite only checks documents 0
through 3, so extra rendered manifests could be added without failing. Update
the cleanup-acme YAML test to assert the total rendered document count with a
hasDocuments count of 4, so templates like cleanup-acme.yaml cannot emit an
unexpected additional hook or RBAC object. Use the existing
cleanup_acme_test.yaml document assertions as the anchor for where to add the
count guard.
- Around line 14-85: The cleanup Job test coverage is missing the security
hardening assertions, so add checks in the bucket cleanup Helm test to verify
the Job pod template preserves the non-root, read-only, dropped-capability, and
RuntimeDefault seccomp settings. Update the existing cleanup Job assertions
around spec.template.spec and the container securityContext in
cleanup_acme_test.yaml so future changes to the cleanup template still enforce
these protections.
🪄 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: 481bd958-3015-401d-b917-3b21df58310e
📒 Files selected for processing (3)
packages/apps/bucket/Makefilepackages/apps/bucket/templates/hooks/cleanup-acme.yamlpackages/apps/bucket/tests/cleanup_acme_test.yaml
✅ Files skipped from review due to trivial changes (1)
- packages/apps/bucket/Makefile
…ctor Address review feedback on the post-delete cleanup hook: - Target Orders/Challenges by cert-manager's own cert-manager.io/certificate-name=<release>-ui-tls label instead of the Helm app.kubernetes.io/instance label, which cert-manager never sets on those resources (the old selector was a silent no-op). - Bound the Job with activeDeadlineSeconds=120 and backoffLimit=0 so a stuck finalizer or hung API call cannot block the uninstall. - Add --wait=false --request-timeout=30s to every kubectl delete/get so the hook never hangs on slow or finalizer-blocked deletes. - Surface list failures in the job log instead of redirecting to /dev/null. - Tests: assert the bounded execution and security-hardening contract, pin the rendered document count to 4, and update selector assertions. Assisted-By: Claude <[email protected]> Signed-off-by: Алексей Артамонов <[email protected]>
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
NOT LGTM — the orphaned-solver cleanup itself is correct and safe, but one line is a no-op carried by an inaccurate comment and pinned by a test, which should be cleaned up before merge.
The fix resolves the orphaned cert-manager ACME solver objects through three mechanisms I verified: the name-based kubectl delete certificate <release>-ui-tls plus owner cascade; a host-scoped Challenge sweep filtered on .spec.dnsName; and a host-scoped solver-Ingress sweep filtered on .spec.rules[0].host. The host value matches the Challenge dnsName and the solver Ingress rule host, and there is no namespace-wide blanket delete over the shared acme.cert-manager.io/http01-solver label — the label-wide get is filtered by host and only matching objects are deleted by name, so sibling apps are untouched. The bounded execution (activeDeadlineSeconds, backoffLimit: 0, --request-timeout=30s --wait=false), RBAC scope, and hardening are all correct, and the negative no-blanket-sweep test assertions are the right call.
Blocker (small, but it's a false-coverage + inaccurate-comment issue):
templates/hooks/cleanup-acme.yaml:72—kubectl delete order,challenge -n <ns> -l cert-manager.io/certificate-name=<release>-ui-tlsis a no-op. Challenge objects carry no labels at all, and on an Order thecert-manager.io/certificate-namevalue is an annotation (copied from the CertificateRequest), not a label — so a label selector matches neither. The accompanying comment (:69-70, "cert-manager stamps them with its own cert-manager.io/certificate-name label") is therefore factually wrong, andtests/cleanup_acme_test.yaml:74pins this no-op as if it were meaningful coverage. Orders are already reaped by the Certificate cascade and Challenges by the working dnsName sweep, so please drop this line along with its test assertion and the inaccurate comment, rather than keeping a selector that deletes nothing and a comment that misstates how cert-manager labels these objects.
Per maintainer review: the label selector cert-manager.io/certificate-name=<release>-ui-tls matches nothing — Challenge objects carry no labels, and on Orders certificate-name is an annotation (not a label). Orders are already reaped by the Certificate owner cascade and Challenges by the host-scoped .spec.dnsName sweep, so the line deleted nothing and its comment misstated cert-manager labeling. Remove the dead delete, replace the inaccurate comment, and drop the test assertion that pinned it; add negative assertions so a label-based order/challenge delete cannot be reintroduced. Assisted-By: Claude <[email protected]> Signed-off-by: Алексей Артамонов <[email protected]>
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM — the previously-raised blocker is resolved; the orphaned-solver cleanup is correct and release-scoped.
The no-op label-based order/challenge delete (-l cert-manager.io/certificate-name=<release>-ui-tls, which matched nothing — Challenges are unlabeled and that value is an annotation on Orders, not a label) has been dropped, together with its inaccurate comment and the test assertion that pinned it. The replacement comment accurately explains why there is no label selector for Order/Challenge.
Re-verified against packages/system/bucket/templates/ingress.yaml:
- Certificate name
<release>-ui-tlsmatches the ingress-shimsecretName. - HOST
<release>.<_namespace.host>matches both the Challenge.spec.dnsNameand the solver Ingress.spec.rules[0].host, by exact equality (not a prefix), so a sibling like<release>-2.<host>is never affected. - The
if not $wildcardSecretguard already suppresses per-host ACME, so wildcard mode has nothing to clean. - RBAC stays least-privilege (no services/pods — solver Service/Pod cascade with their Challenge), execution is bounded (activeDeadlineSeconds 120, backoffLimit 0, per-call --request-timeout/--wait=false), and the script always exits 0 so a failure never blocks uninstall.
Tests cover the contract, including the negative assertions (no namespace-wide solver sweep, no services/pods RBAC).
What this PR does
After deleting a
Bucketapp CR, cert-manager's temporary ACME HTTP-01 solverresources (
cm-acme-http-solver-*Ingress/Pod/Service) were left orphaned in thetenant namespace. They are created by cert-manager to answer an ACME challenge
and are owned by the
Challenge/Order→Certificate, not by the app Ingress.If a challenge gets stuck and the app is deleted, the parent Ingress is removed
but the orphaned challenge leaves the solver objects with no live owner to
cascade-delete them, so Helm/Flux uninstall cannot reap them.
This adds a
post-deletecleanup hook that deletes the release'sCertificate/Order/Challenge(cert-manager then cascade-removes the solverobjects) and then sweeps any leftover solver objects by the reliable
acme.cert-manager.io/http01-solver=truelabel.RBAC is verified against the vendored cert-manager CRDs
(
cert-manager.io→ certificates;acme.cert-manager.io→ orders, challenges)plus core
services/podsandnetworking.k8s.ioingresses. The cleanup Job ishardened: non-root (uid 65534), read-only root fs, all caps dropped,
seccompProfile: RuntimeDefault. Failures are logged but never block teardown.The preventive wildcard-cert guard the issue mentions already exists in the
vendored
bucket-systemIngress template (it drops the ACME annotations inwildcard mode), so no preventive change is needed — only cleanup.
No user data is affected: only cert-manager's transient challenge artifacts are
removed.
Fixes #3060.
Release note
Summary by CodeRabbit
testcommand that runs Helm unit tests.