Skip to content

feat(platform, ingress): propagate operator wildcard certificate to per-tenant termination points - #2990

Merged
Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
feat/wildcard-secret-tenant-propagation
Jul 15, 2026
Merged

feat(platform, ingress): propagate operator wildcard certificate to per-tenant termination points#2990
Aleksei Sviridkin (lexfrei) merged 2 commits into
mainfrom
feat/wildcard-secret-tenant-propagation

Conversation

@lexfrei

@lexfrei Aleksei Sviridkin (lexfrei) commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

What this PR does

The operator-provided wildcard certificate (publishing.certificates.wildcardSecretName) currently serves the root tenant only: everything is same-namespace, so the root ingress controller and Gateway read the operator's TLS Secret directly. Child tenants run their own ingress controller / Gateway in their own namespace under a default-deny policy and cannot read that Secret across namespaces, so they fall back to per-host ACME.

This propagates the operator wildcard to per-tenant termination points by replicating the Secret into each tenant namespace that owns one, keeping the root-MVP invariant intact: the wildcard is deliberately distributed to tenant namespaces, but no tenant RBAC is widened — every consumer reads only its own-namespace copy.

A new controller in cozystack-controller does the replication with no extra operator input. It reads the same platform values channel the consumers read (cozy-system/cozystack-values): the wildcard Secret name from _cluster.wildcard-secret-name and the publishing namespace from _cluster.expose-ingress. It mirrors that Secret into every tenant namespace whose namespace.cozystack.io/ingress or namespace.cozystack.io/gateway label equals its own name — i.e. the namespace runs its own controller / Gateway. Because the source is derived from the same value that makes the consumers reference it, the replica is created whenever the consumers expect it — no manual labelling and no upgrade hazard where a child controller references a Secret that nothing creates.

Replicas are refreshed on rotation and pruned when a namespace stops terminating TLS. Teardown of every replica happens on exactly one trigger: clearing wildcardSecretName (explicit disable). A source that is merely absent or mistyped, or a transiently-missing values channel, leaves existing replicas in place, so a brief gap, a delete+recreate rotation, or a misconfigured publishing namespace never drops tenant TLS. The platform controller's own ServiceAccount gains Secret writes; tenant RBAC is untouched.

The controller does not cache every Secret in the cluster: the manager's Secret informer is scoped to managed replicas and the values channel only, and the operator source / foreign collisions are read through the uncached APIReader. The source is not watched (its name is dynamic), so an in-place rotation is picked up by a bounded periodic resync while the publishing tenant serves the source with no lag.

On the consumer side, each tenant ingress controller now sets --default-ssl-certificate to the wildcard Secret in its own namespace (.Release.Namespace/<name>), so a child tenant serves the replica instead of minting per-host ACME. The Gateway path already renders certMode: existingSecret with a same-namespace wildcardSecretRef for every tenant, so it consumes the replica with no chart change.

Why not clustersecret-operator

The issue proposed the shipped clustersecret-operator. It materializes inline template data into selected namespaces; it cannot mirror an existing source Secret, and it is not installed in any default bundle. Driving it would mean embedding the certificate and private key as literal data in a cluster-scoped object rendered from a Helm lookup — that broadcasts key material the design keeps off any shared channel, and a lookup is invisible to the Flux helm-controller digest so it would not re-render on rotation. Its namespace selector also cannot express "the namespace whose owner label equals its own name", so it would over-replicate the key into every tenant namespace. A small dedicated reconciler targets exactly the termination namespaces and keeps the key off the values channel.

Coverage caveat

Replication delivers the certificate bytes, not SAN coverage: a single *.<root-host> does not match *.<child-apex>. Operators serving child-tenant subdomains supply a certificate whose SANs cover those apexes; the docs spell this out. The replica carries the wildcard private key into each terminating tenant namespace — the same exposure as the per-host ACME Secret it replaces, now shared across tenants.

Tests

  • Go fake-client tests for the reconciler: termination-only replication (ingress-owning and gateway-owning namespaces only, never inheriting ones, never over the source), copy markers, rotation, prune on namespace demotion, the non-destructive source-deletion contract, replica self-heal after out-of-band deletion, the publishing-namespace self-replication guard, disable-via-config teardown, source rename, foreign-Secret collision safety (no overwrite, no requeue, does not block siblings), transient-error requeue, non-TLS source ignored, absent-channel keeps replicas, publishing-namespace mismatch keeps and requeues, the cache-scope pin, the enqueue-wiring map, an ownsTerminationPoint table, and the active-only requeue contract.
  • helm-unittest for the ingress default-ssl-certificate wiring (including a child-tenant controller pointing at its own-namespace replica) and for the cozystack-controller ClusterRole's new Secret-write rule.

Screenshots

N/A — no UI changes.

Release note

feat(platform): the operator-provided wildcard TLS certificate (`publishing.certificates.wildcardSecretName`) is now served by per-tenant ingress controllers and Gateways automatically — the platform controller replicates it into each tenant namespace that terminates TLS, with no extra operator input and no cross-namespace Secret read

