From 4261d327c678e52abdd568a27168ea7cdd0484a0 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Mon, 13 Oct 2025 13:51:40 +0200 Subject: [PATCH] Include agenix module and package directly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Avoids adding an extra flake input only to fetch a single module and package. Reviewed-by: Aleix Boné Tested-by: Rodrigo Arias Mallo --- flake.lock | 82 ---- flake.nix | 6 +- m/common/base/agenix.nix | 9 +- m/module/agenix.nix | 357 ++++++++++++++++++ overlay.nix | 1 + pkgs/agenix/agenix.sh | 212 +++++++++++ pkgs/agenix/default.nix | 66 ++++ .../example/-leading-hyphen-filename.age | 7 + pkgs/agenix/example/armored-secret.age | 7 + pkgs/agenix/example/passwordfile-user1.age | 9 + pkgs/agenix/example/secret1.age | Bin 0 -> 328 bytes pkgs/agenix/example/secret2.age | 5 + pkgs/agenix/example/secrets.nix | 23 ++ pkgs/agenix/example_keys/system1 | 7 + pkgs/agenix/example_keys/system1.pub | 1 + pkgs/agenix/example_keys/user1 | 7 + pkgs/agenix/example_keys/user1.pub | 1 + pkgs/agenix/update.sh | 23 ++ 18 files changed, 732 insertions(+), 91 deletions(-) create mode 100644 m/module/agenix.nix create mode 100644 pkgs/agenix/agenix.sh create mode 100644 pkgs/agenix/default.nix create mode 100644 pkgs/agenix/example/-leading-hyphen-filename.age create mode 100644 pkgs/agenix/example/armored-secret.age create mode 100644 pkgs/agenix/example/passwordfile-user1.age create mode 100644 pkgs/agenix/example/secret1.age create mode 100644 pkgs/agenix/example/secret2.age create mode 100644 pkgs/agenix/example/secrets.nix create mode 100644 pkgs/agenix/example_keys/system1 create mode 100644 pkgs/agenix/example_keys/system1.pub create mode 100644 pkgs/agenix/example_keys/user1 create mode 100644 pkgs/agenix/example_keys/user1.pub create mode 100755 pkgs/agenix/update.sh diff --git a/flake.lock b/flake.lock index 9b3e8bef..72c9c8e0 100644 --- a/flake.lock +++ b/flake.lock @@ -1,71 +1,5 @@ { "nodes": { - "agenix": { - "inputs": { - "darwin": "darwin", - "home-manager": "home-manager", - "nixpkgs": [ - "nixpkgs" - ], - "systems": "systems" - }, - "locked": { - "lastModified": 1750173260, - "narHash": "sha256-9P1FziAwl5+3edkfFcr5HeGtQUtrSdk/MksX39GieoA=", - "owner": "ryantm", - "repo": "agenix", - "rev": "531beac616433bac6f9e2a19feb8e99a22a66baf", - "type": "github" - }, - "original": { - "owner": "ryantm", - "repo": "agenix", - "type": "github" - } - }, - "darwin": { - "inputs": { - "nixpkgs": [ - "agenix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1744478979, - "narHash": "sha256-dyN+teG9G82G+m+PX/aSAagkC+vUv0SgUw3XkPhQodQ=", - "owner": "lnl7", - "repo": "nix-darwin", - "rev": "43975d782b418ebf4969e9ccba82466728c2851b", - "type": "github" - }, - "original": { - "owner": "lnl7", - "ref": "master", - "repo": "nix-darwin", - "type": "github" - } - }, - "home-manager": { - "inputs": { - "nixpkgs": [ - "agenix", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1745494811, - "narHash": "sha256-YZCh2o9Ua1n9uCvrvi5pRxtuVNml8X2a03qIFfRKpFs=", - "owner": "nix-community", - "repo": "home-manager", - "rev": "abfad3d2958c9e6300a883bd443512c55dfeb1be", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "home-manager", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1752436162, @@ -84,24 +18,8 @@ }, "root": { "inputs": { - "agenix": "agenix", "nixpkgs": "nixpkgs" } - }, - "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 041a396e..8025158f 100644 --- a/flake.nix +++ b/flake.nix @@ -1,15 +1,13 @@ { inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; - agenix.url = "github:ryantm/agenix"; - agenix.inputs.nixpkgs.follows = "nixpkgs"; }; - outputs = { self, nixpkgs, agenix, ... }: + outputs = { self, nixpkgs, ... }: let mkConf = name: nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - specialArgs = { inherit nixpkgs agenix; theFlake = self; }; + specialArgs = { inherit nixpkgs; theFlake = self; }; modules = [ "${self.outPath}/m/${name}/configuration.nix" ]; }; # For now we only support x86 diff --git a/m/common/base/agenix.nix b/m/common/base/agenix.nix index 3d5bb656..bfd309d5 100644 --- a/m/common/base/agenix.nix +++ b/m/common/base/agenix.nix @@ -1,9 +1,8 @@ -{ agenix, ... }: +{ pkgs, ... }: { - imports = [ agenix.nixosModules.default ]; + imports = [ ../../module/agenix.nix ]; - environment.systemPackages = [ - agenix.packages.x86_64-linux.default - ]; + # Add agenix to system packages + environment.systemPackages = [ pkgs.agenix ]; } diff --git a/m/module/agenix.nix b/m/module/agenix.nix new file mode 100644 index 00000000..acae79fe --- /dev/null +++ b/m/module/agenix.nix @@ -0,0 +1,357 @@ +{ + config, + options, + lib, + pkgs, + ... +}: +with lib; +let + cfg = config.age; + + 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); + + 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 + ''; + newGeneration = '' + _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" + (( ++_agenix_generation )) + echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation" + mkdir -p "${cfg.secretsMountPoint}" + chmod 0751 "${cfg.secretsMountPoint}" + ${mountCommand} + mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation" + chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation" + ''; + + 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 = '' + chown :${chownGroup} "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation" + ''; + + setTruePath = secretType: '' + ${ + if secretType.symlink then + '' + _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" + '' + else + '' + _truePath="${secretType.path}" + '' + } + ''; + + installSecret = secretType: '' + ${setTruePath secretType} + echo "decrypting '${secretType.file}' to '$_truePath'..." + TMP_FILE="$_truePath.tmp" + + IDENTITIES=() + for identity in ${toString cfg.identityPaths}; do + test -r "$identity" || continue + test -s "$identity" || continue + IDENTITIES+=(-i) + IDENTITIES+=("$identity") + done + + test "''${#IDENTITIES[@]}" -eq 0 && echo "[agenix] WARNING: no readable identities found!" + + mkdir -p "$(dirname "$_truePath")" + [ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && mkdir -p "$(dirname "${secretType.path}")" + ( + 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}" + ) + chmod ${secretType.mode} "$TMP_FILE" + mv -f "$TMP_FILE" "$_truePath" + + ${optionalString secretType.symlink '' + [ "${secretType.path}" != "${cfg.secretsDir}/${secretType.name}" ] && ln -sfT "${cfg.secretsDir}/${secretType.name}" "${secretType.path}" + ''} + ''; + + 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)" + (( ++_agenix_generation )) + echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..." + ln -sfT "${cfg.secretsMountPoint}/$_agenix_generation" ${cfg.secretsDir} + + (( _agenix_generation > 1 )) && { + echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..." + rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))" + } + ''; + + installSecrets = builtins.concatStringsSep "\n" ( + [ "echo '[agenix] decrypting secrets...'" ] + ++ testIdentities + ++ (map installSecret (builtins.attrValues cfg.secrets)) + ++ [ cleanupAndLink ] + ); + + chownSecret = secretType: '' + ${setTruePath secretType} + chown ${secretType.owner}:${secretType.group} "$_truePath" + ''; + + chownSecrets = builtins.concatStringsSep "\n" ( + [ "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` + ''; + }; + 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 +{ + imports = [ + (mkRenamedOptionModule [ "age" "sshKeyPaths" ] [ "age" "identityPaths" ]) + ]; + + options.age = { + ageBin = mkOption { + type = types.str; + default = "${pkgs.age}/bin/age"; + defaultText = literalExpression '' + "''${pkgs.age}/bin/age" + ''; + description = '' + The age executable to use. + ''; + }; + secrets = mkOption { + type = types.attrsOf secretType; + default = { }; + description = '' + Attrset of secrets. + ''; + }; + secretsDir = mkOption { + type = types.path; + default = "/run/agenix"; + description = '' + Folder where secrets are symlinked to + ''; + }; + 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)"; + }; + default = "/run/agenix.d"; + description = '' + Where secrets are created before they are symlinked to {option}`age.secretsDir` + ''; + }; + 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 + [ ]; + defaultText = literalExpression '' + 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 []; + ''; + description = '' + Path to SSH keys to be used as identities in age decryption. + ''; + }; + }; + + config = mkIf (cfg.secrets != { }) (mkMerge [ + { + assertions = [ + { + assertion = cfg.identityPaths != [ ]; + message = "age.identityPaths must be set, for example by enabling openssh."; + } + ]; + } + (optionalAttrs (!isDarwin) { + # 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" ]; + unitConfig.DefaultDependencies = "no"; + + path = [ pkgs.mount ]; + serviceConfig = { + Type = "oneshot"; + ExecStart = pkgs.writeShellScript "agenix-install" (concatLines [ + newGeneration + installSecrets + chownSecrets + ]); + RemainAfterExit = true; + }; + }; + + # Create a new directory full of secrets for symlinking (this helps + # ensure removed secrets are actually removed, or at least become + # invalid symlinks). + system.activationScripts = mkIf (!sysusersEnabled) { + agenixNewGeneration = { + text = newGeneration; + deps = [ + "specialfs" + ]; + }; + + agenixInstall = { + text = installSecrets; + deps = [ + "agenixNewGeneration" + "specialfs" + ]; + }; + + # So user passwords can be encrypted. + users.deps = [ "agenixInstall" ]; + + # Change ownership and group after users and groups are made. + agenixChown = { + text = chownSecrets; + deps = [ + "users" + "groups" + ]; + }; + + # So other activation scripts can depend on agenix being done. + agenix = { + text = ""; + deps = [ "agenixChown" ]; + }; + }; + }) + + (optionalAttrs isDarwin { + launchd.daemons.activate-agenix = { + script = '' + set -e + set -o pipefail + export PATH="${pkgs.gnugrep}/bin:${pkgs.coreutils}/bin:@out@/sw/bin:/usr/bin:/bin:/usr/sbin:/sbin" + ${newGeneration} + ${installSecrets} + ${chownSecrets} + exit 0 + ''; + serviceConfig = { + RunAtLoad = true; + KeepAlive.SuccessfulExit = false; + }; + }; + }) + ]); +} diff --git a/overlay.nix b/overlay.nix index 09e689b2..27e4bda2 100644 --- a/overlay.nix +++ b/overlay.nix @@ -7,6 +7,7 @@ let callPackage = final.callPackage; bscPkgs = { + agenix = prev.callPackage ./pkgs/agenix/default.nix { }; amd-uprof = prev.callPackage ./pkgs/amd-uprof/default.nix { }; bench6 = callPackage ./pkgs/bench6/default.nix { }; bigotes = callPackage ./pkgs/bigotes/default.nix { }; diff --git a/pkgs/agenix/agenix.sh b/pkgs/agenix/agenix.sh new file mode 100644 index 00000000..0b08a3a8 --- /dev/null +++ b/pkgs/agenix/agenix.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +set -Eeuo pipefail + +PACKAGE="agenix" + +function show_help () { + echo "$PACKAGE - edit and rekey age secret files" + echo " " + echo "$PACKAGE -e FILE [-i PRIVATE_KEY]" + echo "$PACKAGE -r [-i PRIVATE_KEY]" + echo ' ' + echo 'options:' + echo '-h, --help show help' + # shellcheck disable=SC2016 + echo '-e, --edit FILE edits FILE using $EDITOR' + echo '-r, --rekey re-encrypts all secrets with specified recipients' + echo '-d, --decrypt FILE decrypts FILE to STDOUT' + echo '-i, --identity identity to use when decrypting' + echo '-v, --verbose verbose output' + echo ' ' + echo 'FILE an age-encrypted file' + echo ' ' + echo 'PRIVATE_KEY a path to a private SSH key used to decrypt file' + echo ' ' + echo 'EDITOR environment variable of editor to use when editing FILE' + echo ' ' + echo 'If STDIN is not interactive, EDITOR will be set to "cp /dev/stdin"' + echo ' ' + echo 'RULES environment variable with path to Nix file specifying recipient public keys.' + echo "Defaults to './secrets.nix'" + echo ' ' + echo "agenix version: @version@" + echo "age binary path: @ageBin@" + echo "age version: $(@ageBin@ --version)" +} + +function warn() { + printf '%s\n' "$*" >&2 +} + +function err() { + warn "$*" + exit 1 +} + +test $# -eq 0 && (show_help && exit 1) + +REKEY=0 +DECRYPT_ONLY=0 +DEFAULT_DECRYPT=(--decrypt) + +while test $# -gt 0; do + case "$1" in + -h|--help) + show_help + exit 0 + ;; + -e|--edit) + shift + if test $# -gt 0; then + export FILE=$1 + else + echo "no FILE specified" + exit 1 + fi + shift + ;; + -i|--identity) + shift + if test $# -gt 0; then + DEFAULT_DECRYPT+=(--identity "$1") + else + echo "no PRIVATE_KEY specified" + exit 1 + fi + shift + ;; + -r|--rekey) + shift + REKEY=1 + ;; + -d|--decrypt) + shift + DECRYPT_ONLY=1 + if test $# -gt 0; then + export FILE=$1 + else + echo "no FILE specified" + exit 1 + fi + shift + ;; + -v|--verbose) + shift + set -x + ;; + *) + show_help + exit 1 + ;; + esac +done + +RULES=${RULES:-./secrets.nix} +function cleanup { + if [ -n "${CLEARTEXT_DIR+x}" ] + then + rm -rf -- "$CLEARTEXT_DIR" + fi + if [ -n "${REENCRYPTED_DIR+x}" ] + then + rm -rf -- "$REENCRYPTED_DIR" + fi +} +trap "cleanup" 0 2 3 15 + +function keys { + (@nixInstantiate@ --json --eval --strict -E "(let rules = import $RULES; in rules.\"$1\".publicKeys)" | @jqBin@ -r .[]) || exit 1 +} + +function armor { + (@nixInstantiate@ --json --eval --strict -E "(let rules = import $RULES; in (builtins.hasAttr \"armor\" rules.\"$1\" && rules.\"$1\".armor))") || exit 1 +} + +function decrypt { + FILE=$1 + KEYS=$2 + if [ -z "$KEYS" ] + then + err "There is no rule for $FILE in $RULES." + fi + + if [ -f "$FILE" ] + then + DECRYPT=("${DEFAULT_DECRYPT[@]}") + if [[ "${DECRYPT[*]}" != *"--identity"* ]]; then + if [ -f "$HOME/.ssh/id_rsa" ]; then + DECRYPT+=(--identity "$HOME/.ssh/id_rsa") + fi + if [ -f "$HOME/.ssh/id_ed25519" ]; then + DECRYPT+=(--identity "$HOME/.ssh/id_ed25519") + fi + fi + if [[ "${DECRYPT[*]}" != *"--identity"* ]]; then + err "No identity found to decrypt $FILE. Try adding an SSH key at $HOME/.ssh/id_rsa or $HOME/.ssh/id_ed25519 or using the --identity flag to specify a file." + fi + + @ageBin@ "${DECRYPT[@]}" -- "$FILE" || exit 1 + fi +} + +function edit { + FILE=$1 + KEYS=$(keys "$FILE") || exit 1 + ARMOR=$(armor "$FILE") || exit 1 + + CLEARTEXT_DIR=$(@mktempBin@ -d) + CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename -- "$FILE")" + DEFAULT_DECRYPT+=(-o "$CLEARTEXT_FILE") + + decrypt "$FILE" "$KEYS" || exit 1 + + [ ! -f "$CLEARTEXT_FILE" ] || cp -- "$CLEARTEXT_FILE" "$CLEARTEXT_FILE.before" + + [ -t 0 ] || EDITOR='cp -- /dev/stdin' + + $EDITOR "$CLEARTEXT_FILE" + + if [ ! -f "$CLEARTEXT_FILE" ] + then + warn "$FILE wasn't created." + return + fi + [ -f "$FILE" ] && [ "$EDITOR" != ":" ] && @diffBin@ -q -- "$CLEARTEXT_FILE.before" "$CLEARTEXT_FILE" && warn "$FILE wasn't changed, skipping re-encryption." && return + + ENCRYPT=() + if [[ "$ARMOR" == "true" ]]; then + ENCRYPT+=(--armor) + fi + while IFS= read -r key + do + if [ -n "$key" ]; then + ENCRYPT+=(--recipient "$key") + fi + done <<< "$KEYS" + + REENCRYPTED_DIR=$(@mktempBin@ -d) + REENCRYPTED_FILE="$REENCRYPTED_DIR/$(basename -- "$FILE")" + + ENCRYPT+=(-o "$REENCRYPTED_FILE") + + @ageBin@ "${ENCRYPT[@]}" <"$CLEARTEXT_FILE" || exit 1 + + mkdir -p -- "$(dirname -- "$FILE")" + + mv -f -- "$REENCRYPTED_FILE" "$FILE" +} + +function rekey { + FILES=$( (@nixInstantiate@ --json --eval -E "(let rules = import $RULES; in builtins.attrNames rules)" | @jqBin@ -r .[]) || exit 1) + + for FILE in $FILES + do + warn "rekeying $FILE..." + EDITOR=: edit "$FILE" + cleanup + done +} + +[ $REKEY -eq 1 ] && rekey && exit 0 +[ $DECRYPT_ONLY -eq 1 ] && DEFAULT_DECRYPT+=("-o" "-") && decrypt "${FILE}" "$(keys "$FILE")" && exit 0 +edit "$FILE" && cleanup && exit 0 diff --git a/pkgs/agenix/default.nix b/pkgs/agenix/default.nix new file mode 100644 index 00000000..694325c4 --- /dev/null +++ b/pkgs/agenix/default.nix @@ -0,0 +1,66 @@ +{ + lib, + stdenv, + age, + jq, + nix, + mktemp, + diffutils, + replaceVars, + ageBin ? "${age}/bin/age", + shellcheck, +}: +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} + + 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 + ) + + cd $HOME/secrets + test $(${bin} -d secret1.age) = "hello" + ''; + + installPhase = '' + install -D $src ${bin} + ''; + + meta.description = "age-encrypted secrets for NixOS"; +} diff --git a/pkgs/agenix/example/-leading-hyphen-filename.age b/pkgs/agenix/example/-leading-hyphen-filename.age new file mode 100644 index 00000000..eb870ddb --- /dev/null +++ b/pkgs/agenix/example/-leading-hyphen-filename.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 V3XmEA zirqdzZZ1E+sedBn7fbEHq4ntLEkokZ4GctarBBOHXY +Rvs5YHaAUeCZyNwPedubPcHClWYIuXXWA5zadXPWY6w +-> ssh-ed25519 KLPP8w BVp4rDkOYSQyn8oVeHFeinSqW+pdVtxBF9+5VM1yORY +bMwppAi8Nhz0328taU4AzUkTVyWtSLvFZG6c5W/Fs78 +--- xCbqLhXAcOziO2wmbjTiSQfZvt5Rlsc4SCvF+iEzpQA +KB/Zr%4Mq5_ݒ11 ܨqM;& Lrf]>N \ No newline at end of file diff --git a/pkgs/agenix/example/armored-secret.age b/pkgs/agenix/example/armored-secret.age new file mode 100644 index 00000000..0e8bff1e --- /dev/null +++ b/pkgs/agenix/example/armored-secret.age @@ -0,0 +1,7 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFYzWG1FQSBpZkZW +aFpLNnJxc0VUMHRmZ2dZS0pjMGVENnR3OHd5K0RiT1RjRUhibFZBCnN5UG5vUjA3 +SXpsNGtiVUw4T0tIVFo5Wkk5QS9NQlBndzVvektiQ0ozc0kKLS0tIGxyY1Q4dEZ1 +VGZEanJyTFNta2JNRmpZb2FnK2JyS1hSVml1UGdMNWZKQXMKYla+wTXcRedyZoEb +LVWaSx49WoUTU0KBPJg9RArxaeC23GoCDzR/aM/1DvYU +-----END AGE ENCRYPTED FILE----- diff --git a/pkgs/agenix/example/passwordfile-user1.age b/pkgs/agenix/example/passwordfile-user1.age new file mode 100644 index 00000000..de43bf45 --- /dev/null +++ b/pkgs/agenix/example/passwordfile-user1.age @@ -0,0 +1,9 @@ +age-encryption.org/v1 +-> ssh-ed25519 KLPP8w s1DYZRlZuSsyhmZCF1lFB+E9vB8bZ/+ZhBRlx8nprwE +nmYVCsVBrX2CFXXPU+D+bbkkIe/foofp+xoUrg9DHZw +-> ssh-ed25519 V3XmEA Pwv3oCwcY0DX8rY48UNfsj9RumWsn4dbgorYHCwObgI +FKxRYkL3JHtJxUwymWDF0rAtJ33BivDI6IfPsfumM90 +-> V'v(/u$-grease em/Vgf 2qDuk +7I3iiQLPGi1COML9u/JeYkr7EqbSLoU +--- 57WJRigUGtmcObrssS3s4PvmR8wgh1AOC/ijJn1s3xI +'KƷY&7GOFjkXBnuJ:9(X#Aڧj,_?ZvV96]oks~%c e^C%JQ5<Hz}C,p*!WA҅dCK)-y \ No newline at end of file diff --git a/pkgs/agenix/example/secret1.age b/pkgs/agenix/example/secret1.age new file mode 100644 index 0000000000000000000000000000000000000000..8cb6f5eb9ae38d8d288644d64a6d3042384b4d48 GIT binary patch literal 328 zcmYdHPt{G$OD?J`D9Oyv)5|YP*Do{V(zR14F3!+RO))YxHMCR+Gmgl0byO%wi3|=5 zOUld&aVZFN3UYVT&nwFcHVH7z^bSuBa<(kX^35+WOLldxG~mh&HpmULs0hz-jLZsl zDy&NK4%0VDFYzhybuKE(EGjNF$jUP^b}5Z=_e8hN+b1BvqFf<4!q_9gL%+}MR zBq_%|J1w))sWjX%&pFb~tS~>IqL@opS63m)ve4Jnq%_sL*w@dX#Mv*yJt)dDAjquH z(7+>CNqvgS~Cp62Ye6g3Z(8%nzH~joA+V<2; K#apYq8Q1|wlx+?G literal 0 HcmV?d00001 diff --git a/pkgs/agenix/example/secret2.age b/pkgs/agenix/example/secret2.age new file mode 100644 index 00000000..fd456b3c --- /dev/null +++ b/pkgs/agenix/example/secret2.age @@ -0,0 +1,5 @@ +age-encryption.org/v1 +-> ssh-ed25519 V3XmEA OB4+1FbPhQ3r6iGksM7peWX5it8NClpXIq/o5nnP7GA +FmHVUj+A5i5+bDFgySQskmlvynnosJiWUTJmBRiNA9I +--- tP+3mFVtd7ogVu1Lkboh55zoi5a77Ht08Uc/QuIviv4 +X{OtMXxvӪ(ImyP+3S3i \ No newline at end of file diff --git a/pkgs/agenix/example/secrets.nix b/pkgs/agenix/example/secrets.nix new file mode 100644 index 00000000..f2e63122 --- /dev/null +++ b/pkgs/agenix/example/secrets.nix @@ -0,0 +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 + ]; + "armored-secret.age" = { + publicKeys = [ user1 ]; + armor = true; + }; +} diff --git a/pkgs/agenix/example_keys/system1 b/pkgs/agenix/example_keys/system1 new file mode 100644 index 00000000..fca65819 --- /dev/null +++ b/pkgs/agenix/example_keys/system1 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDyQ8iK/xUs9XCXXKFuvUfja1s8Biv/t4Caag9bfC9sxAAAAJA3yvCWN8rw +lgAAAAtzc2gtZWQyNTUxOQAAACDyQ8iK/xUs9XCXXKFuvUfja1s8Biv/t4Caag9bfC9sxA +AAAEA+J2V6AG1NriAIvnNKRauIEh1JE9HSdhvKJ68a5Fm0w/JDyIr/FSz1cJdcoW69R+Nr +WzwGK/+3gJpqD1t8L2zEAAAADHJ5YW50bUBob21lMQE= +-----END OPENSSH PRIVATE KEY----- diff --git a/pkgs/agenix/example_keys/system1.pub b/pkgs/agenix/example_keys/system1.pub new file mode 100644 index 00000000..8b52dbe3 --- /dev/null +++ b/pkgs/agenix/example_keys/system1.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE diff --git a/pkgs/agenix/example_keys/user1 b/pkgs/agenix/example_keys/user1 new file mode 100644 index 00000000..4314295e --- /dev/null +++ b/pkgs/agenix/example_keys/user1 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACC9InTb4BornFoLqf5j+/M8gtt7hY2KtHr3FnYxkFGgRwAAAJC2JJ8htiSf +IQAAAAtzc2gtZWQyNTUxOQAAACC9InTb4BornFoLqf5j+/M8gtt7hY2KtHr3FnYxkFGgRw +AAAEDxt5gC/s53IxiKAjfZJVCCcFIsdeERdIgbYhLO719+Kb0idNvgGiucWgup/mP78zyC +23uFjYq0evcWdjGQUaBHAAAADHJ5YW50bUBob21lMQE= +-----END OPENSSH PRIVATE KEY----- diff --git a/pkgs/agenix/example_keys/user1.pub b/pkgs/agenix/example_keys/user1.pub new file mode 100644 index 00000000..10009369 --- /dev/null +++ b/pkgs/agenix/example_keys/user1.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH diff --git a/pkgs/agenix/update.sh b/pkgs/agenix/update.sh new file mode 100755 index 00000000..64ec8afc --- /dev/null +++ b/pkgs/agenix/update.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +set -e + +# All operations are done relative to root +GITROOT=$(git rev-parse --show-toplevel) +cd "$GITROOT" + +REVISION=${1:-main} + +TMPCLONE=$(mktemp -d) +trap "rm -rf ${TMPCLONE}" EXIT + +git clone https://github.com/ryantm/agenix.git --revision="$REVISION" "$TMPCLONE" --depth=1 + +cp "${TMPCLONE}/pkgs/agenix.sh" pkgs/agenix/agenix.sh +cp "${TMPCLONE}/pkgs/agenix.nix" pkgs/agenix/default.nix +sed -i 's#../example#./example#' pkgs/agenix/default.nix + +cp "${TMPCLONE}/example/"* pkgs/agenix/example/ +cp "${TMPCLONE}/example_keys/"* pkgs/agenix/example_keys/ + +cp "${TMPCLONE}/modules/age.nix" m/module/agenix.nix