diff --git a/modules/age.nix b/modules/age.nix index c1528c5..a4631f7 100644 --- a/modules/age.nix +++ b/modules/age.nix @@ -14,13 +14,29 @@ let users = config.users.users; + newGeneration = '' + _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" + (( ++_agenix_generation )) + echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation" + mkdir -p "${cfg.secretsMountPoint}" + chmod 0751 "${cfg.secretsMountPoint}" + grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751 + mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation" + chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation" + ''; + identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.identityPaths); - installSecret = secretType: '' + + setTruePath = secretType: '' ${if secretType.symlink then '' _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" '' else '' _truePath="${secretType.path}" ''} + ''; + + installSecret = secretType: '' + ${setTruePath secretType} echo "decrypting '${secretType.file}' to '$_truePath'..." TMP_FILE="$_truePath.tmp" mkdir -p "$(dirname "$_truePath")" @@ -32,7 +48,6 @@ let LANG=${config.i18n.defaultLocale} ${ageBin} --decrypt ${identities} -o "$TMP_FILE" "${secretType.file}" ) chmod ${secretType.mode} "$TMP_FILE" - chown ${secretType.owner}:${secretType.group} "$TMP_FILE" mv -f "$TMP_FILE" "$_truePath" ${optionalString secretType.symlink '' @@ -42,16 +57,42 @@ let testIdentities = map (path: '' test -f ${path} || echo '[agenix] WARNING: config.age.identityPaths entry ${path} not present!' - '') cfg.identityPaths; + '') cfg.identityPaths; - isRootSecret = st: (st.owner == "root" || st.owner == "0") && (st.group == "root" || st.group == "0"); - isNotRootSecret = st: !(isRootSecret st); + cleanupAndLink = '' + _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" + (( ++_agenix_generation )) + echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..." + ln -sfn "${cfg.secretsMountPoint}/$_agenix_generation" ${cfg.secretsDir} - rootOwnedSecrets = builtins.filter isRootSecret (builtins.attrValues cfg.secrets); - installRootOwnedSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting root secrets...'" ] ++ testIdentities ++ (map installSecret rootOwnedSecrets)); + (( _agenix_generation > 1 )) && { + echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..." + rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))" + } + ''; - nonRootSecrets = builtins.filter isNotRootSecret (builtins.attrValues cfg.secrets); - installNonRootSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting non-root secrets...'" ] ++ (map installSecret nonRootSecrets)); + installSecrets = builtins.concatStringsSep "\n" ( + [ "echo '[agenix] decrypting secrets...'" ] + ++ testIdentities + ++ (map installSecret (builtins.attrValues cfg.secrets)) + ++ [ cleanupAndLink ] + ); + + chownSecret = secretType: '' + ${setTruePath secretType} + chown ${secretType.owner}:${secretType.group} "$_truePath" + ''; + + # chown the secrets mountpoint and the current generation to the keys group + # instead of leaving it root:root. + chownMountPoint = '' + chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation" + ''; + + chownSecrets = builtins.concatStringsSep "\n" ( + [ "echo '[agenix] chowning...'" ] + ++ [ chownMountPoint ] + ++ (map chownSecret (builtins.attrValues cfg.secrets))); secretType = types.submodule ({ config, ... }: { options = { @@ -162,66 +203,36 @@ in # ensure removed secrets are actually removed, or at least become # invalid symlinks). system.activationScripts.agenixNewGeneration = { - text = '' - _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" - (( ++_agenix_generation )) - echo "[agenix] creating new generation in ${cfg.secretsMountPoint}/$_agenix_generation" - mkdir -p "${cfg.secretsMountPoint}" - chmod 0751 "${cfg.secretsMountPoint}" - grep -q "${cfg.secretsMountPoint} ramfs" /proc/mounts || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0751 - mkdir -p "${cfg.secretsMountPoint}/$_agenix_generation" - chmod 0751 "${cfg.secretsMountPoint}/$_agenix_generation" - ''; + text = newGeneration; deps = [ "specialfs" ]; }; - # Symlink new generation in place and cleanup old generation - system.activationScripts.agenixCleanupAndLink= { - text = '' - _agenix_generation="$(basename "$(readlink ${cfg.secretsDir})" || echo 0)" - (( ++_agenix_generation )) - echo "[agenix] symlinking new secrets to ${cfg.secretsDir} (generation $_agenix_generation)..." - ln -sfn "${cfg.secretsMountPoint}/$_agenix_generation" ${cfg.secretsDir} - - (( _agenix_generation > 1 )) && { - echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..." - rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))" - } - ''; + system.activationScripts.agenixInstall = { + text = installSecrets; deps = [ - "agenixRoot" - "agenixNonRoot" + "agenixNewGeneration" + "specialfs" ]; }; - # Secrets with root owner and group can be installed before users - # exist. This allows user password files to be encrypted. - system.activationScripts.agenixRoot = { - text = installRootOwnedSecrets; - deps = [ "agenixNewGeneration" "specialfs" ]; - }; - system.activationScripts.users.deps = [ "agenixRoot" ]; + # So user passwords can be encrypted. + system.activationScripts.users.deps = [ "agenixInstall" ]; - # chown the secrets mountpoint and the current generation to the keys group - # instead of leaving it root:root. - system.activationScripts.agenixChownKeys = { - text = '' - chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation" - ''; + # Change ownership and group after users and groups are made. + system.activationScripts.agenixChown = { + text = chownSecrets; deps = [ "users" "groups" - "agenixCleanupAndLink" ]; }; - - # Other secrets need to wait for users and groups to exist. - system.activationScripts.agenixNonRoot = { - text = installNonRootSecrets; - deps = [ "agenixNewGeneration" "specialfs" ]; + # So other activation scripts can depend on agenix being done. + system.activationScripts.agenix = { + text = ""; + deps = [ "agenixChown"]; }; }; diff --git a/test/install_ssh_host_keys.nix b/test/install_ssh_host_keys.nix index 028845b..93f383d 100644 --- a/test/install_ssh_host_keys.nix +++ b/test/install_ssh_host_keys.nix @@ -1,6 +1,6 @@ # Do not copy this! It is insecure. This is only okay because we are testing. { - system.activationScripts.agenixRoot.deps = [ "installSSHHostKeys" ]; + system.activationScripts.agenixInstall.deps = [ "installSSHHostKeys" ]; system.activationScripts.installSSHHostKeys.text = '' mkdir -p /etc/ssh