From d91f653a652da9bf0eeaf1c5e82cf9db61348d5a Mon Sep 17 00:00:00 2001 From: lassulus Date: Wed, 18 Dec 2024 15:09:20 +0100 Subject: [PATCH] vars: allow setting files as needed for activation --- docs/site/manual/secrets.md | 4 ++-- nixosModules/clanCore/vars/default.nix | 2 +- nixosModules/clanCore/vars/interface.nix | 10 +++++++--- .../clanCore/vars/secret/password-store.nix | 9 +++++++-- nixosModules/clanCore/vars/secret/sops/default.nix | 7 +++++-- nixosModules/clanCore/vars/secret/sops/funcs.nix | 7 +++++-- .../clan_cli/vars/secret_modules/password_store.py | 13 +++++++++++-- pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py | 10 ++++++++++ pkgs/clan-cli/clan_cli/vars/var.py | 4 ++-- 9 files changed, 50 insertions(+), 16 deletions(-) diff --git a/docs/site/manual/secrets.md b/docs/site/manual/secrets.md index 1632e402b..b42559400 100644 --- a/docs/site/manual/secrets.md +++ b/docs/site/manual/secrets.md @@ -23,12 +23,12 @@ clan secrets list A NixOS machine will automatically import all secrets that are encrypted for the current machine. At runtime it will use the host key to decrypt all secrets into -an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix). +an in-memory, non-persistent filesystem using [sops-nix](https://github.com/Mic92/sops-nix). In your nixos configuration you can get a path to secrets like this `config.sops.secrets..path`. For example: ```nix { config, ...}: { - sops.secrets.my-password.neededForUsers = true; + sops.secrets.my-password.neededFor = "users"; users.users.mic92 = { isNormalUser = true; diff --git a/nixosModules/clanCore/vars/default.nix b/nixosModules/clanCore/vars/default.nix index 0c8042e7b..92c29d893 100644 --- a/nixosModules/clanCore/vars/default.nix +++ b/nixosModules/clanCore/vars/default.nix @@ -55,7 +55,7 @@ in mode deploy secret - neededForUsers + neededFor ; } ); diff --git a/nixosModules/clanCore/vars/interface.nix b/nixosModules/clanCore/vars/interface.nix index 64532dae0..7204f9a67 100644 --- a/nixosModules/clanCore/vars/interface.nix +++ b/nixosModules/clanCore/vars/interface.nix @@ -196,14 +196,18 @@ in ''; type = str; }; - neededForUsers = lib.mkOption { + neededFor = lib.mkOption { description = '' 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. Setting this option moves the secret to /run/secrets-for-users and disallows setting owner and group to anything else than root. ''; - type = bool; - default = false; + type = lib.types.enum [ + "activation" + "users" + "services" + ]; + default = "services"; }; owner = lib.mkOption { description = "The user name or id that will own the file."; diff --git a/nixosModules/clanCore/vars/secret/password-store.nix b/nixosModules/clanCore/vars/secret/password-store.nix index a992d2511..c544798ed 100644 --- a/nixosModules/clanCore/vars/secret/password-store.nix +++ b/nixosModules/clanCore/vars/secret/password-store.nix @@ -69,10 +69,15 @@ in file: lib.mkIf file.config.secret { path = - if file.config.neededForUsers then + if file.config.neededFor == "users" then "/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 - "/run/secrets/${file.config.generatorName}/${file.config.name}"; + throw "unknown neededFor ${file.config.neededFor}"; + }; secretModule = "clan_cli.vars.secret_modules.password_store"; }; diff --git a/nixosModules/clanCore/vars/secret/sops/default.nix b/nixosModules/clanCore/vars/secret/sops/default.nix index dd791e531..8d9c00e30 100644 --- a/nixosModules/clanCore/vars/secret/sops/default.nix +++ b/nixosModules/clanCore/vars/secret/sops/default.nix @@ -27,8 +27,11 @@ in # Before we generate a secret we cannot know the path yet, so we need to set it to an empty string fileModule = file: { path = lib.mkIf file.config.secret ( - config.sops.secrets.${"vars/${file.config.generatorName}/${file.config.name}"}.path - or "/no-such-path" + 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 + or "/no-such-path" ); }; secretModule = "clan_cli.vars.secret_modules.sops"; diff --git a/nixosModules/clanCore/vars/secret/sops/funcs.nix b/nixosModules/clanCore/vars/secret/sops/funcs.nix index 2d633f0f1..63f06ab23 100644 --- a/nixosModules/clanCore/vars/secret/sops/funcs.nix +++ b/nixosModules/clanCore/vars/secret/sops/funcs.nix @@ -16,7 +16,9 @@ in collectFiles = vars: 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 ( flip mapAttrsToList vars.generators ( gen_name: generator: @@ -24,8 +26,9 @@ in fname: file: { name = fname; generator = gen_name; + neededForUsers = file.neededFor == "users"; inherit (generator) share; - inherit (file) owner group neededForUsers; + inherit (file) owner group; } ) ) diff --git a/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py b/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py index d2c60aebc..3c4d0096f 100644 --- a/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py +++ b/pkgs/clan-cli/clan_cli/vars/secret_modules/password_store.py @@ -160,11 +160,20 @@ class SecretStore(StoreBase): for generator in self.machine.vars_generators: dir_exists = False 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: continue if not file.secret: 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.type = tarfile.DIRTYPE tar_dir.mode = 0o511 @@ -176,7 +185,7 @@ class SecretStore(StoreBase): tar_file.mode = file.mode tar_file.uname = file.owner 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)) else: tar.addfile(tarinfo=tar_file, fileobj=io.BytesIO(content)) diff --git a/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py b/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py index 5b1b8182f..e1aa19937 100644 --- a/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py +++ b/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py @@ -173,6 +173,16 @@ class SecretStore(StoreBase): sops_secrets_folder(self.machine.flake_dir) / key_name, ) (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: with TemporaryDirectory(prefix="sops-upload-") as tempdir: diff --git a/pkgs/clan-cli/clan_cli/vars/var.py b/pkgs/clan-cli/clan_cli/vars/var.py index f6030da53..427a3a72d 100644 --- a/pkgs/clan-cli/clan_cli/vars/var.py +++ b/pkgs/clan-cli/clan_cli/vars/var.py @@ -17,7 +17,7 @@ class Var: owner: str = "root" group: str = "root" mode: int = 0o400 - needed_for_users: bool = False + needed_for: str = "services" # TODO: those shouldn't be set here _store: "StoreBase | None" = None @@ -78,5 +78,5 @@ class Var: owner=data.get("owner", "root"), group=data.get("group", "root"), mode=int(data.get("mode", "400"), 8), - needed_for_users=data.get("neededForUsers", False), + needed_for=data.get("neededFor", "services"), )