Skip to content

contrib: use mkDerivation for agenix cli #139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
210 changes: 30 additions & 180 deletions pkgs/agenix.nix
Original file line number Diff line number Diff line change
@@ -1,187 +1,37 @@
{
lib,
writeShellScriptBin,
runtimeShell,
callPackage,
stdenv,
rage,
gnused,
nix,
mktemp,
diffutils,
ageBin ? "${
# we need at least rage 0.5.0 to support ssh keys
if rage.version < "0.5.0"
then callPackage ./rage.nix {}
else rage
}/bin/rage",
}: let
sedBin = "${gnused}/bin/sed";
nixInstantiate = "${nix}/bin/nix-instantiate";
mktempBin = "${mktemp}/bin/mktemp";
diffBin = "${diffutils}/bin/diff";
in
lib.recursiveUpdate (writeShellScriptBin "agenix" ''
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'
echo '-e, --edit FILE edits FILE using $EDITOR'
echo '-r, --rekey re-encrypts all secrets with specified recipients'
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 'RULES environment variable with path to Nix file specifying recipient public keys.'
echo "Defaults to './secrets.nix'"
echo ' '
echo "agenix version: 0.13.0"
echo "age binary path: ${ageBin}"
echo "age version: $(${ageBin} --version)"
}

test $# -eq 0 && (show_help && exit 1)

REKEY=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
;;
-v|--verbose)
shift
set -x
;;
*)
show_help
exit 1
;;
esac
done

RULES=''${RULES:-./secrets.nix}

function cleanup {
if [ ! -z ''${CLEARTEXT_DIR+x} ]
then
rm -rf "$CLEARTEXT_DIR"
fi
if [ ! -z ''${REENCRYPTED_DIR+x} ]
then
rm -rf "$REENCRYPTED_DIR"
fi
}
trap "cleanup" 0 2 3 15

function edit {
FILE=$1
KEYS=$((${nixInstantiate} --eval -E "(let rules = import $RULES; in builtins.concatStringsSep \"\n\" rules.\"$FILE\".publicKeys)" | ${sedBin} 's/"//g' | ${sedBin} 's/\\n/\n/g') | ${sedBin} '/^$/d' || exit 1)

if [ -z "$KEYS" ]
then
>&2 echo "There is no rule for $FILE in $RULES."
exit 1
fi

CLEARTEXT_DIR=$(${mktempBin} -d)
CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename "$FILE")"

if [ -f "$FILE" ]
then
DECRYPT=("''${DEFAULT_DECRYPT[@]}")
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
if [[ "''${DECRYPT[*]}" != *"--identity"* ]]; then
echo "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."
exit 1
fi
DECRYPT+=(-o "$CLEARTEXT_FILE" "$FILE")
${ageBin} "''${DECRYPT[@]}" || exit 1
cp "$CLEARTEXT_FILE" "$CLEARTEXT_FILE.before"
fi

$EDITOR "$CLEARTEXT_FILE"

if [ ! -f "$CLEARTEXT_FILE" ]
then
echo "$FILE wasn't created."
return
fi
[ -f "$FILE" ] && [ "$EDITOR" != ":" ] && ${diffBin} "$CLEARTEXT_FILE.before" "$CLEARTEXT_FILE" 1>/dev/null && echo "$FILE wasn't changed, skipping re-encryption." && return

ENCRYPT=()
while IFS= read -r key
do
ENCRYPT+=(--recipient "$key")
done <<< "$KEYS"

REENCRYPTED_DIR=$(${mktempBin} -d)
REENCRYPTED_FILE="$REENCRYPTED_DIR/$(basename "$FILE")"

ENCRYPT+=(-o "$REENCRYPTED_FILE")

${ageBin} "''${ENCRYPT[@]}" <"$CLEARTEXT_FILE" || exit 1

mv -f "$REENCRYPTED_FILE" "$1"
}

