diff --git a/.sops.yaml b/.sops.yaml new file mode 100644 index 0000000..4394256 --- /dev/null +++ b/.sops.yaml @@ -0,0 +1,14 @@ +# sops-nix encryption rules. +# Secrets under secrets/ are encrypted to the age recipient(s) below. +# The matching PRIVATE key lives OFF-repo (~/.config/sops/age/keys.txt for +# editing; /var/lib/sops-nix/key.txt on the host for decryption). +# +# Add the host's own age key here later (derived from its ssh host key) so the +# machine can decrypt without shipping a separate key. +keys: + - &admin age1cekcqyf7073fsytcjxaa9dr9zwkmn4vjg36rv2tgxdglzfv4jvxqvcj6z2 +creation_rules: + - path_regex: secrets/.*\.yaml$ + key_groups: + - age: + - *admin diff --git a/flake.lock b/flake.lock index b4fd525..d601739 100644 --- a/flake.lock +++ b/flake.lock @@ -39,7 +39,28 @@ "root": { "inputs": { "disko": "disko", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "sops-nix": "sops-nix" + } + }, + "sops-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1783174389, + "narHash": "sha256-aCWC8ngycU7OdJrU2+Je3qf+1a2ykuBvpPhZT/9tXMc=", + "owner": "Mic92", + "repo": "sops-nix", + "rev": "f1406619a3884cd5c47992a70b8b35c9c0fcb4c9", + "type": "github" + }, + "original": { + "owner": "Mic92", + "repo": "sops-nix", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 082f141..82759d2 100644 --- a/flake.nix +++ b/flake.nix @@ -7,9 +7,13 @@ url = "github:nix-community/disko"; inputs.nixpkgs.follows = "nixpkgs"; }; + sops-nix = { + url = "github:Mic92/sops-nix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs, disko, ... }@inputs: + outputs = { self, nixpkgs, disko, sops-nix, ... }@inputs: let system = "x86_64-linux"; in @@ -22,6 +26,7 @@ specialArgs = { inherit inputs; }; modules = [ disko.nixosModules.disko + sops-nix.nixosModules.sops ./jupiter/configuration.nix ]; }; diff --git a/jupiter/configuration.nix b/jupiter/configuration.nix index a29aad8..09ea8b7 100644 --- a/jupiter/configuration.nix +++ b/jupiter/configuration.nix @@ -5,6 +5,7 @@ imports = [ ./hardware-configuration.nix ./disk-config.nix # disko: OS-disk partitions + filesystems + ./secrets.nix # sops-nix: samba password etc. ./services.nix ]; diff --git a/jupiter/secrets.nix b/jupiter/secrets.nix new file mode 100644 index 0000000..d41edfd --- /dev/null +++ b/jupiter/secrets.nix @@ -0,0 +1,16 @@ +{ ... }: + +# sops-nix secret wiring (real host only; not imported by vm.nix). +# Encrypted values live in ../secrets/jupiter.yaml, decrypted at activation to +# /run/secrets/. The host needs the age PRIVATE key at the keyFile path. +# +# Deploy the private key with the install, e.g. nixos-anywhere: +# --extra-files, placing your key at /var/lib/sops-nix/key.txt +# or later derive a host age key from its ssh host key and add it to .sops.yaml. +{ + sops.defaultSopsFile = ../secrets/jupiter.yaml; + sops.age.keyFile = "/var/lib/sops-nix/key.txt"; + + # Decrypts to /run/secrets/samba_password (root-only by default). + sops.secrets.samba_password = { }; +} diff --git a/jupiter/services.nix b/jupiter/services.nix index 356e047..69ca975 100644 --- a/jupiter/services.nix +++ b/jupiter/services.nix @@ -68,9 +68,10 @@ # `services.samba` never sets it, so logins fail until provisioned. # This runs AFTER samba-smbd so its state dir (/var/lib/samba/private) exists # — an activation script runs too early and smbpasswd fails to init the passdb. - # The secret file must contain the password twice (new + confirm), one per line. - # VM test: vm.nix writes /etc/samba/smb-password with a throwaway value. - # Real host: supply it via sops-nix/agenix — do NOT commit plaintext. + # Reads a single-line password from the first file that exists: + # Real host: /run/secrets/samba_password (sops-nix, see secrets.nix) + # VM test: /etc/samba/smb-password (plaintext, see vm.nix) + # smbpasswd prompts new + confirm, so the value is fed twice. systemd.services.samba-smbpasswd = { description = "Provision Samba password for darman"; after = [ "samba-smbd.service" ]; @@ -81,9 +82,14 @@ RemainAfterExit = true; }; script = '' - if [ -f /etc/samba/smb-password ]; then - ${pkgs.samba}/bin/smbpasswd -a -s darman < /etc/samba/smb-password - fi + for f in /run/secrets/samba_password /etc/samba/smb-password; do + if [ -f "$f" ]; then + pw=$(head -n1 "$f") + printf '%s\n%s\n' "$pw" "$pw" | ${pkgs.samba}/bin/smbpasswd -a -s darman + exit 0 + fi + done + echo "no samba password source found" >&2 ''; }; services.avahi = { diff --git a/jupiter/vm.nix b/jupiter/vm.nix index c3f7844..29d5754 100644 --- a/jupiter/vm.nix +++ b/jupiter/vm.nix @@ -18,14 +18,11 @@ users.users.root.initialPassword = "test"; # Throwaway SMB password for testing (samba-smbd login = darman / test). - # Two lines: smbpasswd wants the new password + confirmation. + # Single line — the provisioning oneshot feeds it twice for smbpasswd. # Real host must NOT do this — plaintext lands in the world-readable Nix - # store. Use sops-nix/agenix to place /etc/samba/smb-password instead. + # store. It uses sops-nix (secrets.nix) instead. environment.etc."samba/smb-password" = { - text = '' - test - test - ''; + text = "test\n"; mode = "0600"; }; diff --git a/secrets/jupiter.yaml b/secrets/jupiter.yaml new file mode 100644 index 0000000..2f9ba8b --- /dev/null +++ b/secrets/jupiter.yaml @@ -0,0 +1,16 @@ +samba_password: ENC[AES256_GCM,data:Ur5CuAlJhdw=,iv:po7oxygUqKS9yoJS8efQJe2mTr7e5zYkZjAVRgeoUB0=,tag:Mf0K3rIga5I4jnNU3glDyg==,type:str] +sops: + age: + - enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBlL0E3cWNIby95Q3RBcjlm + QklPREM4cWRaelZEckFGUWpKUWExYmxrcWdrClByTVBlWDNlRGhMUkhEQzJONzFn + alJVSzRMODN3U0cyRG93T3lHZkhXTWcKLS0tIFJ2V3I0V1NwakVlc2dCUW1iMHdR + RlNsWnJJaDkvVnJnVlcyRXZmK1VkMUUKnv02m3vGjy8nTZV9ouItOEWmfZOABKDP + VCvWcSy8R0NikkQ004XrBd1WhvyrUvqC6X9f4N9208BvmyDxEpKZbw== + -----END AGE ENCRYPTED FILE----- + recipient: age1cekcqyf7073fsytcjxaa9dr9zwkmn4vjg36rv2tgxdglzfv4jvxqvcj6z2 + lastmodified: "2026-07-12T18:10:45Z" + mac: ENC[AES256_GCM,data:ynQU/B5RvwGw1ILJJexrZWtzNr7z3IUyy86v8q0KhRsMH2qSHF0BK0Pzq/DcT3vru+MwKat1hJfO21Pdu+HRsqtzUZRViTCbjtz+vmnjwyQvBqBPhEtxDeBoTgd5RqjXxzcuOADfv0NkfvU9p06x8YF8AVYq9ONvOEZiC4MamoY=,iv:1Wm4JP5Djyc9jdExMBcSjWw/zF1EXexjjMhePOEWCt8=,tag:A51vbK8fpb8sg+eCLwbWmg==,type:str] + unencrypted_suffix: _unencrypted + version: 3.13.1