modules/age: add secrets change detection

This commit is contained in:
Erik Arvstedt 2023-01-09 13:23:46 +01:00
parent 657442730c
commit 69e4bfbe75

View file

@ -15,14 +15,21 @@ let
users = config.users.users; users = config.users.users;
newGeneration = '' newGeneration = ''
_agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" _agenix_last_generation=$(basename "$(readlink ${cfg.secretsDir})" || true)
(( ++_agenix_generation )) if [[ $_agenix_last_generation == ${secretsHash} ]]; then
_agenix_is_current=1
else
_agenix_is_current=
fi
if [[ ! $_agenix_is_current ]]; then
_agenix_generation="${secretsHash}"
echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation" echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation"
mkdir -p "${cfg.secretsMountPoint}" mkdir -p "${cfg.secretsMountPoint}"
chmod 0751 "${cfg.secretsMountPoint}" chmod 0751 "${cfg.secretsMountPoint}"
grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751 grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751
mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation" mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation"
chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation" chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation"
fi
''; '';
identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.identityPaths); identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.identityPaths);
@ -59,23 +66,30 @@ let
test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!'
'') cfg.identityPaths; '') cfg.identityPaths;
# Add suffix `-incomplete` to the generation when creating some secrets has failed.
# This ensures that we can try to re-create the generation on subsequent runs.
renameOnFailure = ''
if (( _localstatus > 0 )); then
mv "${cfg.secretsMountPoint}/$_agenix_generation"{,-incomplete}
_agenix_generation+=-incomplete
fi
'';
cleanupAndLink = '' cleanupAndLink = ''
_agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)"
(( ++_agenix_generation ))
echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..." echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..."
ln -sfn "${cfg.secretsMountPoint}/$_agenix_generation" ${cfg.secretsDir} ln -sfn "${cfg.secretsMountPoint}/$_agenix_generation" ${cfg.secretsDir}
(( _agenix_generation > 1 )) && { [[ $_agenix_last_generation ]] && {
echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..." echo "[agenix] removing old secrets (generation $_agenix_last_generation)..."
rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))" rm -rf "${cfg.secretsMountPoint}/$_agenix_last_generation"
} }
''; '';
installSecrets = builtins.concatStringsSep "\n" ( installSecrets = mkInstallScript (
[ "echo '[agenix] decrypting secrets...'" ] [ "echo '[agenix] decrypting secrets...'" ]
++ testIdentities ++ testIdentities
++ (map installSecret (builtins.attrValues cfg.secrets)) ++ (map installSecret (builtins.attrValues cfg.secrets))
++ [ cleanupAndLink ] ++ [ renameOnFailure cleanupAndLink ]
); );
chownSecret = secretType: '' chownSecret = secretType: ''
@ -89,11 +103,23 @@ let
chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation" chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation"
''; '';
chownSecrets = builtins.concatStringsSep "\n" ( chownSecrets = mkInstallScript (
[ "echo '[agenix] chowning...'" ] [ "echo '[agenix] chowning...'" ]
++ [ chownMountPoint ] ++ [ chownMountPoint ]
++ (map chownSecret (builtins.attrValues cfg.secrets))); ++ (map chownSecret (builtins.attrValues cfg.secrets)));
mkInstallScript = strings: ''
[[ $_agenix_is_current ]] || {
${builtins.concatStringsSep "\n" strings}
}
'';
secretsHash = let
sha256-base16 = builtins.hashString "sha256" (builtins.toJSON cfg.secrets);
in
# Truncate to 128 bits to increase readability
substring 0 32 sha256-base16;
secretType = types.submodule ({ config, ... }: { secretType = types.submodule ({ config, ... }: {
options = { options = {
name = mkOption { name = mkOption {
@ -104,7 +130,13 @@ let
''; '';
}; };
file = mkOption { file = mkOption {
type = types.path; type = mkOptionType {
name = "nix-path";
descriptionClass = "noun";
check = builtins.isPath;
merge = mergeEqualOption;
};
description = '' description = ''
Age file the secret is loaded from. Age file the secret is loaded from.
''; '';