function rekey {
FILES=$((${nixInstantiate} --eval -E "(let rules = import $RULES; in builtins.concatStringsSep \"\n\" (builtins.attrNames rules))" | ${sedBin} 's/"//g' | ${sedBin} 's/\\n/\n/g') || exit 1)

for FILE in $FILES
do
echo "rekeying $FILE..."
EDITOR=: edit "$FILE"
cleanup
done
}

[ $REKEY -eq 1 ] && rekey && exit 0
edit "$FILE" && cleanup && exit 0
'')
{
meta.description = "age-encrypted secrets for NixOS";
}
substituteAll,
ageBin ? "${rage}/bin/rage",
shellcheck,
}:
stdenv.mkDerivation rec {
pname = "agenix";
version = "0.13.0";
src = substituteAll {
inherit ageBin version;
sedBin = "${gnused}/bin/sed";
nixInstantiate = "${nix}/bin/nix-instantiate";
mktempBin = "${mktemp}/bin/mktemp";
diffBin = "${diffutils}/bin/diff";
src = ./agenix.sh;
};
dontUnpack = true;

doCheck = true;
checkInputs = [shellcheck];
postCheck = ''
shellcheck $src
'';

installPhase = ''
install -D $src ${placeholder "out"}/bin/agenix
'';

meta.description = "age-encrypted secrets for NixOS";
}
162 changes: 162 additions & 0 deletions pkgs/agenix.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/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 '-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 '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)"
}

test $# -eq 0 && (show_help && exit 1)

REKEY=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
;;
-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 edit {
FILE=$1
KEYS=$( (@nixInstantiate@ --eval -E "(let rules = import $RULES; in builtins.concatStringsSep \"\n\" rules.\"$FILE\".publicKeys)" | @sedBin@ 's/"//g' | @sedBin@ 's/\\n/\n/g') | @sedBin@ '/^$/d' || exit 1)

if [ -z "$KEYS" ]
then
>&2 echo "There is no rule for $FILE in $RULES."
exit 1
fi

CLEARTEXT_DIR=$(@mktempBin@ -d)
CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename "$FILE")"

if [ -f "$FILE" ]
then
DECRYPT=("${DEFAULT_DECRYPT[@]}")
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
if [[ "${DECRYPT[*]}" != *"--identity"* ]]; then
echo "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."
exit 1
fi
DECRYPT+=(-o "$CLEARTEXT_FILE" "$FILE")
@ageBin@ "${DECRYPT[@]}" || exit 1
cp "$CLEARTEXT_FILE" "$CLEARTEXT_FILE.before"
fi

$EDITOR "$CLEARTEXT_FILE"

if [ ! -f "$CLEARTEXT_FILE" ]
then
echo "$FILE wasn't created."
return
fi
[ -f "$FILE" ] && [ "$EDITOR" != ":" ] && @diffBin@ "$CLEARTEXT_FILE.before" "$CLEARTEXT_FILE" 1>/dev/null && echo "$FILE wasn't changed, skipping re-encryption." && return

ENCRYPT=()
while IFS= read -r key
do
ENCRYPT+=(--recipient "$key")
done <<< "$KEYS"

REENCRYPTED_DIR=$(@mktempBin@ -d)
REENCRYPTED_FILE="$REENCRYPTED_DIR/$(basename "$FILE")"

ENCRYPT+=(-o "$REENCRYPTED_FILE")

@ageBin@ "${ENCRYPT[@]}" <"$CLEARTEXT_FILE" || exit 1

mv -f "$REENCRYPTED_FILE" "$1"
}

function rekey {
FILES=$( (@nixInstantiate@ --eval -E "(let rules = import $RULES; in builtins.concatStringsSep \"\n\" (builtins.attrNames rules))" | @sedBin@ 's/"//g' | @sedBin@ 's/\\n/\n/g') || exit 1)

for FILE in $FILES
do
echo "rekeying $FILE..."
EDITOR=: edit "$FILE"
cleanup
done
}

[ $REKEY -eq 1 ] && rekey && exit 0
edit "$FILE" && cleanup && exit 0