From 0814fdc0dea23e6c2656109e66d69e056f466358 Mon Sep 17 00:00:00 2001 From: Ryan Mulligan Date: Mon, 4 Aug 2025 09:26:41 -0700 Subject: [PATCH] format nix with rfc style --- .github/workflows/ci.yaml | 4 +- default.nix | 7 +- example/secrets.nix | 22 ++- flake.nix | 124 ++++++++-------- modules/age-home.nix | 165 +++++++++++---------- modules/age.nix | 255 +++++++++++++++++---------------- overlay.nix | 2 +- pkgs/agenix.nix | 95 ++++++------ pkgs/doc.nix | 2 +- test/install_ssh_host_keys.nix | 5 +- test/integration.nix | 229 ++++++++++++++--------------- test/integration_darwin.nix | 10 +- test/integration_hm_darwin.nix | 33 +++-- 13 files changed, 500 insertions(+), 453 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ed10521..236d75b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -14,7 +14,7 @@ jobs: extra-experimental-features = recursive-nix nix-command flakes - run: nix build - run: nix build .#doc - - run: nix fmt . -- --check + - run: nix fmt . -- --ci - run: nix flake check tests-darwin: runs-on: macos-latest @@ -27,7 +27,7 @@ jobs: extra-experimental-features = recursive-nix nix-command flakes - run: nix build - run: nix build .#doc - - run: nix fmt . -- --check + - run: nix fmt . -- --ci - run: nix flake check - name: "Install nix-darwin module" run: | diff --git a/default.nix b/default.nix index 555358c..f4c181d 100644 --- a/default.nix +++ b/default.nix @@ -1,3 +1,6 @@ -{pkgs ? import {}}: { - agenix = pkgs.callPackage ./pkgs/agenix.nix {}; +{ + pkgs ? import { }, +}: +{ + agenix = pkgs.callPackage ./pkgs/agenix.nix { }; } diff --git a/example/secrets.nix b/example/secrets.nix index 08d8dc3..f2e6312 100644 --- a/example/secrets.nix +++ b/example/secrets.nix @@ -1,13 +1,23 @@ let user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH"; system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE"; -in { - "secret1.age".publicKeys = [user1 system1]; - "secret2.age".publicKeys = [user1]; - "passwordfile-user1.age".publicKeys = [user1 system1]; - "-leading-hyphen-filename.age".publicKeys = [user1 system1]; +in +{ + "secret1.age".publicKeys = [ + user1 + system1 + ]; + "secret2.age".publicKeys = [ user1 ]; + "passwordfile-user1.age".publicKeys = [ + user1 + system1 + ]; + "-leading-hyphen-filename.age".publicKeys = [ + user1 + system1 + ]; "armored-secret.age" = { - publicKeys = [user1]; + publicKeys = [ user1 ]; armor = true; }; } diff --git a/flake.nix b/flake.nix index 247e575..ee8340b 100644 --- a/flake.nix +++ b/flake.nix @@ -14,76 +14,78 @@ systems.url = "github:nix-systems/default"; }; - outputs = { - self, - nixpkgs, - darwin, - home-manager, - systems, - }: let - eachSystem = nixpkgs.lib.genAttrs (import systems); - in { - nixosModules.age = ./modules/age.nix; - nixosModules.default = self.nixosModules.age; + outputs = + { + self, + nixpkgs, + darwin, + home-manager, + systems, + }: + let + eachSystem = nixpkgs.lib.genAttrs (import systems); + in + { + nixosModules.age = ./modules/age.nix; + nixosModules.default = self.nixosModules.age; - darwinModules.age = ./modules/age.nix; - darwinModules.default = self.darwinModules.age; + darwinModules.age = ./modules/age.nix; + darwinModules.default = self.darwinModules.age; - homeManagerModules.age = ./modules/age-home.nix; - homeManagerModules.default = self.homeManagerModules.age; + homeManagerModules.age = ./modules/age-home.nix; + homeManagerModules.default = self.homeManagerModules.age; - overlays.default = import ./overlay.nix; + overlays.default = import ./overlay.nix; - formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.alejandra); + formatter = eachSystem (system: nixpkgs.legacyPackages.${system}.nixfmt-tree); - packages = eachSystem (system: { - agenix = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix {}; - doc = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/doc.nix {inherit self;}; - default = self.packages.${system}.agenix; - }); + packages = eachSystem (system: { + agenix = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/agenix.nix { }; + doc = nixpkgs.legacyPackages.${system}.callPackage ./pkgs/doc.nix { inherit self; }; + default = self.packages.${system}.agenix; + }); - checks = - nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: { - integration = - (darwin.lib.darwinSystem { - inherit system; - modules = [ - ./test/integration_darwin.nix + checks = + nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-darwin" ] (system: { + integration = + (darwin.lib.darwinSystem { + inherit system; + modules = [ + ./test/integration_darwin.nix - # Allow new-style nix commands in CI - {nix.extraOptions = "experimental-features = nix-command flakes";} + # Allow new-style nix commands in CI + { nix.extraOptions = "experimental-features = nix-command flakes"; } - home-manager.darwinModules.home-manager - { - home-manager = { - verbose = true; - useGlobalPkgs = true; - useUserPackages = true; - backupFileExtension = "hmbak"; - users.runner = ./test/integration_hm_darwin.nix; - }; - } - ]; - }) - .system; - }) - // { - x86_64-linux.integration = import ./test/integration.nix { - inherit nixpkgs home-manager; - pkgs = nixpkgs.legacyPackages.x86_64-linux; - system = "x86_64-linux"; + home-manager.darwinModules.home-manager + { + home-manager = { + verbose = true; + useGlobalPkgs = true; + useUserPackages = true; + backupFileExtension = "hmbak"; + users.runner = ./test/integration_hm_darwin.nix; + }; + } + ]; + }).system; + }) + // { + x86_64-linux.integration = import ./test/integration.nix { + inherit nixpkgs home-manager; + pkgs = nixpkgs.legacyPackages.x86_64-linux; + system = "x86_64-linux"; + }; }; - }; - darwinConfigurations.integration-x86_64.system = self.checks.x86_64-darwin.integration; - darwinConfigurations.integration-aarch64.system = self.checks.aarch64-darwin.integration; + darwinConfigurations.integration-x86_64.system = self.checks.x86_64-darwin.integration; + darwinConfigurations.integration-aarch64.system = self.checks.aarch64-darwin.integration; - # Work-around for https://github.com/nix-community/home-manager/issues/3075 - legacyPackages = nixpkgs.lib.genAttrs ["aarch64-darwin" "x86_64-darwin"] (system: { - homeConfigurations.integration-darwin = home-manager.lib.homeManagerConfiguration { - pkgs = nixpkgs.legacyPackages.${system}; - modules = [./test/integration_hm_darwin.nix]; - }; - }); - }; + # Work-around for https://github.com/nix-community/home-manager/issues/3075 + legacyPackages = nixpkgs.lib.genAttrs [ "aarch64-darwin" "x86_64-darwin" ] (system: { + homeConfigurations.integration-darwin = home-manager.lib.homeManagerConfiguration { + pkgs = nixpkgs.legacyPackages.${system}; + modules = [ ./test/integration_hm_darwin.nix ]; + }; + }); + }; } diff --git a/modules/age-home.nix b/modules/age-home.nix index 0438a3a..1bcf0d7 100644 --- a/modules/age-home.nix +++ b/modules/age-home.nix @@ -5,7 +5,8 @@ pkgs, ... }: -with lib; let +with lib; +let cfg = config.age; ageBin = lib.getExe config.age.package; @@ -22,13 +23,14 @@ with lib; let setTruePath = secretType: '' ${ - if secretType.symlink - then '' - _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" - '' - else '' - _truePath="${secretType.path}" - '' + if secretType.symlink then + '' + _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" + '' + else + '' + _truePath="${secretType.path}" + '' } ''; @@ -54,7 +56,9 @@ with lib; let umask u=r,g=,o= test -f "${secretType.file}" || echo '[agenix] WARNING: encrypted file ${secretType.file} does not exist!' test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!" - LANG=${config.i18n.defaultLocale or "C"} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}" + LANG=${ + config.i18n.defaultLocale or "C" + } ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}" ) chmod ${secretType.mode} "$TMP_FILE" mv -f "$TMP_FILE" "$_truePath" @@ -65,12 +69,9 @@ with lib; let ''} ''; - testIdentities = - map - (path: '' - test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' - '') - cfg.identityPaths; + testIdentities = map (path: '' + test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' + '') cfg.identityPaths; cleanupAndLink = '' _agenix_generation="$(basename "$(readlink "${cfg.secretsDir}")" || echo 0)" @@ -85,81 +86,89 @@ with lib; let ''; installSecrets = builtins.concatStringsSep "\n" ( - ["echo '[agenix] decrypting secrets...'"] + [ "echo '[agenix] decrypting secrets...'" ] ++ testIdentities ++ (map installSecret (builtins.attrValues cfg.secrets)) - ++ [cleanupAndLink] + ++ [ cleanupAndLink ] ); - secretType = types.submodule ({ - config, - name, - ... - }: { - options = { - name = mkOption { - type = types.str; - default = name; - description = '' - Name of the file used in ''${cfg.secretsDir} - ''; + secretType = types.submodule ( + { + config, + name, + ... + }: + { + options = { + name = mkOption { + type = types.str; + default = name; + description = '' + Name of the file used in ''${cfg.secretsDir} + ''; + }; + file = mkOption { + type = types.path; + description = '' + Age file the secret is loaded from. + ''; + }; + path = mkOption { + type = types.str; + default = "${cfg.secretsDir}/${config.name}"; + description = '' + Path where the decrypted secret is installed. + ''; + }; + mode = mkOption { + type = types.str; + default = "0400"; + description = '' + Permissions mode of the decrypted secret in a format understood by chmod. + ''; + }; + symlink = mkEnableOption "symlinking secrets to their destination" // { + default = true; + }; }; - file = mkOption { - type = types.path; - description = '' - Age file the secret is loaded from. - ''; - }; - path = mkOption { - type = types.str; - default = "${cfg.secretsDir}/${config.name}"; - description = '' - Path where the decrypted secret is installed. - ''; - }; - mode = mkOption { - type = types.str; - default = "0400"; - description = '' - Permissions mode of the decrypted secret in a format understood by chmod. - ''; - }; - symlink = mkEnableOption "symlinking secrets to their destination" // {default = true;}; - }; - }); + } + ); - mountingScript = let - app = pkgs.writeShellApplication { - name = "agenix-home-manager-mount-secrets"; - runtimeInputs = with pkgs; [coreutils]; - text = '' - ${newGeneration} - ${installSecrets} - exit 0 - ''; - }; - in + mountingScript = + let + app = pkgs.writeShellApplication { + name = "agenix-home-manager-mount-secrets"; + runtimeInputs = with pkgs; [ coreutils ]; + text = '' + ${newGeneration} + ${installSecrets} + exit 0 + ''; + }; + in lib.getExe app; - userDirectory = dir: let - inherit (pkgs.stdenv.hostPlatform) isDarwin; - baseDir = - if isDarwin - then "$(getconf DARWIN_USER_TEMP_DIR)" - else "\${XDG_RUNTIME_DIR}"; - in "${baseDir}/${dir}"; + userDirectory = + dir: + let + inherit (pkgs.stdenv.hostPlatform) isDarwin; + baseDir = if isDarwin then "$(getconf DARWIN_USER_TEMP_DIR)" else "\${XDG_RUNTIME_DIR}"; + in + "${baseDir}/${dir}"; - userDirectoryDescription = dir: + userDirectoryDescription = + dir: literalExpression '' "''${XDG_RUNTIME_DIR}"/''${dir} on linux or "$(getconf DARWIN_USER_TEMP_DIR)"/''${dir} on darwin. ''; -in { +in +{ options.age = { - package = mkPackageOption pkgs "age" {}; + package = mkPackageOption pkgs "age" { }; secrets = mkOption { type = types.attrsOf secretType; - default = {}; + default = { }; description = '' Attrset of secrets. ''; @@ -200,10 +209,10 @@ in { }; }; - config = mkIf (cfg.secrets != {}) { + config = mkIf (cfg.secrets != { }) { assertions = [ { - assertion = cfg.identityPaths != []; + assertion = cfg.identityPaths != [ ]; message = "age.identityPaths must be set."; } ]; @@ -216,13 +225,13 @@ in { Type = "oneshot"; ExecStart = mountingScript; }; - Install.WantedBy = ["default.target"]; + Install.WantedBy = [ "default.target" ]; }; launchd.agents.activate-agenix = { enable = true; config = { - ProgramArguments = [mountingScript]; + ProgramArguments = [ mountingScript ]; KeepAlive = { Crashed = false; SuccessfulExit = false; diff --git a/modules/age.nix b/modules/age.nix index 7811215..acae79f 100644 --- a/modules/age.nix +++ b/modules/age.nix @@ -5,34 +5,37 @@ pkgs, ... }: -with lib; let +with lib; +let cfg = config.age; - isDarwin = lib.attrsets.hasAttrByPath ["environment" "darwinConfig"] options; + isDarwin = lib.attrsets.hasAttrByPath [ "environment" "darwinConfig" ] options; ageBin = config.age.ageBin; users = config.users.users; sysusersEnabled = - if isDarwin - then false - else options.systemd ? sysusers && (config.systemd.sysusers.enable || config.services.userborn.enable); + if isDarwin then + false + else + options.systemd ? sysusers && (config.systemd.sysusers.enable || config.services.userborn.enable); mountCommand = - if isDarwin - then '' - if ! diskutil info "${cfg.secretsMountPoint}" &> /dev/null; then - num_sectors=1048576 - dev=$(hdiutil attach -nomount ram://"$num_sectors" | sed 's/[[:space:]]*$//') - newfs_hfs -v agenix "$dev" - mount -t hfs -o nobrowse,nodev,nosuid,-m=0751 "$dev" "${cfg.secretsMountPoint}" - fi - '' - else '' - grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || - mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751 - ''; + if isDarwin then + '' + if ! diskutil info "${cfg.secretsMountPoint}" &> /dev/null; then + num_sectors=1048576 + dev=$(hdiutil attach -nomount ram://"$num_sectors" | sed 's/[[:space:]]*$//') + newfs_hfs -v agenix "$dev" + mount -t hfs -o nobrowse,nodev,nosuid,-m=0751 "$dev" "${cfg.secretsMountPoint}" + fi + '' + else + '' + grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || + mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751 + ''; newGeneration = '' _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" (( ++_agenix_generation )) @@ -44,10 +47,7 @@ with lib; let chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation" ''; - chownGroup = - if isDarwin - then "admin" - else "keys"; + chownGroup = if isDarwin then "admin" else "keys"; # chown the secrets mountpoint and the current generation to the keys group # instead of leaving it root:root. chownMountPoint = '' @@ -56,13 +56,14 @@ with lib; let setTruePath = secretType: '' ${ - if secretType.symlink - then '' - _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" - '' - else '' - _truePath="${secretType.path}" - '' + if secretType.symlink then + '' + _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" + '' + else + '' + _truePath="${secretType.path}" + '' } ''; @@ -87,7 +88,9 @@ with lib; let umask u=r,g=,o= test -f "${secretType.file}" || echo '[agenix] WARNING: encrypted file ${secretType.file} does not exist!' test -d "$(dirname "$TMP_FILE")" || echo "[agenix] WARNING: $(dirname "$TMP_FILE") does not exist!" - LANG=${config.i18n.defaultLocale or "C"} ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}" + LANG=${ + config.i18n.defaultLocale or "C" + } ${ageBin} --decrypt "''${IDENTITIES[@]}" -o "$TMP_FILE" "${secretType.file}" ) chmod ${secretType.mode} "$TMP_FILE" mv -f "$TMP_FILE" "$_truePath" @@ -97,12 +100,9 @@ with lib; let ''} ''; - testIdentities = - map - (path: '' - test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' - '') - cfg.identityPaths; + testIdentities = map (path: '' + test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' + '') cfg.identityPaths; cleanupAndLink = '' _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" @@ -117,10 +117,10 @@ with lib; let ''; installSecrets = builtins.concatStringsSep "\n" ( - ["echo '[agenix] decrypting secrets...'"] + [ "echo '[agenix] decrypting secrets...'" ] ++ testIdentities ++ (map installSecret (builtins.attrValues cfg.secrets)) - ++ [cleanupAndLink] + ++ [ cleanupAndLink ] ); chownSecret = secretType: '' @@ -129,67 +129,73 @@ with lib; let ''; chownSecrets = builtins.concatStringsSep "\n" ( - ["echo '[agenix] chowning...'"] - ++ [chownMountPoint] + [ "echo '[agenix] chowning...'" ] + ++ [ chownMountPoint ] ++ (map chownSecret (builtins.attrValues cfg.secrets)) ); - secretType = types.submodule ({config, ...}: { - options = { - name = mkOption { - type = types.str; - default = config._module.args.name; - defaultText = literalExpression "config._module.args.name"; - description = '' - Name of the file used in {option}`age.secretsDir` - ''; + secretType = types.submodule ( + { config, ... }: + { + options = { + name = mkOption { + type = types.str; + default = config._module.args.name; + defaultText = literalExpression "config._module.args.name"; + description = '' + Name of the file used in {option}`age.secretsDir` + ''; + }; + file = mkOption { + type = types.path; + description = '' + Age file the secret is loaded from. + ''; + }; + path = mkOption { + type = types.str; + default = "${cfg.secretsDir}/${config.name}"; + defaultText = literalExpression '' + "''${cfg.secretsDir}/''${config.name}" + ''; + description = '' + Path where the decrypted secret is installed. + ''; + }; + mode = mkOption { + type = types.str; + default = "0400"; + description = '' + Permissions mode of the decrypted secret in a format understood by chmod. + ''; + }; + owner = mkOption { + type = types.str; + default = "0"; + description = '' + User of the decrypted secret. + ''; + }; + group = mkOption { + type = types.str; + default = users.${config.owner}.group or "0"; + defaultText = literalExpression '' + users.''${config.owner}.group or "0" + ''; + description = '' + Group of the decrypted secret. + ''; + }; + symlink = mkEnableOption "symlinking secrets to their destination" // { + default = true; + }; }; - file = mkOption { - type = types.path; - description = '' - Age file the secret is loaded from. - ''; - }; - path = mkOption { - type = types.str; - default = "${cfg.secretsDir}/${config.name}"; - defaultText = literalExpression '' - "''${cfg.secretsDir}/''${config.name}" - ''; - description = '' - Path where the decrypted secret is installed. - ''; - }; - mode = mkOption { - type = types.str; - default = "0400"; - description = '' - Permissions mode of the decrypted secret in a format understood by chmod. - ''; - }; - owner = mkOption { - type = types.str; - default = "0"; - description = '' - User of the decrypted secret. - ''; - }; - group = mkOption { - type = types.str; - default = users.${config.owner}.group or "0"; - defaultText = literalExpression '' - users.''${config.owner}.group or "0" - ''; - description = '' - Group of the decrypted secret. - ''; - }; - symlink = mkEnableOption "symlinking secrets to their destination" // {default = true;}; - }; - }); -in { + } + ); +in +{ imports = [ - (mkRenamedOptionModule ["age" "sshKeyPaths"] ["age" "identityPaths"]) + (mkRenamedOptionModule [ "age" "sshKeyPaths" ] [ "age" "identityPaths" ]) ]; options.age = { @@ -205,7 +211,7 @@ in { }; secrets = mkOption { type = types.attrsOf secretType; - default = {}; + default = { }; description = '' Attrset of secrets. ''; @@ -219,12 +225,14 @@ in { }; secretsMountPoint = mkOption { type = - types.addCheck types.str - (s: - (builtins.match "[ \t\n]*" s) - == null # non-empty - && (builtins.match ".+/" s) == null) # without trailing slash - // {description = "${types.str.description} (with check: non-empty without trailing slash)";}; + types.addCheck types.str ( + s: + (builtins.match "[ \t\n]*" s) == null # non-empty + && (builtins.match ".+/" s) == null + ) # without trailing slash + // { + description = "${types.str.description} (with check: non-empty without trailing slash)"; + }; default = "/run/agenix.d"; description = '' Where secrets are created before they are symlinked to {option}`age.secretsDir` @@ -233,14 +241,17 @@ in { identityPaths = mkOption { type = types.listOf types.path; default = - if isDarwin - then [ - "/etc/ssh/ssh_host_ed25519_key" - "/etc/ssh/ssh_host_rsa_key" - ] - else if (config.services.openssh.enable or false) - then map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys) - else []; + if isDarwin then + [ + "/etc/ssh/ssh_host_ed25519_key" + "/etc/ssh/ssh_host_rsa_key" + ] + else if (config.services.openssh.enable or false) then + map (e: e.path) ( + lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys + ) + else + [ ]; defaultText = literalExpression '' if isDarwin then [ @@ -257,11 +268,11 @@ in { }; }; - config = mkIf (cfg.secrets != {}) (mkMerge [ + config = mkIf (cfg.secrets != { }) (mkMerge [ { assertions = [ { - assertion = cfg.identityPaths != []; + assertion = cfg.identityPaths != [ ]; message = "age.identityPaths must be set, for example by enabling openssh."; } ]; @@ -270,20 +281,18 @@ in { # When using sysusers we no longer be started as an activation script # because those are started in initrd while sysusers is started later. systemd.services.agenix-install-secrets = mkIf sysusersEnabled { - wantedBy = ["sysinit.target"]; - after = ["systemd-sysusers.service"]; + wantedBy = [ "sysinit.target" ]; + after = [ "systemd-sysusers.service" ]; unitConfig.DefaultDependencies = "no"; - path = [pkgs.mount]; + path = [ pkgs.mount ]; serviceConfig = { Type = "oneshot"; - ExecStart = pkgs.writeShellScript "agenix-install" ( - concatLines [ - newGeneration - installSecrets - chownSecrets - ] - ); + ExecStart = pkgs.writeShellScript "agenix-install" (concatLines [ + newGeneration + installSecrets + chownSecrets + ]); RemainAfterExit = true; }; }; @@ -308,7 +317,7 @@ in { }; # So user passwords can be encrypted. - users.deps = ["agenixInstall"]; + users.deps = [ "agenixInstall" ]; # Change ownership and group after users and groups are made. agenixChown = { @@ -322,7 +331,7 @@ in { # So other activation scripts can depend on agenix being done. agenix = { text = ""; - deps = ["agenixChown"]; + deps = [ "agenixChown" ]; }; }; }) diff --git a/overlay.nix b/overlay.nix index f484e44..546c89d 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,3 +1,3 @@ final: prev: { - agenix = prev.callPackage ./pkgs/agenix.nix {}; + agenix = prev.callPackage ./pkgs/agenix.nix { }; } diff --git a/pkgs/agenix.nix b/pkgs/agenix.nix index 7ce1177..da5385c 100644 --- a/pkgs/agenix.nix +++ b/pkgs/agenix.nix @@ -9,57 +9,58 @@ replaceVars, ageBin ? "${age}/bin/age", shellcheck, -}: let +}: +let bin = "${placeholder "out"}/bin/agenix"; in - stdenv.mkDerivation rec { - pname = "agenix"; - version = "0.15.0"; - src = replaceVars ./agenix.sh { - inherit ageBin version; - jqBin = "${jq}/bin/jq"; - nixInstantiate = "${nix}/bin/nix-instantiate"; - mktempBin = "${mktemp}/bin/mktemp"; - diffBin = "${diffutils}/bin/diff"; - }; - dontUnpack = true; - doInstallCheck = true; - installCheckInputs = [shellcheck]; - postInstallCheck = '' - shellcheck ${bin} - ${bin} -h | grep ${version} +stdenv.mkDerivation rec { + pname = "agenix"; + version = "0.15.0"; + src = replaceVars ./agenix.sh { + inherit ageBin version; + jqBin = "${jq}/bin/jq"; + nixInstantiate = "${nix}/bin/nix-instantiate"; + mktempBin = "${mktemp}/bin/mktemp"; + diffBin = "${diffutils}/bin/diff"; + }; + dontUnpack = true; + doInstallCheck = true; + installCheckInputs = [ shellcheck ]; + postInstallCheck = '' + shellcheck ${bin} + ${bin} -h | grep ${version} - test_tmp=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir') - export HOME="$test_tmp/home" - export NIX_STORE_DIR="$test_tmp/nix/store" - export NIX_STATE_DIR="$test_tmp/nix/var" - mkdir -p "$HOME" "$NIX_STORE_DIR" "$NIX_STATE_DIR" - function cleanup { - rm -rf "$test_tmp" - } - trap "cleanup" 0 2 3 15 + test_tmp=$(mktemp -d 2>/dev/null || mktemp -d -t 'mytmpdir') + export HOME="$test_tmp/home" + export NIX_STORE_DIR="$test_tmp/nix/store" + export NIX_STATE_DIR="$test_tmp/nix/var" + mkdir -p "$HOME" "$NIX_STORE_DIR" "$NIX_STATE_DIR" + function cleanup { + rm -rf "$test_tmp" + } + trap "cleanup" 0 2 3 15 - mkdir -p $HOME/.ssh - cp -r "${../example}" $HOME/secrets - chmod -R u+rw $HOME/secrets - ( - umask u=rw,g=r,o=r - cp ${../example_keys/user1.pub} $HOME/.ssh/id_ed25519.pub - chown $UID $HOME/.ssh/id_ed25519.pub - ) - ( - umask u=rw,g=,o= - cp ${../example_keys/user1} $HOME/.ssh/id_ed25519 - chown $UID $HOME/.ssh/id_ed25519 - ) + mkdir -p $HOME/.ssh + cp -r "${../example}" $HOME/secrets + chmod -R u+rw $HOME/secrets + ( + umask u=rw,g=r,o=r + cp ${../example_keys/user1.pub} $HOME/.ssh/id_ed25519.pub + chown $UID $HOME/.ssh/id_ed25519.pub + ) + ( + umask u=rw,g=,o= + cp ${../example_keys/user1} $HOME/.ssh/id_ed25519 + chown $UID $HOME/.ssh/id_ed25519 + ) - cd $HOME/secrets - test $(${bin} -d secret1.age) = "hello" - ''; + cd $HOME/secrets + test $(${bin} -d secret1.age) = "hello" + ''; - installPhase = '' - install -D $src ${bin} - ''; + installPhase = '' + install -D $src ${bin} + ''; - meta.description = "age-encrypted secrets for NixOS"; - } + meta.description = "age-encrypted secrets for NixOS"; +} diff --git a/pkgs/doc.nix b/pkgs/doc.nix index db441dc..e62194b 100644 --- a/pkgs/doc.nix +++ b/pkgs/doc.nix @@ -6,6 +6,6 @@ stdenvNoCC.mkDerivation rec { name = "agenix-doc"; src = ../doc; - phases = ["mmdocPhase"]; + phases = [ "mmdocPhase" ]; mmdocPhase = "${mmdoc}/bin/mmdoc agenix $src $out"; } diff --git a/test/install_ssh_host_keys.nix b/test/install_ssh_host_keys.nix index 72d82ee..8152814 100644 --- a/test/install_ssh_host_keys.nix +++ b/test/install_ssh_host_keys.nix @@ -1,6 +1,7 @@ # Do not copy this! It is insecure. This is only okay because we are testing. -{config, ...}: { - system.activationScripts.agenixInstall.deps = ["installSSHHostKeys"]; +{ config, ... }: +{ + system.activationScripts.agenixInstall.deps = [ "installSSHHostKeys" ]; system.activationScripts.installSSHHostKeys.text = '' USER1_UID="${toString config.users.users.user1.uid}" diff --git a/test/integration.nix b/test/integration.nix index 0c911f7..3e3230c 100644 --- a/test/integration.nix +++ b/test/integration.nix @@ -1,137 +1,142 @@ { nixpkgs ? , - pkgs ? - import { - inherit system; - config = {}; - }, + pkgs ? import { + inherit system; + config = { }; + }, system ? builtins.currentSystem, home-manager ? , }: pkgs.nixosTest { name = "agenix-integration"; - nodes.system1 = { - config, - pkgs, - options, - ... - }: { - imports = [ - ../modules/age.nix - ./install_ssh_host_keys.nix - "${home-manager}/nixos" - ]; - - services.openssh.enable = true; - - age.secrets = { - passwordfile-user1.file = ../example/passwordfile-user1.age; - leading-hyphen.file = ../example/-leading-hyphen-filename.age; - }; - - age.identityPaths = options.age.identityPaths.default ++ ["/etc/ssh/this_key_wont_exist"]; - - environment.systemPackages = [ - (pkgs.callPackage ../pkgs/agenix.nix {}) - ]; - - users = { - mutableUsers = false; - - users = { - user1 = { - isNormalUser = true; - passwordFile = config.age.secrets.passwordfile-user1.path; - uid = 1000; - }; - }; - }; - - home-manager.users.user1 = {options, ...}: { + nodes.system1 = + { + config, + pkgs, + options, + ... + }: + { imports = [ - ../modules/age-home.nix + ../modules/age.nix + ./install_ssh_host_keys.nix + "${home-manager}/nixos" ]; - home.stateVersion = pkgs.lib.trivial.release; + services.openssh.enable = true; - age = { - identityPaths = options.age.identityPaths.default ++ ["/home/user1/.ssh/this_key_wont_exist"]; - secrets.secret2 = { - # Only decryptable by user1's key - file = ../example/secret2.age; - }; - secrets.secret2Path = { - file = ../example/secret2.age; - path = "/home/user1/secret2"; - }; - secrets.armored-secret = { - file = ../example/armored-secret.age; + age.secrets = { + passwordfile-user1.file = ../example/passwordfile-user1.age; + leading-hyphen.file = ../example/-leading-hyphen-filename.age; + }; + + age.identityPaths = options.age.identityPaths.default ++ [ "/etc/ssh/this_key_wont_exist" ]; + + environment.systemPackages = [ + (pkgs.callPackage ../pkgs/agenix.nix { }) + ]; + + users = { + mutableUsers = false; + + users = { + user1 = { + isNormalUser = true; + passwordFile = config.age.secrets.passwordfile-user1.path; + uid = 1000; + }; }; }; + + home-manager.users.user1 = + { options, ... }: + { + imports = [ + ../modules/age-home.nix + ]; + + home.stateVersion = pkgs.lib.trivial.release; + + age = { + identityPaths = options.age.identityPaths.default ++ [ "/home/user1/.ssh/this_key_wont_exist" ]; + secrets.secret2 = { + # Only decryptable by user1's key + file = ../example/secret2.age; + }; + secrets.secret2Path = { + file = ../example/secret2.age; + path = "/home/user1/secret2"; + }; + secrets.armored-secret = { + file = ../example/armored-secret.age; + }; + }; + }; }; - }; - testScript = let - user = "user1"; - password = "password1234"; - secret2 = "world!"; - hyphen-secret = "filename started with hyphen"; - armored-secret = "Hello World!"; - in '' - system1.wait_for_unit("multi-user.target") - system1.wait_until_succeeds("pgrep -f 'agetty.*tty1'") - system1.sleep(2) - system1.send_key("alt-f2") - system1.wait_until_succeeds("[ $(fgconsole) = 2 ]") - system1.wait_for_unit("getty@tty2.service") - system1.wait_until_succeeds("pgrep -f 'agetty.*tty2'") - system1.wait_until_tty_matches("2", "login: ") - system1.send_chars("${user}\n") - system1.wait_until_tty_matches("2", "login: ${user}") - system1.wait_until_succeeds("pgrep login") - system1.sleep(2) - system1.send_chars("${password}\n") - system1.send_chars("whoami > /tmp/1\n") - system1.wait_for_file("/tmp/1") - assert "${user}" in system1.succeed("cat /tmp/1") - system1.send_chars("cat /run/user/$(id -u)/agenix/secret2 > /tmp/2\n") - system1.wait_for_file("/tmp/2") - assert "${secret2}" in system1.succeed("cat /tmp/2") - system1.send_chars("cat /run/user/$(id -u)/agenix/armored-secret > /tmp/3\n") - system1.wait_for_file("/tmp/3") - assert "${armored-secret}" in system1.succeed("cat /tmp/3") + testScript = + let + user = "user1"; + password = "password1234"; + secret2 = "world!"; + hyphen-secret = "filename started with hyphen"; + armored-secret = "Hello World!"; + in + '' + system1.wait_for_unit("multi-user.target") + system1.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + system1.sleep(2) + system1.send_key("alt-f2") + system1.wait_until_succeeds("[ $(fgconsole) = 2 ]") + system1.wait_for_unit("getty@tty2.service") + system1.wait_until_succeeds("pgrep -f 'agetty.*tty2'") + system1.wait_until_tty_matches("2", "login: ") + system1.send_chars("${user}\n") + system1.wait_until_tty_matches("2", "login: ${user}") + system1.wait_until_succeeds("pgrep login") + system1.sleep(2) + system1.send_chars("${password}\n") + system1.send_chars("whoami > /tmp/1\n") + system1.wait_for_file("/tmp/1") + assert "${user}" in system1.succeed("cat /tmp/1") + system1.send_chars("cat /run/user/$(id -u)/agenix/secret2 > /tmp/2\n") + system1.wait_for_file("/tmp/2") + assert "${secret2}" in system1.succeed("cat /tmp/2") + system1.send_chars("cat /run/user/$(id -u)/agenix/armored-secret > /tmp/3\n") + system1.wait_for_file("/tmp/3") + assert "${armored-secret}" in system1.succeed("cat /tmp/3") - assert "${hyphen-secret}" in system1.succeed("cat /run/agenix/leading-hyphen") + assert "${hyphen-secret}" in system1.succeed("cat /run/agenix/leading-hyphen") - userDo = lambda input : f"sudo -u user1 -- bash -c 'set -eou pipefail; cd /tmp/secrets; {input}'" + userDo = lambda input : f"sudo -u user1 -- bash -c 'set -eou pipefail; cd /tmp/secrets; {input}'" - before_hash = system1.succeed(userDo('sha256sum passwordfile-user1.age')).split() - print(system1.succeed(userDo('agenix -r -i /home/user1/.ssh/id_ed25519'))) - after_hash = system1.succeed(userDo('sha256sum passwordfile-user1.age')).split() + before_hash = system1.succeed(userDo('sha256sum passwordfile-user1.age')).split() + print(system1.succeed(userDo('agenix -r -i /home/user1/.ssh/id_ed25519'))) + after_hash = system1.succeed(userDo('sha256sum passwordfile-user1.age')).split() - # Ensure we actually have hashes - for h in [before_hash, after_hash]: - assert len(h) == 2, "hash should be [hash, filename]" - assert h[1] == "passwordfile-user1.age", "filename is incorrect" - assert len(h[0].strip()) == 64, "hash length is incorrect" - assert before_hash[0] != after_hash[0], "hash did not change with rekeying" + # Ensure we actually have hashes + for h in [before_hash, after_hash]: + assert len(h) == 2, "hash should be [hash, filename]" + assert h[1] == "passwordfile-user1.age", "filename is incorrect" + assert len(h[0].strip()) == 64, "hash length is incorrect" + assert before_hash[0] != after_hash[0], "hash did not change with rekeying" - # user1 can edit passwordfile-user1.age - system1.succeed(userDo("EDITOR=cat agenix -e passwordfile-user1.age")) + # user1 can edit passwordfile-user1.age + system1.succeed(userDo("EDITOR=cat agenix -e passwordfile-user1.age")) - # user1 can edit even if bogus id_rsa present - system1.succeed(userDo("echo bogus > ~/.ssh/id_rsa")) - system1.fail(userDo("EDITOR=cat agenix -e passwordfile-user1.age")) - system1.succeed(userDo("EDITOR=cat agenix -e passwordfile-user1.age -i /home/user1/.ssh/id_ed25519")) - system1.succeed(userDo("rm ~/.ssh/id_rsa")) + # user1 can edit even if bogus id_rsa present + system1.succeed(userDo("echo bogus > ~/.ssh/id_rsa")) + system1.fail(userDo("EDITOR=cat agenix -e passwordfile-user1.age")) + system1.succeed(userDo("EDITOR=cat agenix -e passwordfile-user1.age -i /home/user1/.ssh/id_ed25519")) + system1.succeed(userDo("rm ~/.ssh/id_rsa")) - # user1 can edit a secret by piping in contents - system1.succeed(userDo("echo 'secret1234' | agenix -e passwordfile-user1.age")) + # user1 can edit a secret by piping in contents + system1.succeed(userDo("echo 'secret1234' | agenix -e passwordfile-user1.age")) - # and get it back out via --decrypt - assert "secret1234" in system1.succeed(userDo("agenix -d passwordfile-user1.age")) + # and get it back out via --decrypt + assert "secret1234" in system1.succeed(userDo("agenix -d passwordfile-user1.age")) - # finally, the plain text should not linger around anywhere in the filesystem. - system1.fail("grep -r secret1234 /tmp") - ''; + # finally, the plain text should not linger around anywhere in the filesystem. + system1.fail("grep -r secret1234 /tmp") + ''; } diff --git a/test/integration_darwin.nix b/test/integration_darwin.nix index d49b77d..92da807 100644 --- a/test/integration_darwin.nix +++ b/test/integration_darwin.nix @@ -3,7 +3,8 @@ pkgs, options, ... -}: let +}: +let secret = "hello"; testScript = pkgs.writeShellApplication { name = "agenix-integration"; @@ -11,18 +12,19 @@ grep "${secret}" "${config.age.secrets.system-secret.path}" ''; }; -in { +in +{ imports = [ ./install_ssh_host_keys_darwin.nix ../modules/age.nix ]; age = { - identityPaths = options.age.identityPaths.default ++ ["/etc/ssh/this_key_wont_exist"]; + identityPaths = options.age.identityPaths.default ++ [ "/etc/ssh/this_key_wont_exist" ]; secrets.system-secret.file = ../example/secret1.age; }; - environment.systemPackages = [testScript]; + environment.systemPackages = [ testScript ]; system.stateVersion = 6; } diff --git a/test/integration_hm_darwin.nix b/test/integration_hm_darwin.nix index d2e14b2..965057d 100644 --- a/test/integration_hm_darwin.nix +++ b/test/integration_hm_darwin.nix @@ -4,11 +4,12 @@ options, lib, ... -}: { - imports = [../modules/age-home.nix]; +}: +{ + imports = [ ../modules/age-home.nix ]; age = { - identityPaths = options.age.identityPaths.default ++ ["/Users/user1/.ssh/this_key_wont_exist"]; + identityPaths = options.age.identityPaths.default ++ [ "/Users/user1/.ssh/this_key_wont_exist" ]; secrets.user-secret.file = ../example/secret2.age; }; @@ -18,16 +19,20 @@ stateVersion = lib.trivial.release; }; - home.file = let - name = "agenix-home-integration"; - in { - ${name}.source = pkgs.writeShellApplication { - inherit name; - text = let - secret = "world!"; - in '' - diff -q "${config.age.secrets.user-secret.path}" <(printf '${secret}\n') - ''; + home.file = + let + name = "agenix-home-integration"; + in + { + ${name}.source = pkgs.writeShellApplication { + inherit name; + text = + let + secret = "world!"; + in + '' + diff -q "${config.age.secrets.user-secret.path}" <(printf '${secret}\n') + ''; + }; }; - }; }