wip: k3s hardening: PodSecurity baseline + NetworkPolicy template + postgres loopback bind #682
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "sec-k8s-platform"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Security-audit follow-up. Hardens the beefcake k3s cluster with PodSecurity admission, provides a real (not no-op) NetworkPolicy baseline, closes a latent postgres exposure, and lays out a podman->k8s migration plan. Build-checked, not deployed.
What's in here
1. PodSecurity admission (implemented, cluster-wide)
k3s.podSecuritymodule option feeds kube-apiserver anAdmissionConfigurationvia--kube-apiserver-arg=admission-control-config-file=(a nix-store config file, referenced by path — the apiserver reads it in-process as root).baselineenforce default,restrictedwarn/audit,kube-systemexempt (traefik/coredns/metrics-server/local-path/helm-install would fail baseline otherwise).echo-server) are untouched until they restart. Safe to deploy.2. NetworkPolicy — audit premise corrected ⚠️
The audit said netpol would be a no-op on flannel. That's wrong for k3s. k3s embeds kube-router's netpol controller alongside flannel and enforces NetworkPolicy by default (docs); this cluster does not pass
--disable-network-policy(verified on the running server). So default-deny is real here.lib/doc/k8s-networkpolicy-template.yaml: reusable per-namespace default-deny-ingress + allow-from-traefik + allow-dns.3. postgres 0.0.0.0 -> loopback (implemented)
enableTCPIP = truemade postgreslisten_addresses = '*'(0.0.0.0:5432). Investigated consumers: the only TCP client is thehappycontainer, which runs--network=hostand connects tolocalhost. Nothing authorized reaches postgres off-loopback (pg_hba trusts only127.0.0.1/32+::1/128; firewall doesn't open 5432). So the 0.0.0.0 bind was pure latent exposure. Now bound tolocalhost(both v4/v6, sohappy's hostname-based URL still resolves). Documented the flannel-bridge (10.42.0.1+ scoped pg_hba) path for future in-cluster pods.4. Migration plan (
lib/doc/podman-to-k8s-migration.md)Thumbs-up but selective. Triage of the ~7 podman workloads:
k3s ctr images import), secrets (sops -> hostPath mount or kubectl-create-secret oneshot), storage (hostPath on /storage, already in restic), and a recommended order.Action items for you (runtime state — not touched here)
default/echo-serverDeployment+Service (169 days old, a test service). Left alone per the read-only-on-prod rule; flagging for you tokubectl delete.🤖 Generated with Claude Code
Security-audit follow-up hardening the beefcake k3s cluster and closing a latent postgres exposure. - PodSecurity admission: new k3s.podSecurity module option feeds kube-apiserver an AdmissionConfiguration (via --kube-apiserver-arg=admission-control-config- file). Enabled on beefcake with a cluster-wide 'baseline' enforce default (restricted warn/audit), kube-system exempt. Gates newly-admitted pods only; running pods (incl. stale echo-server) are untouched until restart. - NetworkPolicy: corrected the audit premise -- k3s DOES enforce NetworkPolicy out of the box via its embedded kube-router netpol controller (not disabled here), so default-deny is real, not a flannel no-op. Added a reusable per-namespace default-deny + allow-from-traefik + allow-dns template (lib/doc/k8s-networkpolicy-template.yaml). Not auto-applied cluster-wide (netpol is namespaced; a blanket deny would sever kube-system) -- each migrated app carries its own copy. - postgres: was binding 0.0.0.0:5432 (enableTCPIP=true -> listen_addresses='*'). Nothing authorized reaches it off-loopback (pg_hba trusts only 127.0.0.1/::1, firewall doesn't open 5432), and the sole TCP client (happy) is --network=host on localhost. Bound to loopback ('localhost', both v4/v6) instead; documented the flannel-bridge path for future in-cluster pods. - Added lib/doc/podman-to-k8s-migration.md: triage of the ~7 podman workloads (early candidates vs stay-podman for host-network/hardware/raw-TCP), the image/secret/storage/netpol mechanics, and a recommended order. Build-checked (nixosConfigurations.beefcake.config.system.build.toplevel). Not deployed.k3s hardening: PodSecurity baseline + NetworkPolicy template + postgres loopback bindto wip: k3s hardening: PodSecurity baseline + NetworkPolicy template + postgres loopback bindPolicy change per Daniel: allow plain NodePorts for simple LAN TCP services instead of forcing everything through caddy/ingress. - Remove the --kube-proxy-arg=nodeport-addresses=127.0.0.0/8 loopback pin. NodePorts now bind all interfaces (kube-proxy default) but the host firewall keeps 30000-32767 closed on the LAN, so a NodePort is only LAN-reachable once its port is explicitly opened (declaratively via networking.firewall, nodePort pinned so the rule stays stable). No reverse proxy needed. - The :443 edge is unaffected: NodePorts live in 30000-32767 and cannot bind :443, and --disable=servicelb (kept) is the actual guard against a LoadBalancer seizing host :80/:443. Dropping the pin only removes one layer of a two-layer defense; the closed firewall still gates LAN NodePort access. - caddy still reaches traefik's NodePort 30081 on loopback; updated the now-inaccurate 'loopback NodePort' comments accordingly. - secrets/beefcake/secrets.yml: sops-unset the orphaned happy.env and garage.toml entries left behind by the happy deletion (re-encrypted + MAC recomputed for all recipients; other 49 secrets untouched). - migration doc: NodePort section reworked to lead with 'NodePort + open the firewall port' as the default for simple TCP; hostPort and caddy-l4 demoted to specific cases. Anti-goal narrowed to 'never re-enable ServiceLB / create a LoadBalancer' (was over-broad 'never widen nodeport-addresses').View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.