From 9cf19670393e4f67f20904683da92bef4996932b Mon Sep 17 00:00:00 2001 From: muvlon Date: Tue, 21 Feb 2023 02:15:37 +0100 Subject: [PATCH] feature: add -d/--decrypt option to decrypt a file to stdout --- README.md | 5 +++++ pkgs/agenix.sh | 38 ++++++++++++++++++++++++++++++++++---- test/integration.nix | 7 ++++++- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3381e7e..976b304 100644 --- a/README.md +++ b/README.md @@ -483,6 +483,8 @@ Overriding `age.secretsMountPoint` example: ### agenix CLI reference ``` +agenix - edit and rekey age secret files + agenix -e FILE [-i PRIVATE_KEY] agenix -r [-i PRIVATE_KEY] @@ -490,6 +492,7 @@ options: -h, --help show help -e, --edit FILE edits FILE using $EDITOR -r, --rekey re-encrypts all secrets with specified recipients +-d, --decrypt FILE decrypts FILE to STDOUT -i, --identity identity to use when decrypting -v, --verbose verbose output @@ -499,6 +502,8 @@ PRIVATE_KEY a path to a private SSH key used to decrypt file EDITOR environment variable of editor to use when editing FILE +If STDIN is not interactive, EDITOR will be set to "cp /dev/stdin" + RULES environment variable with path to Nix file specifying recipient public keys. Defaults to './secrets.nix' ``` diff --git a/pkgs/agenix.sh b/pkgs/agenix.sh index c1e89c3..5839a7f 100644 --- a/pkgs/agenix.sh +++ b/pkgs/agenix.sh @@ -14,6 +14,7 @@ function 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 ' ' @@ -45,6 +46,7 @@ function err() { test $# -eq 0 && (show_help && exit 1) REKEY=0 +DECRYPT_ONLY=0 DEFAULT_DECRYPT=(--decrypt) while test $# -gt 0; do @@ -77,6 +79,17 @@ while test $# -gt 0; do 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 @@ -89,7 +102,6 @@ while test $# -gt 0; do done RULES=${RULES:-./secrets.nix} - function cleanup { if [ -n "${CLEARTEXT_DIR+x}" ] then @@ -102,10 +114,13 @@ function cleanup { } 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) +function keys { + (@nixInstantiate@ --eval -E "(let rules = import $RULES; in builtins.concatStringsSep \"\n\" rules.\"$1\".publicKeys)" | @sedBin@ 's/"//g' | @sedBin@ 's/\\n/\n/g') | @sedBin@ '/^$/d' || exit 1 +} +function decrypt { + FILE=$1 + KEYS=$2 if [ -z "$KEYS" ] then err "There is no rule for $FILE in $RULES." @@ -132,6 +147,12 @@ function edit { @ageBin@ "${DECRYPT[@]}" || exit 1 cp "$CLEARTEXT_FILE" "$CLEARTEXT_FILE.before" fi +} + +function edit { + FILE=$1 + KEYS=$(keys "$FILE") || exit 1 + decrypt "$FILE" "$KEYS" || exit 1 [ -t 0 ] || EDITOR='cp /dev/stdin' @@ -160,6 +181,14 @@ function edit { mv -f "$REENCRYPTED_FILE" "$1" } +function decrypt_only { + FILE=$1 + KEYS=$(keys "$FILE") || exit 1 + decrypt "$FILE" "$KEYS" + printf "%s" "$(<"${CLEARTEXT_FILE}")" + cleanup +} + 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) @@ -172,4 +201,5 @@ function rekey { } [ $REKEY -eq 1 ] && rekey && exit 0 +[ $DECRYPT_ONLY -eq 1 ] && decrypt_only "${FILE}" && exit 0 edit "$FILE" && cleanup && exit 0 diff --git a/test/integration.nix b/test/integration.nix index ff1bbac..64f5c50 100644 --- a/test/integration.nix +++ b/test/integration.nix @@ -90,6 +90,11 @@ pkgs.nixosTest { # user1 can edit a secret by piping in contents system1.succeed(userDo("echo 'secret1234' | agenix -e passwordfile-user1.age")) - assert "secret1234" in system1.succeed(userDo("EDITOR=cat agenix -e 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") ''; }