mirror of
https://github.com/ryan4yin/nix-config.git
synced 2026-11-13 12:13:36 +01:00
docs: centralize documentation under docs/
Move standalone repository docs from the root into docs/ and add a documentation index that links the moved docs and the code-local runbooks. Update Markdown links, source comments, and the root entry points to the new locations. Keep README.md and AGENTS.md at the root as required entry points. Assisted-by: DeepSeek Harness (deepseek-flash)
This commit is contained in:
1 parent
be3e27c49b
commit
4470b3d014
30 files changed
+176
-88
No files matched your search
+163
@@ -0,0 +1,163 @@
|
||||
# Backups
|
||||
|
||||
How this fleet is backed up, where the data lives, and how to restore it.
|
||||
|
||||
## What protects against what
|
||||
|
||||
| Threat | Defence |
|
||||
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| accidental deletion, bad edit | btrbk: local btrfs snapshots |
|
||||
| disk or host loss | restic: encrypted copies on youko |
|
||||
| a compromised backup server | desktop repositories use a password youko never holds |
|
||||
| a compromised host | youko takes hourly read-only btrfs snapshots of the REST store, which a desktop cannot reach; the immutable cloud copy (planned) is still the offsite layer |
|
||||
|
||||
## Layers
|
||||
|
||||
1. **btrbk — local snapshots** ([`modules/nixos/base/btrbk.nix`](../modules/nixos/base/btrbk.nix)).
|
||||
Same-filesystem btrfs snapshots of `@persistent`, run on Tue/Sat 03:45:20, kept for 7 days
|
||||
(minimum 2). Cheap rollback for mistakes; it does **not** survive disk loss.
|
||||
2. **restic — encrypted copies on youko**
|
||||
([`modules/nixos/base/restic-backup.nix`](../modules/nixos/base/restic-backup.nix)). Encryption
|
||||
happens on the client, so the server only ever stores ciphertext. youko runs `restic-rest-server`
|
||||
with `--private-repos`, authenticated by htpasswd, published behind caddy as
|
||||
`restic.writefor.fun`. Deliberately not append-only: a desktop holds both the plaintext and the
|
||||
repository password, so blocking deletion there protects little while denying the client the
|
||||
automatic retention it needs. A compromised desktop can still `forget --prune` its own repository
|
||||
through the REST API, so youko also snapshots `/data/backups` hourly into a read-only btrfs
|
||||
subvolume the desktop cannot reach (see
|
||||
`hosts/12kingdoms-youko/homelab-services/restic-server-snapshots.nix`).
|
||||
3. **cloud copy** (planned). youko will copy the homelab repositories with `restic copy` (it holds
|
||||
that password). Desktops copy their own: youko does not hold the desktop password. This layer is
|
||||
what protects history against a compromised host, so the target should be immutable or versioned
|
||||
(object lock or equivalent) rather than a plain bucket.
|
||||
|
||||
## Password model
|
||||
|
||||
Two independent repository passwords, deliberately not shared:
|
||||
|
||||
| Group | Secret | Recipients | Decrypts |
|
||||
| ----------- | ----------------------------- | ----------------------- | -------------------- |
|
||||
| desktop | `restic-password-desktop.age` | desktops | desktop repositories |
|
||||
| homelab | `restic-password-homelab.age` | homelab hosts | homelab repositories |
|
||||
| REST access | `restic-rest-credentials.age` | desktops | nothing (auth only) |
|
||||
| REST access | `restic-rest-htpasswd.age` | the REST server (youko) | nothing (auth only) |
|
||||
|
||||
Consequences worth remembering:
|
||||
|
||||
- youko stores desktop repositories but cannot read or verify them. Any content check
|
||||
(`restic snapshots`, `restic check`) for a desktop repository must run on that desktop.
|
||||
- Keep both repository passwords offline (password manager or paper). If a host dies, its password
|
||||
is the only way to restore its data.
|
||||
- restic's own password is never stored inside a backed-up tree: `etc/agenix` and all key material
|
||||
are excluded (see below).
|
||||
- Recipients follow the secrets repository's rule: every secret is decryptable by the desktops
|
||||
(`desktop_keys`, which also carries the offline `recovery_key`), so a desktop can edit or rekey
|
||||
anything and no secret becomes unrecoverable when a host is lost. The desktop repository password
|
||||
is the one secret the servers do not get.
|
||||
|
||||
## Schedule
|
||||
|
||||
Slots are assigned per host; the random delay only smooths load and must not be relied on to prevent
|
||||
collisions.
|
||||
|
||||
| Time | Job |
|
||||
| ---------------- | ----------------------------------- |
|
||||
| 01:30 + 15m | desktops push to youko |
|
||||
| 02:30 + 15m | youko backs itself up |
|
||||
| hourly | youko snapshots the REST store |
|
||||
| 03:45:20 Tue/Sat | btrbk local snapshots |
|
||||
| 06:00 (planned) | youko copies homelab repos to cloud |
|
||||
|
||||
Set per host via `modules.restic-backup.onCalendar` and `randomizedDelaySec`.
|
||||
|
||||
## What is never backed up
|
||||
|
||||
- **Keys and credentials**, excluded by the module on every host: `etc/agenix`,
|
||||
`etc/ssh/ssh_host_*`, `**/.ssh`, `**/.gnupg`, `**/.aws`, `**/.config/gcloud`. Backing these up
|
||||
would put repository and host credentials inside the repository itself.
|
||||
- **Regenerable bulk**, excluded per host: podman's storage tree, microVM/libvirt images, NFS and
|
||||
cache/log directories on youko; language build artefacts, caches, model weights and anything
|
||||
larger than 500M on desktops.
|
||||
|
||||
Prefer a specific `paths` allowlist plus a size cap over a long `exclude` list.
|
||||
|
||||
## Operations
|
||||
|
||||
```bash
|
||||
# run a backup now, and read its log
|
||||
systemctl start restic-backups-homelab
|
||||
journalctl -u restic-backups-homelab -n 50 --no-pager
|
||||
|
||||
# timer state
|
||||
systemctl list-timers 'restic-backups*'
|
||||
```
|
||||
|
||||
Inspect or restore a repository where its password lives (a desktop repository on a desktop, a
|
||||
homelab repository on a homelab host):
|
||||
|
||||
```bash
|
||||
restic -r <repository> --password-file /run/agenix/restic-password snapshots
|
||||
restic -r <repository> --password-file /run/agenix/restic-password check
|
||||
restic -r <repository> --password-file /run/agenix/restic-password \
|
||||
restore latest --target /tmp/restore --include /home/ryan/Documents
|
||||
```
|
||||
|
||||
Remote (REST) repositories also need the service credentials:
|
||||
|
||||
```bash
|
||||
set -a; . /run/agenix/restic-rest-credentials; set +a
|
||||
restic -r rest:https://restic.writefor.fun/idols-ai/ \
|
||||
--password-file /run/agenix/restic-password snapshots
|
||||
```
|
||||
|
||||
Restoring a btrbk snapshot (offline; stop writers first):
|
||||
|
||||
1. `btrfs subvolume delete /btr_pool/@persistent`
|
||||
2. `btrfs subvolume snapshot /btr_pool/@snapshots/@persistent.<timestamp> /btr_pool/@persistent`
|
||||
3. reboot, or remount `/persistent`, to pick up the restored subvolume.
|
||||
|
||||
## Retention
|
||||
|
||||
- btrbk: automatic, 7 days with a 2 day minimum.
|
||||
- youko's own restic repository: automatic, `--keep-daily 3 --keep-weekly 2 --keep-monthly 2`.
|
||||
- Desktop restic repositories: automatic, the module defaults
|
||||
(`--keep-daily 3 --keep-weekly 2 --keep-monthly 2`). Immutability is the cloud copy's job
|
||||
(planned), not the local server's.
|
||||
|
||||
## Verifying
|
||||
|
||||
- Units: `systemctl status restic-backups-homelab`, `systemctl list-timers 'restic-backups*'`.
|
||||
- Repository integrity (where the password is): `restic check`, which runs after every backup via
|
||||
`modules.restic-backup.checkOpts` (a 5% data read, `1/20`, by default).
|
||||
- Server side, ciphertext only: repository directories under `/data/backups/rest-server/<user>/`,
|
||||
with no leftover files in `locks/` and no stale `.tmp` files.
|
||||
- `restic-rest-server`'s log must not print `Invalid htpasswd entry`: that means the client's REST
|
||||
password and the server's htpasswd disagree, and the credentials pair needs rebuilding from one
|
||||
password.
|
||||
- Metrics: the rest-server exposes the Go/promhttp defaults on loopback (scraped as the
|
||||
`restic-rest-server` job; `/metrics` is blocked on the public vhost). Repository growth is charted
|
||||
from `node-exporter`'s filesystem metrics for youko's `/data` mount instead — rest-server itself
|
||||
has no per-repository size metrics.
|
||||
- Failures are not silent: a failed `restic-backups-homelab` starts `restic-backup-notify`, which
|
||||
posts to alertmanager and, above info severity, on to telegram. alertmanager clears it after
|
||||
`resolve_timeout`.
|
||||
|
||||
## Planned
|
||||
|
||||
- **Restore drill**: actually restore a file from a restic snapshot and from a btrbk snapshot, on a
|
||||
schedule. An unverified restore is not a backup.
|
||||
- **Cloud copy**: youko copies the homelab repositories with `restic copy`; desktops copy their own.
|
||||
The target must not live in the homelab, and should be immutable or versioned (object lock) — that
|
||||
is the layer which survives a compromised host.
|
||||
|
||||
## Components
|
||||
|
||||
| Path | Role |
|
||||
| --------------------------------------------------------------------------------- | ---------------------------------------------------- |
|
||||
| [`modules/nixos/base/restic-backup.nix`](../modules/nixos/base/restic-backup.nix) | client module: repository, paths, excludes, schedule |
|
||||
| [`modules/nixos/base/btrbk.nix`](../modules/nixos/base/btrbk.nix) | local btrfs snapshots |
|
||||
| `hosts/12kingdoms-youko/default.nix` | the REST server and youko's own backup |
|
||||
| `hosts/12kingdoms-youko/homelab-services/restic-server-snapshots.nix` | hourly read-only snapshots of the REST store |
|
||||
| `hosts/12kingdoms-youko/homelab-services/caddy.nix` | the `restic.writefor.fun` vhost |
|
||||
| `secrets/nixos.nix` | which secret is defined on which host |
|
||||
| `hosts/idols-ai/restic.nix`, `hosts/12kingdoms-youko/default.nix` | per-host backup configuration |
|
||||
+87
-3
@@ -1,5 +1,89 @@
|
||||
# Documentation
|
||||
|
||||
- [BIOS settings](./bios.md)
|
||||
- [Wake-on-LAN](./wake-on-lan.md)
|
||||
- [NixOS modernization options](./nixos-modernization-options.md)
|
||||
Canonical index for this repository. Standalone reference material lives in this directory.
|
||||
Component runbooks stay next to the code they describe so they remain visible during maintenance;
|
||||
every human-facing document is linked here.
|
||||
|
||||
`README.md` and `AGENTS.md` remain at the repository root because GitHub and coding agents require
|
||||
those entry points.
|
||||
|
||||
## Repository
|
||||
|
||||
- [Repository overview](../README.md) — systems, components, deployment and validation quick start.
|
||||
- [Agent guide](../AGENTS.md) — repository conventions, command hazards and validation rules.
|
||||
- [NixOS modernization options](nixos-modernization-options.md) — design options and decisions.
|
||||
|
||||
## Operations and policy
|
||||
|
||||
- [Security](SECURITY.md) — threat model, trust boundaries, implemented controls and audits.
|
||||
- [Backups](BACKUP.md) — backup layers, password model, retention and restore procedure.
|
||||
- [Workarounds and known gaps](WORKAROUNDS.md) — pins, temporary workarounds and removal conditions.
|
||||
- [BIOS settings](bios.md) — firmware settings for the physical hosts.
|
||||
- [Wake-on-LAN](wake-on-lan.md) — firmware prerequisites, NixOS setup and wake commands.
|
||||
- [Runtime hardening validation](../hardening/tests/runtime-validation.md).
|
||||
|
||||
## Hosts and services
|
||||
|
||||
- [Host inventory and deployment notes](../hosts/README.md).
|
||||
- AI desktop: [host runbook](../hosts/idols-ai/README.md) and [VR notes](../hosts/idols-ai/VR.md).
|
||||
- Akane: [host runbook](../hosts/idols-akane/README.md).
|
||||
- Aquamarine: [host runbook](../hosts/idols-aquamarine/README.md),
|
||||
[iGPU notes](../hosts/idols-aquamarine/IGPU.md) and
|
||||
[ROCm notes](../hosts/idols-aquamarine/ROCM.md).
|
||||
- Kana: [host runbook](../hosts/idols-kana/README.md).
|
||||
- Ruby: [host runbook](../hosts/idols-ruby/README.md).
|
||||
- Shoukei: [host runbook](../hosts/12kingdoms-shoukei/README.md).
|
||||
- Youko homelab: [services overview](../hosts/12kingdoms-youko/homelab-services/README.md),
|
||||
[Immich](../hosts/12kingdoms-youko/homelab-services/immich/README.md),
|
||||
[monitoring](../hosts/12kingdoms-youko/homelab-services/monitoring/README.md),
|
||||
[alert rules](../hosts/12kingdoms-youko/homelab-services/monitoring/alert_rules/README.md),
|
||||
[recording rules](../hosts/12kingdoms-youko/homelab-services/monitoring/recording_rules/README.md)
|
||||
and [Grafana dashboards](../hosts/12kingdoms-youko/homelab-services/grafana/dashboards/README.md).
|
||||
- Kubernetes cluster: [k8s host notes](../hosts/k8s/README.md) and
|
||||
[disko configuration](../hosts/k8s/disko-config/README.md).
|
||||
- Infrastructure: [infra overview](../infra/README.md), [RustFS](../infra/rustfs/README.md),
|
||||
[Loki storage](../infra/rustfs/loki/README.md) and
|
||||
[Terraform S3 backend](../infra/rustfs/tf-s3-backend/README.md).
|
||||
- NixOS installation: [installer guide](../nixos-installer/README.md) and
|
||||
[Shoukei notes](../nixos-installer/README.shoukei.md).
|
||||
- Certificates: [certificate setup](../certs/README.md).
|
||||
|
||||
## Configuration structure
|
||||
|
||||
- [NixOS and nix-darwin modules](../modules/README.md), including
|
||||
[Darwin modules](../modules/darwin/README.md),
|
||||
[Mihomo networking](../modules/nixos/desktop/networking/mihomo/README.md) and
|
||||
[Sunshine](../modules/nixos/desktop/networking/sunshine/README.md).
|
||||
- [Home Manager modules](../home/README.md).
|
||||
- [Flake outputs and tests](../outputs/README.md).
|
||||
- [Secrets management](../secrets/README.md).
|
||||
- [Overlays](../overlays/README.md), [shared library helpers](../lib/README.md) and
|
||||
[shared variables](../vars/README.md).
|
||||
- [Hardening wrappers and policy](../hardening/README.md).
|
||||
|
||||
## Desktop and Home Manager details
|
||||
|
||||
- [Darwin](../home/darwin/README.md), [per-host Home Manager profiles](../home/hosts/README.md) and
|
||||
[Linux](../home/linux/README.md).
|
||||
- [Linux desktop stack](../home/linux/gui/README.md): [base GUI](../home/linux/gui/base/README.md),
|
||||
[Fcitx5](../home/linux/gui/base/fcitx5/README.md),
|
||||
[hypridle](../home/linux/gui/base/hypridle/README.md),
|
||||
[vinput](../home/linux/gui/base/vinput/README.md) and [i3](../home/linux/gui/i3/README.md).
|
||||
- [Base Home Manager profile](../home/base/README.md):
|
||||
[editors](../home/base/core/editors/README.md),
|
||||
[editor glossary](../home/base/core/editors/Glossary.md),
|
||||
[Helix](../home/base/core/editors/helix/README.md),
|
||||
[Neovim](../home/base/core/editors/neovim/README.md), [Rime](../home/base/gui/rime/README.md) and
|
||||
[terminal](../home/base/gui/terminal/README.md).
|
||||
- Terminal tools: [agents](../home/base/tui/agents/README.md) and
|
||||
[dsh notes](../home/base/tui/agents/dsh.md), [editors](../home/base/tui/editors/README.md),
|
||||
[encryption](../home/base/tui/encryption/README.md), [GPG](../home/base/tui/gpg/README.md) and
|
||||
[password store](../home/base/tui/password-store/README.md).
|
||||
|
||||
## Agent resources
|
||||
|
||||
- [Global rules, skills and evaluations](../agents/README.md).
|
||||
- [Skill catalog](../agents/skills/README.md).
|
||||
- [Global agent rules](../agents/global-rules.md),
|
||||
[evaluation guide](../agents/evals/global-rules.md) and
|
||||
[tooling installation notes](../agents/install-tooling.md).
|
||||
@@ -0,0 +1,431 @@
|
||||
# OS and Homelab Security
|
||||
|
||||
This is a personal infrastructure repository, not a supported security product. This document
|
||||
describes the intended architecture, controls present in source, their limitations, and remaining
|
||||
work. A configured control is not evidence that it is active on a running machine.
|
||||
|
||||
Related documents: [application hardening](../hardening/README.md) for sandbox wrappers and Linux
|
||||
hardening notes, and [WORKAROUNDS.md](./WORKAROUNDS.md) for temporary exceptions and known gaps.
|
||||
|
||||
## Reporting problems
|
||||
|
||||
For non-sensitive configuration bugs, open an issue with the affected host role and a redacted
|
||||
reproduction. Do not post credentials, kubeconfigs, private keys, secret contents, or exploit
|
||||
details that would expose a live service. For sensitive findings, contact the maintainer privately
|
||||
through the contact information on their GitHub profile first. There is no guaranteed response SLA.
|
||||
|
||||
## Threat model and priorities
|
||||
|
||||
Protect administrator credentials, desktop application data, cluster/storage state, and the ability
|
||||
to recover after compromise or an unsuccessful update. Relevant attackers include compromised
|
||||
applications and dependencies, malicious workloads, and devices or accounts on trusted networks.
|
||||
Physical theft and boot tampering matter for desktops and physical hosts too.
|
||||
|
||||
Prioritize **patching, reducing exposure, least privilege, isolation, and recovery**, in that order
|
||||
for each affected service. Kernel mitigations and module deny lists are defence in depth, not fixes
|
||||
for vulnerabilities. A container shares its host kernel; a VM provides a separate kernel but still
|
||||
exposes a hypervisor and host-side device/share implementations. Neither makes arbitrary code safe.
|
||||
|
||||
Use proportionate hardening: preserve normal performance, developer workflows and required cluster
|
||||
features. Do not globally disable user namespaces, io_uring, SMT or hardware acceleration, switch to
|
||||
a hardened kernel, or enforce untested AppArmor policies just to maximize restrictions. Prefer
|
||||
small, reversible controls and narrow exceptions; benchmark before adopting expensive mitigations.
|
||||
|
||||
## Architecture and trust boundaries
|
||||
|
||||
| Boundary | Current design | Limitation |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| Administration | Key-based SSH; root key login retained for Colmena/MicroVM deployment; only keys declared in Nix are honoured, and no password or keyboard-interactive login; remote-builder host keys only reach `nix-daemon` | Deployment credentials are root-equivalent file keys, usable by any same-user process through the desktop SSH agent; one stolen key reaches every host |
|
||||
| Host network | Shared nftables firewall trusts the home LAN, tailnet interface and local Podman bridge; IPv6 link-local is accepted | These are broad trust zones, not service-level least privilege; router exposure and tailnet ACLs require separate verification |
|
||||
| Monitoring | node/smartctl exporters bind the host LAN IPv4 (9100/9633); firewall rules restrict exporter ports 9100, 9835 and 9633 to the monitoring host over IPv4. RAPL counters are exposed only on x86 server hosts, to `node-exporter` through the `rapl` group. | nvidia_gpu_exporter (9835) still binds wildcard; the IPv6 denial for those three ports is in the shared firewall; current running hosts require separate verification. The node_exporter account can measure package energy for local side channels; interactive users, desktops, and guests keep the kernel-default root-only permission. |
|
||||
| VM hosts | shoryu, shushou and youko run guests behind `br0`; ruby/kana are libvirt guests | A bridge is not network isolation; host compromise affects its guests |
|
||||
| k3s | Three control-plane and three worker MicroVMs; Cilium, API VIP and NFS CSI | Cluster-admin, privileged/host-mounted pods and writable NFS data are powerful trust boundaries |
|
||||
| MicroVM storage | Host store shared read-only through virtiofs; guest `/etc`, `/var` and `/home` persisted in separate images | Read-only store does not isolate the host-side virtiofs implementation or protect writable guest state |
|
||||
| Desktop apps | Selected apps use nixpak/bubblewrap; Podman is rootless | Rules and shared directories vary per app; user namespaces are needed for these sandboxes |
|
||||
| Secrets | agenix declarations here, encrypted data in the private nix-secrets repository | Recipients and plaintext file permissions must be reviewed; root on a recipient can read its decrypted secrets |
|
||||
| Supply chain | Locked flake inputs; explicit binary-cache trust keys on NixOS; normal user is not a Nix trusted-user | A trusted cache or imported Nix module can supply code executed as root; a lock file is not a security audit |
|
||||
| Recovery | NixOS generations, btrbk/restic, hourly read-only snapshots of the REST store, and documented backup procedures | A rollback does not undo compromised data or secrets; backup configuration alone does not prove restore works; the offsite immutable copy is still planned |
|
||||
|
||||
Sources: [host layout](../hosts/README.md), [k3s deployment](../hosts/k8s/README.md),
|
||||
[firewall](../modules/nixos/base/networking/firewall.nix), [SSH](../modules/nixos/base/ssh.nix),
|
||||
[Nix trust](../modules/base/nix.nix), [secrets](../secrets/README.md), and [backup](./BACKUP.md).
|
||||
Cluster resources are maintained in the separate k8s-gitops repository.
|
||||
|
||||
The Macs set `nix.enable = false` ([nix-core.nix](../modules/darwin/nix-core.nix)), so nix-darwin
|
||||
writes no `/etc/nix/nix.conf`: the substituters, trusted keys and access-token include declared here
|
||||
do not reach them. Their Nix settings come from the Determinate installation, which this repository
|
||||
does not manage; those settings were not verified on the Macs.
|
||||
|
||||
## Kernel and process baseline
|
||||
|
||||
The following explicit, overridable baseline is implemented in
|
||||
[kernel-hardening.nix](../hardening/kernel-hardening.nix), which holds the values; the table below
|
||||
summarizes them. It has Linux eval coverage and explicit ARM/RISC-V server imports. Source
|
||||
implementation is not runtime verification. Some values are already kernel/systemd/NixOS defaults;
|
||||
Darwin does not receive Linux sysctls.
|
||||
|
||||
| Setting | Value | Effect and compatibility |
|
||||
| ------------------------------------------------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `kernel.dmesg_restrict` | 1 | Kernel log access requires CAP_SYSLOG; journal access has separate permissions |
|
||||
| `kernel.kptr_restrict` | 2 | Hide pointers printed with `%pK`, even from privileged readers; not a blanket ban on address leaks |
|
||||
| `kernel.yama.ptrace_scope` | 1 | Restrict unrelated-process tracing; child-process debugging and explicit PR_SET_PTRACER exceptions remain possible |
|
||||
| `kernel.perf_event_paranoid` | 2 | Restrict unprivileged kernel profiling; user-space profiling remains available; do not assume distro-specific 3/4 semantics |
|
||||
| `kernel.unprivileged_bpf_disabled` | 2 | Disable unprivileged `bpf()` with an administrator-reversible setting; privileged Cilium/agents retain access |
|
||||
| `net.core.bpf_jit_harden` | 1 | Harden only unprivileged BPF JIT; do not enable JIT blinding for privileged Cilium/observability workloads |
|
||||
| `vm.unprivileged_userfaultfd` | 0 | Restrict unprivileged kernel-mode fault handling, not all userfaultfd usage; `/dev/userfaultfd` permissions are a separate boundary |
|
||||
| `fs.protected_symlinks`, `fs.protected_hardlinks` | 1 | Mitigate cross-user link attacks; these controls are boolean |
|
||||
| `fs.protected_fifos`, `fs.protected_regular` | 2 | Restrict unsafe O_CREAT access in world/group-writable sticky directories; shared temporary-file workflows may need adjustment |
|
||||
|
||||
Some values already come from kernel/systemd/NixOS defaults. Explicit declarations make the desired
|
||||
baseline reviewable; they do not imply the previous system had no protection. Unsupported kernel
|
||||
features may lack their sysctl paths, especially on custom kernels: runtime verification is
|
||||
required.
|
||||
|
||||
JIT hardening is deliberately limited to unprivileged programs rather than forced on privileged
|
||||
agents. Together with `unprivileged_bpf_disabled=2`, this preserves Cilium's normal privileged BPF
|
||||
path while retaining protection if an administrator temporarily permits unprivileged BPF. This is a
|
||||
configuration trade-off, not a measured claim of zero overhead.
|
||||
|
||||
User namespaces remain enabled: disabling them globally conflicts with Nix sandboxing and breaks
|
||||
rootless containers and application sandboxes. Nonprivileged seccomp filters are not disabled by the
|
||||
unprivileged `bpf()` restriction. Do not disable Cilium's privileged BPF, tc, container networking,
|
||||
NFS, or required VM/filesystem modules in pursuit of a larger deny list.
|
||||
|
||||
The existing `esp4`, `esp6`, `rxrpc` deny list blocks modprobe/autoload paths. It does not unload
|
||||
already loaded modules, affect built-in code, or stop a privileged direct module insertion. Verify
|
||||
an advisory and affected kernel versions before adding CVE claims; search snippets are not evidence.
|
||||
|
||||
### Boot integrity and mandatory access control
|
||||
|
||||
- ai configures Lanzaboote in [secureboot.nix](../hosts/idols-ai/secureboot.nix). Confirm firmware
|
||||
key enrollment and actual Secure Boot state with `sbctl status`; do not assume all hosts have it.
|
||||
- Secure Boot, signed boot images, kernel lockdown and module signature enforcement are distinct
|
||||
controls. Test custom kernels and NVIDIA/out-of-tree modules before extending enforcement.
|
||||
- AppArmor is enabled, but the only explicit policy, sudo's, is in complain mode. Complain mode logs
|
||||
rather than blocks. Existing FHS-oriented abstractions need Nix store path review; this is not
|
||||
broad enforced application confinement.
|
||||
- Systemd service sandboxing is per-service, not a global policy. The exposed services nixpkgs
|
||||
leaves weakly sandboxed (caddy, rustfs, vmalert, whisper-asr) use the preset in
|
||||
[systemdHardening.nix](../lib/systemdHardening.nix). Review capabilities, writable paths, network
|
||||
access and syscall requirements before tightening an individual service.
|
||||
|
||||
See [application hardening](../hardening/README.md) for wrapper details.
|
||||
|
||||
### Audit trail
|
||||
|
||||
`modules.security.audit` ([auditd.nix](../modules/nixos/base/auditd.nix)) runs auditd on youko,
|
||||
shoryu, shushou and aquamarine. It records, for login sessions only (SSH, and sudo or su from one):
|
||||
every root command (`root-session`), access to decrypted secrets and SSH host keys
|
||||
(`credential-access`), writes to SSH, account/PAM, Nix trust and audit configuration, module loads,
|
||||
and denials.
|
||||
|
||||
- Services are not audited: a `never,task` rule gives tasks without a login uid no audit context, so
|
||||
virtiofsd, hypervisors, NFS, PostgreSQL and Nix builds skip the per-syscall audit path. A
|
||||
compromised service is therefore invisible until it opens a login session.
|
||||
- Logs stay on the host and root can erase them; shipping them off-host is a P1 TODO.
|
||||
- One watch on a missing path makes `auditctl -R` stop and leaves auditing off;
|
||||
`nix build .#checks.x86_64-linux.security-auditd` loads the rules in a VM.
|
||||
|
||||
Query as root; sshd is built without libaudit, so use PAM's `USER_START` records for logins:
|
||||
|
||||
```sh
|
||||
ausearch -i -m USER_START --start today # logins with address and session id
|
||||
ausearch -i -k root-session --start today # root commands, with auid and ses
|
||||
ausearch -i --session <ses> # everything one session did
|
||||
```
|
||||
|
||||
## Updating, deploying and recovering
|
||||
|
||||
1. Check the advisory and affected package/kernel; prioritize active exploitation and reachable
|
||||
attack paths. Review changed inputs and cache trust instead of treating updates as inherently
|
||||
safe.
|
||||
2. Run `just test` (must pass), build affected systems/runners and review closure differences.
|
||||
Evaluate tests cannot prove runtime enforcement or workload compatibility.
|
||||
3. Record the old system generation/runner and ensure backups and recovery access exist. Use `boot`
|
||||
mode for bridge/network-stack changes or broad VM-host updates.
|
||||
4. Deploy MicroVM closures to the **physical host**, not the guest's read-only store. Install the
|
||||
reviewed runner and activate/restart through the documented MicroVM path. Preserve all images.
|
||||
5. Keep control-plane restarts sequential to retain etcd quorum. Worker concurrency is an explicit
|
||||
availability trade-off; simultaneous worker restarts can interrupt all worker workloads.
|
||||
6. Verify the active/booted generation, guest boot ID, failed units, API VIP `/readyz`, Nodes,
|
||||
Cilium, CSI and workloads. Updating a kernel on disk does not patch the running kernel: reboot
|
||||
and verify.
|
||||
7. On failure, restore the recorded generation/runner and recheck health before continuing. Preserve
|
||||
rollback roots until stable. If compromise is suspected, isolate first, rotate affected secrets,
|
||||
and restore verified data; merely switching generations is not incident recovery.
|
||||
|
||||
For the sysctl baseline, query the keys above with `sysctl <key>`, check
|
||||
`journalctl -b -u systemd-sysctl`, and test rootless Podman, a sandboxed Nix build, desktop
|
||||
sandboxes, debugging, and cluster networking/storage after activation. Do not globally relax
|
||||
hardening to hide one application's incompatibility: make a narrow documented exception and test it.
|
||||
|
||||
These sysctls are reversible by administrator configuration, unlike `unprivileged_bpf_disabled=1` or
|
||||
`kexec_load_disabled=1`, which cannot be relaxed without rebooting. This baseline does not enable
|
||||
the latter or `security.protectKernelImage` (which also disables hibernation).
|
||||
|
||||
## API endpoint and TLS
|
||||
|
||||
The canonical endpoint is **`https://test-cluster-1.writefor.fun:6443`**. DNS points it to
|
||||
kube-vip's `192.168.5.193`; the three servers include the DNS name in their serving-certificate
|
||||
SANs, and the joining servers/agents use that domain. The first server initializes the cluster
|
||||
without a join address. This is already a domain-based HA endpoint, not a bare-IP client design.
|
||||
|
||||
Use the existing authenticated client configuration to verify it:
|
||||
|
||||
```sh
|
||||
k3s kubectl --server=https://test-cluster-1.writefor.fun:6443 --request-timeout=15s get --raw=/readyz
|
||||
```
|
||||
|
||||
Bare-IP access may fail TLS hostname verification because the certificate does not include the VIP
|
||||
IP SAN. That is not a fault in the canonical endpoint and does not require certificate rotation. If
|
||||
bare-IP disaster-recovery access becomes an explicit requirement, add that SAN through normal
|
||||
configuration and certificate lifecycle procedures. Never bypass TLS verification to conceal a
|
||||
hostname mismatch. Eval tests protect the domain join endpoint and server SAN declarations; live TLS
|
||||
checks remain necessary after deployment.
|
||||
|
||||
## Implementation status and completed audit findings
|
||||
|
||||
### Implementation status
|
||||
|
||||
| Component | Status | Runtime verification |
|
||||
| ---------------------------------------------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------- |
|
||||
| Architecture, agent-first policy, audits and canonical API tests | In this repository | Only the dated observations below were checked live |
|
||||
| Explicit sysctl baseline and IPv6 exporter defence | In `hardening/` and the shared firewall | Live on 13 reachable hosts (readback 2026-10-22); ai root-privileged items still owed |
|
||||
|
||||
### Baseline implementation checks
|
||||
|
||||
- An explicit, overridable sysctl baseline preserves user namespaces and privileged BPF performance.
|
||||
- IPv6 exporter denial precedes broad LAN/tailnet/container rules. The monitoring configuration uses
|
||||
IPv4 targets for these ports, so no scrape endpoint migration is needed.
|
||||
- x86_64/aarch64 eval checks cover baseline values, normal host overrides and generated exporter
|
||||
rules. Canonical API endpoint checks cover all six cluster nodes.
|
||||
- A reusable isolated VM check exercises allowed monitoring IPv4 access, denied other IPv4/IPv6
|
||||
access to all three exporter ports, retained IPv6 loopback and a reachable ordinary IPv4/IPv6
|
||||
service. Run it with `nix build .#checks.x86_64-linux.security-exporters`. It imports only the
|
||||
shared firewall, not real-host secrets or workloads, and does not replace deployment-time checks.
|
||||
|
||||
### Read-only audit snapshot — 2026-10-03
|
||||
|
||||
- **API:** the canonical domain resolves to the VIP; authenticated `/readyz` returned `ok` with
|
||||
normal TLS validation. No DNS or certificate changes were needed.
|
||||
- **ai boot:** `sbctl status` reports Secure Boot enabled and Setup Mode disabled. It also reports
|
||||
legacy sbctl configuration; migration is separate work, not permission to change firmware keys.
|
||||
- **Listeners:** SSH listens on IPv4/IPv6 on sampled VM hosts and a master. SMART exporters bind the
|
||||
physical hosts' LAN IPv4 addresses; node_exporter has wildcard listeners. youko's NFS listens on
|
||||
IPv4/IPv6, and the sampled master API has a wildcard listener. These are listener observations,
|
||||
not proof of reachability from every network or of router/WAN protection.
|
||||
- **Workload inventory:** 36 of 78 observed pods had host networking, hostPath or explicitly
|
||||
privileged containers. They were infrastructure components: Cilium, Istio CNI/ztunnel, NFS CSI,
|
||||
kube-vip and OpenTelemetry log agents. Their host access is not automatically a misconfiguration;
|
||||
remove it only after validating the component's requirements. Application pods were not flagged by
|
||||
this limited check; that does not establish full workload isolation.
|
||||
- **RBAC:** explicit cluster-admin bindings include `system:masters` and the Flux kustomize/Helm
|
||||
controller service accounts. Operators also have broad resource permissions. Flux's bootstrap
|
||||
privileges are intentional for cluster reconciliation; Git write access and controller credentials
|
||||
therefore remain administration boundaries. This inventory is not a complete effective-access or
|
||||
privilege-escalation analysis.
|
||||
- **NFS:** the golden store is `0755 root:root`, exported read-write to an explicit client list (the
|
||||
k3s nodes and the VM hosts) with `sec=sys,no_root_squash`. The client list stops other LAN devices
|
||||
reaching the export, but a root user on an allowed client still has server-root write access.
|
||||
`root_squash` is deferred: the existing PVC subdirectories are root-owned (observed
|
||||
`drwxr-xr-x root root` and `drwxrwsr-x root 107`), so enabling it needs a one-time ownership
|
||||
migration and a maintenance window. See docs/WORKAROUNDS.md.
|
||||
- **Remote consoles:** the libvirt VNC consoles listen on `127.0.0.1` only, and the k3s firewall
|
||||
drops 5900-5903 from pod sources. Reach a console through an SSH tunnel.
|
||||
- **Pod-to-host firewall:** two independent layers deny pod access to the credential-bearing host
|
||||
ports (SSH, NFS, Postgres, restic, VNC, Immich, etcd, exporter/monitoring). The
|
||||
`deny-pod-to-node-admin-ports` `CiliumClusterwideNetworkPolicy` in
|
||||
[k8s-gitops#52](https://github.com/ryan4yin/k8s-gitops/pull/52) denies pod egress to
|
||||
`host`/`remote-node` on those ports at the pod's own node; the k3s nodes' nftables rule drops the
|
||||
same ports for pod CIDR sources (`10.0.0.0/8`, `fd00::/104`). The nftables rule only matches when
|
||||
the pod IP survives to the destination, which requires Cilium's eBPF datapath: kube-proxy's
|
||||
iptables masquerade rewrote pod -> node traffic to the source node IP and defeated it. That is why
|
||||
k3s runs `--disable-kube-proxy` ([#404](https://github.com/ryan4yin/nix-config/pull/404)) and
|
||||
Cilium runs `kubeProxyReplacement` with `bpf.masquerade`. Verified live on all nodes: pod ->
|
||||
node:22 is dropped while kubelet, ingress (80/443 stay open), ClusterIP and DNS keep working.
|
||||
|
||||
Only selected pod security fields, RBAC rules/binding metadata, listeners and export metadata were
|
||||
queried. Secret contents and raw kubeconfigs were not inspected. No live access policy, workload,
|
||||
export, credential, DNS record or firmware configuration was changed during the audit.
|
||||
|
||||
### Effective-access and credential follow-up — 2026-10-03
|
||||
|
||||
- Four authenticated `kubectl auth can-i` probes covered all 40 ServiceAccounts used by current
|
||||
pods: reading secrets across namespaces, creating ClusterRoleBindings, escalating ClusterRoles,
|
||||
and creating pods in the account's own namespace. Impersonation included the account's normal
|
||||
service-account and authenticated groups; no secret contents or real pods/bindings were created.
|
||||
- Flux Helm/Kustomize controllers and the VictoriaMetrics operator were allowed all four probes.
|
||||
Kiali's operator could create ClusterRoleBindings, but the escalation probe was denied; create
|
||||
permission alone does not prove unrestricted binding authority. Several controllers, including
|
||||
Loki's account, can read secrets across namespaces and need a feature-specific scope review.
|
||||
- Sampled staging application/default accounts were denied all four probes. This does not prove they
|
||||
have no other permissions or cannot read particular secrets in their own namespace; unused
|
||||
accounts, named-resource grants, token issuance and indirect escalation remain separate checks.
|
||||
- Metadata checks on ai, reachable youko and the sampled master found owner-only decrypted files
|
||||
(`0400`, `0500`, `0600` or `0000`), not group/world-readable files. ai's user-consumed
|
||||
`/etc/agenix` copies have explicit owner-only modes. A symlink's `0777` is not the effective
|
||||
target's access mode.
|
||||
- User-readable credentials are intentional, including `nix-access-tokens` on servers as well as
|
||||
desktops. Same-user agents are not isolated from these credentials by Unix file permissions. Mode
|
||||
`0000` also does not stop privileged root. Other host/runtime ownership checks and recipient
|
||||
membership were not completed; no private recipient repository or secret value was inspected.
|
||||
- Darwin's `/etc/agenix` entries are agenix `path` symlinks, so agenix applies each secret's
|
||||
declared owner and mode and the root-only `rclone.conf` stays root-owned. Runtime ownership
|
||||
(`ls -lL /etc/agenix`) on each Darwin host still needs a check after the next activation.
|
||||
- PostgreSQL's loopback TCP listener still trusts every role except `postgres` without a password,
|
||||
so any local process can log in as `immich` or `playground`. No service configured here uses that
|
||||
path: Immich and the exporter connect over the Unix socket with peer authentication. `postgres`
|
||||
and replication now require `scram-sha-256`, closing the local-superuser path. Provisioning
|
||||
per-role passwords for full loopback `scram-sha-256` is the follow-up; the disabled Grafana
|
||||
`postgres-playground` datasource also waits on a `playground` password.
|
||||
|
||||
### Follow-up audit — 2026-10-22
|
||||
|
||||
- Baseline readback on live hosts: the ten unprivileged sysctl keys and the esp4/esp6/rxrpc modprobe
|
||||
deny are in effect on all 13 reachable hosts; the #399/#400/#401 nft rules, NFS export scope and
|
||||
pg_hba rules are live. `net.core.bpf_jit_harden` and ai's exact rule order need a root readback;
|
||||
shoukei and akane were unreachable. k3s MicroVM guests have no system profile — compare
|
||||
`/run/current-system` with `/run/booted-system` there.
|
||||
- Exporter sockets are LAN-IPv4-bound on every scraped host except ai's NVIDIA exporter (`*:9835`),
|
||||
a gap #373 missed; `hosts/idols-ai/hardware-nvidia.nix` now binds it and the `security-exporters`
|
||||
eval covers every enabled exporter's `listenAddress`.
|
||||
- Runtime drift on shoryu: the libvirt domains on 5902/5903 predate #400 and still bind wildcard;
|
||||
only the nft VNC drop protects them until the guests are redefined and restarted.
|
||||
- The pod-to-node deny lists omitted etcd's client port (2379, bound `192.168.5.114:2379`); both
|
||||
layers now include it. 2381 (etcd metrics, scraped by in-cluster monitoring) stays reachable by
|
||||
design.
|
||||
- ServiceAccount effective access is now measured for all 41 live ns/SA pairs (on-node probes): the
|
||||
Kiali operator gained a self-escalation path (`update`/`patch clusterroles` on a role bound to
|
||||
itself, no `bind`/`escalate` verb needed); Kiali and kube-state-metrics deny `get secrets -A` but
|
||||
allow `list`/`watch`, so probe sets must pair both verbs; 16 accounts enumerate secrets
|
||||
cluster-wide and cert-manager can delete them. Narrowing proposals target k8s-gitops.
|
||||
- k3s-test-1 pod networking has been down since the 2026-10-14 full guest cold-restart: Cilium
|
||||
bootstraps through the VIP while the VIP requires kube-vip's lease over ClusterIP — a cycle the
|
||||
kube-proxy-replacement switch created. The host nftables drops stay loaded, but the CLiCNP layer
|
||||
enforces nothing while Cilium is down and the nft rule's pod-source-IP precondition is not met, so
|
||||
the pod-to-node boundary is degraded until the cluster recovers.
|
||||
- The cluster recovered manually (VIP attached on one master, then handed back to kube-vip), and the
|
||||
cycle itself is fixed: kube-vip's pod now overrides the kubelet-injected
|
||||
`KUBERNETES_SERVICE_HOST/PORT` to reach its own node's apiserver (port 6443) instead of the
|
||||
ClusterIP, so the VIP no longer needs Cilium on a cold start. A full cold start of all six guests
|
||||
on 2026-10-24 brought the VIP back with no manual action (6/6 nodes, Cilium 6/6, kube-vip 3/3, no
|
||||
connection to the ClusterIP), and k8s-gitops#54 is merged and reconciled. A probe from a pod
|
||||
confirms 2379 is denied while 2381 stays open for in-cluster metric scraping.
|
||||
|
||||
### Sandbox and agent boundaries
|
||||
|
||||
The installed agent CLIs run as the normal user, not inside a repository-defined OS sandbox.
|
||||
AGENTS.md/tool permissions remain important execution policy, but they are not Unix process
|
||||
isolation. Source review and socket metadata—not reads of private credentials—established:
|
||||
|
||||
- The current desktop session's SSH-agent socket is owned by and writable to the current UID.
|
||||
Same-user subprocesses can potentially use its signing interface; read-only mounts do not prevent
|
||||
socket use. Private-key bytes were not inspected and no signing operation was requested.
|
||||
- `ssh -G` reports forwarding for explicit `192.168.*` destinations, but not the sampled `shoryu`
|
||||
alias or GitHub destination. Do not treat forwarding as enabled for every LAN alias, or remove it
|
||||
without checking deployment/build workflows.
|
||||
- All nine desktop wrappers use shared nixpak policies: cleared environment, filtered session D-Bus
|
||||
and selected mounts. The [permission inventory](../hardening/README.md#app-permissions) lists each
|
||||
app's file, network, credential and media grants.
|
||||
- Only Firefox/Chromium share host networking. Chromium is an intentional local-development
|
||||
exception; all other apps isolate loopback with pasta but can still reach the LAN/Internet.
|
||||
Browser media and browserpass are enabled only for Firefox/Chromium/Chrome. Brave Origin and Edge
|
||||
are backup browsers with no media grants. browserpass exposes password-store/GPG data and agent
|
||||
operations.
|
||||
- Nixpak supplements Chromium's renderer sandbox, which remains enabled. Zoom/WeChat retain their
|
||||
FHS runtimes inside the outer boundary; inner mounts cannot recover hidden host resources.
|
||||
- Desktop access has limits: GPU drivers remain exposed, PulseAudio permits recording, read-only
|
||||
sockets permit agent operations, and Zoom/WeChat's X11 connections have shared-display trust.
|
||||
Zoom's 7.2.1 client receives only `/dev/video0` and `/dev/video1` because it captures the webcam
|
||||
directly via V4L2; no other app gets raw camera nodes and no raw PipeWire access is added.
|
||||
Chromium-family browsers use only the app-scoped Secret portal for v12 cookie encryption; no
|
||||
browser receives broad GNOME Keyring authority. Firefox uses its own NSS storage. Firefox camera
|
||||
and remote screen sharing are intentionally disabled: Firefox does not support the XDG
|
||||
Camera/ScreenCast portal path used by this policy, and enabling those features would require
|
||||
granting raw video-device or direct PipeWire access. We deliberately keep that privilege closed.
|
||||
- **Rollout note (WA-045):** v12 is verified on a disposable profile, but if the portal is
|
||||
unavailable Chromium may fall back to weaker v10 storage; portal initialization is monitored.
|
||||
- Policy/build passes for all nine wrappers. Interactive validation passed for Firefox, Chrome,
|
||||
Chromium, Brave Origin, Edge, QQ, Telegram and Zoom; the Chromium smoke test also validates
|
||||
renderer sandboxing, environment clearing, bind scope and profile persistence. WeChat's
|
||||
interactive Wayland check remains pending. See the
|
||||
[runtime validation](../hardening/tests/runtime-validation.md).
|
||||
|
||||
#### Agent-first diagnostics by host role
|
||||
|
||||
- **Homelab:** configured root SSH is an acceptable path for autonomous, task-scoped read-only
|
||||
diagnosis. Root access does not authorize deployment, restart, permission changes, destructive
|
||||
operations or secret inspection merely because a diagnostic command is available.
|
||||
- **Core desktops:** prefer normal-user service status, network information, metrics, own-process
|
||||
inspection and journal access. Do not add passwordless sudo or remove existing human-admin
|
||||
membership/workflows as an incidental security change. ai's normal user can query system-journal
|
||||
metadata, including the kernel transport, without sudo; other desktops need their own readback.
|
||||
Escalation to root, including local root SSH, needs explicit authorization rather than an
|
||||
automatic fallback when a normal-user diagnostic is denied.
|
||||
- A read-only API or log can still reveal credentials. Use bounded service/time filters and redact
|
||||
sensitive output; never dump process environments or raw kubeconfigs. Narrow, reviewed per-service
|
||||
log access is preferable when a desktop lacks access. Do not blanket-grant all journals or add a
|
||||
generic privileged diagnostic-command dispatcher just for convenience.
|
||||
- Preserve functioning SSH/browser/GPU integration. Stronger agent OS isolation, if needed, should
|
||||
be an explicit workflow design with positive/negative tests, not a collection of surprise denials.
|
||||
|
||||
For routine diagnosis, prefer bounded reads such as `systemctl --failed --no-pager`,
|
||||
`systemctl status SERVICE --no-pager`, `journalctl -u SERVICE -n 200 --no-pager`,
|
||||
`journalctl --user -u SERVICE -n 200 --no-pager`, `ip -br address`, `ss -lntu` and
|
||||
`ps -eo pid,comm,stat`. On Homelab these may use the configured root SSH connection; on desktops try
|
||||
normal-user access first. Do not confuse read-only status with permission to restart a service, or
|
||||
diagnose a log-access denial by reading credential files or removing protection globally.
|
||||
|
||||
### Update/reboot routine and kernel visibility
|
||||
|
||||
Recommended cadence: review advisories and pinned inputs weekly, with an out-of-band review for
|
||||
active exploitation or a reachable high-impact vulnerability. Check affected versions and exposure,
|
||||
then validate an update and schedule the required rollout/reboot. Do not auto-apply every input bump
|
||||
or promise that a kernel version string alone proves security coverage.
|
||||
|
||||
Verify the booted kernel after each rollout instead of trusting the selected generation. The booted
|
||||
image and the image selected for the next boot are independent:
|
||||
|
||||
- `readlink -f /run/booted-system/kernel` — kernel of the running system closure.
|
||||
- `readlink -f /nix/var/nix/profiles/system/kernel` — kernel selected for the next boot.
|
||||
|
||||
A mismatch means the host still runs the old kernel and a reboot is pending. This catches boot-only
|
||||
deployments even when `/run/current-system` is unchanged; `uname -r` gives the running release
|
||||
string. A matching kernel path is not proof of vulnerability age, livepatch state or driver
|
||||
compatibility, and a generation change that only affects the initrd, modules or userspace leaves the
|
||||
kernel path unchanged. Guest profiles and a runner staged only on a MicroVM's physical host require
|
||||
separate verification.
|
||||
|
||||
## Remaining prioritized TODOs
|
||||
|
||||
| Priority | Work | Acceptance / constraints |
|
||||
| -------- | -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| P1 | Move root/deploy SSH to hardware-backed keys | `sk-ssh-ed25519` keys with touch for root, with SSH multiplexing to keep Colmena usable; an agent lifetime or confirmation; a tested break-glass path |
|
||||
| P1 | Alert on SSH logins and ship auth/audit logs off-host | Off-host copy host root cannot rewrite; alert on an unknown key or source, a session-rate spike and `root-session`/`credential-access`; redact key material |
|
||||
| P1 | Roll out and verify the implemented baseline/exporter rules | Runtime sysctl readback, allowed IPv4 scrape and denied IPv6/non-monitoring scrape tests; preserve Nix/Podman/Cilium/NFS functionality |
|
||||
| P1 | Review router mappings and tailnet ACLs; narrow service exposure where warranted | Audit was limited to sampled host listeners; test intended and denied access without locking out deployment |
|
||||
| P1 | Review NFS client scope and root identity requirements | Restrict to required VM hosts/CSI nodes where practical; test provisioning, permissions and existing PVCs before changing squash semantics |
|
||||
| P1 | Narrow operator/secret access where justified; finish permission checks | Selected effective probes and ai/youko modes audited; validate broad secret-reader needs, remaining hosts and recipient scope; Darwin per-file ownership is now explicit and needs a runtime check |
|
||||
| P1 | Adopt the documented cadence and verify the booted kernel after rollouts | Routine documented; compare booted and selected kernel paths and recheck API/workloads after reboot; no automated metric is deployed |
|
||||
| P1 | Exercise backup restore and credential rotation | Use an approved isolated restore target and recovery access; never overwrite live data for a drill |
|
||||
| P2 | Promote selected AppArmor profiles to enforce; harden exposed systemd services | Per-app positive/negative tests, store-path coverage, reviewed capabilities and reversible rollout |
|
||||
| P2 | Maintain ai boot integrity and assess other host roles | Review sbctl configuration migration, recovery boot and signed custom/NVIDIA modules; ai Secure Boot state already verified |
|
||||
| P2 | Review unused-module deny list against real workloads and verified advisories | Check loaded/built-in modules and autoload dependencies; do not block required filesystems, WiFi, VM or Cilium/NFS functionality |
|
||||
| P2 | Evaluate stronger controls only against a concrete threat | Account for hibernation, crash recovery, applications and performance; no blanket io_uring/SMT/userns ban or hardened-kernel switch |
|
||||
| P2 | Validate narrower sandbox/agent boundaries where needed | Source and selected SSH/journal metadata audited; use disposable data/socket probes and preserve core-desktop diagnosis, browserpass and Homelab root workflows |
|
||||
| P3 | Improve security-event alerts and configuration drift checks | Actionable redacted alerts and periodic runtime control readback |
|
||||
|
||||
Record temporary exceptions in [WORKAROUNDS.md](./WORKAROUNDS.md) with a removal condition. Revisit
|
||||
this architecture after a new exposed service, network/storage change, host addition or significant
|
||||
kernel/nixpkgs update. No control here guarantees containment after host root compromise.
|
||||
|
||||
## References
|
||||
|
||||
- [Linux kernel sysctls](https://docs.kernel.org/admin-guide/sysctl/kernel.html)
|
||||
- [Filesystem protections](https://docs.kernel.org/admin-guide/sysctl/fs.html)
|
||||
- [BPF JIT hardening](https://docs.kernel.org/admin-guide/sysctl/net.html#bpf-jit-harden)
|
||||
- [userfaultfd restrictions](https://docs.kernel.org/admin-guide/sysctl/vm.html#unprivileged-userfaultfd)
|
||||
- [Yama](https://docs.kernel.org/admin-guide/LSM/Yama.html)
|
||||
- [Kernel module signing](https://docs.kernel.org/admin-guide/module-signing.html)
|
||||
- [NixOS security advisories](https://github.com/NixOS/nixpkgs/security/advisories)
|
||||
- [CISA Known Exploited Vulnerabilities](https://www.cisa.gov/known-exploited-vulnerabilities-catalog)
|
||||
@@ -0,0 +1,92 @@
|
||||
# Workarounds & known gaps
|
||||
|
||||
Temporary workarounds, version pins, carried patches, and known gaps are collected here, grouped by
|
||||
type, each with the condition under which it can be removed. Review the list periodically so
|
||||
obsolete workarounds get rolled back instead of lingering.
|
||||
|
||||
## How to use
|
||||
|
||||
- **Adding** — a change that introduces a workaround, pin, carried patch, or known gap adds a row
|
||||
under the matching section below in the same PR, with a concrete **removal condition**.
|
||||
- **Removing** — once the removal condition is met, delete the change and set `Status` to `removed`
|
||||
(mention the ID in the commit), or delete the row.
|
||||
- **Reviewing** — re-evaluate rows whose `Revisit` trigger is due and refresh `Status`.
|
||||
|
||||
## Pins
|
||||
|
||||
A version or revision pinned for a reason that can go away.
|
||||
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------- | -------------------------- | ------ |
|
||||
| WA-004 | `catppuccin` on `main` — `flake.nix` | `main` carries the nodejs→nodejs-slim deprecation fix, which is not in a release | Switch to a release tag once it includes the fix | 2026-09-25 | on catppuccin release | active |
|
||||
| WA-006 | `nixpkgs-blender` — `flake.nix` | Blender 5.2 LTS + CUDA/OptiX pin; keeps `just up` from triggering a full source rebuild | Bump deliberately when needed; drop the pin if the current nixpkgs build is acceptable | 2026-07-24 | deliberate | active |
|
||||
| WA-020 | `fcitx5-vinput` pinned to release tag `v2.4.2` — `flake.nix`; consumer `home/linux/gui/base/vinput/default.nix` | Tracks a release tag instead of a branch, so `just up` stays on that release | Bump to a newer release tag when its changes are wanted | 2026-10-04 | on vinput release | active |
|
||||
| WA-021 | `nix-x86-on-aarch64` input — our fork of rowanG077's patched FEX + muvm runtime — `flake.nix`; `hosts/12kingdoms-shoukei/game/` | FEX does not support 16K pages, so x86/x86_64 apps and games on shoukei need this pinned flake's 4K-page microVM runtime; our fork lets upstream changes be reviewed before use | Drop once FEX supports 16K pages directly, or nixpkgs/nixos-apple-silicon ships an equivalent runtime | 2026-10-07 | on FEX/nixpkgs/muvm update | active |
|
||||
| WA-023 | `nix.package = pkgs.nixVersions.latest` — `modules/nixos/base/nix.nix` | NixOS is opted into Nix 2.35 (2.35.3), which nixpkgs ships as `nixVersions.latest`, while `nixVersions.stable` is still 2.34 (2.34.9) | Drop the override and fall back to the default `pkgs.nix` once `nixVersions.stable` is 2.35 or newer | 2026-10-07 | eval warning | active |
|
||||
| WA-037 | `nixpkgs-npu` input pinned to `2ddccb854d89` — the `openvino-npu-compiler` branch of `ryan4yin/nixpkgs`, carrying [NixOS/nixpkgs#570230](https://github.com/NixOS/nixpkgs/pull/570230) — `flake.nix`; consumer `pkgs-npu` in `outputs/default.nix`, `hosts/idols-aquamarine/asr.nix` | The Intel NPU compiler, and the driver change that loads it, are unmerged, so the NPU driver the Whisper service needs comes from that branch | Drop the input and load `intel-npu-driver` from `nixos-unstable` once #570230 merges | 2026-10-17 | when #570230 merges | active |
|
||||
|
||||
## Workarounds
|
||||
|
||||
A setting that works around a bug or a hardware quirk.
|
||||
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | --------------------------------------------------------------------------------------------------- | ------ |
|
||||
| WA-009 | USB-SATA bridge mitigations (`usbcore.autosuspend=-1`, `usb-storage.delay_use=10`) — `hosts/12kingdoms-youko/default.nix` | flaky JMicron JMS567 resets with UDMA-CRC and I/O errors | Drop once the bridge/controller is replaced; the signal is smartctl CRC/media errors | 2026-09-21 | on hardware change or CRC errors | active |
|
||||
| WA-010 | Insta360 Link reset-resume quirk (`usbcore.quirks=2e1a:4c01:b`) — `hosts/idols-ai/default.nix` | the camera stays enumerated with a stalled UVC endpoint after resume | Drop after the Insta360 firmware or the kernel `uvcvideo` fixes it | 2026-08-01 | on kernel or firmware change | active |
|
||||
| WA-011 | Kernel module blacklist (`esp4`, `esp6`, `rxrpc`) — `hardening/kernel-hardening.nix` | attack surface for the Dirty Frag LPE (CVE-2026-43284 / CVE-2026-43500) | Kept as defence in depth; drop only if something needs those modules | 2026-10-03 | rarely | active |
|
||||
| WA-018 | user-owned `~/.ssh/config` + disabled systemd ssh-proxy include — `home/base/tui/ssh.nix`, `modules/nixos/base/ssh.nix` | bwrap exposes root-owned store files as `nobody` | Drop once sandboxes preserve store ownership | 2026-10-06 | when sandbox layout changes | active |
|
||||
| WA-019 | programs.steam.extraCompatPackages = [ pkgs.dwproton-bin pkgs.proton-ge-bin ] — modules/nixos/desktop/gaming.nix | ACE games fail on GE/mainline Proton, work with DW-Proton | Drop once mainline or GE-Proton runs these games (GE-Proton#433) | 2026-10-07 | on Proton/GE bump | active |
|
||||
| WA-024 | Wi-Fi power saving disabled (`networking.networkmanager.wifi.powersave = false`) — `hosts/12kingdoms-shoukei/apple-silicon.nix` | On this BCM4378/brcmfmac laptop, powersave is a common source of intermittent ASSOC-REJECT / authentication-timeout failures; NetworkManager then mistakes the failure for a bad key, re-prompts, and the client (Noctalia, nmtui, nmcli) saves the answer as a duplicate `<SSID> 1` profile | Drop once the kernel carries the brcmfmac `port_authorized` fix (WA-025) and no association failures remain | 2026-10-07 | watch `journalctl -u wpa_supplicant` for ASSOC-REJECT / authentication timeout | active |
|
||||
| WA-026 | guest deploys run `installOnHost` then `systemctl restart microvm@<guest>` on the physical host — `Justfile`, `lib/genMicrovmGuestModule.nix` | this is the fallback that microvm.nix's own `microvm.deploy.rebuild` uses when it cannot switch in place (`doc/src/ssh-deploy.md`), so only the in-place switch is lost: its sshSwitch step runs `nix-store --load-db` against the guest's read-only virtiofs `/nix/store` and dies on `cannot remount "/nix/store" writable` (nix 2.35) | Switch guests in place with sshSwitch once microvm.nix or nix handles a read-only store. Until then, note that a host activation re-links `/var/lib/microvms/<guest>/current` to the runner baked into the host's system, so switch the host after installing a guest from a newer tree | 2026-10-09 | on microvm.nix or nix bump | active |
|
||||
| WA-028 | GitHub SSH pinned to IPv4 (`AddressFamily = "inet"`) — `home/base/tui/ssh.nix` | a v6 fake AAAA for `ssh.github.com` from the `2001:2::/64` pool (WA-029) is not proven on the 443 SSH tunnel yet | Drop once the v6 fake-ip pool is proven for GitHub SSH | 2026-10-10 | on a mihomo bump or a v6 pool change | active |
|
||||
| WA-029 | mihomo `fake-ip-range6` is `2001:2::/64`, not the shipped ULA default — `modules/nixos/desktop/networking/mihomo/generate.nu` | the default `fdfe:dcba:9876::/64` sits in `fc00::/7`, which Local Network Access calls local, so a public page loaded over the v4 fake pool and pulling a dual-stack subresource prompts for local-network access (Chrome 154, Firefox). The same range also stalled ssh: a session to a fake AAAA authenticated and then never closed, on both `gvisor` and `mips`. `2001:2::/48` is RFC 5180 benchmarking -- the v6 twin of the `198.18.0.0/15` v4 default, absent from that table -- and both symptoms went with the move | Go back to the upstream default once mihomo ships a pool outside the LNA non-public table; the ULA default brings the ssh stall back | 2026-10-09 | on a browser or mihomo bump -- that table already grew to cover `2001:db8::/32` and `198.18.0.0/15` | active |
|
||||
| WA-030 | mihomo unit sets `SKIP_SYSTEM_IPV6_CHECK=1` — `modules/nixos/desktop/networking/mihomo/default.nix` | mihomo checks at startup whether another NIC already holds an IPv6 address and, finding none, skips the TUN's v6 half -- but `strict-route` still installs its v6 blackhole (`ip -6 rule add priority 9000 unreachable`). On a cold boot that rule lands before networkd's Router Solicitation is answered, so the host can never send RS/NS, never gets a GUA, and mihomo never re-checks: self-locking until the core restarts. The env var forces the TUN v6 address up, which installs mihomo's normal v6 rules whose `lookup main suppress_prefixlength 0` keeps on-link neighbor discovery and the RA default route off the TUN | Drop once mihomo either re-runs the IPv6 check or skips the v6 strict-route blackhole when the v6 TUN is disabled | 2026-10-10 | on a mihomo bump | active |
|
||||
| WA-031 | `iommu=pt` kernel parameter — `hosts/idols-aquamarine/hardware-amd-gpu.nix` | ROCm's system requirements warn that multi-GPU systems may need IOMMU passthrough to prevent application hangs, and this host has two Radeon AI PRO R9700 cards. Added before the first inference run; not yet observed on gfx1201. | Drop once a ROCm release states that the default IOMMU mode is safe for multi-GPU gfx1201, or when the host runs a single GPU | 2026-10-13 | on a ROCm bump, or on the first hang report | active |
|
||||
| WA-032 | `amdgpu.runpm=0` kernel parameter — `hosts/idols-aquamarine/hardware-amd-gpu.nix` | this board's DSDT advertises ATPX power control (`\_SB.PC00.GFX0.ATPX`) but never defines the `PEG0.PEGP._OFF`/`_ON` symbols its body calls, so the first idle resume of the headless R9700 leaves it in `runtime_status = error` (`resume of IP block <smu> failed -62`) until reboot, and amdgpu discards the ACPI error. Root cause, kernel-source trail and the measured field comparison: `hosts/idols-aquamarine/ROCM.md`. | Drop once a BIOS update defines those ACPI methods (upstream releases 1023/1066 exist for this board; a flash changes PCR7, so re-enroll TPM2), or a kernel release stops trusting ATPX when its power methods are missing | 2026-10-14 | on a BIOS update or kernel bump | active |
|
||||
| WA-036 | catppuccin eval-time ports kept in the home closure (`home.extraDependencies`) — `home/base/core/theme.nix` | catppuccin/nix reads some ports at eval time (`lib.importTOML "${sources.starship}/<flavor>.toml"`) and inlines the result, so the built port, catppuccin's build/install hooks and the fetched port source stay unreachable: every gc deletes them, and the deploy after it rebuilds them (build logs: 2026-10-16). `home.extraDependencies` records the port in the generation, which is a gc root. Also read at eval time upstream: `bottom`, `element` (option `element-desktop`), `firefox`, `rio`, `palette` (options `skim`, `tty`), `spotify-player`. | Drop once catppuccin/nix stops reading port files at eval time, or those ports come from a binary cache | 2026-10-16 | on a catppuccin bump | active |
|
||||
| WA-039 | niri's NVIDIA free-buffer pool bounded (`GLVidHeapReuseRatio` 0) — `hosts/idols-ai/hardware-nvidia.nix` | the driver does not return freed vidmem buffers to its free pool, so niri's VRAM grows to ~1.1 GiB instead of the ~100 MiB its NVIDIA notes expect (measured 1137 MiB after 24 h uptime; [NVIDIA/egl-wayland#126](https://github.com/NVIDIA/egl-wayland/issues/126)) | Drop once a driver stops growing niri's VRAM without the profile, or niri ships its own default | 2026-10-19 | on an nvidia driver bump | active |
|
||||
| WA-041 | Monitor OSD "no-signal auto power-off" disabled — no code in this repo (the P32A2V OSD setting) | during a DPMS-off idle the panel drops and re-adds HPD every ~13 s, and after wake niri keeps scanning out while the panel stays dark until the monitor is power cycled; the panel's own auto power-off drives that state, so it is now disabled. Pinning the mode to 120 Hz did not change the symptom | Drop once a driver retrains the DP link after a DPMS wake, the monitor firmware stops cycling HPD, or this monitor is replaced | 2026-10-21 | confirm on the next full idle cycle, then on an nvidia or monitor firmware update | active |
|
||||
| WA-042 | Colmena nodes set `system.nixos.versionSuffix` and `revision` from the flake's lib — `outputs/default.nix` (`rawColmenaHive`) | Colmena evaluates each node with nixpkgs' `eval-config.nix` and plain lib, which reports `26.11pre-git`, so without the override a Colmena deploy differs from the `nixosConfigurations` toplevel; the `colmena-parity` eval test guards the match | Drop once Colmena evaluates nodes with the flake's lib or accepts `nixosConfigurations` directly, keeping `colmena-parity` green | 2026-10-21 | on a colmena bump | active |
|
||||
| WA-046 | xdg-desktop-portal-wlr pinned to 0.8.2 — `modules/nixos/desktop/xdg.nix` | niri's GNOME ScreenCast path exposes only DMA-BUF with modifiers, which Zoom 7.2.1 rejects; xdg-desktop-portal-wlr 0.8.2 provides the working SHM path, while 0.8.4 regresses Zoom's SelectSources. | Drop once niri's ScreenCast path works with Zoom or xdg-desktop-portal-wlr 0.8.4+ is fixed. | 2026-10-31 | eval assertion, or on a Zoom update | active |
|
||||
| WA-048 | Nixpak GUI fontconfig comes from the host — `hardening/nixpaks/modules/desktop-base.nix` | Nixpak normally generates a standalone `/etc/fonts` and font package list for each sandbox; that bypasses the host `defaultFonts` and made browser chrome, Telegram, QQ and other sandboxed apps choose a different CJK fallback from the host. The wrapper now disables Nixpak fonts and bind-mounts `/etc/static/fonts` at `/etc/fonts`, so sandboxed GUI apps reuse the host font packages and family preferences through the already-mounted `/nix/store`. | Drop once Nixpak can directly reuse the host fontconfig or no longer overrides `/etc/fonts`, then remove the bind and restore the library-supported configuration path | 2026-11-01 | on a nixpak update | active |
|
||||
|
||||
If the KINGBANK KP260 NVMe freezes on `ai` recur (formerly WA-008, now back at the kernel's APST
|
||||
defaults), restore the parameters commented out in `hosts/idols-ai/hardware-configuration.nix` —
|
||||
`nvme_core.default_ps_max_latency_us=0 nvme_core.io_timeout=4294967295 pcie_aspm=off`.
|
||||
|
||||
If Wi-Fi wedges after a failed association, restart the stack instead of rebooting:
|
||||
`sudo systemctl restart wpa_supplicant`, then `sudo systemctl restart NetworkManager`. Deleting an
|
||||
_active_ NetworkManager profile wedges the supplicant as well; switch to another profile first, and
|
||||
only delete inactive duplicates.
|
||||
|
||||
## Known issues
|
||||
|
||||
A known limitation with no fix yet.
|
||||
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | -------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------- | ---------- | -------------------------------------------------------------- | ------ |
|
||||
| WA-016 | subscription rule snapshots — `modules/nixos/desktop/networking/mihomo/` (`rules_from:`) | mihomo's `proxy-providers` imports proxies only, so the `rules` and `proxy-groups` a subscription ships with are dropped by the core; the generator re-imports them from a local YAML copy that has to be refreshed by hand when the provider changes its routing policy | Drop the snapshots once mihomo can import rules from a proxy-provider, or the providers publish their rules as rule-providers | 2026-10-04 | when mihomo gains rule import | active |
|
||||
| WA-022 | qemu-user binfmt removed — `hosts/12kingdoms-shoukei/hardware-configuration.nix` | the x86-on-arm module owns the x86 ELF handlers and this host does not need riscv64, but dropping `boot.binfmt.emulatedSystems` also drops `nix.settings.extra-platforms` and qemu-user, so `podman run --platform=linux/amd64`, x86 chroots and local builds of uncached x86 derivations need a remote x86_64 builder | Restore `boot.binfmt.emulatedSystems` (without x86_64/i686, which the module asserts against) if such a workload is needed | 2026-10-07 | when an amd64 container/chroot or uncached x86 build is needed | active |
|
||||
| WA-025 | brcmfmac 4-way-handshake offload authentication — upstream kernel/wpa_supplicant, no code in this repo | Since hostap commit 416386060, wpa_supplicant 2.11+ expects `NL80211_CMD_PORT_AUTHORIZED` from drivers that offload the 4-way handshake; brcmfmac never reports it, so authentication intermittently fails on Apple Silicon (BRCM4378/4387/4388). nixpkgs already carries the Broadcom revert in wpa_supplicant 2.12; the proper fix, Janne Grunau's `wifi: brcmfmac: cfg80211: Report port_authorized for 4-way HS offload` (2026-09), is still under review. Entering the key again after such a failure only creates a duplicate profile, so cancel and retry instead | Fix lands via a nixpkgs kernel update (or by carrying the patch through `nixpkgs-patched`) | 2026-10-07 | on every kernel bump | active |
|
||||
| WA-033 | USB-C/XHCI port `_PLD` ACPI noise — this board's DSDT, no code in this repo | every boot logs 16 `ACPI Error: Aborting method \_SB.PC00.TXHC.RHUB.SS01._PLD ... (AE_NOT_FOUND)` lines, plus `\_SB.PC02.XHCI.RHUB.HS01._PLD`; the ports work, and these messages are unrelated to the GPU's ATPX failure, which names `\_SB.PC00.GFX0.ATPX` instead | Drop if a BIOS update defines the `_PLD` descriptors | 2026-10-14 | on a BIOS update | active |
|
||||
| WA-035 | SSH retry + multiplexing to LAN peers, ARP fallback tuning — `home/base/tui/ssh.nix`, `hosts/idols-ai/neighbour-probe.nix` | the desktop reaches the LAN over a Mesh WiFi backhaul, which drops unicast to a peer whose MAC it has aged out of its forwarding table while still flooding broadcast: a capture shows seven SYNs from one socket addressed to the cached MAC with no answer, three unicast ARP probes unanswered, then a broadcast ARP answered in 0.4 ms after ~12 s of failed connects. The ssh settings retry a short connect and keep a master warm so the entry stays fresh; the neighbour-probe tunings walk the kernel to that broadcast fallback in ~1 s | Drop once the backhaul is wired or the mesh stops dropping unknown unicast | 2026-10-16 | when the backhaul changes | active |
|
||||
|
||||
## WIP
|
||||
|
||||
An unfinished feature or gap.
|
||||
|
||||
| ID | What & where | Why | Removal condition | Added | Revisit | Status |
|
||||
| ------ | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ------------------------ | ---------- |
|
||||
| WA-013 | NixOS tests — `outputs/README.md` | full-host tests still rebuild custom packages (no private cache) and need the host ssh key to decrypt agenix; the shared-firewall security-exporters VM test now runs in CI (KVM); `outputs/x86_64-linux/nixos-tests/idols-ruby.nix` is exposed as `packages.x86_64-linux.ruby-nixos-tests` but is not in `checks` or CI | Resolve when a private cache exists and tests can run per-service | 2024-03-13 | quarterly | active |
|
||||
| WA-027 | NFS golden store `no_root_squash` — `hosts/12kingdoms-youko/nfs-golden.nix` | the existing PVC subdirectories are root-owned, so enabling `root_squash` would break running workloads until they are migrated; the CSI node plugin is hostNetwork and sources from the allowlisted node IPs | Drop when the PVC subdirectories are migrated and `root_squash` is enabled | 2026-10-07 | next maintenance window | active |
|
||||
| WA-044 | transmission AppArmor lock permission — `hosts/12kingdoms-youko/homelab-services/transmission.nix` | nixpkgs' transmission 4.1.4 profile grants `rw` on the daemon's config dir but not `k`, while 4.1.4 takes an fcntl lock on `~/.config/transmission-daemon/lock`; the daemon exits with "Another process is already using". A `local/bin.transmission-daemon` include adds `rwk` for that lock file only | Drop once the nixpkgs profile includes the lock permission | 2026-10-31 | on the next nixpkgs bump | active |
|
||||
| WA-045 | Chromium cookie crypto — `hardening/nixpaks/modules/chromium-browser.nix` | Chromium-family browsers use the app-scoped Secret portal and write v12 cookies. If the portal is unavailable, Chromium may fall back to weak v10 storage. | Drop once the v10 fallback is eliminated; monitor portal initialization and the `Local State.os_crypt.portal.prev_init_success` flag | 2026-10-31 | on Chromium updates | active gap |
|
||||
|
||||
## Related
|
||||
|
||||
- Security architecture, threat model and audit status: [SECURITY.md](./SECURITY.md).
|
||||
- Unmerged **nixpkgs** PRs are carried via the `nixpkgs-patched` input and the
|
||||
[`.agents/skills/nixpkgs-patched`](../.agents/skills/nixpkgs-patched/SKILL.md) workflow (currently
|
||||
none).
|
||||
- Routine version pins (colmena, agenix, disko, lanzaboote, haumea, home-manager, `nixpkgs-stable`,
|
||||
`nixpkgs-darwin`, …) are the versions in use, not workarounds, so they are not listed here.
|
||||
@@ -39,7 +39,7 @@ fallback, and a root-readable Cloudflare token in agenix.
|
||||
**Pros:** certificates become automatic and every tailnet device trusts them (today a phone without
|
||||
`ecc-ca` gets a certificate error); removes the Cloudflare token and the DNS-01 propagation
|
||||
workaround from those services; access control moves from "IP trust zone" to tailnet identity/ACL,
|
||||
which is exactly the limitation `SECURITY.md` records; `--tcp=<port>` can also expose raw TCP.
|
||||
which is exactly the limitation `docs/SECURITY.md` records; `--tcp=<port>` can also expose raw TCP.
|
||||
|
||||
**Cons:** Serve routes by mount path on one name, or (via the nixpkgs module) as Tailscale Services
|
||||
with `tcp:<port>` endpoints only — there is no Host/SNI vhost equivalent, so path-sensitive apps
|
||||
@@ -285,10 +285,10 @@ flake input is maintained separately for Secure Boot.
|
||||
the lanzaboote flake input; checksum validation of kernel and initrd; nicer menu.
|
||||
|
||||
**Cons:** different trust model — it signs **limine itself** and validates artifacts by checksum,
|
||||
rather than shipping a firmware-verified UKI, so `SECURITY.md` and the threat model must be updated;
|
||||
key enrollment is a manual firmware dance (Setup Mode + `sbctl enroll-keys`) and is the one change
|
||||
in this repository that can leave a machine unbootable; the existing lzpt bundle may or may not be
|
||||
reusable; less NixOS mileage than systemd-boot.
|
||||
rather than shipping a firmware-verified UKI, so `docs/SECURITY.md` and the threat model must be
|
||||
updated; key enrollment is a manual firmware dance (Setup Mode + `sbctl enroll-keys`) and is the one
|
||||
change in this repository that can leave a machine unbootable; the existing lzpt bundle may or may
|
||||
not be reusable; less NixOS mileage than systemd-boot.
|
||||
|
||||
**Reversibility:** low on the physical desktop (firmware + ESP state), high on a VM.
|
||||
|
||||
@@ -466,7 +466,7 @@ prevent boot or login. **Recommendation for the group: evaluate in a disposable
|
||||
| D4 | `system.nixos-init.enable` | `activationScripts` | bashless init | makes `activationScripts` a silent no-op — youko's `data-ro-backing`, `installCaddyWallpapers` and `installHomepageDashboardConfig` would stop running without an error | lab only |
|
||||
| D5 | `security.pam.services.<name>.rules` | per-service booleans | freeform PAM config | upstream marks it subject to breaking changes; a mistake locks you out | lab only |
|
||||
| D6 | `security.shadow.enable = false` + greetd | shadow suite provides login/su | fewer setuid programs | assertion couples the two; changes VT login semantics | lab only |
|
||||
| D7 | `security.apparmor.policies` | `policies.sudo` already in use (`hardening/apparmor/default.nix`); the old `profiles` option is removed upstream | policy set is now data | writing profiles is real work and breakage-prone; `SECURITY.md` explicitly warns against untested profiles | lab only |
|
||||
| D7 | `security.apparmor.policies` | `policies.sudo` already in use (`hardening/apparmor/default.nix`); the old `profiles` option is removed upstream | policy set is now data | writing profiles is real work and breakage-prone; `docs/SECURITY.md` explicitly warns against untested profiles | lab only |
|
||||
|
||||
---
|
||||
|
||||
@@ -520,7 +520,7 @@ header) should then be trimmed to **adopt** items only.
|
||||
`services/system/{userborn,nix-daemon,zram-generator}.nix`,
|
||||
`services/monitoring/prometheus/exporters.nix`.
|
||||
- NixOS 26.05 release notes; Nix manual, Experimental Features.
|
||||
- Repo context: `SECURITY.md` (trust zones and control limits), `WORKAROUNDS.md` (WA-001, WA-008,
|
||||
WA-009, WA-023), `modules/nixos/base/networking/mdns.nix`,
|
||||
- Repo context: `docs/SECURITY.md` (trust zones and control limits), `docs/WORKAROUNDS.md` (WA-001,
|
||||
WA-008, WA-009, WA-023), `modules/nixos/base/networking/mdns.nix`,
|
||||
`hosts/12kingdoms-youko/homelab-services/caddy.nix`,
|
||||
`hosts/12kingdoms-youko/homelab-services/README.md`.
|
||||
Reference in new issue
Block a user