docs/deployment: record security hardening pass + webapp + APNs
Mark roadmap items done (network policies, Traefik middleware, CF Full strict, CF IP UFW restriction, webapp deploy, APNs wired up, admin URL-baking fix, admin probe bug). Update Chapter 4 (firewall rule inventory now shows CF-only :443, no :80), Chapter 6 (request flow walks through TLS on :443 and middleware hops), Chapter 13 (CF SSL mode is Full strict, not Flexible; documents the origin cert install), Chapter 7 (adds the web service section — proxy pattern, 3 replicas, PostHog build-args), and Appendix C (web manifests, CF origin cert paths on disk, APNs .p8 path, updated network-policies applied status). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,8 +5,9 @@
|
||||
Cloudflare sits in front of every public request. It provides DNS
|
||||
(authoritative nameservers for `myhoneydue.com`), TLS termination at
|
||||
the edge, DDoS mitigation, caching, and the round-robin fan-out across
|
||||
our three node IPs. We use the Free plan. TLS mode is "Flexible"
|
||||
(HTTP between CF and origin). This chapter documents every Cloudflare
|
||||
our three node IPs. We use the Free plan. TLS mode is **Full (strict)**
|
||||
— CF connects to origin over HTTPS and verifies the origin's cert
|
||||
against CF's own Origin CA. This chapter documents every Cloudflare
|
||||
setting that matters.
|
||||
|
||||
## DNS
|
||||
@@ -72,53 +73,49 @@ when you want sub-second failover.
|
||||
|
||||
## TLS
|
||||
|
||||
### Mode: Flexible
|
||||
### Mode: Full (strict)
|
||||
|
||||
CF Dashboard → SSL/TLS → Overview → **Flexible**.
|
||||
CF Dashboard → SSL/TLS → Overview → **Full (strict)**.
|
||||
|
||||
**What this means:**
|
||||
- User ↔ Cloudflare: **TLS** (HTTPS)
|
||||
- Cloudflare ↔ Origin: **plaintext HTTP** (port 80)
|
||||
- User ↔ Cloudflare: **TLS** (HTTPS) — CF serves its own Let's Encrypt cert
|
||||
- Cloudflare ↔ Origin: **TLS** (HTTPS :443) — origin serves our CF Origin CA cert; CF verifies it chains to CF's Origin CA root
|
||||
|
||||
**Why we chose it:**
|
||||
- No origin cert required on the Hetzner nodes
|
||||
- Zero Traefik cert-management complexity
|
||||
- Fine for a site where CF terminates all user-facing TLS
|
||||
**How it's wired:**
|
||||
- k8s secret `cloudflare-origin-cert` (type `kubernetes.io/tls`) holds
|
||||
`tls.crt` + `tls.key`. The cert is valid for `*.myhoneydue.com` +
|
||||
`myhoneydue.com`, 15-year validity, issued by
|
||||
`CloudFlare Origin CA SSL Certificate Authority`.
|
||||
- All three `Ingress` resources in `deploy-k3s/manifests/ingress/ingress-simple.yaml`
|
||||
reference the secret via `spec.tls[].secretName`.
|
||||
- Traefik terminates TLS on :443 using the cert. Backend pods still
|
||||
speak plain HTTP over the cluster network (Traefik → pod is an
|
||||
intra-cluster hop, encrypted at the Flannel overlay layer).
|
||||
|
||||
**Downsides:**
|
||||
- An attacker with network access between CF and Hetzner could read
|
||||
traffic. Realistically: nobody between CF's POPs and Hetzner's
|
||||
Nuremberg DC, but it's theoretically plaintext on the wire.
|
||||
- MitM risk if DNS gets hijacked and traffic is routed through an
|
||||
unintended origin.
|
||||
**Why we chose Full (strict) over Flexible:**
|
||||
- CF → origin traffic was plaintext on Flexible. Between Cloudflare's
|
||||
POPs and Hetzner Nuremberg is a lot of internet. Full (strict)
|
||||
closes that gap.
|
||||
- Origin cert is a CF-internal-only CA, so it's useless to anyone who
|
||||
isn't CF. Non-CF clients that somehow bypass the UFW CF-IP allowlist
|
||||
can't impersonate the origin because their cert wouldn't chain to
|
||||
CF's Origin CA root.
|
||||
|
||||
### Future: Full (strict)
|
||||
**Maintenance:** the Origin CA cert is valid for 15 years (expires
|
||||
Apr 2041). No action needed until then. If rotation is ever required,
|
||||
regenerate in CF dashboard → SSL/TLS → Origin Server, re-run the
|
||||
`kubectl create secret tls cloudflare-origin-cert --dry-run=client -o yaml | kubectl apply -f -`
|
||||
command, Traefik picks it up on next secret reload (no pod restart).
|
||||
|
||||
The next step up is **Full (strict)**: CF verifies origin's TLS cert
|
||||
and connects over HTTPS. Cloudflare provides free **Origin CA
|
||||
certificates** for this: they're issued by a CF-internal CA that only
|
||||
CF's own edge accepts. An attacker without a CF-signed cert can't
|
||||
impersonate our origin.
|
||||
### Regenerating the cert (for the record)
|
||||
|
||||
Path to enable:
|
||||
1. Generate Origin CA cert in CF dashboard → SSL/TLS → Origin Server
|
||||
2. Download as PEM
|
||||
3. Create k8s Secret `cloudflare-origin-cert`:
|
||||
```bash
|
||||
kubectl create secret tls cloudflare-origin-cert -n honeydue \
|
||||
--cert=origin.crt --key=origin.key
|
||||
```
|
||||
4. Add `tls:` block to our Ingress:
|
||||
```yaml
|
||||
spec:
|
||||
tls:
|
||||
- hosts: [api.myhoneydue.com]
|
||||
secretName: cloudflare-origin-cert
|
||||
```
|
||||
5. Switch CF SSL mode to Full (strict)
|
||||
|
||||
Trad-off: the `cloudflare-origin-cert` expires (default 15 years), so
|
||||
low maintenance. **TODO** (Chapter 20).
|
||||
```bash
|
||||
# After downloading cf-origin-cert.pem + cf-origin-key.pem from CF dashboard:
|
||||
kubectl -n honeydue create secret tls cloudflare-origin-cert \
|
||||
--cert=cf-origin-cert.pem \
|
||||
--key=cf-origin-key.pem \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
### Edge certificate
|
||||
|
||||
|
||||
Reference in New Issue
Block a user