nix and nixos configs
nix docs security-audit.md
13 kB
Markdown
at main

Nix Configuration Security Audit #

Audit date: 2026-08-14

Scope: Nix and NixOS configuration under this repository. The audit did not open or inspect any wireguard.nix file because those files contain private keys. Findings below are based on configuration review, not on a live host inspection.

Remediation status: configuration changes from the follow-up pass are marked below. The installer test image still has a root SSH test-key exception because tests/iso-test.sh requires it; the installed laptop no longer authorizes that key for root.

Findings #

1. NUR community package supply-chain risk (Medium) #

Status: Mitigated by removing the NUR input, overlay, and NUR-only Firefox add-on packages.

Why it matters: NUR packages are community-maintained and do not receive the same centralized review, reproducibility expectations, or provenance assurance as the nixpkgs packages selected by this repository. Avoiding them reduces exposure to unvetted package changes and potential supply-chain or malware risks. This is a risk-management decision, not a claim that NUR itself is malicious.

Verification: nix flake check --no-build passes and no NUR references remain in the flake, lock file, or Nix modules.

If Firefox add-ons are restored: use exact-version XPI files from official Mozilla Add-ons releases, fetch them with Nix fetchurl and a committed content hash, review the extension manifest and requested permissions, and install them through Home Manager's extensions.packages. Do not use NUR packages or addons.mozilla.org/.../latest.xpi runtime URLs. Mozilla signing helps establish publisher authenticity; the Nix hash provides reproducibility and prevents silent content changes until an explicit reviewed update.

2. Insecure Electron package is explicitly allowed (High) #

Status: Fixed by removing both insecure-package exceptions from flake.nix.

Location: Previously in both desktop configurations.

What: Both desktop configurations previously allowed an insecure Electron package through permittedInsecurePackages.

Why it matters: This bypasses nixpkgs' insecure-package rejection. Any application using that Electron runtime inherits known upstream security risk, and the exception applies to the whole system evaluation rather than one carefully isolated package.

Recommended action: Keep the exception absent and identify any package evaluation failure that appears after future input updates before reintroducing an explicit, time-bounded exception.

3. Root SSH access is baked into the laptop and installer (High) #

Status: Partially fixed. Root SSH is disabled on the installed laptop. The live installer retains its test-only root key until the ISO test harness is migrated to non-root access.

Locations: 13-inch-thin-cannon/configuration.nix:196-198 and 13-inch-thin-cannon/installer.nix:85-95

What: The same tracked public test key was authorized for root SSH access on the installed laptop and the live installer. The installed laptop now sets PermitRootLogin = "no" and no longer configures a root key; the installer still uses the key for its SSH-based test harness.

Why it matters: Anyone who obtains the corresponding private test key gets root access to both environments. The installer is especially sensitive: it boots a privileged root environment and can rewrite the target disk. A key named and stored as a test key is also easy to leave shared or insufficiently rotated.

Recommended action: Keep the installed-host root restriction. Migrate tests/iso-test.sh to console or non-root test access, then remove the installer's root key as well.

4. Wi-Fi password is configured in a Nix expression (High if replaced) #

Status: Fixed. The profile now uses NetworkManager's agent-owned password flag and does not place a password in the flake.

Location: 13-inch-thin-cannon/configuration.nix:74-80

What: The NetworkManager profile previously contained a literal password attribute. It now sets password-flags = 1, so NetworkManager prompts for the credential instead.

Why it matters: Values embedded in Nix configuration can be copied into the world-readable Nix store and may also be present in build logs or system closures. Replacing the placeholder with a real 802.1X password would expose that password to users who can read the store or configuration generations.

Recommended action: Keep using NetworkManager's agent-owned credential storage. Never commit the real value or interpolate it into a derivation.

5. LUKS and TPM hardening options are currently no-ops (High) #

Status: Partially fixed. hardened.luks.enable now asserts that an initrd LUKS device exists, and the unused hardened.luks.tpm option was removed. TPM enrollment itself remains a separate follow-up.

Locations: modules/luks.nix:8-14, harden/configuration.nix:30-37, and 13-inch-thin-cannon/configuration.nix:200-205

What: These options previously suggested that enabling them enforced LUKS and TPM-backed unlocking without doing so. The LUKS option now has an initrd assertion; TPM enrollment is no longer exposed as a non-functional option.

Why it matters: This creates a false security guarantee for callers of the shared module. The current hosts happen to define LUKS through their disko layouts, but changing a layout or adding a host can silently produce an unencrypted system while the hardening option remains enabled.

Recommended action: Keep the LUKS assertion and test the generated fileSystems, initrd crypt configuration, and unlock policy for every host. Implement TPM enrollment separately before enabling it on physical hardware.

6. Main laptop does not enable the shared kernel/AppArmor hardening (Medium) #

Status: Fixed. The laptop now enables both shared AppArmor and kernel hardening settings.

Locations: 13-inch-thin-cannon/configuration.nix:200-206 and modules/hardened.nix:8-25

What: The laptop previously left hardened.kernel.hardened and hardened.apparmor.enable unset. Both are now enabled alongside Secure Boot, LUKS, and impermanence.

Why it matters: Secure Boot protects boot-chain integrity, but it does not replace runtime isolation. This host also enables libvirt, USB redirection, desktop applications, and a webcam module, so runtime attack surface is material.

