feature: combine root and nonroot secret install; delay chowning

This commit is contained in:
Ryan Mulligan 2022-07-10 11:47:58 -07:00
parent fe206b4306
commit f86b56229b
2 changed files with 65 additions and 54 deletions

View file

@ -14,13 +14,29 @@ let
users = config.users.users; 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); identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.identityPaths);
installSecret = secretType: ''
setTruePath = secretType: ''
${if secretType.symlink then '' ${if secretType.symlink then ''
_truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}" _truePath="${cfg.secretsMountPoint}/$_agenix_generation/${secretType.name}"
'' else '' '' else ''
_truePath="${secretType.path}" _truePath="${secretType.path}"
''} ''}
'';
installSecret = secretType: ''
${setTruePath secretType}
echo "decrypting '${secretType.file}' to '$_truePath'..." echo "decrypting '${secretType.file}' to '$_truePath'..."
TMP_FILE="$_truePath.tmp" TMP_FILE="$_truePath.tmp"
mkdir -p "$(dirname "$_truePath")" mkdir -p "$(dirname "$_truePath")"
@ -32,7 +48,6 @@ let
LANG=${config.i18n.defaultLocale} ${ageBin} --decrypt ${identities} -o "$TMP_FILE" "${secretType.file}" LANG=${config.i18n.defaultLocale} ${ageBin} --decrypt ${identities} -o "$TMP_FILE" "${secretType.file}"
) )
chmod ${secretType.mode} "$TMP_FILE" chmod ${secretType.mode} "$TMP_FILE"
chown ${secretType.owner}:${secretType.group} "$TMP_FILE"
mv -f "$TMP_FILE" "$_truePath" mv -f "$TMP_FILE" "$_truePath"
${optionalString secretType.symlink '' ${optionalString secretType.symlink ''
@ -44,14 +59,40 @@ 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;
isRootSecret = st: (st.owner == "root" || st.owner == "0") && (st.group == "root" || st.group == "0"); cleanupAndLink = ''
isNotRootSecret = st: !(isRootSecret st); _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); (( _agenix_generation > 1 )) && {
installRootOwnedSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting root secrets...'" ] ++ testIdentities ++ (map installSecret rootOwnedSecrets)); echo "[agenix] removing old secrets (generation $(( _agenix_generation - 1 )))..."
rm -rf "${cfg.secretsMountPoint}/$(( _agenix_generation - 1 ))"
}
'';
nonRootSecrets = builtins.filter isNotRootSecret (builtins.attrValues cfg.secrets); installSecrets = builtins.concatStringsSep "\n" (
installNonRootSecrets = builtins.concatStringsSep "\n" ([ "echo '[agenix] decrypting non-root secrets...'" ] ++ (map installSecret nonRootSecrets)); [ "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, ... }: { secretType = types.submodule ({ config, ... }: {
options = { options = {
@ -162,66 +203,36 @@ in
# ensure removed secrets are actually removed, or at least become # ensure removed secrets are actually removed, or at least become
# invalid symlinks). # invalid symlinks).
system.activationScripts.agenixNewGeneration = { system.activationScripts.agenixNewGeneration = {
text = '' text = 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"
'';
deps = [ deps = [
"specialfs" "specialfs"
]; ];
}; };
# Symlink new generation in place and cleanup old generation system.activationScripts.agenixInstall = {
system.activationScripts.agenixCleanupAndLink= { text = installSecrets;
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 ))"
}
'';
deps = [ deps = [
"agenixRoot" "agenixNewGeneration"
"agenixNonRoot" "specialfs"
]; ];
}; };
# Secrets with root owner and group can be installed before users # So user passwords can be encrypted.
# exist. This allows user password files to be encrypted. system.activationScripts.users.deps = [ "agenixInstall" ];
system.activationScripts.agenixRoot = {
text = installRootOwnedSecrets;
deps = [ "agenixNewGeneration" "specialfs" ];
};
system.activationScripts.users.deps = [ "agenixRoot" ];
# chown the secrets mountpoint and the current generation to the keys group # Change ownership and group after users and groups are made.
# instead of leaving it root:root. system.activationScripts.agenixChown = {
system.activationScripts.agenixChownKeys = { text = chownSecrets;
text = ''
chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_agenix_generation"
'';
deps = [ deps = [
"users" "users"
"groups" "groups"
"agenixCleanupAndLink"
]; ];
}; };
# So other activation scripts can depend on agenix being done.
# Other secrets need to wait for users and groups to exist. system.activationScripts.agenix = {
system.activationScripts.agenixNonRoot = { text = "";
text = installNonRootSecrets; deps = [ "agenixChown"];
deps = [ "agenixNewGeneration" "specialfs" ];
}; };
}; };

View file

@ -1,6 +1,6 @@
# Do not copy this! It is insecure. This is only okay because we are testing. # 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 = '' system.activationScripts.installSSHHostKeys.text = ''
mkdir -p /etc/ssh mkdir -p /etc/ssh