Closes #2820

Part of #2811

Summary by CodeRabbit

Release Notes

  • New Features

    • Wildcard TLS Secret now replicates from the platform to tenant namespaces that terminate TLS via their own ingress controller or Gateway, including child-tenant scenarios.
  • Documentation

    • Clarified configuration, timing, and SAN matching requirements for wildcard replication.
    • Updated gateway and ingress-nginx default certificate documentation for replica mirroring behavior.
  • Bug Fixes

    • Improved handling for missing/invalid source secrets (no unnecessary pruning) and safe collision behavior.
  • Tests

    • Added comprehensive reconciliation/replication test coverage.
    • Added Helm unittest target for the controller chart.
  • Chores

    • Updated controller RBAC to support wildcard TLS Secret replication.

@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new wildcardsecret.Reconciler controller that replicates an operator-provided wildcard TLS Secret from a publishing namespace into every tenant namespace that owns a TLS termination point (ingress or gateway). Updates ingress-nginx Helm templates to reference the Secret from each controller's own namespace instead of cross-namespace. Grants required RBAC, adds comprehensive test coverage, and revises documentation to reflect the new per-tenant replication behavior.

Changes

Wildcard TLS Secret Replication Controller

Layer / File(s) Summary
Controller constants, types, and configuration loading
internal/controller/wildcardsecret/reconciler.go
Defines Reconciler struct, exported CopyLabel and SourceRefAnnotation constants, platform values Secret identity fields, singleton reconcile key, RBAC annotations, and readConfig logic that reads wildcard-secret-name and publishing namespace from cozy-system/cozystack-values.
Core reconciliation loop, namespace targeting, and replica management
internal/controller/wildcardsecret/reconciler.go
Implements Reconcile: reads config, prunes replicas when disabled, fetches the source TLS Secret, computes target namespaces via termination-owner labels, upserts managed replicas with foreign-collision detection, prunes stale replicas by label, and aggregates transient errors for requeue. Includes terminationNamespaces, upsertCopy, pruneCopies, and helper utilities for source reference formatting and deep-copying Secret data.
Cache scoping and SetupWithManager wiring
internal/controller/wildcardsecret/reconciler.go
Implements SecretCacheByObject to scope the manager's secret cache to managed replicas and the platform values Secret only, avoiding cache pollution from dynamic source Secrets. Wires controller in SetupWithManager as a singleton reconciler keyed to the platform values Secret, registering watches for Secret and Namespace events.
RBAC, Makefile test target, and main() wiring
cmd/cozystack-controller/main.go, packages/system/cozystack-controller/templates/rbac.yaml, packages/system/cozystack-controller/Makefile
Adds ClusterRole RBAC rule granting full Secrets management verbs. Introduces test Makefile target. Registers wildcardsecret.Reconciler in main() with cache configuration and failure logging.
RBAC test fixture
packages/system/cozystack-controller/tests/rbac_test.yaml
Validates the cozystack-controller ClusterRole includes the secrets rule with full CRUD and watch verbs required for wildcard Secret replication.
Reconciler test suite
internal/controller/wildcardsecret/reconciler_test.go
Comprehensive tests covering replication targeting to termination namespaces only, copy markers (label and source annotation), rotation propagation, pruning on namespace ownership change or feature disable, self-heal after out-of-band deletion, prevention of publishing-namespace self-replication, foreign Secret collision avoidance, transient write error requeue, non-TLS source no-op, absent platform values no-op, publishing namespace mismatch handling, and cache scoping validation.
ingress-nginx same-namespace wiring, tests, and documentation
packages/extra/ingress/templates/nginx-ingress.yaml, packages/extra/ingress/tests/default_ssl_certificate_test.yaml, packages/core/platform/values.yaml, packages/extra/gateway/README.md
Changes default-ssl-certificate condition to activate whenever a wildcard Secret is configured and uses .Release.Namespace instead of expose-ingress namespace. Updates Helm test fixture to expect child-tenant controller to reference its own-namespace replica. Revises platform docs to describe automatic per-tenant replication, GC triggers, and certificate SAN requirements for child apexes.

Sequence Diagram(s)

