The homelab hosts accept root SSH with long-lived file keys, run several
exposed services with almost no systemd sandboxing, and keep no audit trail of
privileged actions. Close those three gaps, weighing each measure against its
cost instead of adopting a policy wholesale.
SSH
- Turn off KbdInteractiveAuthentication: nixpkgs defaults it to true and PAM
then still accepts the Unix password despite PasswordAuthentication = false.
- authorizedKeysInHomedir = false: only keys declared in Nix take effect;
youko keeps Gitea's forced-command file through `Match User gitea`.
- Pin aquamarine's passphrase-free remote-builder keys to
`restrict,command="nix-daemon --stdio --store daemon"`, so builds run in the
system daemon instead of an audited root login session.
systemd sandboxing
- Add the mylib.systemdHardening preset and apply it to the exposed, weakly
sandboxed services: caddy 7.8 -> 1.5, vmalert 8.2 -> 1.4, rustfs 6.6 -> 1.4,
whisper-asr 7.7 -> 1.6. An eval test pins the key restrictions.
- Rejected: a global all-keys policy (<=0.2 score gains on services nixpkgs
already sandboxes) and per-service confinement (bind paths to maintain).
auditd on all four hosts, reusing nixpkgs' loader and unit
- Audit login sessions only: a first `-a never,task -F auid=unset` rule keeps
services out of the per-syscall audit path. A compromised service is
therefore unrecorded until it opens a session.
- Record root commands, credential and host-key access, writes to SSH, account,
PAM, Nix-trust and audit configuration, module loads and session denials.
- Rejected: global denial logging, service-UID watches and
--loginuid-immutable (the kernel already records loginuid changes).
Verified on all four hosts after deployment: zero failed units, k3s 6/6 Ready,
and the exposed services healthy.
Assisted-by: Claude Code (claude-opus-5-5)
* chore(workarounds): re-evaluate stale hardware/kernel fixes
Prune workarounds whose root cause is gone, fix one vendor typo, and
document the ones we keep:
- drop `transparent_hugepage=madvise` on shoryu/shushou/youko: it was tied to
the old fixed 1G hugetlb pool, and the VMs now use ordinary memory
- shoryu: `hardware.cpu.intel.updateMicrocode` -> `amd` (it is an AMD APU)
- ai: drop `i915.enable_guc=3` (GuC/HuC submission is the default on modern
i915/Xe)
- kernel-hardening: name the Dirty Frag CVEs (CVE-2026-43284 / CVE-2026-43500)
and the re-evaluation condition
- document re-eval conditions for the Insta360 resume fix, the darwin
auto-optimise-store workaround, and the youko USB-SATA bridge mitigations
* chore(workarounds): keep the Dirty Frag module blacklist as defence in depth
* chore(ai): correct the i915.enable_guc rationale (verified against the kernel)
* chore(darwin): drop the auto-optimise-store workaround (nix#7273 fixed)
Drop the now-meaningless kubevirt- prefix and move the hosts out of
hosts/k8s/, following the existing 12kingdoms-shoukei layout: the
directory and output carry the theme prefix while the configuration,
hostname and colmena target are the bare name (shoryu, shushou, youko).
- hosts/k8s/kubevirt-* -> hosts/12kingdoms-*; the shared disko config
becomes host-disko-fs.nix; the now-different relative imports are fixed
- outputs, vars/networking.nix keys, the Justfile recipes/tags, the eval
tests and the docs follow
- colmena reaches these hosts by IP (a new targetHost override), since
their DNS name changes with the hostname and is not resolvable until
the router learns it