Skip to content

fix(gateway): clean up orphaned ACME account secret on tenant module delete (#3089) - #3093

Merged
scooby87 merged 1 commit into
mainfrom
fix/gateway-acme-account-orphaned
Jun 29, 2026
Merged

scooby87 merged 1 commit into
mainfrom
fix/gateway-acme-account-orphaned

Conversation

@scooby87

@scooby87 scooby87 commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

When the gateway tenant module is disabled (its HelmRelease uninstalled), cert-manager's ACME account private-key secret cozystack-acme-account was left orphaned in the tenant namespace (label app.kubernetes.io/managed-by: cert-manager, no ownerReferences).

This adds a post-delete cleanup hook (packages/extra/gateway/templates/hooks/cleanup-acme.yaml) that deletes the secret by name. Verified on a live cluster that cozystack-acme-account is referenced by exactly one Issuer — the gateway module's own cozystack-gateway Issuer — so it is gateway-specific and safe to remove on gateway teardown (it is not shared with other modules, which use the cluster-wide letsencrypt-prod issuer).

The cleanup Job is hardened: non-root (uid 65534), read-only root fs, all capabilities dropped, seccompProfile: RuntimeDefault, resource requests/limits; RBAC scoped via resourceNames to exactly cozystack-acme-account (get/delete). A failure is logged but never blocks teardown.

No user data is affected — only cert-manager's ACME account key (re-registered on next enable).

Fixes #3089.

Release note

fix(gateway): clean up the orphaned cert-manager ACME account secret (cozystack-acme-account) left behind when a tenant's gateway module is disabled.

Summary by CodeRabbit

  • New Features

    • Added automatic cleanup after gateway removal to delete the stored ACME account secret in the release namespace.
    • Included a dedicated, restricted cleanup job with the required permissions to run safely during uninstall.
  • Bug Fixes

    • Made the gateway test target always runnable.
    • Cleanup now ignores missing secrets and warns on failure instead of stopping uninstall flows.

…delete (#3089)

When the gateway tenant module is disabled, cert-manager's ACME account
private-key secret cozystack-acme-account was left orphaned (no ownerRef).
Add a post-delete cleanup hook that deletes it by name. Verified the secret
is referenced only by the gateway module's own cozystack-gateway Issuer, so
removing it on gateway teardown is safe. The cleanup Job runs non-root with a
read-only root fs and dropped capabilities; RBAC is scoped via resourceNames.

Signed-off-by: Алексей Артамонов <[email protected]>
@github-actions github-actions Bot added area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files labels Jun 26, 2026
@coderabbitai

coderabbitai Bot commented Jun 26, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a post-delete Helm hook for the gateway chart that removes the release namespace's cozystack-acme-account secret. The change also adds dedicated ServiceAccount, Role, and RoleBinding resources, plus chart tests and a phony test target.

Changes

Gateway ACME cleanup

Layer / File(s) Summary
Hook manifest
packages/extra/gateway/templates/hooks/cleanup-acme.yaml
Adds the post-delete Job that deletes cozystack-acme-account, plus the ServiceAccount, Role, and RoleBinding used by the hook.
Tests and target
packages/extra/gateway/tests/cleanup_acme_test.yaml, packages/extra/gateway/Makefile
Adds Helm assertions for the hook Job, security context, RBAC, and ServiceAccount, and marks the gateway test target phony.

Sequence Diagram(s)

sequenceDiagram
  participant Helm
  participant gateway-cleanup Job
  participant Kubernetes API
  participant cozystack-acme-account Secret
  Helm->>gateway-cleanup Job: create post-delete hook
  gateway-cleanup Job->>Kubernetes API: delete cozystack-acme-account with --ignore-not-found
  Kubernetes API->>cozystack-acme-account Secret: remove secret in release namespace
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

(_/)
( •_•)
/ >🧹 I hop through the hooks at night,
one secret swept out of sight.
The burrow stays tidy and bright.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The Makefile .PHONY: test change is unrelated to the secret-cleanup objective and appears out of scope. Move the Makefile tweak to a separate PR or explain why it is required for the gateway cleanup work.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: removing orphaned ACME account secrets when the gateway module is deleted.
Linked Issues check ✅ Passed [#3089] The post-delete hook deletes cozystack-acme-account during gateway teardown, which matches the issue's required cleanup.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gateway-acme-account-orphaned

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dosubot dosubot Bot added the area/extra Issues or PRs related to tenant-specific modules (packages/extra/) label Jun 26, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 the 'cozystack-acme-account' secret remains in the tenant namespace after the gateway module is deleted. By implementing a hardened post-delete Helm hook, the system now ensures that this gateway-specific secret is cleaned up during teardown, preventing resource leakage while maintaining strict security standards.

Highlights

  • Cleanup Hook Implementation: Added a new post-delete Helm hook that automatically removes the orphaned 'cozystack-acme-account' secret when the gateway module is uninstalled.
  • Security Hardening: The cleanup Job is configured with non-root execution, read-only root filesystem, dropped capabilities, and scoped RBAC permissions to ensure minimal privilege.
  • Testing: Introduced comprehensive unit tests to verify the correct rendering and configuration of the cleanup Job, ServiceAccount, Role, and RoleBinding.
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 Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment Gemini (@gemini-code-assist) Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a post-delete Helm hook to clean up the orphaned cozystack-acme-account secret when the gateway package is deleted, along with corresponding unit tests and a Makefile update. The review feedback recommends explicitly defining the namespace for the ServiceAccount subject in the RoleBinding to ensure proper RBAC resolution, and updating the unit tests to assert this namespace.

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.

Comment on lines +101 to +103
subjects:
- kind: ServiceAccount
name: {{ .Release.Name }}-cleanup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For ServiceAccount subjects in a RoleBinding, it is highly recommended (and in some Kubernetes environments/validators, strictly required) to explicitly specify the namespace field. This ensures the RBAC controller can correctly resolve the ServiceAccount.

subjects:
  - kind: ServiceAccount
    name: {{ .Release.Name }}-cleanup
    namespace: {{ .Release.Namespace }}

Comment on lines +113 to +118
- equal:
path: subjects[0].kind
value: ServiceAccount
- equal:
path: subjects[0].name
value: gateway-cleanup

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

Add an assertion to verify that the namespace of the ServiceAccount subject in the RoleBinding is correctly set to the release namespace.

      - equal:
          path: subjects[0].kind
          value: ServiceAccount
      - equal:
          path: subjects[0].name
          value: gateway-cleanup
      - equal:
          path: subjects[0].namespace
          value: tenant-root

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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/extra/gateway/templates/hooks/cleanup-acme.yaml`:
- Around line 8-11: The Helm hook delete policy is missing failure cleanup,
which can leave orphaned resources behind when the post-delete hook fails.
Update the `helm.sh/hook-delete-policy` annotations in the `cleanup-acme.yaml`
template for the hook resources (including the Job, ServiceAccount, Role, and
RoleBinding) to add `hook-failed` alongside the existing policies. Use the
existing annotation blocks with the `helm.sh/hook-delete-policy` symbol to
ensure all cleanup resources are removed on both success and failure.
🪄 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: 85e2ade1-b229-4c43-bada-5364038dc539

📥 Commits

Reviewing files that changed from the base of the PR and between 01addfc and 6e093f1.

📒 Files selected for processing (3)
  • packages/extra/gateway/Makefile
  • packages/extra/gateway/templates/hooks/cleanup-acme.yaml
  • packages/extra/gateway/tests/cleanup_acme_test.yaml

Comment on lines +8 to +11
annotations:
"helm.sh/hook": post-delete
"helm.sh/hook-weight": "10"
"helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Helm 3 hook annotations, does helm.sh/hook-delete-policy: before-hook-creation,hook-succeededremove failedpost-deletehook resources automatically, or ishook-failed required to clean them up?

💡 Result:

The hook-succeeded policy does not remove a hook resource if the hook fails [1][2]. To automatically remove a hook resource when it fails, you must explicitly include hook-failed in your helm.sh/hook-delete-policy annotation [1][3][4]. If you define the annotation as helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded, Helm will only delete the resource if the hook execution completes successfully [1][3]. If the hook fails, that specific resource will remain in the cluster, which is often preferred to allow for debugging [3][4]. Important considerations regarding hook deletion: 1. Explicit Inclusion: If you want automatic cleanup of failed hooks, use helm.sh/hook-delete-policy: hook-failed (or combine it with others, such as hook-succeeded,hook-failed) [1][5][4]. 2. Cumulative Failures: In versions of Helm where specific issues with hook cleanup logic have been reported (e.g., when multiple hooks are defined), a failure in one hook may prevent the hook-succeeded policy from triggering for previous, successful hooks [6][7][8]. 3. Job TTL: Because Helm hooks are not tracked as part of the standard release lifecycle, many practitioners use the Kubernetes-native spec.ttlSecondsAfterFinished field on Job resources as a secondary or alternative method to ensure resources are cleaned up regardless of Helm's internal hook logic [1][9][3].

Citations:


Add hook-failed to delete policies to prevent orphaned RBAC resources

The current helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded only removes hook resources if the job succeeds. If the post-delete hook fails (e.g., due to image pull errors, scheduling issues, or pre-flight failures), the Job, ServiceAccount, Role, and RoleBinding will remain in the namespace, leaving orphaned permissions behind.

Include hook-failed to ensure cleanup on any failure:

  • Lines 11, 69, 80, and 96 need hook-failed added to their helm.sh/hook-delete-policy annotations.
Suggested fix
-    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
...
-    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
...
-    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
...
-    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
+    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded,hook-failed
🤖 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/extra/gateway/templates/hooks/cleanup-acme.yaml` around lines 8 -
11, The Helm hook delete policy is missing failure cleanup, which can leave
orphaned resources behind when the post-delete hook fails. Update the
`helm.sh/hook-delete-policy` annotations in the `cleanup-acme.yaml` template for
the hook resources (including the Job, ServiceAccount, Role, and RoleBinding) to
add `hook-failed` alongside the existing policies. Use the existing annotation
blocks with the `helm.sh/hook-delete-policy` symbol to ensure all cleanup
resources are removed on both success and failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Verified the deleted secret is genuinely per-tenant and orphaned, so the cleanup is safe:

  • the gateway renders a namespaced Issuer in the tenant namespace (internal/controller/tenantgateway/renderers.go:234), and its ACME account key is <TenantGateway>-acme-account; the TenantGateway is named cozystack (packages/extra/gateway/templates/tenantgateway.yaml), so the target is cozystack-acme-account in the tenant namespace — exactly what the hook deletes in .Release.Namespace.
  • it is not the shared platform account: the cluster-wide letsencrypt-prod/letsencrypt-stage issuers use account secrets named letsencrypt-prod/letsencrypt-stage in the cert-manager namespace — no collision.
  • the Issuer is owned by the TenantGateway and cascades; the cert-manager-created account key has no ownerReference and is the genuine orphan.

RBAC is tightly scoped (resourceNames: [cozystack-acme-account], get,delete), the pod is hardened, and the tests pin the hook shape, RBAC scope, and that the delete targets the per-tenant secret by name and namespace.

Non-blocking suggestions:

  1. Set subjects[0].namespace: {{ .Release.Namespace }} on the RoleBinding to match the existing teardown convention and satisfy strict RBAC validators (it works without it, but explicit is the house style).
  2. Consider adding hook-failed to the helm.sh/hook-delete-policy annotations so a failed hook does not leave the Job/SA/Role/RoleBinding behind until the next uninstall.
  3. Out of scope here, but with enableCertificateOwnerRef: false the per-listener TLS certificate secrets are also not garbage-collected on teardown — worth a follow-up.
  4. Optional: assert the SA (weight 0) and Role/RoleBinding (weight 5) hook weights in the unit test, since the SA->RBAC->Job ordering is the correctness-critical part of the hook.

@scooby87
scooby87 merged commit 080ceac into main Jun 29, 2026
14 checks passed
@scooby87
scooby87 deleted the fix/gateway-acme-account-orphaned branch June 29, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extra Issues or PRs related to tenant-specific modules (packages/extra/) area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) kind/bug Categorizes issue or PR as related to a bug size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gateway (tenant module): orphaned cozystack-acme-account secret after the module is disabled

2 participants