Skip to content

Commit

Permalink
modules/age: symlink files into place
Browse files Browse the repository at this point in the history
This follows sops-nix's implementation, where it creates a
`/run/secrets.d` ramfs mountpoint and a "generation" each time
the activation script runs, and then symlinks `/run/secrets` to
`/run/secrets.d/[generation]`.
  • Loading branch information
cole-h committed Feb 26, 2021
1 parent ed7e69b commit 28b3369
Showing 1 changed file with 35 additions and 5 deletions.
40 changes: 35 additions & 5 deletions modules/age.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ let

identities = builtins.concatStringsSep " " (map (path: "-i ${path}") cfg.sshKeyPaths);
installSecret = secretType: ''
echo "decrypting ${secretType.file} to ${secretType.path}..."
TMP_FILE="${secretType.path}.tmp"
mkdir -p $(dirname ${secretType.path})
echo "decrypting '${secretType.file}' to '${cfg.secretsMountPoint}/$_count/${secretType.name}'..."
TMP_FILE="${cfg.secretsMountPoint}/$_count/${secretType.name}.tmp"
mkdir -p "$(dirname "${cfg.secretsMountPoint}/$_count/${secretType.name}")"
mkdir -p "$(dirname "${secretType.path}")"
(umask 0400; 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" '${secretType.path}'
mv -f "$TMP_FILE" "${cfg.secretsMountPoint}/$_count/${secretType.name}"
[ "${secretType.path}" != "/run/secrets/${secretType.name}" ] && ln -sfn "/run/secrets/${secretType.name}" "${secretType.path}"
'';

rootOwnedSecrets = builtins.filter (st: st.owner == "root" && st.group == "root") (builtins.attrValues cfg.secrets);
Expand Down Expand Up @@ -80,6 +82,17 @@ in {
Attrset of secrets.
'';
};
secretsMountPoint = mkOption {
type = types.addCheck types.str
(s:
(builtins.match "[ \t\n]*" s) == null # non-empty
&& (builtins.match ".+/" s) == null) # without trailing slash
// { description = "${types.str.description} (with check: non-empty without trailing slash)"; };
default = "/run/secrets.d";
description = ''
Where secrets are created before they are symlinked to /run/secrets
'';
};
sshKeyPaths = mkOption {
type = types.listOf types.path;
default = if config.services.openssh.enable then
Expand All @@ -96,9 +109,26 @@ in {
message = "age.sshKeyPaths must be set.";
}];

# Create a new directory full of secrets for symlinking (this helps
# ensure removed secrets are actually removed, or at least become
# invalid symlinks).
system.activationScripts.agenixMountSecrets = ''
_count="$(basename "$(readlink /run/secrets)" || echo 0)"
(( ++_count ))
echo "[agenix] symlinking new secrets generation $_count to /run/secrets..."
mkdir -pm 0750 "${cfg.secretsMountPoint}"
mount | grep "${cfg.secretsMountPoint} type ramfs" -q || mount -t ramfs none "${cfg.secretsMountPoint}" -o nodev,nosuid,mode=0750
mkdir -pm 0750 "${cfg.secretsMountPoint}/$_count"
chown :keys "${cfg.secretsMountPoint}" "${cfg.secretsMountPoint}/$_count"
ln -sfn "${cfg.secretsMountPoint}/$_count" /run/secrets
'';

# Secrets with root owner and group can be installed before users
# exist. This allows user password files to be encrypted.
system.activationScripts.agenixRoot = installRootOwnedSecrets;
system.activationScripts.agenixRoot = {
text = installRootOwnedSecrets;
deps = [ "agenixMountSecrets" ];
};
system.activationScripts.users.deps = [ "agenixRoot" ];

# Other secrets need to wait for users and groups to exist.
Expand Down

0 comments on commit 28b3369

Please sign in to comment.