commit 6f601f265b83ce28f4f45dcc0aa4aaafaeb26717
parent 144bd39b673a894c4bad73c4897e703de4d68deb
Author: Leah (ctucx) <git@ctu.cx>
Date: Tue, 28 Mar 2023 16:20:52 +0200
parent 144bd39b673a894c4bad73c4897e703de4d68deb
Author: Leah (ctucx) <git@ctu.cx>
Date: Tue, 28 Mar 2023 16:20:52 +0200
pkgs: add agenix script that encrypts using PEM encoded format
4 files changed, 239 insertions(+), 1 deletion(-)
A
|
200
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/flake.nix b/flake.nix @@ -11,7 +11,6 @@ unstable = inputs.nixpkgsUnstable.legacyPackages.${prev.system}; }) - inputs.agenix.overlays.default inputs.colmena.overlay inputs.stagit.overlay
diff --git a/pkgs/agenix/agenix.sh b/pkgs/agenix/agenix.sh @@ -0,0 +1,200 @@ +#!/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@ --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." + 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 + + CLEARTEXT_DIR=$(@mktempBin@ -d) + CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename "$FILE")" + DEFAULT_DECRYPT+=(-o "$CLEARTEXT_FILE") + + decrypt "$FILE" "$KEYS" || exit 1 + + 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=() + 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@ --armor "${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 + warn "rekeying $FILE..." + EDITOR=: edit "$FILE" + cleanup + done +} + +[ $REKEY -eq 1 ] && rekey && exit 0 +[ $DECRYPT_ONLY -eq 1 ] && decrypt "${FILE}" "$(keys "$FILE")" && exit 0 +edit "$FILE" && cleanup && exit 0
diff --git a/pkgs/agenix/default.nix b/pkgs/agenix/default.nix @@ -0,0 +1,37 @@ +{ + lib, + stdenv, + rage, + gnused, + nix, + mktemp, + diffutils, + 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"; +}
diff --git a/pkgs/overlay.nix b/pkgs/overlay.nix @@ -17,6 +17,8 @@ final: prev: cinny = final.callPackage ./cinny.nix {}; mbusd = final.callPackage ./mbusd.nix {}; homebridge = final.callPackage ./homebridge {}; + agenix = final.callPackage ./agenix {}; kvg-station-departures = final.callPackage ./kvg-station-departures.nix {}; + }