# Per-name TLS Certificates for the Cilium Gateway listeners.
#
# Architecture change (2026-05-15): switched from ONE wildcard cert
# `*.<sovereignFQDN>` to N per-name certs (console, auth, gitea, harbor,
# registry, api, bao, grafana, hubble, openova-flow, pdns, marketplace).
#
# Why: Let's Encrypt enforces "5 New Certificates per Exact Set of
# Identifiers per 168h". The wildcard pattern bundled ALL hostnames
# under ONE identifier set `[*.<fqdn>, <fqdn>]` — every prov-cycle
# burned 1 of 5 slots from that single bucket. Five iterations on the
# same FQDN locked the apex for a week. Hit live three times:
#   - omantel.biz exhausted 2026-05-13 (12 reprovs)
#   - omani.works exhausted 2026-05-14 (5 reprovs in 90 min)
#   - omantel.biz exhausted again 2026-05-15 (this PR's origin)
#
# Per-name model gives each hostname its OWN 5/168h bucket:
#   - console.<fqdn>  → 5 reprovs/week
#   - auth.<fqdn>     → 5 reprovs/week
#   - gitea.<fqdn>    → 5 reprovs/week
#   ... × 12 hostnames = 60 effective reprov-slots/week
#
# 2026-05-17 t143 dual-cert collision cleanup
# -------------------------------------------
# Previously this Certificate was named `sovereign-wildcard-tls` and
# wrote a Secret of the same name. After PR O (2026-05-17) moved the
# Cilium Gateway listener's certificateRefs to the per-zone Secret
# `sovereign-wildcard-tls-${SOVEREIGN_FQDN_DASHED}` (see
# clusters/_template/sovereign-tls/cilium-gateway.yaml:44), the legacy
# Secret stopped being referenced by anything — but the Certificate
# kept renewing, burning LE budget for no production value and showing
# up in audits as an orphan TLS Secret on every Sovereign.
#
# Single-source-of-truth fix: this Certificate now writes to the SAME
# dashed-suffix Secret the Gateway already references. One Cert, one
# Secret, one LE issuance per renewal. No more dual-cert collision
# and no extra LE budget consumed.
#
# This pattern is the standard production approach (see Cloudflare,
# Vercel, Render). Wildcards are reserved for the limited cases where
# customer-provided subdomains aren't known in advance.

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  # Match the Secret name the Gateway listener references
  # (clusters/_template/sovereign-tls/cilium-gateway.yaml:44). Cloud-init
  # substitutes SOVEREIGN_FQDN_DASHED = SOVEREIGN_FQDN with `.` → `-`
  # (infra/hetzner/cloudinit-control-plane.tftpl §SOVEREIGN_FQDN_DASHED).
  name: sovereign-wildcard-tls-${SOVEREIGN_FQDN_DASHED}
  namespace: kube-system
  labels:
    catalyst.openova.io/sovereign: ${SOVEREIGN_FQDN}
    catalyst.openova.io/component: cilium-gateway
spec:
  secretName: sovereign-wildcard-tls-${SOVEREIGN_FQDN_DASHED}
  issuerRef:
    name: ${WILDCARD_CERT_ISSUER}
    kind: ClusterIssuer
  commonName: "console.${SOVEREIGN_FQDN}"
  # SubjectAltNames — explicit list of Sovereign-facing surfaces. New
  # services added to bootstrap-kit MUST be added here so the cert
  # covers them at issuance time. Order is preserved in the cert; for
  # cosmetic reasons the operator-facing names come first.
  dnsNames:
    - "console.${SOVEREIGN_FQDN}"
    - "auth.${SOVEREIGN_FQDN}"
    - "gitea.${SOVEREIGN_FQDN}"
    - "registry.${SOVEREIGN_FQDN}"
    - "api.${SOVEREIGN_FQDN}"
    - "bao.${SOVEREIGN_FQDN}"
    - "grafana.${SOVEREIGN_FQDN}"
    - "hubble.${SOVEREIGN_FQDN}"
    - "pdns.${SOVEREIGN_FQDN}"
    - "openova-flow.${SOVEREIGN_FQDN}"
    - "guacamole.${SOVEREIGN_FQDN}"
    - "marketplace.${SOVEREIGN_FQDN}"
    # sandbox.<sov-fqdn> — public URL for the per-Sandbox pty-server (PR #1641).
    # The sandbox-controller renders an HTTPRoute on sandbox.<sov-fqdn>/
    # sessions/<owner-uid>/* attached to the cilium-gateway. The wildcard
    # `*.<sov-fqdn>` Gateway listener already matches the hostname, but the
    # per-name SAN cert here must include `sandbox.<sov-fqdn>` for
    # cilium-envoy to serve the right cert (otherwise browsers see
    # NET::ERR_CERT_COMMON_NAME_INVALID). Matches the entry in
    # products/catalyst/bootstrap/api/internal/handler/sovereign_dns_records.go
    # CanonicalSovereignSubdomains so the parent-zone A-record set + cert
    # SAN list stay aligned.
    - "sandbox.${SOVEREIGN_FQDN}"
