Merge pull request 'vars: allow setting files as needed for activation' (#2633) from vars-needed_activation into main

This commit is contained in:
clan-bot
2024-12-19 12:26:58 +00:00
9 changed files with 50 additions and 16 deletions

View File

@@ -28,7 +28,7 @@ In your nixos configuration you can get a path to secrets like this `config.sops
```nix ```nix
{ config, ...}: { { config, ...}: {
sops.secrets.my-password.neededForUsers = true; sops.secrets.my-password.neededFor = "users";
users.users.mic92 = { users.users.mic92 = {
isNormalUser = true; isNormalUser = true;

View File

@@ -55,7 +55,7 @@ in
mode mode
deploy deploy
secret secret
neededForUsers neededFor
; ;
} }
); );

View File

@@ -196,14 +196,18 @@ in
''; '';
type = str; type = str;
}; };
neededForUsers = lib.mkOption { neededFor = lib.mkOption {
description = '' description = ''
Enabling this option causes the secret to be decrypted/installed before users and groups are created. Enabling this option causes the secret to be decrypted/installed before users and groups are created.
This can be used to retrieve user's passwords. This can be used to retrieve user's passwords.
Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root. Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root.
''; '';
type = bool; type = lib.types.enum [
default = false; "activation"
"users"
"services"
];
default = "services";
}; };
owner = lib.mkOption { owner = lib.mkOption {
description = "The user name or id that will own the file."; description = "The user name or id that will own the file.";

View File

@@ -69,10 +69,15 @@ in
file: file:
lib.mkIf file.config.secret { lib.mkIf file.config.secret {
path = path =
if file.config.neededForUsers then if file.config.neededFor == "users" then
"/run/user-secrets/${file.config.generatorName}/${file.config.name}" "/run/user-secrets/${file.config.generatorName}/${file.config.name}"
else if file.config.neededFor == "services" then
"/run/secrets/${file.config.generatorName}/${file.config.name}"
else if file.config.neededFor == "activation" then
"${config.clan.password-store.secretLocation}/${file.config.generatorName}/${file.config.name}"
else else
"/run/secrets/${file.config.generatorName}/${file.config.name}"; throw "unknown neededFor ${file.config.neededFor}";
}; };
secretModule = "clan_cli.vars.secret_modules.password_store"; secretModule = "clan_cli.vars.secret_modules.password_store";
}; };

View File

@@ -27,6 +27,9 @@ in
# Before we generate a secret we cannot know the path yet, so we need to set it to an empty string # Before we generate a secret we cannot know the path yet, so we need to set it to an empty string
fileModule = file: { fileModule = file: {
path = lib.mkIf file.config.secret ( path = lib.mkIf file.config.secret (
if file.config.neededFor == "activation" then
"/var/lib/sops-nix/${file.config.generatorName}/${file.config.name}"
else
config.sops.secrets.${"vars/${file.config.generatorName}/${file.config.name}"}.path config.sops.secrets.${"vars/${file.config.generatorName}/${file.config.name}"}.path
or "/no-such-path" or "/no-such-path"
); );

View File

@@ -16,7 +16,9 @@ in
collectFiles = collectFiles =
vars: vars:
let let
relevantFiles = generator: flip filterAttrs generator.files (_name: f: f.secret && f.deploy); relevantFiles =
generator:
flip filterAttrs generator.files (_name: f: f.secret && f.deploy && (f.neededFor != "activation"));
allFiles = flatten ( allFiles = flatten (
flip mapAttrsToList vars.generators ( flip mapAttrsToList vars.generators (
gen_name: generator: gen_name: generator:
@@ -24,8 +26,9 @@ in
fname: file: { fname: file: {
name = fname; name = fname;
generator = gen_name; generator = gen_name;
neededForUsers = file.neededFor == "users";
inherit (generator) share; inherit (generator) share;
inherit (file) owner group neededForUsers; inherit (file) owner group;
} }
) )
) )

View File

@@ -160,11 +160,20 @@ class SecretStore(StoreBase):
for generator in self.machine.vars_generators: for generator in self.machine.vars_generators:
dir_exists = False dir_exists = False
for file in generator.files: for file in generator.files:
if file.needed_for == "activation":
(output_dir / generator.name / file.name).parent.mkdir(
parents=True,
exist_ok=True,
)
(output_dir / generator.name / file.name).write_bytes(
self.get(generator, file.name)
)
continue
if not file.deploy: if not file.deploy:
continue continue
if not file.secret: if not file.secret:
continue continue
if not dir_exists and not file.needed_for_users: if not dir_exists and file.needed_for == "services":
tar_dir = tarfile.TarInfo(name=generator.name) tar_dir = tarfile.TarInfo(name=generator.name)
tar_dir.type = tarfile.DIRTYPE tar_dir.type = tarfile.DIRTYPE
tar_dir.mode = 0o511 tar_dir.mode = 0o511
@@ -176,7 +185,7 @@ class SecretStore(StoreBase):
tar_file.mode = file.mode tar_file.mode = file.mode
tar_file.uname = file.owner tar_file.uname = file.owner
tar_file.gname = file.group tar_file.gname = file.group
if file.needed_for_users: if file.needed_for == "users":
user_tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content)) user_tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content))
else: else:
tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content)) tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content))

View File

@@ -173,6 +173,16 @@ class SecretStore(StoreBase):
sops_secrets_folder(self.machine.flake_dir) / key_name, sops_secrets_folder(self.machine.flake_dir) / key_name,
) )
(output_dir / "key.txt").write_text(key) (output_dir / "key.txt").write_text(key)
for generator in self.machine.vars_generators:
for file in generator.files:
if file.needed_for == "activation":
(output_dir / generator.name / file.name).parent.mkdir(
parents=True,
exist_ok=True,
)
(output_dir / generator.name / file.name).write_bytes(
self.get(generator, file.name)
)
def upload(self) -> None: def upload(self) -> None:
with TemporaryDirectory(prefix="sops-upload-") as tempdir: with TemporaryDirectory(prefix="sops-upload-") as tempdir:

View File

@@ -17,7 +17,7 @@ class Var:
owner: str = "root" owner: str = "root"
group: str = "root" group: str = "root"
mode: int = 0o400 mode: int = 0o400
needed_for_users: bool = False needed_for: str = "services"
# TODO: those shouldn't be set here # TODO: those shouldn't be set here
_store: "StoreBase | None" = None _store: "StoreBase | None" = None
@@ -78,5 +78,5 @@ class Var:
owner=data.get("owner", "root"), owner=data.get("owner", "root"),
group=data.get("group", "root"), group=data.get("group", "root"),
mode=int(data.get("mode", "400"), 8), mode=int(data.get("mode", "400"), 8),
needed_for_users=data.get("neededForUsers", False), needed_for=data.get("neededFor", "services"),
) )