Commit Graph
117 Commits
Author SHA1 Message Date
jakman85 b50433d705 Merge feature/vpn-openvpn-enable into main 2026-08-22 14:49:17 -05:00
jakman85andClaude Opus 4.8 015b423353 feat(vpn): enable vpn-egress on OpenVPN-UDP; align docs to ADR-0013 amendment
Gluetun cannot do PIA over WireGuard (upstream unsupported; live "provider name
is not valid for Wireguard"). Operator approved OpenVPN-UDP and ADR-0013 was
amended in the spec. This aligns the impl:

- platform/kustomization.yaml: re-enable `- vpn-egress` (Phase 3, OpenVPN-UDP)
- gluetun-gateway.yaml: clean stale WireGuard comments (header, image note,
  VPN_TYPE/creds comments); config already VPN_TYPE=openvpn
- README.md: PIA-over-OpenVPN, status=enabled, steering iface wg0 -> tun0
- DECISIONS-LOG.md: disk/drive incident (k3s -> NVMe; failing Seagate) + Phase-3
  OpenVPN resolution

Stale WireGuard comments inside the operator's encrypted pia-credentials.sops.yaml
header left untouched (editing breaks the SOPS MAC; cosmetic).

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-22 14:49:16 -05:00
jakman85 41da3d96e5 fix(vpn): Attempting to try OpenVPN protocol with PIA 2026-08-21 23:49:49 -05:00
jakman85 6a2c7c3020 Merge Gluetun v3.41.0 (PIA WireGuard support) 2026-08-21 23:10:48 -05:00
jakman85andClaude Opus 4.8 13a8df4e4a fix(vpn): bump Gluetun to v3.41.0 (v3.40.0 lacks PIA WireGuard support)
The deployed v3.40.0 rejected PIA+WireGuard ('private internet access is not valid for
Wireguard'). Verified v3.41.0 accepts the PIA+WireGuard provider/protocol (passes config
validation; ADR-0013's model holds — v3.40.0 was just too old). Bump the pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 23:10:47 -05:00
jakman85andClaude Opus 4.8 cbcfae01e1 feat(phase3): enable vpn-egress (deploy the Gluetun PIA/WireGuard gateway)
PIA creds provisioned (SOPS) + wired. Enable vpn-egress in platform/kustomization.yaml
to deploy the shared Gluetun gateway (PIA over WireGuard, Sweden region, kill-switch).
Egress-class steering (vpn-class pods -> tunnel) still to be validated against the live
tunnel next. Gate PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 23:06:27 -05:00
jakman85 643c3225ac Merge PIA_REGION=Sweden default 2026-08-21 23:00:51 -05:00
jakman85andClaude Opus 4.8 0d55c793f3 fix(vpn): default PIA_REGION to Sweden (operator choice, PF-capable)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 23:00:51 -05:00
jakman85 9667dce419 Merge VPN cluster-vars definition + pod-CIDR fix 2026-08-21 22:56:15 -05:00
jakman85andClaude Opus 4.8 6591e11ad1 fix(vpn): define PIA_REGION + CLUSTER_POD_CIDR cluster-vars; correct pod CIDR + doc where
gluetun-gateway.yaml referenced ${PIA_REGION}/${CLUSTER_POD_CIDR} but they were never defined
in cluster-vars, and the README didn't say where to set them. Add both to
clusters/base/cluster-vars.yaml (ConfigMap data:, string values, Flux postBuild substitution).
CLUSTER_POD_CIDR is 10.0.0.0/8 (Cilium cluster-pool — pods get 10.x, confirmed live), NOT the
k3s 10.42.0.0/16 I'd assumed. README now names the file + format + per-env override + PF-region note.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 22:56:14 -05:00
jakman85 140cb651d2 Merge Phase-3/4 + infra-recovery log 2026-08-21 22:40:18 -05:00
jakman85 2223e8403d docs: Phase-4 monitoring deployed + Phase-3 VPN authored + infra recovery + F-MON1 2026-08-21 22:40:17 -05:00
jakman85 4f17c5334e Merge monitoring PrometheusRule ordering fix 2026-08-21 22:30:02 -05:00
jakman85andClaude Opus 4.8 eeb97208fa fix(monitoring): move PrometheusRule alerts to platform-config (CRD ordering)
Enabling monitoring deadlocked the platform Kustomization: alerts.yaml ships
PrometheusRule CRs whose monitoring.coreos.com CRDs are installed by the
kube-prometheus-stack HelmRelease in the SAME pass → 'no matches for kind
PrometheusRule' → atomic apply failure → NO monitoring HelmReleases created.
Same controllers/configs fix as cert-manager/kyverno: drop alerts.yaml from
platform/monitoring and apply it via platform-config (dependsOn platform), so the
monitoring CRDs exist first. Verified: platform has 0 PrometheusRule (keeps the 3
monitoring HRs); platform-config has the 8 alert rules; gate PASS.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 22:30:01 -05:00
jakman85 3bfd055df2 Merge Phase-3 VPN egress gateway (author-first) 2026-08-21 22:19:24 -05:00
jakman85andClaude Opus 4.8 1a98429434 feat(phase3): author shared VPN egress gateway (Gluetun/PIA/WireGuard) — author-first
Per ADR-0013: platform/vpn-egress/ = a single shared Gluetun gateway (PIA over WireGuard,
built-in kill-switch FIREWALL=on, native port forwarding), namespace (non-app, NET_ADMIN +
/dev/net/tun), control-API Service, and a pre-wired (commented) pia-credentials SOPS line.
Defined the PIA secret location (platform/vpn-egress, keys username/password → OPENVPN_USER/
OPENVPN_PASSWORD) and updated the SOPS template accordingly.

NOT wired into platform/kustomization.yaml (Phase 3, author-first) — deploy needs PIA creds +
validation of the egress-steering mechanism (Cilium Egress Gateway → the Gluetun node's wg0;
documented in README with options + a fail-closed rationale, deliberately NOT committed as an
untested CiliumEgressGatewayPolicy). WireGuard chosen (UDP, protocol != TCP); PIA supports it on
all regions and the secret stays username/password.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 22:19:22 -05:00
jakman85 e6c61c97b3 Merge Phase-4 monitoring deploy 2026-08-21 22:06:24 -05:00
jakman85andClaude Opus 4.8 dfde5292d7 feat(phase4): deploy monitoring (kube-prometheus-stack + Loki + Alloy + Alertmanager)
Enable monitoring in platform/kustomization.yaml (Phase 4). Grafana admin +
Alertmanager SMTP secrets are provisioned (SOPS) and wired; Longhorn SC is up for
Prometheus/Loki PVCs. Set the Alertmanager receiver 'to:' to a valid inbox
([email protected]) so the config loads (REPLACE-ME is not a valid address);
operator can change it. NOTE: smtp_from ([email protected]) differs from
the Gmail auth user — Gmail may reject/rewrite unless a send-as alias is configured;
that only affects email delivery at alert time, not stack health.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-21 22:06:23 -05:00
jakman85 75764580d8 Merge fail-closed green log 2026-08-17 17:43:27 -05:00
jakman85 e8b5cae34d docs: R-NET fail-closed (L3) green; conformance status 2026-08-17 17:43:27 -05:00
jakman85 cde3335086 Merge L3 default-deny-failclosed (R-NET fail-closed green) 2026-08-17 17:42:35 -05:00
jakman85andClaude Opus 4.8 1bf8e3bfc4 feat(conformance): implement L3 default-deny-failclosed (R-NET-01/02, fault injection)
Was a scaffold (fixture TODO, fault-injection a no-op echo). Implemented against the
live datapath: fixtures/allowed-pair.yaml = default-deny baseline + source + dest(:8080)
+ the sole explicit allow. The test verifies source->dest works (ALLOWED_OK), then
REVOKES both halves of the allow (real fault: kubectl delete networkpolicy), and asserts
connectivity fails CLOSED (FAIL_CLOSED_OK) while DNS still resolves (DNS_OK). Proves
default-deny is datapath-enforced and fails closed on revocation. DNS check asserts the
resolved answer, since busybox nslookup exits non-zero on a successful forward lookup.
PASSES (74.8s).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:42:34 -05:00
jakman85 1631a7bde8 Merge R-STATE-01 + harness fix log 2026-08-17 17:02:40 -05:00
jakman85andClaude Opus 4.8 ce55332028 docs: R-STATE-01 green + chainsaw =~ harness fix (F-TEST1)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:02:39 -05:00
jakman85 76478f2651 Merge chainsaw check block-style fix 2026-08-17 17:02:05 -05:00
jakman85andClaude Opus 4.8 70c0701d6f fix(conformance): block-style chainsaw checks (flow-map broke on contains comma)
My previous commit converted =~ to contains() but kept flow style
'check: { (contains($stdout, 'X')): true }' — the comma inside contains() is a
YAML flow-map separator, so 9 tests failed yamllint ('expected , or }'). Convert
those checks to block style (matching data-survival), where a comma in the key
scalar is fine. Gate PASS; data-survival still green.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:02:05 -05:00
jakman85 3440bbad92 Merge chainsaw check contains() fix (data-survival R-STATE-01 green) 2026-08-17 16:58:38 -05:00
jakman85andClaude Opus 4.8 e0c5c58710 fix(conformance): chainsaw checks use contains(), not unsupported =~
Every chainsaw `check` used the regex operator `=~` (`($stdout =~ 'X'): true`),
which chainsaw's expression engine rejects ('SyntaxError: Unknown char: ~') — so
EVERY such test failed on the assertion even when the behavior passed. Proven with
data-survival (R-STATE-01): the Longhorn canary genuinely SURVIVED pod destruction
(DATA_SURVIVED_OK) but the test FAILED on the `=~` syntax. Converted all 10 tests to
JMESPath `contains($stdout, 'X')` (data-survival's alternation → contains||contains).
data-survival now PASSES; the other 9 (later-phase components) are syntactically fixed
for when their phases deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 16:58:38 -05:00
jakman85 54fd963cc4 Merge F-OPS1 resolved log 2026-08-17 16:51:30 -05:00
jakman85andClaude Opus 4.8 429708f0a7 docs: F-OPS1 resolved (self-managing root)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 16:51:30 -05:00
jakman85 4381a71964 Merge self-managing root Kustomization 2026-08-17 16:48:58 -05:00
jakman85andClaude Opus 4.8 d06947cbfd feat(gitops): self-managing root Kustomization (fixes F-OPS1)
The on-cluster cluster-vars ConfigMap wasn't reconciled by Flux — this bootstrap
model had no Kustomization watching clusters/dev, so cluster-vars/overlay changes
needed a manual 'kubectl apply -k clusters/dev' (e.g. DNS_ZONE substituted empty
until re-applied). Add clusters/dev/sync.yaml: a self-managing root Kustomization
(flux2 gotk-sync pattern, name flux-system) that reconciles ./clusters/dev from git
— cluster-vars, the GitRepository, platform/platform-config/apps, and itself. A git
push now suffices. Bootstrap unchanged ('kubectl apply -k clusters/dev' creates it
once). Verified: dev build contains all 6 objects (root + existing), prod unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 16:48:57 -05:00
jakman85 3c8b339463 Merge kyverno image fix log 2026-08-17 08:43:08 -05:00
jakman85andClaude Opus 4.8 892fdc6a15 docs: log kyverno bitnami/kubectl cleanup-image fix (F-KY1)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 08:43:08 -05:00
jakman85 928cd5e122 Merge kyverno cleanup image fix (bitnamilegacy) 2026-08-17 08:38:53 -05:00
jakman85andClaude Opus 4.8 5521059c04 fix(kyverno): replace purged bitnami/kubectl cleanup image with bitnamilegacy
The kyverno chart's cleanup jobs (policyReportsCleanup, webhooksCleanup) default to
bitnami/kubectl:1.30.2, which Bitnami removed from Docker Hub (2024 catalog purge) ->
ImagePullBackOff. policyReportsCleanup is a Helm post-upgrade HOOK, so its failure
stalls every helm upgrade (5m timeout -> HelmRelease not-Ready -> platform Kustomization
blocked -> platform-config/apps cascade). Override both to Bitnami's relocated
bitnamilegacy/kubectl (identical image: /bin/bash + awk/wc/xargs + kubectl). Verified
via helm template that no bitnami/kubectl reference remains.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 08:38:53 -05:00
jakman85 de81bafdc1 Merge Phase-2 conformance exit decision log 2026-08-17 08:33:24 -05:00
jakman85andClaude Opus 4.8 f78ce4ebe7 docs: Phase-2 conformance exit achieved (R-NET/R-TLS/R-DNS green) + findings
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 08:33:24 -05:00
jakman85 17622069fc Merge Phase-2 TLS/DNS conformance tests + ExternalDNS zone fix 2026-08-17 08:24:53 -05:00
jakman85andClaude Opus 4.8 20de78f5eb feat(conformance): author R-TLS + R-DNS L2 tests; fix ExternalDNS zone filter
Phase-2 exit tests (were Phase-0 scaffolds), authored against the live platform:
- trusted-tls (R-TLS-01/03): in-cluster probe dials the gateway Service (env-agnostic
  via --connect-to) with wildcard SNI and verifies the served cert CHAINS TO THE LE
  STAGING ROOT (operator decision A; dev uses LE-staging per ADR-0006). Fixture embeds
  the '(STAGING) Pretend Pear X1' root. Probes public + internal-only hostnames (R-TLS-03).
  PASSES.
- dns-exposure (R-DNS-02): a LoadBalancer Service with an external-dns hostname; polls
  public DNS until ExternalDNS auto-publishes the record; teardown + policy=sync removes it.

ExternalDNS fix (root cause of the DNS test): domain-filter was the platform SUBDOMAIN
home.keithjackson.dev, which filters OUT the actual Cloudflare zone keithjackson.dev
('no hosted zone matching record DNS Name' — record generated but skipped). Add a
DNS_ZONE cluster-var (keithjackson.dev) and set domainFilters to it. policy=sync +
txt-registry still only manage ExternalDNS-owned records (the pre-existing wildcard is
untouched). Resolves the file's WS-NET R-DNS-02 TODO.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 08:24:51 -05:00
jakman85 15048f3889 Merge autonomous session 2 decision log 2026-08-17 07:41:32 -05:00
jakman85andClaude Opus 4.8 8732c8f4fd docs: autonomous session 2 (R-NET conformance green, TLS verified, SC + report fixes)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 07:41:32 -05:00
jakman85 ee5ad90258 Merge conformance report-path CWD fix 2026-08-17 07:40:04 -05:00
jakman85andClaude Opus 4.8 416e16bdbf fix(conformance): pass absolute --report-path so run.sh is CWD-robust
run.sh exited non-zero (breaking the L2 gate) when invoked from the repo root:
chainsaw's config report.path is relative (_artifacts), so it resolved against
$PWD and failed to write ('open _artifacts/chainsaw-report.xml: no such file')
even though all tests passed. Pass --report-path ${ARTIFACTS} (absolute) +
mkdir -p first. Verified: R-NET-01 from repo root now exits 0 with report written.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 07:40:03 -05:00
jakman85 7a45eb105a Merge k3s local-storage disable (single default SC) 2026-08-17 07:34:32 -05:00
jakman85andClaude Opus 4.8 4cc04147a2 fix(k3s): disable local-storage so Longhorn is the sole default StorageClass
The deployed cluster has TWO default StorageClasses — k3s's built-in local-path
AND longhorn — which is ambiguous for PVCs without an explicit class. Longhorn
(ADR-0003) is the cluster storage, so add local-storage to k3s --disable. Effective
on fresh installs (the k3s install task is idempotent → no restart of the running
cluster); the running dev cluster keeps both defaults harmlessly (all platform PVCs
pin storageClassName: longhorn). Rebuilds get a single default.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 07:34:31 -05:00
jakman85 a07151ab7c Merge Phase-2 deployed decision log 2026-08-17 01:53:13 -05:00
jakman85andClaude Opus 4.8 0811e2533b docs: Phase-2 platform fully deployed + green (fixes 4-6, follow-ups)
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 01:53:13 -05:00
jakman85 59dd3d9526 Merge cert-manager DNS-01 public-resolver fix 2026-08-17 01:44:29 -05:00
jakman85andClaude Opus 4.8 cdb546bc6e fix(cert-manager): DNS-01 self-check via public resolvers (split-horizon)
The wildcard cert stalled: challenge Presented=true (Cloudflare TXT created OK —
token works) but propagation self-check looped 'DNS record not yet propagated'.
Verified the record IS publicly resolvable (1.1.1.1/8.8.8.8); the NAT'd k3s-dev
cluster's own DNS just can't see it. Set dns01RecursiveNameservers=1.1.1.1:53,
8.8.8.8:53 + dns01RecursiveNameserversOnly=true so the self-check bypasses cluster
DNS. Realises the pre-existing TODO in this file.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 01:44:28 -05:00