2023-01-29 22:58:38 +03:00
#!/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 ' '
2023-02-19 21:20:07 +03:00
echo 'If STDIN is not interactive, EDITOR will be set to "cp /dev/stdin"'
echo ' '
2023-01-29 22:58:38 +03:00
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) "
}
2023-02-21 22:46:44 +03:00
function warn( ) {
printf '%s\n' " $* " >& 2
}
function err( ) {
warn " $* "
exit 1
}
2023-01-29 22:58:38 +03:00
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
2023-02-21 22:46:44 +03:00
err " There is no rule for $FILE in $RULES . "
2023-01-29 22:58:38 +03:00
fi
CLEARTEXT_DIR = $( @mktempBin@ -d)
CLEARTEXT_FILE = " $CLEARTEXT_DIR / $( basename " $FILE " ) "
if [ -f " $FILE " ]
then
DECRYPT = ( " ${ DEFAULT_DECRYPT [@] } " )
2023-02-18 22:55:58 +03:00
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
2023-01-29 22:58:38 +03:00
fi
if [ [ " ${ DECRYPT [*] } " != *"--identity" * ] ] ; then
2023-02-21 22:46:44 +03:00
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. "
2023-01-29 22:58:38 +03:00
fi
DECRYPT += ( -o " $CLEARTEXT_FILE " " $FILE " )
@ageBin@ " ${ DECRYPT [@] } " || exit 1
cp " $CLEARTEXT_FILE " " $CLEARTEXT_FILE .before "
fi
2023-02-19 21:20:07 +03:00
[ -t 0 ] || EDITOR = 'cp /dev/stdin'
2023-01-29 22:58:38 +03:00
$EDITOR " $CLEARTEXT_FILE "
if [ ! -f " $CLEARTEXT_FILE " ]
then
2023-02-21 22:46:44 +03:00
warn " $FILE wasn't created. "
2023-01-29 22:58:38 +03:00
return
fi
2023-02-21 22:46:44 +03:00
[ -f " $FILE " ] && [ " $EDITOR " != ":" ] && @diffBin@ -q " $CLEARTEXT_FILE .before " " $CLEARTEXT_FILE " && warn " $FILE wasn't changed, skipping re-encryption. " && return
2023-01-29 22:58:38 +03:00
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
2023-02-21 22:46:44 +03:00
warn " rekeying $FILE ... "
2023-01-29 22:58:38 +03:00
EDITOR = : edit " $FILE "
cleanup
done
}
[ $REKEY -eq 1 ] && rekey && exit 0
edit " $FILE " && cleanup && exit 0