sequenceDiagram
  participant TenantNS as Tenant Namespace
  participant Reconciler as wildcardsecret.Reconciler
  participant APIServer as Kubernetes API
  participant ValuesSecret as cozy-system/cozystack-values
  participant SourceSecret as Publishing Namespace TLS Secret
  participant ReplicaSecret as Tenant Namespace TLS Secret (replica)

  APIServer->>Reconciler: Enqueue singleton key (Secret/Namespace event)
  Reconciler->>APIServer: Get cozy-system/cozystack-values
  APIServer-->>ValuesSecret: Return wildcard-secret-name, expose-ingress
  Reconciler->>APIServer: Get source TLS Secret from publishing namespace
  APIServer-->>SourceSecret: Return TLS Secret data
  Reconciler->>APIServer: List Namespaces with termination-owner label
  APIServer-->>TenantNS: Return matching tenant namespaces
  Reconciler->>APIServer: Get existing replica in TenantNS
  APIServer-->>Reconciler: NotFound or existing replica
  Reconciler->>APIServer: Create/Update ReplicaSecret with CopyLabel+SourceRefAnnotation
  APIServer-->>ReplicaSecret: Managed replica persisted
  Reconciler->>APIServer: List managed replicas by CopyLabel for pruning
  Reconciler->>APIServer: Delete stale replicas outside target set
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #2820 (feat(platform): propagate operator wildcard certificate to per-tenant ingress controllers): This PR directly implements the wildcard TLS Secret replication described in that issue—controller-managed per-tenant copies with full lifecycle management (rotation, pruning, collision avoidance).
  • #2811 (WS1 epic): The PR is explicitly scoped as part of this workstream, implementing the per-tenant Secret propagation that #2820 prescribes.

Suggested reviewers

  • kvaps
  • androndo
  • IvanHunters
  • sircthulhu
  • myasnikovdaniil
  • lllamnyp

Poem

🐇 Hop, hop, a secret to share,
TLS wildcard flies through the air!
Each tenant gets a copy so neat,
With labels and annotations complete.
Stale replicas pruned with a flick—
The rabbit controller does the trick! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: propagating the operator wildcard certificate to per-tenant termination points (ingress and Gateway controllers).
Linked Issues check ✅ Passed The PR comprehensively addresses all objectives from #2820: replicates wildcard Secret into termination-point-owning namespaces, manages lifecycle on rotation/deletion, works with Gateways via same-namespace copies, and accepts the key material threat model.
Out of Scope Changes check ✅ Passed All changes directly support the PR objective: wildcard controller implementation, RBAC grants for Secret replication, documentation updates, ingress-nginx wiring, and test coverage—no unrelated modifications.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wildcard-secret-tenant-propagation

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.

@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature labels Jun 22, 2026
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch from 1cc30d5 to 1f3f1d7 Compare June 22, 2026 17:01
@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels Jun 22, 2026
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review June 22, 2026 22:29

@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: 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 `@internal/controller/wildcardsecret/reconciler_test.go`:
- Around line 476-489: The test TestReconcile_NoPlatformValuesIsNoOp currently
locks in destructive prune-on-missing-config behavior by asserting that an
absent values channel causes the wildcard-tls secret to not exist. This
conflicts with the intended design where secrets should only be pruned when
wildcard-secret-name is explicitly cleared, not when the values channel is
transiently absent. Modify the test to create an existing wildcard-tls secret in
the setup, then verify that the reconciler preserves it unchanged when the
values channel is missing, rather than asserting the secret should not exist.

In `@packages/extra/gateway/README.md`:
- Line 60: The documentation describes replica garbage collection as occurring
when "the source is removed," but the actual controller behavior only performs
garbage collection on explicit disable (clearing `wildcardSecretName`). Replicas
persist even when the source Secret is absent or mistyped. Update the wording in
the paragraph to clarify that replicas are garbage-collected only when
`wildcardSecretName` is explicitly cleared or disabled, not during transient
gaps or when the source Secret is temporarily unavailable, to accurately reflect
the controller's reconciliation logic and prevent operator confusion during
certificate rotation scenarios.
🪄 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: 4e6d523d-ded8-4d0c-87f8-63025db14320

📥 Commits

Reviewing files that changed from the base of the PR and between 82b8c46 and 1f3f1d7.

📒 Files selected for processing (8)
  • cmd/cozystack-controller/main.go
  • internal/controller/wildcardsecret/reconciler.go
  • internal/controller/wildcardsecret/reconciler_test.go
  • packages/core/platform/values.yaml
  • packages/extra/gateway/README.md
  • packages/extra/ingress/templates/nginx-ingress.yaml
  • packages/extra/ingress/tests/default_ssl_certificate_test.yaml
  • packages/system/cozystack-controller/templates/rbac.yaml

Comment thread internal/controller/wildcardsecret/reconciler_test.go Outdated
Comment thread packages/extra/gateway/README.md Outdated
@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 introduces a mechanism to propagate the operator-provided wildcard TLS certificate to child tenant namespaces. By replicating the secret into namespaces that own ingress or gateway termination points, child tenants can now securely serve the wildcard certificate without requiring cross-namespace access or widened RBAC.

