From 37fbec8ec7e8b9a474fcb62d4674a3e41f4bb431 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Mon, 22 Jun 2026 21:14:58 +0300 Subject: [PATCH 1/2] docs(platform): document publishing.certificates wildcard options 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 --- .../en/docs/next/operations/configuration/platform-package.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/content/en/docs/next/operations/configuration/platform-package.md b/content/en/docs/next/operations/configuration/platform-package.md index e4d7b26f..347c3fc7 100644 --- a/content/en/docs/next/operations/configuration/platform-package.md +++ b/content/en/docs/next/operations/configuration/platform-package.md @@ -66,6 +66,8 @@ spec: | `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 `*.` + `` `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 `*.` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested 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 `*.` + ``). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | | `publishing.certificates.dns01.provider` | `"cloudflare"` | DNS-01 provider when `solver=dns01`. Possible values: `cloudflare`, `route53`, `digitalocean`, `rfc2136`. Both the per-tenant Issuer (rendered by `cozystack-controller` from the `TenantGateway` CR) and the cluster-wide `letsencrypt-prod` / `letsencrypt-stage` `ClusterIssuer`s used by the legacy ingress flow read this. | | `publishing.certificates.dns01.cloudflare.secretName` | `"cloudflare-api-token-secret"` | Secret name holding a Cloudflare API token with `Zone:Read` + `Zone:DNS:Edit` on the apex zone. | | `publishing.certificates.dns01.cloudflare.secretKey` | `"api-token"` | Key inside the Secret holding the API token. | From 7d02b6dbccbc72e4f151445f05d27cb50b6a862a Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Tue, 23 Jun 2026 01:54:57 +0300 Subject: [PATCH 2/2] docs(platform): clarify wildcard certificate option wording 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 --- .../en/docs/next/operations/configuration/platform-package.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/en/docs/next/operations/configuration/platform-package.md b/content/en/docs/next/operations/configuration/platform-package.md index 347c3fc7..74d4fd68 100644 --- a/content/en/docs/next/operations/configuration/platform-package.md +++ b/content/en/docs/next/operations/configuration/platform-package.md @@ -66,8 +66,8 @@ spec: | `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 `*.` + `` `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 `*.` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested 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 `*.` + ``). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | +| `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 `*.` + `` `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 `*.` (e.g. a keycloak `ingress.host` on another domain) or a child tenant's nested host (`..`); those services fall back to the default certificate, which would not cover them. Enabling it propagates the issued wildcard Secret name through 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 (`tenant-root` by default — the namespace running the root ingress controller selected by `publishing.ingressName`), be of type `kubernetes.io/tls`, and cover the served hosts (typically `*.` + ``). Takes precedence over `publishing.certificates.wildcard` — when set, no ACME issuance happens. Leave empty to keep ACME issuance. | | `publishing.certificates.dns01.provider` | `"cloudflare"` | DNS-01 provider when `solver=dns01`. Possible values: `cloudflare`, `route53`, `digitalocean`, `rfc2136`. Both the per-tenant Issuer (rendered by `cozystack-controller` from the `TenantGateway` CR) and the cluster-wide `letsencrypt-prod` / `letsencrypt-stage` `ClusterIssuer`s used by the legacy ingress flow read this. | | `publishing.certificates.dns01.cloudflare.secretName` | `"cloudflare-api-token-secret"` | Secret name holding a Cloudflare API token with `Zone:Read` + `Zone:DNS:Edit` on the apex zone. | | `publishing.certificates.dns01.cloudflare.secretKey` | `"api-token"` | Key inside the Secret holding the API token. |