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;
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"];
};
};

View file

@ -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