docs(platform): document publishing.certificates wildcard options#588
docs(platform): document publishing.certificates wildcard options#588Aleksei Sviridkin (lexfrei) wants to merge 2 commits into
Conversation
Add reference rows for publishing.certificates.wildcard (opt-in shared DNS-01 wildcard issuance on the default ingress-nginx path, default false, with the gating and coverage caveat) and the previously undocumented publishing.certificates.wildcardSecretName (operator-provided wildcard Secret) to the platform-package value table. Signed-off-by: Aleksei Sviridkin <f@lex.la>
✅ Deploy Preview for cozystack ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Warning Review limit reached
More reviews will be available in 34 minutes and 7 seconds. Learn how PR review limits work. Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file). ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits. 🚦 How do rate limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughAdds two new rows to the Platform Package "Publishing" configuration reference table: ChangesWildcard Certificate Configuration Docs
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes 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 |
There was a problem hiding this comment.
Code Review
This pull request updates the platform package configuration documentation by adding descriptions for two new parameters: publishing.certificates.wildcard and publishing.certificates.wildcardSecretName. The review feedback suggests minor wording improvements to clarify that publishing.certificates.wildcard is a boolean setting rather than a "name", and to clarify the relationship between the publishing namespace and publishing.ingressName in the description of publishing.certificates.wildcardSecretName.
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.
| | `publishing.exposure` | `"externalIPs"` | Exposure mode for the ingress-nginx Service. Possible values: `externalIPs`, `loadBalancer`. The default writes `Service.spec.externalIPs` from `publishing.externalIPs`; `loadBalancer` switches to `Service.type: LoadBalancer` and a `CiliumLoadBalancerIPPool` over the same IPs (with `externalTrafficPolicy: Local` to preserve client source IP). `Service.spec.externalIPs` is deprecated upstream in v1.36 (KEP-5707); plan to switch to `loadBalancer` before upgrading past Kubernetes v1.40 when the `AllowServiceExternalIPs` feature gate flips off. The `loadBalancer` mode requires Cilium L2/BGP announcements to reach the IP from outside the cluster (off by default in cozystack), and at least one address in `publishing.externalIPs` (otherwise render fails). | | ||
| | `publishing.certificates.solver` | `"http01"` | ACME challenge solver type for default letsencrypt issuer. Possible values: `http01`, `dns01`. | | ||
| | `publishing.certificates.issuerName` | `"letsencrypt-prod"` | `ClusterIssuer` name for TLS certificates used in system Helm releases. | | ||
| | `publishing.certificates.wildcard` | `false` | Opt-in shared wildcard certificate on the default ingress-nginx path (`gateway.enabled=false`). When `true` with `solver=dns01` and no `publishing.certificates.wildcardSecretName`, the platform issues one `*.<root-host>` + `<root-host>` `Certificate` via the DNS-01 `ClusterIssuer` and serves it as the ingress controller's default SSL certificate, so system services stop minting a per-host ACME certificate each — avoiding Let's Encrypt rate limits at scale, at parity with the Gateway API path. Ignored on `http01` (cannot issue wildcards) and when `gateway.enabled=true` (the `TenantGateway` controller issues the wildcard there). Coverage caveat: a single-label wildcard does not cover a custom service host outside `*.<root-host>` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested host (`<service>.<tenant>.<root-host>`); those services fall back to the default certificate, which would not cover them. The chosen name rides the cluster values channel that child tenants inherit, so this is not root-tenant scoped — only enable it when every exposed host is covered, or supply a covering `publishing.certificates.wildcardSecretName` instead. Off by default so a `dns01` cluster is never switched silently on upgrade. | |
There was a problem hiding this comment.
In the description for publishing.certificates.wildcard, the phrase "The chosen name" is confusing because this parameter is a boolean (true/false) rather than a name. It would be clearer to refer to "This setting" or "The chosen option" instead.
| | `publishing.certificates.wildcard` | `false` | Opt-in shared wildcard certificate on the default ingress-nginx path (`gateway.enabled=false`). When `true` with `solver=dns01` and no `publishing.certificates.wildcardSecretName`, the platform issues one `*.<root-host>` + `<root-host>` `Certificate` via the DNS-01 `ClusterIssuer` and serves it as the ingress controller's default SSL certificate, so system services stop minting a per-host ACME certificate each — avoiding Let's Encrypt rate limits at scale, at parity with the Gateway API path. Ignored on `http01` (cannot issue wildcards) and when `gateway.enabled=true` (the `TenantGateway` controller issues the wildcard there). Coverage caveat: a single-label wildcard does not cover a custom service host outside `*.<root-host>` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested host (`<service>.<tenant>.<root-host>`); those services fall back to the default certificate, which would not cover them. The chosen name rides the cluster values channel that child tenants inherit, so this is not root-tenant scoped — only enable it when every exposed host is covered, or supply a covering `publishing.certificates.wildcardSecretName` instead. Off by default so a `dns01` cluster is never switched silently on upgrade. | | |
| | `publishing.certificates.wildcard` | `false` | Opt-in shared wildcard certificate on the default ingress-nginx path (`gateway.enabled=false`). When `true` with `solver=dns01` and no `publishing.certificates.wildcardSecretName`, the platform issues one `*.<root-host>` + `<root-host>` `Certificate` via the DNS-01 `ClusterIssuer` and serves it as the ingress controller's default SSL certificate, so system services stop minting a per-host ACME certificate each — avoiding Let's Encrypt rate limits at scale, at parity with the Gateway API path. Ignored on `http01` (cannot issue wildcards) and when `gateway.enabled=true` (the `TenantGateway` controller issues the wildcard there). Coverage caveat: a single-label wildcard does not cover a custom service host outside `*.<root-host>` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested host (`<service>.<tenant>.<root-host>`); those services fall back to the default certificate, which would not cover them. This setting rides the cluster values channel that child tenants inherit, so this is not root-tenant scoped — only enable it when every exposed host is covered, or supply a covering `publishing.certificates.wildcardSecretName` instead. Off by default so a `dns01` cluster is never switched silently on upgrade. | |
There was a problem hiding this comment.
Reworded to "Enabling it propagates the issued wildcard Secret name through the cluster values channel that child tenants inherit" — it refers to the auto-issued Secret name (set when the flag is on), now stated explicitly rather than "the chosen name". Fixed in 7d02b6d.
| | `publishing.certificates.solver` | `"http01"` | ACME challenge solver type for default letsencrypt issuer. Possible values: `http01`, `dns01`. | | ||
| | `publishing.certificates.issuerName` | `"letsencrypt-prod"` | `ClusterIssuer` name for TLS certificates used in system Helm releases. | | ||
| | `publishing.certificates.wildcard` | `false` | Opt-in shared wildcard certificate on the default ingress-nginx path (`gateway.enabled=false`). When `true` with `solver=dns01` and no `publishing.certificates.wildcardSecretName`, the platform issues one `*.<root-host>` + `<root-host>` `Certificate` via the DNS-01 `ClusterIssuer` and serves it as the ingress controller's default SSL certificate, so system services stop minting a per-host ACME certificate each — avoiding Let's Encrypt rate limits at scale, at parity with the Gateway API path. Ignored on `http01` (cannot issue wildcards) and when `gateway.enabled=true` (the `TenantGateway` controller issues the wildcard there). Coverage caveat: a single-label wildcard does not cover a custom service host outside `*.<root-host>` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested host (`<service>.<tenant>.<root-host>`); those services fall back to the default certificate, which would not cover them. The chosen name rides the cluster values channel that child tenants inherit, so this is not root-tenant scoped — only enable it when every exposed host is covered, or supply a covering `publishing.certificates.wildcardSecretName` instead. Off by default so a `dns01` cluster is never switched silently on upgrade. | | ||
| | `publishing.certificates.wildcardSecretName` | `""` | Operator-provided wildcard TLS Secret. When set, platform services and the root tenant's ingress/Gateway serve under this pre-existing Secret instead of minting per-host ACME certificates; only the NAME travels through the platform values channel, never the certificate or private key. The Secret must already exist in the publishing namespace (`publishing.ingressName`, `tenant-root` by default), be of type `kubernetes.io/tls`, and cover the served hosts (typically `*.<root-host>` + `<root-host>`). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | |
There was a problem hiding this comment.
The phrase "publishing namespace (publishing.ingressName, tenant-root by default)" is slightly confusing because publishing.ingressName is defined elsewhere as the ingress controller name, not a namespace. Clarifying that the namespace matches or is derived from this value would improve readability.
| | `publishing.certificates.wildcardSecretName` | `""` | Operator-provided wildcard TLS Secret. When set, platform services and the root tenant's ingress/Gateway serve under this pre-existing Secret instead of minting per-host ACME certificates; only the NAME travels through the platform values channel, never the certificate or private key. The Secret must already exist in the publishing namespace (`publishing.ingressName`, `tenant-root` by default), be of type `kubernetes.io/tls`, and cover the served hosts (typically `*.<root-host>` + `<root-host>`). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | | |
| | `publishing.certificates.wildcardSecretName` | `""` | Operator-provided wildcard TLS Secret. When set, platform services and the root tenant's ingress/Gateway serve under this pre-existing Secret instead of minting per-host ACME certificates; only the NAME travels through the platform values channel, never the certificate or private key. The Secret must already exist in the publishing namespace (which matches `publishing.ingressName`, `tenant-root` by default), be of type `kubernetes.io/tls`, and cover the served hosts (typically `*.<root-host>` + `<root-host>`). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | |
There was a problem hiding this comment.
Reworded to point at the publishing namespace directly: "tenant-root by default — the namespace running the root ingress controller selected by publishing.ingressName". It no longer reads as if publishing.ingressName were itself the namespace. Fixed in 7d02b6d.
Reword the publishing.certificates.wildcard row so it refers to the issued wildcard Secret name (not 'the chosen name', which read as if the boolean had a name), and the wildcardSecretName row so it points at the publishing namespace directly (tenant-root) instead of citing publishing.ingressName, which is the ingress controller name. Signed-off-by: Aleksei Sviridkin <f@lex.la>
myasnikovdaniil
left a comment
There was a problem hiding this comment.
Accurate and well-scoped. I cross-checked every documented claim against the chart source on the implementation branch (packages/core/platform/values.yaml, templates/apps.yaml, and packages/extra/ingress/templates/{wildcard-certificate,nginx-ingress}.yaml):
- Key names match exactly —
publishing.certificates.wildcardandpublishing.certificates.wildcardSecretName, no casing typo. - Defaults match:
wildcard: false,wildcardSecretName: "". - Gating is correct: issuance only on
solver=dns01+gateway.enabled=false+ no BYO secret (apps.yaml computeswildcard-issueexactly this way). - Precedence is correct:
wildcardSecretNamewins and suppresses ACME issuance. - The coverage footgun is real and correctly stated — the issued Certificate's dnsNames are
<root-host>+*.<root-host>(single label), so custom/nested hosts fall back to the default cert. Neither overstated nor understated. - The
_cluster-channel / child-tenant blast-radius note and thetenant-rootpublishing-namespace default both check out.
CI is green (Netlify deploy preview, CodeRabbit, DCO).
Optional nit (non-blocking): the wildcard row describes issuance as "when true with solver=dns01 and no wildcardSecretName" — the fourth gating condition (gateway.enabled=false) is conveyed later in the same cell ("Ignored … when gateway.enabled=true"), so the full condition is present, just split across two sentences. Folding it into one would read a touch cleaner. LGTM.
…x path (#2988) ## What this PR does On the default ingress-nginx path (`gateway.enabled=false`) every published hostname still minted its own per-host ACME certificate via ingress-shim, even with a DNS-01 solver configured. That hits the Let's Encrypt rate limit (50 certs per registered domain per week) once a deployment exceeds ~50 endpoints. The Gateway API path already issues a single per-apex wildcard in DNS-01 mode; this brings the ingress-nginx path to parity. It implements the remaining ingress-nginx gap that #2400 was narrowed to in its triage comment (DNS-01 is already multi-provider, and the Gateway path already issues wildcards). The new `publishing.certificates.wildcard` toggle is opt-in and OFF by default. When set to `true` with `solver=dns01` and no operator-provided wildcard Secret, the platform issues one shared wildcard `Certificate` for `<root-host>` + `*.<root-host>` and serves the resulting Secret as the publishing controller's `--default-ssl-certificate`. The system service Ingresses (dashboard, grafana, keycloak, harbor, …) then stop requesting a per-host cert. How it works: - `core/platform` `apps.yaml` computes the effective wildcard Secret name and a new `_cluster.wildcard-issue` signal. An operator-provided `wildcardSecretName` (the BYO path) always wins and never triggers issuance; HTTP-01 cannot issue wildcards; and when Gateway API is enabled the TenantGateway controller issues the wildcard instead — so all three are excluded from auto-issuance. - `extra/ingress` renders the wildcard `Certificate` only on the publishing controller (`Release.Namespace == expose-ingress`), because the Secret must be same-namespace for ingress-nginx to read it. The Certificate references the DNS-01 `ClusterIssuer` that `cert-manager-issuers` already renders in `dns01` mode, so no per-tenant Issuer is minted. This reuses the existing wildcard-secret consumption path, so no per-service Ingress template changes were needed — only the issuance side is new. Why opt-in (default off): the chosen wildcard Secret name rides the `_cluster` channel, which every child tenant inherits verbatim, so this is not root-tenant scoped. A single-label wildcard `*.<root-host>` covers `<service>.<root-host>` but not a custom service host outside it (a keycloak `ingress.host`, harbor `host`, or grafana `host` pointed at another domain) nor a child tenant's nested host (`<service>.<tenant>.<root-host>`). Enabling issuance makes every such service drop its per-host ACME cert and fall back to the default certificate, which does not cover it. Leaving it off by default means a dns01 cluster is never silently switched on upgrade; an operator who enables it accepts the same coverage responsibility as the operator-provided wildcard path, which propagates identically. The tradeoff is pinned by contract tests (keycloak custom host, bucket nested child-tenant host). Per-tenant wildcards remain on the Gateway path and are tracked separately. Docs: cozystack/website#588 Closes #2400. Part of #2811. ### Screenshots N/A — no UI changes. ### Release note ```release-note feat(platform): add opt-in shared wildcard certificate issuance on the default ingress-nginx path via publishing.certificates.wildcard (default false). When enabled with a DNS-01 solver, the platform issues one *.<root-host> wildcard for system services instead of a per-host ACME certificate, avoiding Let's Encrypt rate limits at scale. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added automatic wildcard certificate issuance support for DNS-01 ACME solver. * New `certificates.wildcard` configuration option to enable shared wildcard TLS certificates. * Ingress rules now properly utilize wildcard certificates when enabled. * **Tests** * Added comprehensive test suites validating wildcard certificate generation and ingress integration across multiple scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What this PR does
Document two
publishing.certificates.*options in the platform-package value table that were missing from the reference docs.publishing.certificates.wildcard(new): opt-in shared wildcard certificate issuance on the default ingress-nginx path. When enabled with a DNS-01 solver it issues one*.<root-host>wildcard for system services instead of a per-host ACME certificate, avoiding Let's Encrypt rate limits at scale. Documented with its default (false), the dns01 / gateway-disabled gating, and the coverage / blast-radius caveat.publishing.certificates.wildcardSecretName(pre-existing, previously undocumented): operator-provided wildcard TLS Secret that platform services serve under instead of minting per-host ACME certificates.Documents the code change in cozystack/cozystack#2988. Part of cozystack/cozystack#2811.
Summary by CodeRabbit