feat(secrets): decrypt the login password from sops (task 0010)

Wire sops-nix into the shared base config as unconditional plumbing, with
a two-tier age identity model: an admin identity held outside the repo, and
a per-host identity generated on the machine and kept on its encrypted root.
Both `sshKeyPaths` defaults are cleared so the SSH host keys stay out of the
decryption path and remain free to become secrets in their own right.

The primary user's password hash moves into a shared secrets file encrypted
to admin plus neogaia, consumed through `hashedPasswordFile` and decrypted
before accounts are created.

This needs `users.mutableUsers = false`: NixOS applies a declared hash to an
already-existing account only when that flag is false, so at the default the
hand-set password would have been kept and the change would have been inert.
Root consequently has no password and is locked; sudo from wheel is the way
in, and generation rollback remains the recovery path.
This commit is contained in:
2026-07-19 23:29:26 -04:00
parent 7711b841dd
commit df5bfbbd3d
8 changed files with 134 additions and 11 deletions

View File

@@ -10,6 +10,8 @@ let
inherit (lib) mkOption types;
user = config.user;
passwordSecret = "${user.name}-password";
# Args to instantiate an extra nixpkgs source on the base platform.
pinArgs = prev: {
inherit (prev.stdenv.hostPlatform) system;
@@ -66,11 +68,32 @@ in
# bare TTY and not only under a graphical session.
console.useXkbConfig = true;
# Primary user, in the wheel group. No password set here.
# Decryption machinery every host depends on.
# The identity sits on the encrypted root,
# which is mounted early enough to satisfy the secret below.
# Clearing both `sshKeyPaths` defaults keeps the SSH host keys
# out of the decryption path.
sops.defaultSopsFile = ../secrets/shared.yaml;
sops.age.keyFile = "/var/lib/sops-nix/key.txt";
sops.age.sshKeyPaths = [ ];
sops.gnupg.sshKeyPaths = [ ];
# A password set by hand on a running machine otherwise takes precedence,
# leaving the declared `hashedPasswordFile` below silently inert.
# Root has no declared password and is therefore locked;
# `sudo` from the wheel group is the way in.
users.mutableUsers = false;
# Decrypted in an earlier activation stage,
# so it exists before the account that reads it.
sops.secrets.${passwordSecret}.neededForUsers = true;
# Primary user, in the wheel group.
users.users.${user.name} = {
isNormalUser = true;
description = user.description;
extraGroups = [ "wheel" ];
hashedPasswordFile = config.sops.secrets.${passwordSecret}.path;
};
# home-manager as a NixOS module: one `nixos-rebuild switch` builds the