From 9241fe5e5c4b814e2d7509a2c0b419df2acf3b63 Mon Sep 17 00:00:00 2001 From: "Casper V. Kristensen" Date: Sat, 24 Feb 2024 02:11:59 +0100 Subject: [PATCH] agenix --- README.md | 30 ++++++++---- flake.lock | 65 +++++++++++++++++++++----- flake.nix | 6 +++ modules/base/agenix.nix | 21 +++++++++ modules/base/default.nix | 1 + modules/base/users.nix | 19 ++++---- secrets/secrets.nix | 23 +++++++++ secrets/users-hashed-password-file.age | 7 +++ 8 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 modules/base/agenix.nix create mode 100644 secrets/secrets.nix create mode 100644 secrets/users-hashed-password-file.age diff --git a/README.md b/README.md index 79c5567..bdf8428 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ the unofficial NixOS wiki [Full Disk Encryption](https://nixos.wiki/wiki/Full_Di gist](https://gist.github.com/martijnvermaat/76f2e24d0239470dd71050358b4d5134). We create a 1GiB EFI boot partition (`/dev/sda1`) and the rest will be our LUKS-encrypted volume: -```bash +```fish # Create partition table parted /dev/sda -- mklabel gpt @@ -58,19 +58,35 @@ sda └─vg-root ext4 1.0 nix ``` -### Installation Whereas the [NixOS manual](https://nixos.org/manual/nixos/stable/index.html#sec-installation-manual-installing) mounts the newly-created `nixos` partition to `/mnt`, we will follow the _tmpfs as root_ blogpost and mount `/mnt` as `tmpfs`: -```bash +```fish mount -t tmpfs none /mnt mount --mkdir /dev/disk/by-label/BOOT /mnt/boot mount --mkdir /dev/disk/by-label/nix /mnt/nix mkdir -p /mnt/nix/persist/ ``` +### Secrets +All files in the Nix store are world-readable, so it is not a suitable place for including cleartext secrets, +even if we had a scheme to securely transfer them to each system. [Agenix](https://github.com/ryantm/agenix) +solves this issue by encrypting the secrets using [age](https://github.com/FiloSottile/age), and then decrypting +and symlinking them using the system's SSH host key during system activation. + +To bootstrap a new system, we must first generate a host key manually using `ssh-keygen -A -f /mnt/nix/persist` +during installation. Then, on an existing system, add the new host's public key to `secrets.nix` and rekey all +secrets using `agenix --rekey`. Commit and push the changes and proceed below. + +When managing secrets, the Keepass recovery key is used like so: +```fish +set AGE_KEY_FILE (mktemp); read -s > $AGE_KEY_FILE +agenix -i $AGE_KEY_FILE -e foo.age +``` + +### Installation The remaining installation can be done (more or less) according to the [NixOS manual](https://nixos.org/manual/nixos/stable/index.html#sec-installation-manual-installing). -```bash +```fish cd /mnt/nix git clone https://git.caspervk.net/caspervk/nixos.git tmp cd tmp/ @@ -78,10 +94,6 @@ nixos-generate-config --root /mnt --show-hardware-config vim hosts/omega/hardware.nix git add . # nix sometimes ignores files outside version control nixos-install --no-root-passwd --flake .#omega - -# Make sure to set a password -mkpasswd > /mnt/nix/persist/passwordfile -chmod 400 /mnt/nix/persist/passwordfile ``` ### Hardware Configuration @@ -94,7 +106,7 @@ enough](https://sourcegraph.com/search?q=context%3Aglobal+repo%3A%5Egithub%5C.co ## Useful Commands -```bash +```fish # upgrade system sudo nixos-rebuild switch --flake . diff --git a/flake.lock b/flake.lock index 89e58bf..74b396b 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,30 @@ { "nodes": { + "agenix": { + "inputs": { + "darwin": [], + "home-manager": [ + "home-manager" + ], + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1707830867, + "narHash": "sha256-PAdwm5QqdlwIqGrfzzvzZubM+FXtilekQ/FA0cI49/o=", + "owner": "ryantm", + "repo": "agenix", + "rev": "8cb01a0e717311680e0cbca06a76cbceba6f3ed6", + "type": "github" + }, + "original": { + "owner": "ryantm", + "repo": "agenix", + "type": "github" + } + }, "home-manager": { "inputs": { "nixpkgs": [ @@ -28,11 +53,11 @@ ] }, "locked": { - "lastModified": 1708451036, - "narHash": "sha256-tgZ38NummEdnXvxj4D0StHBzXgceAw8CptytHljH790=", + "lastModified": 1708591310, + "narHash": "sha256-8mQGVs8JccWTnORgoLOTh9zvf6Np+x2JzhIc+LDcJ9s=", "owner": "nix-community", "repo": "home-manager", - "rev": "517601b37c6d495274454f63c5a483c8e3ca6be1", + "rev": "0e0e9669547e45ea6cca2de4044c1a384fd0fe55", "type": "github" }, "original": { @@ -79,11 +104,11 @@ }, "nixos-hardware": { "locked": { - "lastModified": 1708091350, - "narHash": "sha256-o28BJYi68qqvHipT7V2jkWxDiMS1LF9nxUsou+eFUPQ=", + "lastModified": 1708594753, + "narHash": "sha256-c/gH7iXS/IYH9NrFOT+aJqTq+iEBkvAkpWuUHGU3+f0=", "owner": "NixOS", "repo": "nixos-hardware", - "rev": "106d3fec43bcea19cb2e061ca02531d54b542ce3", + "rev": "3f7d0bca003eac1a1a7f4659bbab9c8f8c2a0958", "type": "github" }, "original": { @@ -95,11 +120,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1708294118, - "narHash": "sha256-evZzmLW7qoHXf76VCepvun1esZDxHfVRFUJtumD7L2M=", + "lastModified": 1708566995, + "narHash": "sha256-e/THimsoxxMAHSbwMKov5f5Yg+utTj6XVGEo24Lhx+0=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e0da498ad77ac8909a980f07eff060862417ccf7", + "rev": "3cb4ae6689d2aa3f363516234572613b31212b78", "type": "github" }, "original": { @@ -111,11 +136,11 @@ }, "nixpkgs-unstable": { "locked": { - "lastModified": 1708296515, - "narHash": "sha256-FyF489fYNAUy7b6dkYV6rGPyzp+4tThhr80KNAaF/yY=", + "lastModified": 1708655239, + "narHash": "sha256-ZrP/yACUvDB+zbqYJsln4iwotbH6CTZiTkANJ0AgDv4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b98a4e1746acceb92c509bc496ef3d0e5ad8d4aa", + "rev": "cbc4211f0afffe6dfd2478a62615dd5175a13f9a", "type": "github" }, "original": { @@ -127,6 +152,7 @@ }, "root": { "inputs": { + "agenix": "agenix", "home-manager": "home-manager", "home-manager-unstable": "home-manager-unstable", "impermanence": "impermanence", @@ -135,6 +161,21 @@ "nixpkgs": "nixpkgs", "nixpkgs-unstable": "nixpkgs-unstable" } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index a7de831..13814a5 100644 --- a/flake.nix +++ b/flake.nix @@ -18,6 +18,12 @@ impermanence = { url = "github:nix-community/impermanence"; }; + agenix = { + url = "github:ryantm/agenix"; + inputs.nixpkgs.follows = "nixpkgs"; # use the same nixpkgs as the system + inputs.home-manager.follows = "home-manager"; # use the same home-manager as the system + inputs.darwin.follows = ""; # don't download dawrin dependencies + }; nix-index-database = { url = "github:nix-community/nix-index-database"; inputs.nixpkgs.follows = "nixpkgs"; # use the same nixpkgs as the system diff --git a/modules/base/agenix.nix b/modules/base/agenix.nix new file mode 100644 index 0000000..d4a26d9 --- /dev/null +++ b/modules/base/agenix.nix @@ -0,0 +1,21 @@ +{ agenix, pkgs, ... }: { + # Agenix manages the deployment of secrets by public-key encrypting them to + # each system's ssh host key. See the README for more information. + # https://github.com/ryantm/agenix + # https://nixos.wiki/wiki/Comparison_of_secret_managing_schemes + + imports = [ + agenix.nixosModules.default + ]; + + # Agenix attempts to decrypt secrets before impermanence symlinks the ssh + # host key. Refer directly to the key on the persistent partition, which is + # mounted in stage 1 of the boot process, before agenix runs. + # https://github.com/ryantm/agenix/issues/45#issuecomment-901383985 + age.identityPaths = [ "/nix/persist/etc/ssh/ssh_host_ed25519_key" ]; + + # `agenix` cli tool + environment.systemPackages = [ + agenix.packages.${pkgs.system}.default + ]; +} diff --git a/modules/base/default.nix b/modules/base/default.nix index b357534..43cc93a 100644 --- a/modules/base/default.nix +++ b/modules/base/default.nix @@ -1,5 +1,6 @@ { ... }: { imports = [ + ./agenix.nix ./docker.nix ./fish.nix ./git.nix diff --git a/modules/base/users.nix b/modules/base/users.nix index 77df21f..32de8ad 100644 --- a/modules/base/users.nix +++ b/modules/base/users.nix @@ -1,22 +1,16 @@ -{ pkgs, ... }: { +{ config, pkgs, ... }: { users = { # Don't allow imperative modifications to users (incompatible with impermanence) mutableUsers = false; users = { root = { - # TODO: The passwordfile is manually generated during the initial setup - # to avoid (hashed) secrets in the public git repo. It should replaced - # with a proper secret management scheme, such as agenix. - hashedPasswordFile = "/nix/persist/passwordfile"; + hashedPasswordFile = config.age.secrets.users-hashed-password-file.path; }; caspervk = { isNormalUser = true; description = "Casper V. Kristensen"; - # TODO: The hashedPasswordFile is manually generated during the initial - # setup to avoid (hashed) secrets in the public git repo. It should - # replaced with a proper secret management scheme, such as agenix. - hashedPasswordFile = "/nix/persist/passwordfile"; + hashedPasswordFile = config.age.secrets.users-hashed-password-file.path; extraGroups = [ "wheel" # allows sudo "video" # allows controlling brightness @@ -27,4 +21,11 @@ }; }; }; + + age.secrets.users-hashed-password-file = { + file = ../../secrets/users-hashed-password-file.age; + mode = "400"; + owner = "root"; + group = "root"; + }; } diff --git a/secrets/secrets.nix b/secrets/secrets.nix new file mode 100644 index 0000000..addc371 --- /dev/null +++ b/secrets/secrets.nix @@ -0,0 +1,23 @@ +# This file is NOT imported into the NixOS configuration. It is only used for +# the agenix CLI tool to know which public keys to use for encryption. See the +# README for more information. +# https://github.com/ryantm/agenix + +let + # Get a system's public key using: + # > cat /etc/ssh/ssh_host_ed25519_key.pub + # If you change or add a key, all secrets need to be `agenix --rekey`'ed. + mu = "todo"; + omega = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILvFN4vnqPX31+4/ZJxOJ7/bSUEu2xB6ovezPQjLm13H root@omega"; + tor = "todo"; + zeta = "todo"; + # Recovery and management key from Keepass. Used like so: + # > set AGE_KEY_FILE (mktemp); read -s > $AGE_KEY_FILE + # > agenix -i $AGE_KEY_FILE -e foo.age + recovery = "age1rd6hhd724s3r9xe4gfuy38rl0xfu8c7pkuefsrdwqfcknujzecyqz7ldyj"; + + all = [ omega recovery ]; +in +{ + "users-hashed-password-file.age".publicKeys = all; +} diff --git a/secrets/users-hashed-password-file.age b/secrets/users-hashed-password-file.age new file mode 100644 index 0000000..3986bca --- /dev/null +++ b/secrets/users-hashed-password-file.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 fY+XUg ThWnidSUv20sqdMebPW0aV512ascEV4WyDia72vhTnI +fbFAnyqqqpp9fEct2EiLG1wWw//U8kWcpf0QnbSh33Y +-> X25519 ZbC+v0St7P+W/AHq1Afst7ylmZUFA7OIhiElfexTHgg +2DbCqyjULhmDji3E1HrPuO8WW74dIia1GFOSCaeGliU +--- 6W7LaM4dc6tJONcmtVAwhI/NcOE8EUYPrg75K6Qpynw +%?4k=/_a#]o>tVưEWR÷~8;m"&˹G]sANpWNKƉpP$8e \ No newline at end of file