Merge feature/oq4-cloudflare-token into main

Resolve OQ-4 (scoped Cloudflare API token). Phase 2 TLS/DNS unblocked.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
jakman85andClaude Opus 4.8 committed 2026-08-15 23:02:59 -05:00
commit fdca4305cf
4 files changed
+37 -38

No files matched your search

+4 -3
View File
@@ -39,8 +39,9 @@ Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workst
| T-2.1 | Longhorn (3-replica StorageClass) + `bulk` external-mount indirection (media lives off-cluster, backing TBD — OQ-2 resolved) | WS-STORAGE | R-STATE-02/04 | T-1.4 | — | todo |
| T-2.2 | Default-deny network baseline (per-namespace) + Hubble | WS-NET | R-NET-01/03 | T-1.3 | — | todo |
| T-2.3 | Render `dependencies` → CiliumNetworkPolicy from ServiceDefinition | WS-NET/WS-GITOPS | R-NET-02 | T-0.3, T-2.2 | — | todo |
| T-2.4 | cert-manager + Let's Encrypt DNS-01 wildcard `*.home.keithjackson.dev` (**blocked on OQ-4**: Cloudflare token vs acme-dns) | WS-SEC | R-TLS-01/02/03 | T-1.4, OQ-4 | — | blocked |
| T-2.5 | ExternalDNS + Gateway API listeners (VIP via **Cilium L2 announcement**) + split-horizon (PiHole) foundation | WS-NET | R-DNS-01/02/03 | T-2.4 | — | todo |
| T-2.4p | **Prereq:** operator creates scoped Cloudflare API token (`Zone·DNS·Edit`, `keithjackson.dev`); store SOPS-encrypted | WS-SEC | R-TLS-01, R-SEC-01 | — | — | todo |
| T-2.4 | cert-manager + Let's Encrypt DNS-01 wildcard `*.home.keithjackson.dev` (via Cloudflare token) | WS-SEC | R-TLS-01/02/03 | T-1.4, T-2.4p | — | todo |
| T-2.5 | ExternalDNS (Cloudflare token) + Gateway API listeners (VIP via **Cilium L2 announcement**) + split-horizon (PiHole) | WS-NET | R-DNS-01/02/03 | T-2.4 | — | todo |
| T-2.6 | Prove: default-deny (DNS-only probe), PVC survives rebuild, trusted cert, auto-DNS | WS-NET/STORAGE/SEC | R-NET/STATE/TLS/DNS | T-2.1..2.5 | — | todo |
| T-2.7 | Deploy **Kyverno in-cluster as admission enforcer** (same policies as the CI L0 gate) | WS-TEST/WS-SEC | R-SEC-02, R-NET-01, R-SEC-01 | T-1.4, T-0.5 | — | todo |
@@ -100,5 +101,5 @@ Seeded from [`../roadmap/phases.md`](../roadmap/phases.md) + [`../roadmap/workst
| OQ-1 | WAN count + edge router | — | ✅ resolved: 1 WAN, dumb SOHO edge; multi-WAN out of scope; VPN + segmentation in-cluster |
| OQ-2 | Bulk-media home | — | ✅ resolved: media outside cluster, backing TBD later (not blocking) |
| OQ-3 | VPN provider/protocol | — | ✅ resolved: ExpressVPN, single shared in-cluster tunnel, OpenVPN UDP (ADR-0012) |
| **OQ-4** | **DNS-01 challenge access** | **T-2.4, T-2.5** | ⛔ **OPEN** — Cloudflare API token (Option A, recommended) vs acme-dns delegation (Option B) |
| OQ-4 | DNS-01 challenge access | — | ✅ resolved: scoped **Cloudflare API token** (Zone·DNS·Edit), SOPS-encrypted; drives cert-manager + ExternalDNS. Operator provides token (T-2.4p) |
| **OQ-BKP** | **Off-cluster DR backup target** (S3/NFS endpoint) | T-4.3 | ⛔ **OPEN** — needed by Phase 4, not Phase 2 |
+9 -6
View File
@@ -195,10 +195,13 @@ implementation.
tunnel**, **non-TCP** (OpenVPN UDP / Lightway-UDP), rules enforced outside the service
(ADR-0012). See [`03`](03-networking-isolation-and-egress.md).
- **OQ-4 (TLS/DNS challenge access)** — **RESOLVED**: a **scoped Cloudflare API token**
(`Zone·DNS·Edit`, `keithjackson.dev`), SOPS-encrypted, drives cert-manager DNS-01 and ExternalDNS.
The static `*.keithjackson.dev` wildcard covers reachability but not TXT-challenge writes; the token
handles issuance + record automation. Operator provides the token once. See
[`08`](08-tls-and-certificates.md).
### Still open
- **OQ-4 (TLS/DNS challenge access)**: Cloudflare already has a wildcard `*.keithjackson.dev` → public
IP (covers reachability), but **ACME DNS-01 needs write access** to create `_acme-challenge` TXT
records, and `*.keithjackson.dev` does **not** match the two-label-deep `*.home.keithjackson.dev`.
Decide the challenge mechanism: **(a)** a scoped Cloudflare **API token** (Zone·DNS·Edit) → fully
automated DNS-01 + ExternalDNS, or **(b)** **acme-dns delegation** (one-time `_acme-challenge`
CNAME edit, then no standing Cloudflare credential). See [`08`](08-tls-and-certificates.md).
- **OQ-BKP (off-cluster DR backup target)**: which S3-compatible endpoint or NFS host stores
Velero/Longhorn backups of the irreplaceable tier. Needed by **Phase 4**, not blocking Phase 2.
See [`04`](04-storage-and-state.md).
+19 -20
View File
@@ -28,24 +28,23 @@ services (R-TLS-01/02/03).
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)
## DNS-01 challenge access (OQ-4 — RESOLVED: scoped Cloudflare API token)
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**:
Cloudflare already has a wildcard `*.keithjackson.dev` → public-IP record, which covers
*reachability* but not certificate issuance (ACME DNS-01 must **write `_acme-challenge` TXT records**,
and that wildcard does not match two-label-deep names like `plex.home.keithjackson.dev`).
- **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.
**Decision (OQ-4):** use a **scoped Cloudflare API token** — `Zone·DNS·Edit` limited to the
`keithjackson.dev` zone — stored **SOPS-encrypted** ([07](07-security-and-secrets.md)). With it:
- **cert-manager** solves DNS-01 by writing/removing TXT challenge records automatically (issue,
renew, revoke — R-TLS-02), including the wildcard `*.home.keithjackson.dev` cert and internal-only
services (R-TLS-03).
- **ExternalDNS** manages the `*.home.keithjackson.dev` records automatically ([09](09-dns-and-ingress.md),
R-DNS-02) — no manual record editing for public services.
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).
**Operator prerequisite:** create the scoped token in Cloudflare and hand it over once; it is then
committed SOPS-encrypted and never touched manually again. The token is least-privilege (single zone,
DNS edit only) so its blast radius is bounded (R-SEC-02). Rotation = re-encrypt + commit.
## Renewal & revocation (R-TLS-02)
@@ -75,8 +74,8 @@ CA distribution. Split-horizon DNS ([09](09-dns-and-ingress.md)) resolves
- 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).
## Resolved
- **OQ-4** — DNS-01 via a **scoped Cloudflare API token** (`Zone·DNS·Edit`, `keithjackson.dev`),
SOPS-encrypted; drives both cert-manager DNS-01 and ExternalDNS. Phase 2 TLS/DNS is unblocked once
the operator provides the token.
- Wildcard vs. per-service certs default — **wildcard** `*.home.keithjackson.dev` (ADR-0006).
+5 -9
View File
@@ -62,9 +62,9 @@ A wildcard `*.keithjackson.dev` matches only **one** label deep — it covers `h
but **not** `plex.home.keithjackson.dev`. Consequences:
- **`internal` services need no public record** — PiHole resolves `<svc>.home.keithjackson.dev` to
the internal Gateway VIP (split-horizon). This is the majority of services.
- **`public` services** need a public record for `<svc>.home.keithjackson.dev`. That is provided by
either a one-time `*.home.keithjackson.dev` → public-IP record, or by **ExternalDNS via the DNS
API** — both hinge on OQ-4 (see [08](08-tls-and-certificates.md)).
- **`public` services** need a public record for `<svc>.home.keithjackson.dev`. Per OQ-4 (resolved),
**ExternalDNS manages these automatically** via the scoped Cloudflare API token — no manual record
editing (see [08](08-tls-and-certificates.md)).
## Interaction with default-deny
@@ -81,9 +81,5 @@ not let other in-cluster services reach it unless an explicit allow rule exists.
## Resolved
- **OQ-1** — single SOHO edge (manual port-forward only); ingress VIP via **Cilium L2 announcement**.
## Open questions
- **OQ-4** — DNS challenge/record access: Cloudflare **API token** (automated ExternalDNS + DNS-01)
vs **acme-dns delegation** (one-time CNAME, no standing credential). Determines whether public
`*.home.keithjackson.dev` records and ACME challenges are automated or partly manual. See
[08](08-tls-and-certificates.md).
- **OQ-4** — scoped **Cloudflare API token** drives ExternalDNS (records) and cert-manager DNS-01
(certs) automatically; see [08](08-tls-and-certificates.md).