Files
home-network-k3s-impl/infra
jakman85andClaude Opus 5 7fa5e84d25
gate / gate (pull_request) Failing after 1m53s
gate / conformance (pull_request) Has been skipped
feat(pm-agent): the kubelet's pull credential, and the seventh field admission had not read
The registry had two blockers. #59 fixed the first (nodes resolved the hostname to the
non-hairpinned WAN and failed TLS against the router's CN=%D cert). This is the second:
the pull is unauthenticated, and this registry refuses unauthenticated clients outright —
measured 2026-10-05, its anonymous token carries access: null and every path 401s.

pm-agent-registry is a dockerconfigjson Secret (SOPS, placeholders) referenced by
imagePullSecrets on the dispatcher Deployment and on the committed worker Job template. It
holds the SAME Gitea token as pm-agent-credentials, because the two consumers need two
formats: the application reads a raw token from env, the kubelet reads a dockerconfigjson
before any container starts. Same one-value-in-two-files pattern as the Keycloak client
secret, documented in both .example files. The operator's part is the value plus one new
scope: read:package.

ADMISSION GAINED A SEVENTH RULE. imagePullSecrets is a pod-spec field no container
references, which is exactly why it is easy to leave unread — the same shape of omission
as initContainers, ephemeralContainers, env and envFrom before it. The kubelet reads the
named Secret, in this namespace, before anything runs. Rule 1's pattern now REQUIRES the
committed entry; rule 7 forbids any other and any second one. Both halves are needed: a
foreach over an absent list yields no result.

The leverage is small and the policy says so: the image is digest-pinned, so a different
pull credential cannot change WHAT runs. Pinned anyway — 'only the committed shape' is the
premise, and the rule costs four lines.

kyverno test: 33 passed, 0 failed (was 28). Three new fixtures, each a real path: the
committed entry plus a second one, a different Secret, and none at all.

The gate also caught me shipping the Secret in plaintext — I never ran sops -e -i on the
placeholder. Recorded in the handoff entry, because the file looked finished.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-10-06 01:03:54 -05:00
..

infra/ — node provisioning and the toolchain

OpenTofu (Proxmox VE) + cloud-init + Ansible bring up the 3-node k3s/Cilium dev cluster. Spec: ../home-network-k3s/docs/02-environments-and-provisioning.md.

Path What
tofu/ The "Create" stage: 3 cloud-init'd VMs on the Proxmox box (bpg/proxmox provider)
cloud-init/ First-boot baseline shipped as a Proxmox user-data snippet
ansible/ The "Baseline"/"Configure" stages: k3s HA, Cilium, kube-vip, the VPN egress gateway
devcontainer/ The pinned toolchain (tofu, ansible, sops, age, flux, kubectl, helm and the whole CI gate toolchain)
cluster.sh RETIRED — drove the old libvirt VMs; does not work against Proxmox
PHASE1-STATUS.md Current state of the cluster and how to reconnect to it
PHASE1-SCOPE.md Historical Phase-1 scoping doc (libvirt era — superseded)

Where the cluster actually lives

The dev cluster is not on this workstation. It runs on a Proxmox VE host; the nodes sit on the Proxmox LAN and are reached across a pfSense NAT:

Nodes k3s-dev-1/2/3 at 192.168.1.50/.51/.52 — 6 vCPU, 24 GiB, Debian 13, k3s v1.36.4
kube-API kube-vip VIP 192.168.1.49:6443, reached from the workstation via NAT 192.168.2.50:6443
Node SSH pfSense NAT 192.168.2.102:2250/2251/2252 → node :22 (Ansible only)
Ingress VIP 192.168.1.53 (Cilium L2, shared Gateway); .53–.60 reserved for LB VIPs

The earlier libvirt/KVM cluster on 10.73.0.0/24 is retired. Any doc or command referencing virsh, qemu:///system, or 10.73.0.x for the cluster is historical.