Allow setting user passwords

This commit is contained in:
Janne Heß 2021-10-19 18:26:43 +02:00
parent 79706f6748
commit bac08f6919
No known key found for this signature in database
GPG key ID: 69165158F05265DF
4 changed files with 142 additions and 51 deletions

View file

@ -5,6 +5,9 @@ with lib;
let
cfg = config.sops;
users = config.users.users;
sops-install-secrets = (pkgs.callPackage ../.. {}).sops-install-secrets;
regularSecrets = lib.filterAttrs (_: v: !v.neededForUsers) cfg.secrets;
secretsForUsers = lib.filterAttrs (_: v: v.neededForUsers) cfg.secrets;
secretType = types.submodule ({ config, ... }: {
config = {
sopsFile = lib.mkOptionDefault cfg.defaultSopsFile;
@ -29,7 +32,8 @@ let
};
path = mkOption {
type = types.str;
default = "/run/secrets/${config.name}";
default = if config.neededForUsers then "/run/secrets-for-users/${config.name}" else "/run/secrets/${config.name}";
defaultText = "/run/secrets-for-users/$name when neededForUsers is set, /run/secrets/$name when otherwise.";
description = ''
Path where secrets are symlinked to.
If the default is kept no symlink is created.
@ -87,31 +91,45 @@ let
This works the same way as <xref linkend="opt-systemd.services._name_.restartTriggers" />.
'';
};
};
});
manifest = pkgs.writeText "manifest.json" (builtins.toJSON {
secrets = builtins.attrValues cfg.secrets;
# Does this need to be configurable?
secretsMountPoint = "/run/secrets.d";
symlinkPath = "/run/secrets";
gnupgHome = cfg.gnupg.home;
sshKeyPaths = cfg.gnupg.sshKeyPaths;
ageKeyFile = cfg.age.keyFile;
ageSshKeyPaths = cfg.age.sshKeyPaths;
logging = {
keyImport = builtins.elem "keyImport" cfg.log;
secretChanges = builtins.elem "secretChanges" cfg.log;
neededForUsers = mkOption {
type = types.bool;
default = false;
description = ''
Enabling this option causes the secret to be decrypted before users and groups are created.
This can be used to retrieve user's passwords from sops-nix.
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
'';
};
};
});
checkedManifest = let
sops-install-secrets = (pkgs.buildPackages.callPackage ../.. {}).sops-install-secrets;
in pkgs.runCommand "checked-manifest.json" {
nativeBuildInputs = [ sops-install-secrets ];
} ''
sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} ${manifest}
cp ${manifest} $out
'';
manifestFor = suffix: secrets: extraJson: pkgs.writeTextFile {
name = "manifest${suffix}.json";
text = builtins.toJSON ({
secrets = builtins.attrValues secrets;
# Does this need to be configurable?
secretsMountPoint = "/run/secrets.d";
symlinkPath = "/run/secrets";
gnupgHome = cfg.gnupg.home;
sshKeyPaths = cfg.gnupg.sshKeyPaths;
ageKeyFile = cfg.age.keyFile;
ageSshKeyPaths = cfg.age.sshKeyPaths;
logging = {
keyImport = builtins.elem "keyImport" cfg.log;
secretChanges = builtins.elem "secretChanges" cfg.log;
};
} // extraJson);
checkPhase = ''
${sops-install-secrets}/bin/sops-install-secrets -check-mode=${if cfg.validateSopsFiles then "sopsfile" else "manifest"} "$out"
'';
};
manifest = manifestFor "" regularSecrets {};
manifestForUsers = manifestFor "-for-users" secretsForUsers {
secretsMountPoint = "/run/secrets.d/users";
symlinkPath = "/run/secrets-for-users";
};
in {
options.sops = {
secrets = mkOption {
@ -214,6 +232,9 @@ in {
} {
assertion = !(cfg.gnupg.home != null && cfg.gnupg.sshKeyPaths != []);
message = "Exactly one of sops.gnupg.home and sops.gnupg.sshKeyPaths must be set";
} {
assertion = (filterAttrs (_: v: v.owner != "root" || v.group != "root") secretsForUsers) == {};
message = "neededForUsers cannot be used for secrets that are not root-owned";
}] ++ optionals cfg.validateSopsFiles (
concatLists (mapAttrsToList (name: secret: [{
assertion = builtins.pathExists secret.sopsFile;
@ -226,22 +247,33 @@ in {
}]) cfg.secrets)
);
system.activationScripts.setup-secrets = let
sops-install-secrets = (pkgs.callPackage ../.. {}).sops-install-secrets;
in (stringAfter ([ "specialfs" "users" "groups" ] ++ optional cfg.age.generateKey "generate-age-key") ''
[ -e /run/current-system ] || echo setting up secrets...
${optionalString (cfg.gnupg.home != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets ${checkedManifest}
'') // lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true;
};
system.activationScripts = {
setupSecretsForUsers = mkIf (secretsForUsers != {}) (stringAfter ([ "specialfs" ] ++ optional cfg.age.generateKey "generate-age-key") ''
[ -e /run/current-system ] || echo setting up secrets for users...
${optionalString (cfg.gnupg.home != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets -ignore-passwd ${manifestForUsers}
'' // lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true;
});
system.activationScripts.generate-age-key = (mkIf cfg.age.generateKey) (stringAfter [] ''
if [[ ! -f '${cfg.age.keyFile}' ]]; then
echo generating machine-specific age key...
mkdir -p $(dirname ${cfg.age.keyFile})
# age-keygen sets 0600 by default, no need to chmod.
${pkgs.age}/bin/age-keygen -o ${cfg.age.keyFile}
fi
'');
users = mkIf (secretsForUsers != {}) {
deps = [ "setupSecretsForUsers" ];
};
setupSecrets = mkIf (regularSecrets != {}) (stringAfter ([ "specialfs" "users" "groups" ] ++ optional cfg.age.generateKey "generate-age-key") ''
[ -e /run/current-system ] || echo setting up secrets...
${optionalString (cfg.gnupg.home != null) "SOPS_GPG_EXEC=${pkgs.gnupg}/bin/gpg"} ${sops-install-secrets}/bin/sops-install-secrets ${manifest}
'' // lib.optionalAttrs (config.system ? dryActivationScript) {
supportsDryActivation = true;
});
generate-age-key = mkIf (cfg.age.generateKey) (stringAfter [] ''
if [[ ! -f '${cfg.age.keyFile}' ]]; then
echo generating machine-specific age key...
mkdir -p $(dirname ${cfg.age.keyFile})
# age-keygen sets 0600 by default, no need to chmod.
${pkgs.age}/bin/age-keygen -o ${cfg.age.keyFile}
fi
'');
};
};
}