# 08 — TLS & Certificates Serves: **R-TLS-01/02/03**, **R-DNS-01**. Related: [ADR-0006 (cert-manager + Let's Encrypt)](../decisions/adr-0006-tls-cert-manager-letsencrypt.md). ## Requirement recap Every externally reachable service presents TLS from a **credible, trusted, non-self-signed** authority, with certificates **auto issued, renewed, and revoked** — including for **internal-only** services (R-TLS-01/02/03). ## Solution: cert-manager + Let's Encrypt via DNS-01 - **cert-manager** runs in the cluster and manages the full certificate lifecycle. - **Let's Encrypt** is the trusted, non-self-signed CA (publicly trusted; free; automatable) — satisfies R-TLS-01. - **ACME DNS-01 challenge** against the `keithjackson.dev` DNS zone (Cloudflare or equivalent, per OQ-4). DNS-01 is the key choice because it validates domain control **without exposing the service publicly** — so internal-only services get real, trusted certs (R-TLS-03), and **wildcard** certificates (`*.home.keithjackson.dev`) are possible. ## Issuance model - A `ClusterIssuer` for Let's Encrypt production (and one for staging, used during bring-up to avoid rate limits). - Certificates are requested declaratively — either a **wildcard** `*.home.keithjackson.dev` cert shared by the Gateway, or **per-service** certs, decided per ADR-0006. Recommendation: a wildcard cert for `*.home.keithjackson.dev` terminated at the Gateway simplifies onboarding (a new service needs no new cert), with per-service certs available where isolation warrants. - The DNS provider API token is a SOPS-encrypted secret ([07](07-security-and-secrets.md)). ## DNS-01 challenge access (OQ-4 — the one open item) Cloudflare already has a wildcard `*.keithjackson.dev` → public-IP record. **That covers reachability but not certificate issuance**: ACME DNS-01 requires cert-manager to **write `_acme-challenge` TXT records**, and the existing wildcard does not match two-label-deep names like `plex.home.keithjackson.dev`. So a **challenge-access decision is still required**: - **Option A — scoped Cloudflare API token** (Zone·DNS·Edit for `keithjackson.dev`), stored SOPS-encrypted ([07](07-security-and-secrets.md)). cert-manager writes TXT challenges and ExternalDNS manages the `*.home.keithjackson.dev` records — **fully automated**. *Recommended (simplest).* - **Option B — acme-dns delegation**: a one-time `_acme-challenge` **CNAME** in Cloudflare points to an in-cluster acme-dns server; cert-manager writes only to acme-dns, so **no standing Cloudflare credential** lives in the cluster. More moving parts; public `*.home` records then need the one-time wildcard record instead of ExternalDNS. Either option supports the wildcard `*.home.keithjackson.dev` cert and internal-only issuance (R-TLS-03). This is the remaining blocker for the TLS/DNS workstream (Phase 2). ## Renewal & revocation (R-TLS-02) - cert-manager renews well before expiry automatically; no human action. - Revocation: removing/rotating a certificate is a declarative change; cert-manager handles issuance of replacements. Expiry monitoring is an alert condition ([10](10-observability-and-alerting.md), R-OBS-04). ## Where TLS terminates - **North-south**: at the **Gateway API** listener (implemented by Cilium, [09](09-dns-and-ingress.md)), using the cert-manager-issued cert. Public services are reached via the edge router forwarding to the Gateway; internal services resolve to the Gateway on the LAN. - Internal service-to-service traffic relies primarily on network policy for security; mTLS between workloads is **optional** future hardening (could be added via Cilium/service mesh) and is not a launch requirement — record as a possible future ADR. ## Trust chain for internal clients Because certs come from Let's Encrypt (publicly trusted), internal clients trust them with no custom CA distribution. Split-horizon DNS ([09](09-dns-and-ingress.md)) resolves `.home.keithjackson.dev` to the internal Gateway while the same trusted cert is served. ## Acceptance gates - `https://.home.keithjackson.dev` serves a valid, non-self-signed, unexpired certificate (verified by an external TLS checker), including for internal-only services. - Certificates renew automatically ahead of expiry with no manual step (observed across a renewal). - Certificate expiry and issuance failures raise alerts. - Onboarding a new service requires no manual certificate step. ## Open questions - **OQ-4** — DNS-01 challenge access: **Option A (scoped Cloudflare API token, recommended)** vs **Option B (acme-dns delegation)**. Provider is Cloudflare (confirmed). This is the only item still blocking Phase 2 TLS. - Wildcard vs. per-service certs default — **resolved: wildcard** `*.home.keithjackson.dev` (ADR-0006).