ctucx.git: nixfiles

ctucx' nixfiles

commit 6f601f265b83ce28f4f45dcc0aa4aaafaeb26717
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(-)
M
flake.nix
|
1
-
A
pkgs/agenix/agenix.sh
|
200
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A
pkgs/agenix/default.nix
|
37
+++++++++++++++++++++++++++++++++++++
M
pkgs/overlay.nix
|
2
++
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 {};
+
 }