Highlights

  • New WildcardSecret Controller: Implemented a new controller that automatically replicates the operator-provided wildcard TLS secret into tenant namespaces that own a termination point.
  • Secure Secret Propagation: Enabled per-tenant ingress controllers and Gateways to serve the wildcard certificate from their own namespace, eliminating the need for cross-namespace secret reads.
  • Configuration & RBAC Updates: Updated platform configuration and controller RBAC to support secret replication while maintaining strict security boundaries.
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 new WildcardSecret controller that replicates the operator-provided wildcard TLS Secret into tenant namespaces that terminate TLS. This allows per-tenant ingress controllers and Gateways to serve the wildcard certificate from their own namespaces without requiring cross-namespace Secret access or widening tenant RBAC. The changes include the reconciler implementation, comprehensive unit tests, RBAC updates, and adjustments to the ingress and gateway charts to utilize the replicated secrets. Feedback is provided to remove the unused Scheme field from the Reconciler struct to simplify the code and clean up unused imports.

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 +123 to +126
type Reconciler struct {
client.Client
Scheme *runtime.Scheme
}

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

The Scheme field is defined on the Reconciler struct but is not used anywhere in the reconciler implementation. Removing it simplifies the struct and allows removing the unused "k8s.io/apimachinery/pkg/runtime" import. Note that you will also need to remove the Scheme initialization in cmd/cozystack-controller/main.go and internal/controller/wildcardsecret/reconciler_test.go.

