feat(platform): operator-provided wildcard certificate for platform and tenant ingress - #2819
Conversation
|
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 (18)
🚧 Files skipped from review as they are similar to previous changes (9)
📝 WalkthroughWalkthroughAdds an operator-provided wildcard TLS path: new TenantGateway cert mode ChangesOperator-provided wildcard TLS certificate mode
Estimated code review effort 🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ 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 |
6aa5e8c to
04217a7
Compare
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 implements a native way for operators to provide their own wildcard TLS certificate for platform services and the root tenant's ingress. By referencing a pre-existing Secret, the system avoids the overhead and complexity of per-host ACME certificate issuance. The changes span the API definitions, controller reconciliation logic, and various Helm chart templates to ensure consistent behavior across the platform, including proper cleanup of legacy ACME resources during mode transitions. 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. Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces an operator-supplied wildcard certificate mode (existingSecret) across Cozystack, allowing platform services and tenant gateways to use a pre-existing TLS Secret instead of issuing certificates via ACME. The changes span the TenantGateway CRD, controller reconciler, and various application ingress templates (such as Harbor, SeaweedFS, Keycloak, and Grafana) along with corresponding unit tests. The review feedback correctly identifies a critical issue across all modified ingress templates: completely omitting the tls block in ingress-nginx disables HTTPS/TLS for the host entirely. To successfully fall back to the controller's default SSL certificate, the tls block must remain defined with the hosts list while only the secretName is omitted. The associated unit tests should also be updated to assert this correct structure.
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.
| {{- if not $wildcardSecret }} | ||
| tls: | ||
| - hosts: | ||
| - dashboard.{{ $host }} | ||
| secretName: dashboard-web-tls | ||
| {{- end }} |
There was a problem hiding this comment.
In ingress-nginx, completely omitting the tls block disables HTTPS/TLS for the host entirely. To use the operator-provided default SSL certificate, the tls block must be present with the hosts list, but the secretName should be omitted. This allows ingress-nginx to fall back to the default SSL certificate configured on the controller.
tls:
- hosts:
- dashboard.{{ $host }}
{{- if not $wildcardSecret }}
secretName: dashboard-web-tls
{{- end }}There was a problem hiding this comment.
The premise isn't quite right: without a tls section ingress-nginx still serves the host on 443 using the default SSL certificate. What is actually lost is the forced HTTP-to-HTTPS redirect — the controller only issues the 308 redirect when TLS is enabled for the Ingress. That redirect is worth keeping, so the tls section now stays with the host list and only the per-host secretName is omitted in wildcard mode. Fixed in 9e3ae74.
| {{- if not $wildcardSecret }} | ||
| tls: | ||
| - hosts: | ||
| - "{{ printf "grafana.%s" (.Values.host | default $host) }}" | ||
| secretName: grafana-ingress-tls | ||
| {{- end }} |
There was a problem hiding this comment.
In ingress-nginx, completely omitting the tls block disables HTTPS/TLS for the host entirely. To use the operator-provided default SSL certificate, the tls block must be present with the hosts list, but the secretName should be omitted. This allows ingress-nginx to fall back to the default SSL certificate configured on the controller.
tls:
- hosts:
- "{{ printf "grafana.%s" (.Values.host | default $host) }}"
{{- if not $wildcardSecret }}
secretName: grafana-ingress-tls
{{- end }}There was a problem hiding this comment.
The premise isn't quite right: without a tls section ingress-nginx still serves the host on 443 using the default SSL certificate. What is actually lost is the forced HTTP-to-HTTPS redirect — the controller only issues the 308 redirect when TLS is enabled for the Ingress. That redirect is worth keeping, so the tls section now stays with the host list and only the per-host secretName is omitted in wildcard mode. Fixed in 9e3ae74.
| {{- if not $wildcardSecret }} | ||
| tls: | ||
| - hosts: | ||
| - "{{ printf "bootbox.%s" (.Values.host | default $host) }}" | ||
| secretName: bootbox-tls | ||
| {{- end }} |
There was a problem hiding this comment.
In ingress-nginx, completely omitting the tls block disables HTTPS/TLS for the host entirely. To use the operator-provided default SSL certificate, the tls block must be present with the hosts list, but the secretName should be omitted. This allows ingress-nginx to fall back to the default SSL certificate configured on the controller.
tls:
- hosts:
- "{{ printf "bootbox.%s" (.Values.host | default $host) }}"
{{- if not $wildcardSecret }}
secretName: bootbox-tls
{{- end }}There was a problem hiding this comment.
The premise isn't quite right: without a tls section ingress-nginx still serves the host on 443 using the default SSL certificate. What is actually lost is the forced HTTP-to-HTTPS redirect — the controller only issues the 308 redirect when TLS is enabled for the Ingress. That redirect is worth keeping, so the tls section now stays with the host list and only the per-host secretName is omitted in wildcard mode. Fixed in 9e3ae74.
| {{- if not $wildcardSecret }} | ||
| tls: | ||
| - hosts: | ||
| - "{{ printf "alerta.%s" (.Values.host | default $host) }}" | ||
| secretName: alerta-tls | ||
| {{- end }} |
There was a problem hiding this comment.
In ingress-nginx, completely omitting the tls block disables HTTPS/TLS for the host entirely. To use the operator-provided default SSL certificate, the tls block must be present with the hosts list, but the secretName should be omitted. This allows ingress-nginx to fall back to the default SSL certificate configured on the controller.
tls:
- hosts:
- "{{ printf "alerta.%s" (.Values.host | default $host) }}"
{{- if not $wildcardSecret }}
secretName: alerta-tls
{{- end }}There was a problem hiding this comment.
The premise isn't quite right: without a tls section ingress-nginx still serves the host on 443 using the default SSL certificate. What is actually lost is the forced HTTP-to-HTTPS redirect — the controller only issues the 308 redirect when TLS is enabled for the Ingress. That redirect is worth keeping, so the tls section now stays with the host list and only the per-host secretName is omitted in wildcard mode. Fixed in 9e3ae74.
| {{- if not $wildcardSecret }} | ||
| tls: | ||
| - hosts: | ||
| - linstor-gui.{{ $host }} | ||
| secretName: linstor-gui-ingress-tls | ||
| {{- end }} |
There was a problem hiding this comment.
In ingress-nginx, completely omitting the tls block disables HTTPS/TLS for the host entirely. To use the operator-provided default SSL certificate, the tls block must be present with the hosts list, but the secretName should be omitted. This allows ingress-nginx to fall back to the default SSL certificate configured on the controller.
tls:
- hosts:
- linstor-gui.{{ $host }}
{{- if not $wildcardSecret }}
secretName: linstor-gui-ingress-tls
{{- end }}There was a problem hiding this comment.
The premise isn't quite right: without a tls section ingress-nginx still serves the host on 443 using the default SSL certificate. What is actually lost is the forced HTTP-to-HTTPS redirect — the controller only issues the 308 redirect when TLS is enabled for the Ingress. That redirect is worth keeping, so the tls section now stays with the host list and only the per-host secretName is omitted in wildcard mode. Fixed in 9e3ae74.
| - notExists: | ||
| path: spec.tls |
There was a problem hiding this comment.
There was a problem hiding this comment.
Updated in 9e3ae74 — the assertion now verifies that the tls section exists and secretName is absent.
| - notExists: | ||
| path: spec.tls |
There was a problem hiding this comment.
There was a problem hiding this comment.
Updated in 9e3ae74 — the assertion now verifies that the tls section exists and secretName is absent.
| - notExists: | ||
| path: spec.tls |
There was a problem hiding this comment.
There was a problem hiding this comment.
Updated in 9e3ae74 — the assertion now verifies that the tls section exists and secretName is absent.
| - notExists: | ||
| path: spec.tls |
There was a problem hiding this comment.
There was a problem hiding this comment.
Updated in 9e3ae74 — the assertion now verifies that the tls section exists and secretName is absent.
| - notExists: | ||
| path: spec.ingress.spec.tls |
There was a problem hiding this comment.
Since the tls block should be kept to allow ingress-nginx to fall back to the default SSL certificate, update the test assertion to verify that spec.ingress.spec.tls exists but spec.ingress.spec.tls[0].secretName is absent.
- exists:
path: spec.ingress.spec.tls
- notExists:
path: spec.ingress.spec.tls[0].secretNameThere was a problem hiding this comment.
Updated in 9e3ae74 — the assertion now verifies that the tls section exists and secretName is absent.
Operators who already hold a wildcard TLS certificate (purchased, or issued by a corporate CA) had no way to make a tenant Gateway serve under it: the only cert modes were http01 (per-listener ACME) and dns01 (controller-minted wildcard via ACME). Add a third mode, existingSecret, that references a pre-existing TLS Secret in the TenantGateway's own namespace via spec.wildcardSecretRef. In this mode the controller mints no ACME Issuer and no Certificate; the wildcard, apex, and per-child-apex HTTPS listeners reference the supplied Secret directly. A missing wildcardSecretRef fails fast with Ready=False rather than producing a Gateway pointing at a nonexistent Secret. Switching into existingSecret from http01/dns01 reclaims the now-unused Issuer and Certificates so no ACME machinery is left behind. The non-cert listeners (http->https redirect, TLS passthrough) are unaffected and continue to render. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add publishing.certificates.wildcardSecretName to the platform chart and propagate it through the cozystack-values channel as _cluster.wildcard-secret-name. Only the Secret NAME travels — the cert/key material is never broadcast, unlike the public kube-root-ca that already rides this channel. When the key is set, the gateway chart switches the rendered TenantGateway to certMode=existingSecret with a wildcardSecretRef pointing at the operator-supplied Secret, and skips the ACME solver/provider/issuer validations that no longer apply. Wildcard mode takes precedence over solver=dns01. TLS-passthrough services and attached namespaces continue to render unchanged. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
When publishing.certificates.wildcardSecretName is set, the root tenant's ingress-nginx controller now serves the operator-supplied wildcard Secret as its --default-ssl-certificate, and the platform service Ingresses stop requesting their own per-host ACME certificates. The flag is set only on the publishing controller (Release.Namespace == expose-ingress) because ingress-nginx loads the Secret from its own namespace; child-tenant controllers cannot reach a Secret in another namespace, so they keep the existing ACME path. Each service Ingress (dashboard, keycloak, grafana, alerta, bucket, linstor-gui, harbor, seaweedfs S3, bootbox) drops its cert-manager cluster-issuer annotation, the http01 challenge-class annotation, and its tls block in wildcard mode; nginx then falls back to the default certificate for those hosts. Other annotations and routing are unchanged. Each file keeps its existing render guard intact, and the annotations block collapses cleanly where the cert annotations were its only content. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
04217a7 to
b1f5d95
Compare
Dropping the whole tls block in wildcard mode disabled the forced HTTP-to-HTTPS redirect: ingress-nginx only issues the 308 redirect when TLS is enabled for the Ingress, while a host without a tls section is still served on 443 with the default certificate but stays reachable over plain HTTP. Keep the tls section with the host list and omit only the per-host secretName, so ingress-nginx falls back to the operator wildcard configured as the default SSL certificate and the redirect stays enforced, matching the non-wildcard behaviour. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
api/gateway/v1alpha1/tenantgateway_types.go (1)
170-177: ⚡ Quick winEnforce
existingSecretrequiredness in the CRD schema (not only in comments).
WildcardSecretRefis documented as required forcertMode=existingSecret, but admission currently allows missing/empty values. Add a kubebuilder XValidation so invalid specs are rejected before reconcile.Suggested marker
type TenantGatewaySpec struct { @@ // +optional WildcardSecretRef *corev1.LocalObjectReference `json:"wildcardSecretRef,omitempty"` + + // +kubebuilder:validation:XValidation:rule="self.certMode != 'existingSecret' || (has(self.wildcardSecretRef) && self.wildcardSecretRef.name != '')",message="spec.wildcardSecretRef.name is required when certMode=existingSecret"As per coding guidelines,
api/**: “Define Kubernetes CRDs in theapi/directory following kubebuilder patterns and controller-runtime conventions.”🤖 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 `@api/gateway/v1alpha1/tenantgateway_types.go` around lines 170 - 177, The CRD currently documents that WildcardSecretRef on the TenantGateway is required when certMode == "existingSecret" but does not enforce it; add a kubebuilder XValidation marker on the TenantGateway spec type to reject empty/missing WildcardSecretRef when CertMode equals "existingSecret" (use an XValidation rule referencing .spec.certMode and .spec.wildcardSecretRef such that when .spec.certMode == "existingSecret" then .spec.wildcardSecretRef must be present and not null/empty). Update the struct tags/markers near the WildcardSecretRef / TenantGateway spec declaration so the generated CRD enforces this constraint at admission time.Source: Coding guidelines
🤖 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/tenantgateway/reconciler.go`:
- Around line 66-73: The helper wildcardListenerCertName only validates the
wildcardSecretRef.name string but does not verify the referenced Secret exists
or is of type kubernetes.io/tls, so reconcile may render listeners referencing a
missing/invalid Secret; update wildcardListenerCertName (or add a new
validateWildcardSecret helper) to fetch the Secret via the controller client
(using the TenantGateway namespace and tgw.Spec.WildcardSecretRef.Name), return
an error if the Secret is not found or its Type != corev1.SecretTypeTLS, and
propagate that error back to the reconciler so reconciliation fails fast when
CertModeExistingSecret is configured.
---
Nitpick comments:
In `@api/gateway/v1alpha1/tenantgateway_types.go`:
- Around line 170-177: The CRD currently documents that WildcardSecretRef on the
TenantGateway is required when certMode == "existingSecret" but does not enforce
it; add a kubebuilder XValidation marker on the TenantGateway spec type to
reject empty/missing WildcardSecretRef when CertMode equals "existingSecret"
(use an XValidation rule referencing .spec.certMode and .spec.wildcardSecretRef
such that when .spec.certMode == "existingSecret" then .spec.wildcardSecretRef
must be present and not null/empty). Update the struct tags/markers near the
WildcardSecretRef / TenantGateway spec declaration so the generated CRD enforces
this constraint at admission time.
🪄 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: 4d986e0e-81dd-458d-8b3a-5a68e722d38a
📒 Files selected for processing (38)
api/gateway/v1alpha1/tenantgateway_types.goapi/gateway/v1alpha1/zz_generated.deepcopy.gointernal/controller/tenantgateway/reconciler.gointernal/controller/tenantgateway/reconciler_test.gointernal/controller/tenantgateway/renderers.gopackages/apps/harbor/Makefilepackages/apps/harbor/templates/ingress.yamlpackages/apps/harbor/tests/ingress_wildcard_test.yamlpackages/core/platform/templates/apps.yamlpackages/core/platform/tests/apps_wildcard_secret_test.yamlpackages/core/platform/values.yamlpackages/extra/bootbox/Makefilepackages/extra/bootbox/templates/matchbox/ingress.yamlpackages/extra/bootbox/tests/ingress_wildcard_test.yamlpackages/extra/gateway/README.mdpackages/extra/gateway/templates/tenantgateway.yamlpackages/extra/gateway/tests/tenantgateway_test.yamlpackages/extra/ingress/templates/nginx-ingress.yamlpackages/extra/ingress/tests/default_ssl_certificate_test.yamlpackages/extra/seaweedfs/Makefilepackages/extra/seaweedfs/templates/seaweedfs.yamlpackages/extra/seaweedfs/tests/ingress_wildcard_test.yamlpackages/system/bucket/Makefilepackages/system/bucket/templates/ingress.yamlpackages/system/bucket/tests/ingress_wildcard_test.yamlpackages/system/cozystack-controller/definitions/gateway.cozystack.io_tenantgateways.yamlpackages/system/dashboard/templates/ingress.yamlpackages/system/dashboard/tests/ingress_wildcard_test.yamlpackages/system/keycloak/Makefilepackages/system/keycloak/templates/ingress.yamlpackages/system/keycloak/tests/ingress_wildcard_test.yamlpackages/system/linstor-gui/templates/ingress.yamlpackages/system/linstor-gui/tests/ingress_wildcard_test.yamlpackages/system/monitoring/Makefilepackages/system/monitoring/templates/alerta/alerta.yamlpackages/system/monitoring/templates/grafana/grafana.yamlpackages/system/monitoring/tests/alerta_ingress_wildcard_test.yamlpackages/system/monitoring/tests/grafana_ingress_wildcard_test.yaml
| func wildcardListenerCertName(tgw *gatewayv1alpha1.TenantGateway) (string, error) { | ||
| if tgw.Spec.CertMode == gatewayv1alpha1.CertModeExistingSecret { | ||
| if tgw.Spec.WildcardSecretRef == nil || tgw.Spec.WildcardSecretRef.Name == "" { | ||
| return "", fmt.Errorf("certMode=existingSecret requires spec.wildcardSecretRef.name to be set") | ||
| } | ||
| return tgw.Spec.WildcardSecretRef.Name, nil | ||
| } | ||
| return gatewayCertificateName(tgw), nil |
There was a problem hiding this comment.
existingSecret mode does not fail fast on missing/invalid Secret objects.
The helper only checks spec.wildcardSecretRef.name; reconcile can still render Gateway listeners that point to a Secret that does not exist or is not kubernetes.io/tls.
Minimal hardening sketch
func (r *Reconciler) runReconcileSteps(ctx context.Context, tgw *gatewayv1alpha1.TenantGateway) error {
+ if err := r.validateExistingSecret(ctx, tgw); err != nil {
+ return fmt.Errorf("validate existingSecret wildcard secret: %w", err)
+ }
claims, err := r.collectHostnameClaims(ctx, tgw)
@@
}
+
+func (r *Reconciler) validateExistingSecret(ctx context.Context, tgw *gatewayv1alpha1.TenantGateway) error {
+ if tgw.Spec.CertMode != gatewayv1alpha1.CertModeExistingSecret {
+ return nil
+ }
+ if tgw.Spec.WildcardSecretRef == nil || tgw.Spec.WildcardSecretRef.Name == "" {
+ return fmt.Errorf("certMode=existingSecret requires spec.wildcardSecretRef.name to be set")
+ }
+ sec := &corev1.Secret{}
+ if err := r.Get(ctx, types.NamespacedName{
+ Namespace: tgw.Namespace,
+ Name: tgw.Spec.WildcardSecretRef.Name,
+ }, sec); err != nil {
+ return err
+ }
+ if sec.Type != corev1.SecretTypeTLS {
+ return fmt.Errorf("secret %s/%s must be type %q", tgw.Namespace, sec.Name, corev1.SecretTypeTLS)
+ }
+ return nil
+}🤖 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/tenantgateway/reconciler.go` around lines 66 - 73, The
helper wildcardListenerCertName only validates the wildcardSecretRef.name string
but does not verify the referenced Secret exists or is of type
kubernetes.io/tls, so reconcile may render listeners referencing a
missing/invalid Secret; update wildcardListenerCertName (or add a new
validateWildcardSecret helper) to fetch the Secret via the controller client
(using the TenantGateway namespace and tgw.Spec.WildcardSecretRef.Name), return
an error if the Secret is not found or its Type != corev1.SecretTypeTLS, and
propagate that error back to the reconciler so reconciliation fails fast when
CertModeExistingSecret is configured.
myasnikovdaniil
left a comment
There was a problem hiding this comment.
LGTM. Reviewed the full change end-to-end — the design is clean and the implementation is careful:
- Only the Secret name travels the
cozystack-valueschannel, never the cert/key material. - All references are same-namespace (no ReferenceGrant / RBAC / NetworkPolicy changes), matching the documented root-tenant scope.
- The
dns01 → existingSecretswitch garbage-collects both the owned Issuer (reconcileIssuer) and the owned wildcard Certificate (reconcileWildcardCertificate's!= dns01branch), both ownership-guarded — so cert-manager can't overwrite the operator-supplied wildcard Secret. - Completeness: every ingress template referencing cert-manager is gated on
wildcard-secret-name; nothing is left minting per-host ACME in wildcard mode. - Keeping the
tls:block withoutsecretName(commitfix(ingress): keep tls section…) is the right call — nginx then enforces the HTTPS redirect and falls back to--default-ssl-certificate.
Verified locally: go test ./internal/controller/tenantgateway/... passes, and helm unittest is green across every affected package (ingress, gateway, dashboard, keycloak, bucket, harbor, linstor-gui, monitoring, seaweedfs, bootbox, platform). Nice touch adding the test: targets so those packages actually execute in the unit-test job.
Two minor, non-blocking nits inline.
| // existingSecret mode references an operator-supplied Secret and | ||
| // mints no ACME Issuer. Delete any owned Issuer left from a | ||
| // previous http01/dns01 phase so the mode switch doesn't leak | ||
| // ACME machinery. Same ownership-guarded cleanup contract as |
There was a problem hiding this comment.
This references "reconcileWildcardCertificate's HTTP-01 branch", but that branch is the if tgw.Spec.CertMode != CertModeDNS01 check — which now also runs for existingSecret. Its inline comment ("HTTP-01 mode: …") and especially its log line ("deleted stale wildcard Certificate after switch to HTTP-01") will mis-state the mode on a dns01 → existingSecret switch. Consider generalizing both to e.g. "non-DNS-01 mode" / "after leaving DNS-01". Purely cosmetic — the behavior is correct.
| // and cover the tenant apex (and *.apex). Cross-namespace refs are | ||
| // intentionally unsupported to avoid requiring a ReferenceGrant. | ||
| // +optional | ||
| WildcardSecretRef *corev1.LocalObjectReference `json:"wildcardSecretRef,omitempty"` |
There was a problem hiding this comment.
existingSecret requiring this field is enforced at reconcile time in wildcardListenerCertName (good fail-fast). Optional enhancement: a CRD +kubebuilder:validation:XValidation rule on the spec (e.g. self.certMode != 'existingSecret' || has(self.wildcardSecretRef)) would reject the misconfiguration at kubectl apply time instead of surfacing it as a degraded reconcile. Non-blocking.
What this PR does
Adds an operator-facing way to serve platform services and the root tenant's ingress under a pre-existing wildcard TLS certificate instead of minting per-host ACME certificates. Implements the "native references" design decided on #2812.
A single value,
publishing.certificates.wildcardSecretName, names a TLS Secret the operator has already created in the publishing namespace (tenant-rootby default). Only the name travels through thecozystack-valueschannel — the certificate and private key are never broadcast, unlike the publickube-root-cathat already rides it.When set:
gateway.enabled=false): the publishing tenant's ingress controller serves the Secret as its--default-ssl-certificate, and the platform service Ingresses drop their cert-manager cluster-issuer annotation andtlsblock. No per-host ACME.gateway.enabled=true): a newTenantGatewaycert mode,existingSecret, references the Secret in the wildcard/apex listenercertificateRefsand mints no Issuer or Certificate. Switching into this mode garbage-collects any ACME machinery left fromhttp01/dns01.Scope is the root tenant — everything is same-namespace, so no RBAC or NetworkPolicy changes. Extending wildcard mode to child tenants needs the Secret replicated into each tenant namespace and is left as a follow-up.
Closes #2812. Part of #2811.
Screenshots
N/A — no UI changes.
Release note
Summary by CodeRabbit
New Features
Platform / Templates
Tests
Documentation
Chores