b0a48f587e
Secrets that are only partly owned by root (i.e. either user or group are not 'root') are now accounted for during activation.
107 lines
3.4 KiB
Nix
107 lines
3.4 KiB
Nix
{ config, lib, pkgs, ... }:
|
|
|
|
with lib;
|
|
|
|
let
|
|
cfg = config.age;
|
|
rage = pkgs.callPackage ../pkgs/rage.nix {};
|
|
ageBin = "${rage}/bin/rage";
|
|
|
|
users = config.users.users;
|
|
|
|
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})
|
|
(umask 0400; ${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}'
|
|
'';
|
|
|
|
rootOwnedSecrets = builtins.filter (st: st.owner == "root" && st.group == "root") (builtins.attrValues cfg.secrets);
|
|
installRootOwnedSecrets = builtins.concatStringsSep "\n" (["echo '[agenix] decrypting root secrets...'"] ++ (map installSecret rootOwnedSecrets));
|
|
|
|
nonRootSecrets = builtins.filter (st: st.owner != "root" || st.group != "root") (builtins.attrValues cfg.secrets);
|
|
installNonRootSecrets = builtins.concatStringsSep "\n" (["echo '[agenix] decrypting non-root secrets...'"] ++ (map installSecret nonRootSecrets));
|
|
|
|
secretType = types.submodule ({ config, ... }: {
|
|
options = {
|
|
name = mkOption {
|
|
type = types.str;
|
|
default = config._module.args.name;
|
|
description = ''
|
|
Name of the file used in /run/secrets
|
|
'';
|
|
};
|
|
file = mkOption {
|
|
type = types.path;
|
|
description = ''
|
|
Age file the secret is loaded from.
|
|
'';
|
|
};
|
|
path = mkOption {
|
|
type = types.str;
|
|
default = "/run/secrets/${config.name}";
|
|
description = ''
|
|
Path where the decrypted secret is installed.
|
|
'';
|
|
};
|
|
mode = mkOption {
|
|
type = types.str;
|
|
default = "0400";
|
|
description = ''
|
|
Permissions mode of the in octal.
|
|
'';
|
|
};
|
|
owner = mkOption {
|
|
type = types.str;
|
|
default = "root";
|
|
description = ''
|
|
User of the file.
|
|
'';
|
|
};
|
|
group = mkOption {
|
|
type = types.str;
|
|
default = users.${config.owner}.group;
|
|
description = ''
|
|
Group of the file.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
in {
|
|
options.age = {
|
|
secrets = mkOption {
|
|
type = types.attrsOf secretType;
|
|
default = {};
|
|
description = ''
|
|
Attrset of secrets.
|
|
'';
|
|
};
|
|
sshKeyPaths = mkOption {
|
|
type = types.listOf types.path;
|
|
default = if config.services.openssh.enable then
|
|
map (e: e.path) (lib.filter (e: e.type == "rsa" || e.type == "ed25519") config.services.openssh.hostKeys)
|
|
else [];
|
|
description = ''
|
|
Path to SSH keys to be used as identities in age decryption.
|
|
'';
|
|
};
|
|
};
|
|
config = mkIf (cfg.secrets != {}) {
|
|
assertions = [{
|
|
assertion = cfg.sshKeyPaths != [];
|
|
message = "age.sshKeyPaths must be set.";
|
|
}];
|
|
|
|
# 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.users.deps = [ "agenixRoot" ];
|
|
|
|
# Other secrets need to wait for users and groups to exist.
|
|
system.activationScripts.agenix = stringAfter [ "users" "groups" ] installNonRootSecrets;
|
|
};
|
|
}
|