type Reconciler struct {
	client.Client
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Keeping Scheme for construction symmetry with the other reconcilers in this manager (all built with Client+Scheme in main.go). This controller tracks replicas by label and annotation rather than owner references — a cross-namespace owner reference would be invalid — so the field is intentionally unused, and I've added a comment on it documenting exactly that. Removing it would break the uniform construction pattern for a cosmetic gain.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch from 1f3f1d7 to 986b153 Compare June 22, 2026 23:01

@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: 2

🧹 Nitpick comments (1)
internal/controller/wildcardsecret/reconciler.go (1)

142-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct the stale pruning comments.

These comments still say source absence/non-TLS removes replicas, but the implementation intentionally preserves them unless the wildcard name is explicitly cleared.

Proposed comment fix
-// longer belong. When the feature is off (no source name), the source is
-// absent, or the source is not a TLS Secret, every replica is removed.
+// longer belong. When the feature is off (no source name), every replica
+// is removed; an absent or non-TLS source keeps existing replicas.
-// removes all managed replicas — used when the feature is off or the
-// source is gone.
+// removes all managed replicas — used when the feature is off.

Also applies to: 338-341

🤖 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 `@internal/controller/wildcardsecret/reconciler.go` around lines 142 - 146, The
documentation comments for the Reconcile method at lines 142-146 and the similar
comments at lines 338-341 are stale and incorrectly describe the behavior. The
comments state that replicas are removed when the feature is off, the source is
absent, or the source is not a TLS Secret, but the actual implementation
preserves replicas unless the wildcard name is explicitly cleared. Update both
comment blocks to accurately reflect that replicas are only removed when the
wildcard name is explicitly cleared, not when the source is merely absent or not
a TLS Secret.
🤖 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 `@internal/controller/wildcardsecret/reconciler.go`:
- Line 168: The pruneCopies function is being called without protecting the
configured source Secret from being pruned. Modify the pruneCopies calls (at
line 168 and line 213) to pass the configured source namespace as a parameter so
that it can be excluded from the pruning logic. Additionally, update the
pruneCopies function implementation (around lines 342-357) to accept the source
namespace parameter and add it to the keepSet to ensure the source Secret is
never deleted even if it carries the wildcard-secret-copy label.
- Around line 200-216: The pruning operation via r.pruneCopies is being executed
unconditionally even when transient upsert failures have been collected in the
errs slice, which can cause previously managed Secrets to be deleted before
replacement copies are successfully created. Add a check to only call
r.pruneCopies when the errs slice is empty, ensuring that pruning is deferred
until all retryable upserts have succeeded.

---

Nitpick comments:
In `@internal/controller/wildcardsecret/reconciler.go`:
- Around line 142-146: The documentation comments for the Reconcile method at
lines 142-146 and the similar comments at lines 338-341 are stale and
incorrectly describe the behavior. The comments state that replicas are removed
when the feature is off, the source is absent, or the source is not a TLS
Secret, but the actual implementation preserves replicas unless the wildcard
name is explicitly cleared. Update both comment blocks to accurately reflect
that replicas are only removed when the wildcard name is explicitly cleared, not
when the source is merely absent or not a TLS Secret.
🪄 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: 33754ee2-5d8e-468c-8244-58570cfd4972

📥 Commits

Reviewing files that changed from the base of the PR and between 1f3f1d7 and 986b153.

📒 Files selected for processing (8)
  • cmd/cozystack-controller/main.go
  • internal/controller/wildcardsecret/reconciler.go
  • internal/controller/wildcardsecret/reconciler_test.go
  • packages/core/platform/values.yaml
  • packages/extra/gateway/README.md
  • packages/extra/ingress/templates/nginx-ingress.yaml
  • packages/extra/ingress/tests/default_ssl_certificate_test.yaml
  • packages/system/cozystack-controller/templates/rbac.yaml
✅ Files skipped from review due to trivial changes (2)
  • packages/extra/gateway/README.md
  • packages/core/platform/values.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • cmd/cozystack-controller/main.go
  • packages/extra/ingress/templates/nginx-ingress.yaml
  • packages/extra/ingress/tests/default_ssl_certificate_test.yaml
  • internal/controller/wildcardsecret/reconciler_test.go

Comment thread internal/controller/wildcardsecret/reconciler.go Outdated
Comment thread internal/controller/wildcardsecret/reconciler.go Outdated
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch 2 times, most recently from 3282256 to 4ea589a Compare June 22, 2026 23:59

@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: 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 `@internal/controller/wildcardsecret/reconciler_test.go`:
- Around line 611-612: The disabled-path assertion that checks `res.RequeueAfter
!= 0` is incomplete and could miss a bug where Requeue is set to true. In
addition to the existing RequeueAfter check, add a second assertion to verify
that `res.Requeue` is false, ensuring that a disabled reconcile neither requeues
immediately nor schedules a requeue after a delay.

In `@internal/controller/wildcardsecret/reconciler.go`:
- Around line 159-163: The platformValues struct uses WildcardSecretName as a
string type that defaults to empty string when the key is missing from the YAML,
causing readConfig to incorrectly return present=true and triggering destructive
pruning behavior. Change WildcardSecretName from a string type to a pointer to
string (*string) so that nil represents a missing key while an explicit empty
string represents a disabled state. Apply this same fix to the similar field at
lines 288-298 in the ExposeIngress or related configuration. Update any code
that reads or uses these fields to check for nil before dereferencing to
properly handle the distinction between missing and explicitly empty values.
🪄 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: aaacdcad-5074-4fa6-9db5-945c7f11adc3

📥 Commits

Reviewing files that changed from the base of the PR and between 986b153 and 3282256.

📒 Files selected for processing (10)
  • cmd/cozystack-controller/main.go
  • internal/controller/wildcardsecret/reconciler.go
  • internal/controller/wildcardsecret/reconciler_test.go
  • packages/core/platform/values.yaml
  • packages/extra/gateway/README.md
  • packages/extra/ingress/templates/nginx-ingress.yaml
  • packages/extra/ingress/tests/default_ssl_certificate_test.yaml
  • packages/system/cozystack-controller/Makefile
  • packages/system/cozystack-controller/templates/rbac.yaml
  • packages/system/cozystack-controller/tests/rbac_test.yaml
✅ Files skipped from review due to trivial changes (3)
  • packages/system/cozystack-controller/tests/rbac_test.yaml
  • packages/core/platform/values.yaml
  • packages/extra/gateway/README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/extra/ingress/templates/nginx-ingress.yaml
  • packages/extra/ingress/tests/default_ssl_certificate_test.yaml

Comment thread internal/controller/wildcardsecret/reconciler_test.go Outdated
Comment thread internal/controller/wildcardsecret/reconciler.go
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch 2 times, most recently from 5dd524a to cf97635 Compare June 23, 2026 00:14
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as draft June 23, 2026 00:22

@myasnikovdaniil myasnikovdaniil 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.

APPROVE — thorough static review (Go build/vet + 18 reconciler unit tests + both helm-unittest suites + pre-commit all pass locally; not deployed: a storage roll is in progress on the dev cluster and this would require an operator redeploy).

This is the right trust model and it's implemented and documented carefully. Highlights I verified:

Security model (the make-or-break). The private-key replica lands only in namespaces that genuinely own a termination point — ownsTerminationPoint keys on the owner label equalling the namespace's own name, and that label is platform-written by packages/apps/tenant/templates/namespace.yaml (set to own-name only when the tenant runs its own ingress/Gateway), not tenant-editable. So the target set isn't spoofable in the normal trust model, inheriting namespaces are correctly excluded, and the publishing namespace is excluded so the source is never self-overwritten. The blast radius (a terminating tenant holding the wildcard key can impersonate any subdomain under the apex) is the same exposure as the per-host ACME Secret it replaces, and it's spelled out plainly in both values.yaml and the gateway README. Default-off (wildcardSecretName: ""), so existing installs are unchanged.

Cache safety. Scoping the manager-wide Secret informer to replicas + the values channel is safe here because none of the five sibling controllers (WorkloadMonitor, Workload, ApplicationDefinition×2, TenantGateway) nor the telemetry collector read Secrets via the cached client, and the other Secret-reading controllers (fluxplunger, backupcontroller, cozyvaluesreplicator) live in separate binaries. TestSecretCacheByObject_* pins the scope against future widening.

Correctness. Non-destructive on transient source/channel absence, prune-everything only on explicit disable, foreign-Secret collisions never clobber and never block siblings, rotation via bounded resync (source name is dynamic, so it can't be watched), replica self-heal. The test matrix is excellent.

Composition with #2988/#2989. Under wildcard mode the gateway renders certMode: existingSecret and mints no cert-manager Certificate, so there's no competing same-named Secret and no double-management.

make generate. The +kubebuilder:rbac markers are documentation only in this repo (no controller-gen role generation); RBAC is hand-maintained in the chart and correctly updated. No drift.

The only red CI check is the known cilium "IP already in use" E2E flake, unrelated to this change — the cozystack-controller HelmRelease upgraded successfully in that same run.

One optional, non-blocking suggestion left inline about surfacing the foreign-collision case as an Event.

// skip it, do not requeue on it. Any other error is transient,
// so aggregate and return it for a back-off requeue.
if errors.Is(err, errForeignCollision) {
logger.Info("skipping wildcard replica: a non-managed Secret of the same name exists", "namespace", ns)

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.

Optional (non-blocking): a foreign-Secret collision is handled with logger.Info + continue and no requeue — which is the correct safety behavior (never clobber a user Secret, retrying can't help). But for an operator it's silent: a tenant that happens to have a Secret of the wildcard name will simply never receive the wildcard, discoverable only by reading controller logs. Consider emitting a Warning Event on the affected namespace (or surfacing a status condition) so the misconfiguration is visible via kubectl get events. Not required for merge.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — done: the skipped collision now emits a Warning Event on the affected namespace (kept the log line), so it shows up in kubectl get events without grepping controller logs.

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch 5 times, most recently from 6ff0254 to a367eae Compare June 23, 2026 09:07
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review June 23, 2026 09:21

@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 new wildcardsecret controller that replicates the operator-provided wildcard TLS Secret into tenant namespaces that terminate TLS, allowing per-tenant ingress controllers and Gateways to serve it locally without cross-namespace reads. The implementation includes the reconciler logic, extensive unit tests, Helm chart updates, and RBAC adjustments. The review feedback suggests optimizing the reconciler by utilizing the cached client (r.Get and r.List) instead of the uncached API reader (r.Reader) for resources that are already cached, such as the platform values Secret, Namespaces, and copy-labeled Secrets, thereby reducing unnecessary API server load.

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.

// and logs a warning naming the namespace it looked in.
func (r *Reconciler) readConfig(ctx context.Context) (name, namespace string, present bool, err error) {
values := &corev1.Secret{}
err = r.Reader.Get(ctx, configKey, values)

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

Since the platform values Secret (cozystack-values) is explicitly watched and cached via SecretCacheByObject, we can read it directly from the cache using r.Get instead of bypassing the cache with r.Reader.Get. This reduces unnecessary direct API server load.

Suggested change
err = r.Reader.Get(ctx, configKey, values)
err = r.Get(ctx, configKey, values)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — cozy-system/cozystack-values is explicitly in the scoped cache (SecretCacheByObject), so this read now goes through the cached client instead of the APIReader.

// Secret already lives there and must not be overwritten by a replica).
func (r *Reconciler) terminationNamespaces(ctx context.Context, sourceNS string) ([]string, error) {
list := &corev1.NamespaceList{}
if err := r.Reader.List(ctx, list); err != nil {

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

Namespaces are cached by default in the manager's cache. We can use r.List instead of r.Reader.List to list namespaces from the cache, avoiding a direct API server call that can be expensive on large clusters.

Suggested change
if err := r.Reader.List(ctx, list); err != nil {
if err := r.List(ctx, list); err != nil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — the namespace list now uses the cached client. Pinned by TestReconcile_ReadsRouteToCacheExceptSourceAndCollision, which asserts the namespace list is served from the cache and not the APIReader.

// reads as its source, and deleting it would flap the source.
func (r *Reconciler) pruneCopies(ctx context.Context, keep []string, sourceNS, sourceName string) error {
list := &corev1.SecretList{}
if err := r.Reader.List(ctx, list, client.MatchingLabels{CopyLabel: "true"}); err != nil {

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

Since the manager's Secret cache is configured to cache all Secrets with the CopyLabel label, we can list them directly from the cache using r.List instead of r.Reader.List. This avoids direct API server queries for resources that are already cached.

Suggested change
if err := r.Reader.List(ctx, list, client.MatchingLabels{CopyLabel: "true"}); err != nil {
if err := r.List(ctx, list, client.MatchingLabels{CopyLabel: "true"}); err != nil {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — the managed-replica prune list now reads from the cached client (the CopyLabel replicas are cached). A stale cache here can only defer a safe prune, never delete a live replica, because of the keepSet and source-slot guards. The two reads the scoped cache cannot serve — the dynamic-name source Get and the foreign-collision check — deliberately stay on the uncached APIReader, and the doc comments were updated to spell out that split.

@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as draft June 23, 2026 11:34
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch from a367eae to 1ac6350 Compare June 23, 2026 12:21
@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review June 23, 2026 12:22

@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 new wildcardsecret controller that replicates the operator-provided wildcard TLS Secret into tenant namespaces terminating TLS, ensuring secure local access without cross-namespace reads. The changes include the reconciler implementation, extensive unit tests, RBAC updates, and integration with ingress and gateway packages. The review feedback recommends a robustness improvement to validate that the source Secret contains non-empty tls.crt and tls.key data before replication to prevent propagating malformed secrets and causing cluster-wide TLS outages.

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 +238 to +243
if src.Type != corev1.SecretTypeTLS {
// Misconfigured source type — keep replicas, poll for a fix.
logger.Info("wildcard source is not a kubernetes.io/tls Secret; keeping existing replicas",
"secret", pubNS+"/"+name, "type", src.Type)
return ctrl.Result{RequeueAfter: sourceResyncInterval}, nil
}

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

While checking that the source Secret is of type kubernetes.io/tls is a good first step, it is also highly recommended to validate that the Secret actually contains non-empty tls.crt and tls.key data before replicating it. If an operator accidentally creates or updates the source Secret to be empty or malformed (e.g., during a failed ACME renewal), propagating this empty Secret to all tenant namespaces would cause a cluster-wide TLS outage for all tenants. Validating the presence of these keys ensures we fail-safe and keep the existing valid replicas in place.

Suggested change
if src.Type != corev1.SecretTypeTLS {
// Misconfigured source type — keep replicas, poll for a fix.
logger.Info("wildcard source is not a kubernetes.io/tls Secret; keeping existing replicas",
"secret", pubNS+"/"+name, "type", src.Type)
return ctrl.Result{RequeueAfter: sourceResyncInterval}, nil
}
if src.Type != corev1.SecretTypeTLS {
// Misconfigured source type — keep replicas, poll for a fix.
logger.Info("wildcard source is not a kubernetes.io/tls Secret; keeping existing replicas",
"secret", pubNS+"/"+name, "type", src.Type)
return ctrl.Result{RequeueAfter: sourceResyncInterval}, nil
}
if len(src.Data["tls.crt"]) == 0 || len(src.Data["tls.key"]) == 0 {
logger.Info("wildcard source Secret is missing tls.crt or tls.key; keeping existing replicas",
"secret", pubNS+"/"+name)
return ctrl.Result{RequeueAfter: sourceResyncInterval}, nil
}

@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch from 1ac6350 to 93f2932 Compare June 30, 2026 10:34
@lexfrei

Copy link
Copy Markdown
Contributor Author

myasnikovdaniil rebased onto main and E2E is green — the controller main.go conflict is resolved, keeping both the serviceexposure and wildcard-secret reconciler registrations. Ready for re-review when you have a moment.

…espaces

The operator-provided wildcard MVP serves the root tenant only: the root
ingress controller and Gateway read a TLS Secret from their own namespace.
Child tenants run their own ingress controller / Gateway in a separate,
default-deny namespace and cannot read that Secret cross-namespace.

Add a controller that mirrors the operator wildcard Secret into every
tenant namespace that owns a TLS termination point (its
namespace.cozystack.io/ingress or .../gateway label equals its own name).
The source is identified by name from the platform values channel
(_cluster.wildcard-secret-name) with no extra operator input; replicas
carry a managed-by label and refresh on rotation. An individual replica is
removed when its namespace stops terminating TLS; every replica is torn
down only on an explicit disable (clearing wildcard-secret-name). A missing
or mistyped source, or a transiently-absent values channel, keeps existing
replicas, so tenant TLS never drops on a transient gap. The controller's
Secret cache is scoped to managed replicas and the values channel, not
every cluster Secret. Only the platform controller's ServiceAccount gains
Secret writes — no tenant RBAC is widened, and every consumer reads only
its own-namespace copy.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…llers

In operator wildcard mode each tenant's ingress controller now sets default-ssl-certificate to the wildcard Secret in its own namespace, so a child tenant serves the replicated wildcard instead of falling back to per-host ACME. ingress-nginx reads the default certificate only from its own namespace, and each ingress HelmRelease renders in the namespace that owns the controller, so the reference stays same-namespace for the root and every child alike.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei
Aleksei Sviridkin (lexfrei) force-pushed the feat/wildcard-secret-tenant-propagation branch from 93f2932 to 8f60b27 Compare July 1, 2026 08:10

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approve.

The wildcard-secret replication controller is mechanically sound and the tenant-isolation invariant holds. ownsTerminationPoint selects a namespace only when the operator-controlled namespace.cozystack.io/{ingress,gateway} label equals the namespace's own name; that label value derives from .Release.Namespace via Flux, and tenants set only boolean flags, so a tenant cannot redirect replication. Prune only touches Secrets carrying the copy label, and the informer cache is scoped to copy-labeled replicas plus the values Secret. go test passes (24 cases incl. foreign-collision no-overwrite, prune-on-demotion, cache-scope pin) and helm unittest passes.

Non-blocking:

  • RBAC: the ClusterRole gains cluster-wide secrets write. With dynamic target namespaces this can't be narrowed via resourceNames, and the reconciler only writes copy-labeled Secrets, but the grant is broad — worth a short justifying comment in the manifest.
  • Suggest an operator-facing upgrade note: on upgrade, child tenants that terminate TLS switch from per-host ACME to the replicated wildcard, and stale per-host ACME Secrets are left in place (inert), not pruned.

Review scope note: verified statically + unit tests. The upgrade-time claim that ingress-nginx picks up the replica without a restart or connection drop was reasoned from how nginx watches the Secret, not observed on a live cluster.

@lexfrei

Copy link
Copy Markdown
Contributor Author

Moving this back to draft. It is approved and the code is sound — the blocker is a collision with work that landed after the review, and it is not one a merge conflict would surface.

This PR scopes the manager's Secret informer through wildcardsecret.SecretCacheByObject(): AllNamespaces with a label selector on cozystack.io/wildcard-secret-copy, plus the values-channel namespace by field selector. That scoping is deliberate and correct — a cluster-wide Secret watch would otherwise cache every tenant's key material in memory.

The CA extraction controller (#3286) needs exactly the same knob, with a different label selector, also over AllNamespaces. And cache.Options.ByObject is a map[client.Object]cache.ByObject — one entry per type. Two AllNamespaces configs for &corev1.Secret{} cannot coexist, and the obvious workaround does not exist either: a label selector is serialised and evaluated server-side, so an OR across two different label keys is not expressible at all.

Whoever merges second therefore cannot fix this by resolving a hunk. It is architectural, and it should be settled before either lands rather than discovered at merge time.

The agreed direction is a shared marker key: both controllers scope their cache off one label key carrying different values, so a single Exists selector covers both classes of Secret, each controller then filtering by value in its own predicate. That keeps one narrow informer, keeps tenant key material out of the cache, and leaves both reconcilers independent.

I will push the reworked scoping here once the exact key and values are settled alongside #3286. Nothing else about this PR changes.

@lexfrei
Aleksei Sviridkin (lexfrei) marked this pull request as ready for review July 14, 2026 14:09
@lexfrei

Copy link
Copy Markdown
Contributor Author

Back to ready — this PR no longer needs to change, and I was wrong about the fix.

I said above that the direction was a shared marker label, so that one Exists selector could cover both this reconciler's replicas and the CA extraction controller's sources. That was the wrong call. A shared marker couples two unrelated features through a label: it stops meaning a domain fact ("this Secret is a managed wildcard replica") and starts meaning a plumbing fact ("this Secret happens to sit in some controller's cache"), which every future controller would then have to know about, invisibly at the point of use.

The CA extraction controller will instead run its own cache with its own selector, leaving the manager-level scoping here untouched. Two narrow Secret informers rather than one — a real but small cost, in exchange for the two features staying entirely independent, with nothing to coordinate at merge time.

One thing that surfaced while working this through, worth recording here because it constrains any future change to this reconciler: upsertCopy compares existing.Data on a Secret it reads through the cache. That is fine today, and it is fine under this decision. But it rules out the tempting shortcut of widening the Secret cache and stripping Data with a Transform — the comparison would then always see nil, and this reconciler would break silently while its tests, which do not exercise the cache, stayed green.

No changes pushed. Requesting merge as previously approved.

@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 new wildcardsecret controller that replicates the operator-provided wildcard TLS Secret into tenant namespaces that terminate TLS. This allows per-tenant ingress controllers and Gateways to serve TLS from their own namespaces without cross-namespace Secret reads or widened RBAC. The controller uses a scoped Secret cache to minimize memory footprint. Corresponding updates are made to the ingress and gateway packages, RBAC permissions, and documentation. The review feedback suggests a minor optimization in cloneData to return nil directly if the input map is empty, avoiding unnecessary map allocation.

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 +462 to +470
func cloneData(in map[string][]byte) map[string][]byte {
out := make(map[string][]byte, len(in))
for k, v := range in {
b := make([]byte, len(v))
copy(b, v)
out[k] = b
}
return out
}

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

If the input map in is nil or empty, we can return nil directly to avoid unnecessary map allocation. In Kubernetes, a nil Data field on a Secret is functionally equivalent to an empty map and is often preferred.

Suggested change
func cloneData(in map[string][]byte) map[string][]byte {
out := make(map[string][]byte, len(in))
for k, v := range in {
b := make([]byte, len(v))
copy(b, v)
out[k] = b
}
return out
}
func cloneData(in map[string][]byte) map[string][]byte {
if len(in) == 0 {
return nil
}
out := make(map[string][]byte, len(in))
for k, v := range in {
b := make([]byte, len(v))
copy(b, v)
out[k] = b
}
return out
}

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit 249f7b5 into main Jul 15, 2026
73 of 75 checks passed
@lexfrei
Aleksei Sviridkin (lexfrei) deleted the feat/wildcard-secret-tenant-propagation branch July 15, 2026 08:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/networking Issues or PRs related to networking (ingress, gateway, vpn, metallb, kube-ovn) area/platform Issues or PRs related to platform infrastructure (bundle, flux, talos, installer) kind/feature Categorizes issue or PR as related to a new feature size/XXL This PR changes 1000+ lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(platform): propagate operator wildcard certificate to per-tenant ingress controllers

3 participants