Recommended action: Verify the generated AppArmor profiles and service behavior on the physical laptop; enabling the framework alone is not a complete policy.

7. TPM-backed agenix is not enabled on the laptop (Medium) #

Status: Partially fixed. The user-home identity fallback was removed from the configured identity list. TPM-backed agenix remains disabled until a TPM identity is enrolled and tested on the physical host.

Locations: modules/secrets.nix:7-20 and 13-inch-thin-cannon/configuration.nix:200-205

What: The shared secret declarations now use only the host SSH key. The laptop does not enable hardened.agenix, so TPM-backed decryption is not yet configured by the shared hardening module.

Why it matters: A host SSH key is still a high-value decryption identity, and it does not provide TPM-bound protection against offline disk theft.

Recommended action: Enroll and test a TPM identity before enabling TPM-backed agenix. Until then, keep the host SSH key protected and verify its permissions and persistence; never place private identities in the repository or Nix store.

8. Disk discard is enabled for the encrypted laptop volume (Low) #

Status: Fixed by removing allowDiscards from the LUKS layout.

Location: 13-inch-thin-cannon/disko.nix:22-29

What: LUKS was previously configured with allowDiscards = true.

Why it matters: Discards can reveal information about filesystem block usage and timing to a storage device or a party observing its behavior. This is usually an availability/performance tradeoff, not a remote vulnerability, but it weakens the metadata-hiding properties expected from encrypted storage.

Recommended action: Keep discards disabled unless SSD space reclamation is explicitly required and the information leak is accepted.

9. Kubernetes artifacts are downloaded without integrity verification (High) #

Status: Open.

Locations: kubernetes/ansible/playbooks/tasks/cilium.yml and kubernetes/ansible/playbooks/tasks/rook-ceph.yml.

What: The deployment downloads the Cilium Helm archive and Rook manifests from helm.cilium.io and raw.githubusercontent.com at deployment time. The URLs contain version strings, but the downloads are not checked against SHA-256 digests, signatures, or repository-committed copies.

Why it matters: A compromised upstream account, repository release, CDN, DNS path, or control machine could supply modified YAML or a modified Helm chart. Kubernetes manifests run with cluster-admin-level effects, and Helm charts can install privileged workloads.

Recommended action: Vendor the exact chart/manifests or pin and verify checksums before applying them. Prefer signed release artifacts and verify their signatures in the deployment workflow. Treat a version tag as a selector, not as an integrity guarantee.

10. Container image is tag-pinned but not digest-pinned (High) #

Status: Open.

Location: kubernetes/ansible/playbooks/tasks/rook-ceph.yml:146.

What: Ceph is configured as quay.io/ceph/ceph:v19.2.0, which identifies a version tag but not an immutable image manifest digest.

Why it matters: Registry tags can be moved. A later deployment or node image pull could receive different bytes under the same tag, bypassing the intended review and reproducibility boundary for a privileged storage component.

Recommended action: Pin the image to quay.io/ceph/ceph@sha256:<digest>, record how the digest was verified against the intended release, and repeat the same policy for every externally pulled Kubernetes image.

11. Moving flake references rely on lock-file discipline (Medium) #

Status: Partially mitigated. The committed lock files pin content by revision and nar hash, but nested flake declarations still use moving channels or refs such as nixos-unstable, latest, and master.

Locations: flake.nix, harden/flake.nix, and kubernetes/flake.nix.

Why it matters: A normal evaluation uses the lock file, but an unlocked checkout, an intentional lock update, or an unreviewed generated lock-file change can pull new upstream code into the build. This is a supply-chain review boundary, especially for bootloader, disk, secret-management, and Kubernetes components.

Recommended action: Keep lock files committed, review every lock-file diff, avoid latest in source declarations, and update one dependency at a time with nix flake check --no-build followed by the full relevant end-to-end test before deployment.

Items Requiring Verification #

  • The installer enables SSH in installer.nix:85-91 but does not define an explicit firewall policy. Verify from a booted ISO that SSH is unreachable unless the intended network and key controls are present.
  • networking.firewall.checkReversePath = "loose" is required by the WireGuard policy-routing design (configuration.nix:118-124) but weakens reverse-path anti-spoofing. Keep the nftables input policy restrictive and verify that spoofed or unsolicited inbound traffic is rejected.
  • Both desktop hosts enable libvirt and SPICE USB redirection. This is a deliberate local virtualization feature, but it expands the privileged device and VM attack surface. Review whether USB redirection is needed on every host and limit access to trusted local users.
  • The repository uses nixos-unstable in flake.nix:5. The lock file pins the current inputs, but the nix flake update shell alias updates them without a review gate. Review lock-file changes before deploying system generations.

Positive Controls Observed #

  • The laptop uses full-disk LUKS in its disko layout and has Secure Boot via Lanzaboote enabled.
  • The laptop's WireGuard kill-switch drops ordinary output when wg0 is down; its policy-routing exception is documented in the configuration.
  • Password authentication is disabled for the configured SSH services, and the root password is locked in modules/user.nix when that module is enabled.
  • The laptop's root filesystem is rolled back with impermanence, while selected state and SSH host keys are intentionally persisted.