diff --git a/flake.lock b/flake.lock index 86403db..0c3cd54 100644 --- a/flake.lock +++ b/flake.lock @@ -23,6 +23,30 @@ "type": "github" } }, + "agenix-rekey": { + "inputs": { + "devshell": "devshell", + "flake-parts": "flake-parts", + "nixpkgs": [ + "nixpkgs" + ], + "pre-commit-hooks": "pre-commit-hooks", + "treefmt-nix": "treefmt-nix" + }, + "locked": { + "lastModified": 1759699908, + "narHash": "sha256-kYVGY8sAfqwpNch706Fy2+/b+xbtfidhXSnzvthAhIQ=", + "owner": "oddlama", + "repo": "agenix-rekey", + "rev": "42362b12f59978aabf3ec3334834ce2f3662013d", + "type": "github" + }, + "original": { + "owner": "oddlama", + "repo": "agenix-rekey", + "type": "github" + } + }, "aquamarine": { "inputs": { "hyprutils": [ @@ -146,6 +170,27 @@ "type": "github" } }, + "devshell": { + "inputs": { + "nixpkgs": [ + "agenix-rekey", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1728330715, + "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", + "owner": "numtide", + "repo": "devshell", + "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, "elephant": { "inputs": { "nixpkgs": [ @@ -184,6 +229,22 @@ } }, "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "flake-compat_2": { "flake": false, "locked": { "lastModified": 1747046372, @@ -199,7 +260,7 @@ "type": "github" } }, - "flake-compat_2": { + "flake-compat_3": { "flake": false, "locked": { "lastModified": 1751685974, @@ -216,6 +277,27 @@ } }, "flake-parts": { + "inputs": { + "nixpkgs-lib": [ + "agenix-rekey", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "flake-parts_2": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, @@ -233,7 +315,7 @@ "type": "github" } }, - "flake-parts_2": { + "flake-parts_3": { "inputs": { "nixpkgs-lib": [ "nur", @@ -254,7 +336,7 @@ "type": "github" } }, - "flake-parts_3": { + "flake-parts_4": { "inputs": { "nixpkgs-lib": [ "nvf", @@ -275,7 +357,7 @@ "type": "github" } }, - "flake-parts_4": { + "flake-parts_5": { "inputs": { "nixpkgs-lib": [ "stylix", @@ -331,6 +413,28 @@ } }, "gitignore": { + "inputs": { + "nixpkgs": [ + "agenix-rekey", + "pre-commit-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "gitignore_2": { "inputs": { "nixpkgs": [ "hyprland", @@ -500,7 +604,7 @@ "hyprutils": "hyprutils", "hyprwayland-scanner": "hyprwayland-scanner_2", "nixpkgs": "nixpkgs_2", - "pre-commit-hooks": "pre-commit-hooks", + "pre-commit-hooks": "pre-commit-hooks_2", "systems": "systems_4", "xdph": "xdph" }, @@ -743,7 +847,7 @@ }, "mango": { "inputs": { - "flake-parts": "flake-parts", + "flake-parts": "flake-parts_2", "nixpkgs": "nixpkgs_3", "scenefx": "scenefx" }, @@ -889,7 +993,7 @@ }, "nur": { "inputs": { - "flake-parts": "flake-parts_2", + "flake-parts": "flake-parts_3", "nixpkgs": [ "nixpkgs" ] @@ -935,8 +1039,8 @@ }, "nvf": { "inputs": { - "flake-compat": "flake-compat_2", - "flake-parts": "flake-parts_3", + "flake-compat": "flake-compat_3", + "flake-parts": "flake-parts_4", "mnw": "mnw", "nixpkgs": [ "nixpkgs" @@ -984,6 +1088,29 @@ "inputs": { "flake-compat": "flake-compat", "gitignore": "gitignore", + "nixpkgs": [ + "agenix-rekey", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1735882644, + "narHash": "sha256-3FZAG+pGt3OElQjesCAWeMkQ7C/nB1oTHLRQ8ceP110=", + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "rev": "a5a961387e75ae44cc20f0a57ae463da5e959656", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "pre-commit-hooks.nix", + "type": "github" + } + }, + "pre-commit-hooks_2": { + "inputs": { + "flake-compat": "flake-compat_2", + "gitignore": "gitignore_2", "nixpkgs": [ "hyprland", "nixpkgs" @@ -1006,6 +1133,7 @@ "root": { "inputs": { "agenix": "agenix", + "agenix-rekey": "agenix-rekey", "elephant": "elephant", "home-manager": "home-manager_2", "hyprdynamicmonitors": "hyprdynamicmonitors", @@ -1069,7 +1197,7 @@ "base16-helix": "base16-helix", "base16-vim": "base16-vim", "firefox-gnome-theme": "firefox-gnome-theme", - "flake-parts": "flake-parts_4", + "flake-parts": "flake-parts_5", "gnome-shell": "gnome-shell", "nixpkgs": [ "nixpkgs" @@ -1282,6 +1410,27 @@ "type": "github" } }, + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "agenix-rekey", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1735135567, + "narHash": "sha256-8T3K5amndEavxnludPyfj3Z1IkcFdRpR23q+T0BVeZE=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "9e09d30a644c57257715902efbb3adc56c79cf28", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "treefmt-nix", + "type": "github" + } + }, "walker": { "inputs": { "elephant": [ diff --git a/flake.nix b/flake.nix index d62f9b6..6ae9fa7 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,11 @@ inputs.nixpkgs.follows = "nixpkgs"; }; + agenix-rekey = { + url = "github:oddlama/agenix-rekey"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + nvf = { url = "github:notashelf/nvf"; inputs.nixpkgs.follows = "nixpkgs"; @@ -60,7 +65,12 @@ }; outputs = - { nixpkgs, ... }@inputs: + { + self, + nixpkgs, + agenix-rekey, + ... + }@inputs: { nixosConfigurations.vanta = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; @@ -68,11 +78,18 @@ inherit inputs; host = "vanta"; wallpaper = "twilight-village.png"; + # Host public SSH key (e.g. /etc/ssh/ssh_host_ed25519_key.pub). + hostPubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAaDVBJdMDFL8r9NQCbaLe+DPHGhGzRv2N7+7m1/U8DP"; }; modules = [ ./modules/system ./hosts/vanta ]; }; + + agenix-rekey = agenix-rekey.configure { + userFlake = self; + nixosConfigurations = self.nixosConfigurations; + }; }; } diff --git a/modules/system/agenix.nix b/modules/system/agenix.nix index 679e331..f2fe2ac 100644 --- a/modules/system/agenix.nix +++ b/modules/system/agenix.nix @@ -1,20 +1,42 @@ { inputs, + config, pkgs, + lib, + host, + hostPubkey ? null, ... }: { imports = [ inputs.agenix.nixosModules.default + inputs.agenix-rekey.nixosModules.default ]; environment.systemPackages = [ - inputs.agenix.packages.${pkgs.stdenv.hostPlatform.system}.default # CLI Tool + # agenix-rekey's CLI tool replaces standard agenix's + inputs.agenix-rekey.packages.${pkgs.stdenv.hostPlatform.system}.default ]; - age.secrets = { - tailscale-auth.file = ../../secrets/tailscale-auth.age; - eclypsecloud-eclypse.file = ../../secrets/eclypsecloud-eclypse.age; - eclypse-password.file = ../../secrets/eclypse-password.age; + age = { + # Need to explicitly set identity paths because OpenSSH daemon is disabled + # but the host keys are still generated via services.openssh.generateHostKeys = true + identityPaths = map (key: key.path) config.services.openssh.hostKeys; + rekey = { + masterIdentities = [ "${inputs.self}/secrets/age-yubikey-identity-d9ed335b.pub" ]; + storageMode = "local"; + localStorageDir = ../../. + "/secrets/rekeyed/${host}"; + } + # We only set the hostPubkey if one is supplied. For new hosts the pub key will not + # exist until it is generated after the first rebuild. Runtime decryption will fail + # but then the ssh host key will be generated in /etc/ssh and can be supplied + // lib.optionalAttrs (hostPubkey != null) { + inherit hostPubkey; + }; + secrets = { + tailscale-auth.rekeyFile = ../../secrets/tailscale-auth.age; + eclypsecloud-eclypse.rekeyFile = ../../secrets/eclypsecloud-eclypse.age; + eclypse-password.rekeyFile = ../../secrets/eclypse-password.age; + }; }; } diff --git a/modules/system/security.nix b/modules/system/security.nix index 77c9b56..318a79a 100644 --- a/modules/system/security.nix +++ b/modules/system/security.nix @@ -1,4 +1,5 @@ { + pkgs, ... }: { @@ -7,6 +8,10 @@ yubikey-touch-detector.enable = true; }; + environment.systemPackages = with pkgs; [ + age-plugin-yubikey + ]; + services = { yubikey-agent.enable = true; }; diff --git a/modules/system/services.nix b/modules/system/services.nix index 006c8ee..7d0293a 100644 --- a/modules/system/services.nix +++ b/modules/system/services.nix @@ -52,14 +52,10 @@ upower.enable = true; - # Enable the OpenSSH daemon. (Look into Fail2Ban in the future) + # Disable SSH daemon but generate host keys anyway for secret rekeying openssh = { - enable = true; - settings = { - PasswordAuthentication = false; - PermitRootLogin = "prohibit-password"; - AllowUsers = [ "eclypse" ]; - }; + enable = false; + generateHostKeys = true; }; system76-scheduler.settings.cfsProfiles.enable = true; diff --git a/secrets/age-yubikey-identity-d9ed335b.pub b/secrets/age-yubikey-identity-d9ed335b.pub new file mode 100644 index 0000000..f119a45 --- /dev/null +++ b/secrets/age-yubikey-identity-d9ed335b.pub @@ -0,0 +1,7 @@ +# Serial: 27501992, Slot: 1 +# Name: agenix-rekey-alpha +# Created: Sat, 20 Dec 2025 06:01:41 +0000 +# PIN policy: Once (A PIN is required once per session, if set) +# Touch policy: Always (A physical touch is required for every decryption) +# Recipient: age1yubikey1qvq48l020xg9xtt5epdpnzp3kvkm2vvc57357p58pyfq557a8q8hv84c82e +AGE-PLUGIN-YUBIKEY-14ZJ6XQVZM8KNXKCT2PKLW diff --git a/secrets/eclypse-password.age b/secrets/eclypse-password.age index 54a2a6c..6f8b3b8 100644 Binary files a/secrets/eclypse-password.age and b/secrets/eclypse-password.age differ diff --git a/secrets/eclypsecloud-eclypse.age b/secrets/eclypsecloud-eclypse.age index 1a70592..8ffda0d 100644 Binary files a/secrets/eclypsecloud-eclypse.age and b/secrets/eclypsecloud-eclypse.age differ diff --git a/secrets/rekeyed/vanta/40b96312f0efd258467853db2ff842cb-tailscale-auth.age b/secrets/rekeyed/vanta/40b96312f0efd258467853db2ff842cb-tailscale-auth.age new file mode 100644 index 0000000..e935a95 --- /dev/null +++ b/secrets/rekeyed/vanta/40b96312f0efd258467853db2ff842cb-tailscale-auth.age @@ -0,0 +1,8 @@ +age-encryption.org/v1 +-> ssh-ed25519 7p4RPw HgBYYM/VqZ4KN4V4TrGmk86wPRhDgM+VaXfa3VlODRM +OdM//HvJTzB7/jw+c+6euiYz9ptUf/z22tzJSgxTD+w +-> B%P@9-grease +Zgr76aiZDhCWBdnbxoOptAfEuM1RWw1bN4rsUCec4VP0cDN856bCtaQjnWWbSTvv +YPHtmw +--- obv+bg63dTlnoke3tQdkAizcAqsYG2sUjYBZrhGZG68 +(2$Y@i7jar7X$U~|'oo`DƆ{#%< ssh-ed25519 7p4RPw FdmJ1odfweTU4HWPTeWuEcoIUq1V4ke28BWmlNNdNHg +9qi5QQHociRgSzZ97HifRDf+/Hh0cCZJzFsobpP1cpU +-> 4pq5-grease +yKZUs4lQM6BQgsyzMn3T1pvUt393/NvcRe7KwuTCDCU +--- N7NO5Ps2SG3SFNNnNNvYUSGgA0b5Dk7H6+x0rt6JtXA +Dl]e p(F0i3 ssh-ed25519 7p4RPw 7GuZj43+NoyPXf//ZLM99vossbJXOpDQSkBi3w51Wl8 -FTMjlyml+T87LQffffY2AJL5IhTAJF2QlfFvhvZpvOs ---- iONf8B3bUxXtCiv0EAv5QO0ZyhE5A6YfRbcxUr/awFg -TwJ`~B -;lOh{2?PF>@moc~X3@.gھeKV7zphSد6.WO@F  \ No newline at end of file +-> piv-p256 2e0zWw ApoXPsP2VGfJnOt+dDk7DfssOkbM/3vkn4jwSfxD4UAj +jtn4DCA/EyrTl9DW1hs84yd3RgVuDU77ggM218HiUdc +-> *E(-grease Ull1npy_ >F7 *? +IM+85AtRNlMrFgqk/uAG +--- nxCTKF6R3E/qaTTgr7jZdz4ZLRE15NsJpyKHizEJnPw +>"lrsNV*FI|0X8 + |PF D\xZP]ʧt-"nm&| %.ӆ \ No newline at end of file