diff --git a/pkgs/clan-cli/clan_cli/vars/keygen.py b/pkgs/clan-cli/clan_cli/vars/keygen.py index e9aee84e6..3d20f94d9 100644 --- a/pkgs/clan-cli/clan_cli/vars/keygen.py +++ b/pkgs/clan-cli/clan_cli/vars/keygen.py @@ -6,6 +6,7 @@ from clan_cli.clan_uri import FlakeId from clan_cli.errors import ClanError from clan_cli.secrets.key import generate_key from clan_cli.secrets.users import add_user +from clan_cli.vars.secret_modules.sops import SecretStore as SopsSecretStore log = logging.getLogger(__name__) @@ -16,9 +17,12 @@ def keygen(user: str | None, flake: FlakeId, force: bool) -> None: if not user: msg = "No user provided and $USER is not set. Please provide a user via --user." raise ClanError(msg) - pub_key = generate_key() + pub_key = SopsSecretStore.maybe_get_admin_public_key() + if not pub_key: + pub_key = generate_key() + # TODO set flake_dir=flake.path / "vars" add_user( - flake_dir=flake.path / "vars", + flake_dir=flake.path, name=user, key=pub_key, force=force, 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 f5d9535b9..761621674 100644 --- a/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py +++ b/pkgs/clan-cli/clan_cli/vars/secret_modules/sops.py @@ -99,13 +99,15 @@ class SecretStore(SecretStoreBase): with os.fdopen(fd, "w") as f: json.dump({"publickey": publickey, "type": "age"}, f, indent=2) - def default_admin_key_path(self) -> Path: + @staticmethod + def default_admin_key_path() -> Path: raw_path = os.environ.get("SOPS_AGE_KEY_FILE") if raw_path: return Path(raw_path) return user_config_dir() / "sops" / "age" / "keys.txt" - def get_public_key(self, privkey: str) -> str: + @staticmethod + def get_public_key(privkey: str) -> str: cmd = nix_shell(["nixpkgs#age"], ["age-keygen", "-y"]) try: res = subprocess.run( @@ -116,19 +118,20 @@ class SecretStore(SecretStoreBase): raise ClanError(msg) from e return res.stdout.strip() - def maybe_get_admin_public_key(self) -> str | None: + @classmethod + def maybe_get_admin_public_key(cls: type["SecretStore"]) -> str | None: key = os.environ.get("SOPS_AGE_KEY") if key: - return self.get_public_key(key) - path = self.default_admin_key_path() + return cls.get_public_key(key) + path = cls.default_admin_key_path() if path.exists(): - return self.get_public_key(path.read_text()) + return cls.get_public_key(path.read_text()) return None # TODO: get rid of `clan secrets generate` dependency def admin_key(self) -> SopsKey: - pub_key = self.maybe_get_admin_public_key() + pub_key = SecretStore.maybe_get_admin_public_key() if not pub_key: raise MissingKeyError return self.ensure_user_or_machine(pub_key) diff --git a/pkgs/clan-cli/tests/age_keys.py b/pkgs/clan-cli/tests/age_keys.py index cf90170f4..41c583b4a 100644 --- a/pkgs/clan-cli/tests/age_keys.py +++ b/pkgs/clan-cli/tests/age_keys.py @@ -21,13 +21,12 @@ class SopsSetup: self.user = os.environ.get("USER", "user") cli.run( [ - "secrets", - "users", - "add", + "vars", + "keygen", "--flake", str(flake_path), + "--user", self.user, - self.keys[0].pubkey, ] ) diff --git a/pkgs/clan-cli/tests/fixtures_flakes.py b/pkgs/clan-cli/tests/fixtures_flakes.py index 5d68481ac..28bd27260 100644 --- a/pkgs/clan-cli/tests/fixtures_flakes.py +++ b/pkgs/clan-cli/tests/fixtures_flakes.py @@ -11,7 +11,6 @@ from typing import NamedTuple import pytest from clan_cli.dirs import nixpkgs_source from fixture_error import FixtureError -from helpers import cli from root import CLAN_CORE log = logging.getLogger(__name__) @@ -51,9 +50,6 @@ class FlakeForTest(NamedTuple): path: Path -from age_keys import KEYS, KeyPair - - def set_machine_settings( flake: Path, machine_name: str, @@ -67,8 +63,6 @@ def set_machine_settings( def generate_flake( temporary_home: Path, flake_template: Path, - monkeypatch: pytest.MonkeyPatch, - sops_key: KeyPair = KEYS[0], substitutions: dict[str, str] | None = None, # define the machines directly including their config machine_configs: dict[str, dict] | None = None, @@ -146,20 +140,6 @@ def generate_flake( sp.run(["git", "config", "user.name", "clan-tool"], cwd=flake, check=True) sp.run(["git", "config", "user.email", "clan@example.com"], cwd=flake, check=True) sp.run(["git", "commit", "-a", "-m", "Initial commit"], cwd=flake, check=True) - monkeypatch.setenv("SOPS_AGE_KEY", sops_key.privkey) - cli.run( - [ - "secrets", - "users", - "add", - "user1", - sops_key.pubkey, - "--flake", - str(flake), - "--debug", - ] - ) - return FlakeForTest(flake) diff --git a/pkgs/clan-cli/tests/test_vars.py b/pkgs/clan-cli/tests/test_vars.py index 52e9a8b80..21d176fef 100644 --- a/pkgs/clan-cli/tests/test_vars.py +++ b/pkgs/clan-cli/tests/test_vars.py @@ -90,7 +90,6 @@ def test_generate_public_var( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) @@ -130,7 +129,6 @@ def test_generate_secret_var_sops( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -172,7 +170,6 @@ def test_generate_secret_var_sops_with_default_group( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -210,7 +207,6 @@ def test_generated_shared_secret_sops( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"machine1": m1_config, "machine2": m2_config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -253,7 +249,6 @@ def test_generate_secret_var_password_store( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) gnupghome = temporary_home / "gpg" @@ -324,7 +319,6 @@ def test_generate_secret_for_multiple_machines( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"machine1": machine1_config, "machine2": machine2_config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -370,7 +364,6 @@ def test_dependant_generators( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) @@ -410,7 +403,6 @@ def test_prompt( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) monkeypatch.setattr("sys.stdin", StringIO(input_value)) @@ -449,7 +441,6 @@ def test_share_flag( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -500,7 +491,6 @@ def test_prompt_create_file( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() @@ -529,7 +519,6 @@ def test_api_get_prompts( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) monkeypatch.setattr("sys.stdin", StringIO("input1")) @@ -558,7 +547,6 @@ def test_api_set_prompts( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) @@ -590,6 +578,7 @@ def test_api_set_prompts( def test_commit_message( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, + sops_setup: SopsSetup, ) -> None: config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] @@ -604,9 +593,9 @@ def test_commit_message( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) + sops_setup.init() cli.run( [ "vars", @@ -662,7 +651,6 @@ def test_default_value( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) # ensure evaluating the default value works without generating the value @@ -692,6 +680,7 @@ def test_stdout_of_generate( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, capture_output: CaptureOutput, + sops_setup: SopsSetup, ) -> None: config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] @@ -706,9 +695,9 @@ def test_stdout_of_generate( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) + sops_setup.init() from clan_cli.vars.generate import generate_vars_for_machine with capture_output as output: @@ -769,6 +758,7 @@ def test_stdout_of_generate( def test_migration_skip( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, + sops_setup: SopsSetup, ) -> None: config = nested_dict() my_service = config["clan"]["core"]["facts"]["services"]["my_service"] @@ -783,9 +773,9 @@ def test_migration_skip( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) + sops_setup.init() cli.run(["facts", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( @@ -799,6 +789,7 @@ def test_migration_skip( def test_migration( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, + sops_setup: SopsSetup, ) -> None: config = nested_dict() my_service = config["clan"]["core"]["facts"]["services"]["my_service"] @@ -812,9 +803,9 @@ def test_migration( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) + sops_setup.init() cli.run(["facts", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( @@ -845,10 +836,9 @@ def test_fails_when_files_are_left_from_other_backend( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, - monkeypatch=monkeypatch, ) - sops_setup.init() monkeypatch.chdir(flake.path) + sops_setup.init() for generator in ["my_secret_generator", "my_value_generator"]: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), @@ -876,13 +866,13 @@ def test_keygen( monkeypatch.chdir(temporary_home) cli.run(["vars", "keygen", "--flake", str(temporary_home), "--user", "user"]) # check public key exists - assert (temporary_home / "vars" / "sops" / "users" / "user").is_dir() + assert (temporary_home / "sops" / "users" / "user").is_dir() # check private key exists assert (temporary_home / ".config" / "sops" / "age" / "keys.txt").is_file() # it should still work, even if the keys already exist import shutil - shutil.rmtree(temporary_home / "vars" / "sops" / "users" / "user") + shutil.rmtree(temporary_home / "sops" / "users" / "user") cli.run(["vars", "keygen", "--flake", str(temporary_home), "--user", "user"]) # check public key exists - assert (temporary_home / "vars" / "sops" / "users" / "user").is_dir() + assert (temporary_home / "sops" / "users" / "user").is_dir() diff --git a/pkgs/clan-cli/tests/test_vars_deployment.py b/pkgs/clan-cli/tests/test_vars_deployment.py index 5cfb9d527..22296909b 100644 --- a/pkgs/clan-cli/tests/test_vars_deployment.py +++ b/pkgs/clan-cli/tests/test_vars_deployment.py @@ -60,7 +60,6 @@ def test_vm_deployment( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"m1_machine": machine1_config, "m2_machine": machine2_config}, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path) sops_setup.init() diff --git a/pkgs/clan-cli/tests/test_vms_cli.py b/pkgs/clan-cli/tests/test_vms_cli.py index 147d6bea3..7944fecf0 100644 --- a/pkgs/clan-cli/tests/test_vms_cli.py +++ b/pkgs/clan-cli/tests/test_vms_cli.py @@ -82,7 +82,6 @@ def test_vm_persistence( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs=config, - monkeypatch=monkeypatch, ) monkeypatch.chdir(flake.path)