add flake and default .nix files; add agenix command
This commit is contained in:
parent
4c2fd23693
commit
79244b4fc3
6 changed files with 205 additions and 20 deletions
|
@ -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
25
flake.lock
Normal 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
|
||||||
|
}
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
122
pkgs/agenix.nix
Normal 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
49
pkgs/agenix.sh
Normal 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
|
Loading…
Reference in a new issue