add flake and default .nix files; add agenix command

This commit is contained in:
Ryan Mulligan 2020-09-03 11:24:33 -07:00
parent 4c2fd23693
commit 79244b4fc3
6 changed files with 205 additions and 20 deletions

View file

@ -1,6 +1,4 @@
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
rec { {
age-nix = pkgs.writeScriptBin "age-nix" '' agenix = pkgs.callPackage ./pkgs/agenix.nix {};
exit 0
'';
} }

25
flake.lock Normal file
View file

@ -0,0 +1,25 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1599148892,
"narHash": "sha256-V76c6DlI0ZZffvbBpxGlpVSpXxZ14QpFHwAvEEujIsY=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "7ff50a7f7b9a701228f870813fe58f01950f870b",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

View file

@ -12,7 +12,7 @@
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
in { in {
nixosModules.age = import ./modules/age.nix; nixosModules.age = import ./modules/age.nix;
# packages = forAllSystems (system: nixpkgs.legacyPackages.${system}.callPackage ./default.nix {}); packages = forAllSystems (system: nixpkgs.legacyPackages.${system}.callPackage ./default.nix {});
# defaultPackage = forAllSystems (system: self.packages.${system}.age-nix); # defaultPackage = forAllSystems (system: self.packages.${system}.agenix);
}; };
} }

View file

@ -13,7 +13,6 @@ let
chmod ${secretType.mode} "$TMP_FILE" chmod ${secretType.mode} "$TMP_FILE"
chown ${secretType.owner}:${secretType.group} "$TMP_FILE" chown ${secretType.owner}:${secretType.group} "$TMP_FILE"
mv -f "$TMP_FILE" '${secretType.path}' mv -f "$TMP_FILE" '${secretType.path}'
''; '';
installAllSecrets = builtins.concatStringsSep "\n" (map installSecret (builtins.attrValues cfg.secrets)); installAllSecrets = builtins.concatStringsSep "\n" (map installSecret (builtins.attrValues cfg.secrets));
@ -27,15 +26,12 @@ let
''; '';
}; };
file = mkOption { file = mkOption {
type = types.either types.str types.path; type = types.path;
description = '' description = ''
Age file the secret is loaded from. Age file the secret is loaded from.
''; '';
}; };
path = assert assertMsg (builtins.pathExists config.file) '' path = mkOption {
Cannot find path '${config.file}' set in 'age.secrets."${config._module.args.name}".file'
'';
mkOption {
type = types.str; type = types.str;
default = "/run/secrets/${config.name}"; default = "/run/secrets/${config.name}";
description = '' description = ''
@ -81,20 +77,15 @@ in {
map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys) map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
else []; else [];
description = '' description = ''
Path to SSH keys to be used as identities in age file decryption. Path to SSH keys to be used as identities in age decryption.
''; '';
}; };
}; };
config = mkIf (cfg.secrets != {}) { config = mkIf (cfg.secrets != {}) {
assertions = [{ assertions = [{
assertion = cfg.sshKeyPaths != []; assertion = cfg.sshKeyPaths != [];
message = "Either age.sshKeyPaths must be set."; message = "age.sshKeyPaths must be set.";
}] ++ map (name: let }];
inherit (cfg.secrets.${name}) file;
in {
assertion = builtins.isPath file;
message = "${file} is not in the nix store. Either add it to the nix store.";
}) (builtins.attrNames cfg.secrets);
system.activationScripts.setup-secrets = stringAfter [ "users" "groups" ] installAllSecrets; system.activationScripts.setup-secrets = stringAfter [ "users" "groups" ] installAllSecrets;
}; };

122
pkgs/agenix.nix Normal file
View file

@ -0,0 +1,122 @@
{writeShellScriptBin, runtimeShell, age, yq-go} :
writeShellScriptBin "agenix" ''
set -euo pipefail
PACKAGE="agenix"
function show_help () {
echo "$PACKAGE - edit and rekey age secret files"
echo " "
echo "$PACKAGE -e FILE"
echo "$PACKAGE -r"
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 ' '
echo 'FILE an age-encrypted file'
echo ' '
echo 'EDITOR environment variable of editor to use when editing FILE'
echo ' '
echo 'RULES environment variable with path to YAML file specifying recipient public keys.'
echo "Defaults to 'secrets.yaml'"
}
test $# -eq 0 && (show_help && exit 1)
REKEY=0
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
;;
-r|--rekey)
shift
REKEY=1
;;
*)
show_help
exit 1
;;
esac
done
RULES=''${RULES:-secrets.yaml}
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=$(${yq-go}/bin/yq r "$RULES" "secrets.(name==$FILE).public_keys.**")
if [ -z "$KEYS" ]
then
>&2 echo "There is no rule for $FILE in $RULES."
exit 1
fi
CLEARTEXT_DIR=$(mktemp -d)
CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename "$FILE")"
if [ -f "$FILE" ]
then
DECRYPT=(--decrypt)
while IFS= read -r key
do
DECRYPT+=(--identity "$key")
done <<<$(find ~/.ssh -maxdepth 1 -type f -not -name "*pub" -not -name "config" -not -name "authorized_keys" -not -name "known_hosts")
DECRYPT+=(-o "$CLEARTEXT_FILE" "$FILE")
${age}/bin/age "''${DECRYPT[@]}"
fi
$EDITOR "$CLEARTEXT_FILE"
ENCRYPT=()
while IFS= read -r key
do
ENCRYPT+=(--recipient "$key")
done <<< "$KEYS"
REENCRYPTED_DIR=$(mktemp -d)
REENCRYPTED_FILE="$REENCRYPTED_DIR/$(basename "$FILE")"
ENCRYPT+=(-o "$REENCRYPTED_FILE")
cat "$CLEARTEXT_FILE" | ${age}/bin/age "''${ENCRYPT[@]}"
mv -f "$REENCRYPTED_FILE" "$1"
}
function rekey {
echo "rekeying..."
FILES=$(${yq-go}/bin/yq r "$RULES" "secrets.*.name")
for FILE in $FILES
do
EDITOR=: edit $FILE
done
}
[ $REKEY -eq 1 ] && rekey && exit 0
edit $FILE && exit 0
''

49
pkgs/agenix.sh Normal file
View file

@ -0,0 +1,49 @@
#! /usr/bin/env nix-shell
#! nix-shell -i bash -p age yq-go moreutils
while test $# -gt 0; do
case "$1" in
-h|--help)
echo "$package - attempt to capture frames"
echo " "
echo "$package [options] application [arguments]"
echo " "
echo "options:"
echo "-h, --help show brief help"
echo "-a, --action=ACTION specify an action to use"
echo "-o, --output-dir=DIR specify a directory to store output in"
exit 0
;;
-a)
shift
if test $# -gt 0; then
export PROCESS=$1
else
echo "no process specified"
exit 1
fi
shift
;;
--action*)
export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
-o)
shift
if test $# -gt 0; then
export OUTPUT=$1
else
echo "no output dir specified"
exit 1
fi
shift
;;
--output-dir*)
export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'`
shift
;;
*)
break
;;
esac
done