From ba1ad5bd43902beda56585a442a943030ca9753a Mon Sep 17 00:00:00 2001 From: DavHau Date: Tue, 8 Apr 2025 20:18:11 +0700 Subject: [PATCH] sops: prioritize SOPS_AGE_KEY_FILE over local key ... instead of loading both keys and raise an error This is important for testing when one wants to override SOPS_AGE_KEY_FILE New prio: `SOPS_AGE_KEY` > `SOPS_AGE_KEY_FILE` > `~/.config/sops/age/keys.txt` --- pkgs/clan-cli/clan_cli/secrets/key.py | 9 ++++-- pkgs/clan-cli/clan_cli/secrets/sops.py | 28 ++++++++++--------- .../clan_cli/tests/test_secrets_cli.py | 2 +- 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/secrets/key.py b/pkgs/clan-cli/clan_cli/secrets/key.py index b8aff5670..a6a737836 100644 --- a/pkgs/clan-cli/clan_cli/secrets/key.py +++ b/pkgs/clan-cli/clan_cli/secrets/key.py @@ -20,7 +20,7 @@ log = logging.getLogger(__name__) def generate_key() -> sops.SopsKey: key = maybe_get_admin_public_key() if key is not None: - print(f"{key.key_type.name} key {key.pubkey} is already set") + print(f"{key.key_type.name} key {key.pubkey} is already set", file=sys.stderr) return key path = default_admin_private_key_path() @@ -34,8 +34,11 @@ def generate_key() -> sops.SopsKey: def generate_command(args: argparse.Namespace) -> None: key = generate_key() key_type = key.key_type.name.lower() - print(f"Add your {key_type} public key to the repository with:") - print(f"clan secrets users add --{key_type}-key {key.pubkey}") + print(f"Add your {key_type} public key to the repository with:", file=sys.stderr) + print( + f"clan secrets users add --{key_type}-key {key.pubkey}", + file=sys.stderr, + ) def show_command(args: argparse.Namespace) -> None: diff --git a/pkgs/clan-cli/clan_cli/secrets/sops.py b/pkgs/clan-cli/clan_cli/secrets/sops.py index 25f6298d7..6da0bdb3a 100644 --- a/pkgs/clan-cli/clan_cli/secrets/sops.py +++ b/pkgs/clan-cli/clan_cli/secrets/sops.py @@ -50,17 +50,6 @@ class KeyType(enum.Enum): keyring: list[str] = [] if self == self.AGE: - if keys := os.environ.get("SOPS_AGE_KEY"): - # SOPS_AGE_KEY is fed into age.ParseIdentities by Sops, and - # reads identities line by line. See age/keysource.go in - # Sops, and age/parse.go in Age. - for private_key in keys.strip().splitlines(): - public_key = get_public_age_key(private_key) - log.info( - f"Found age public key from a private key " - f"in the environment (SOPS_AGE_KEY): {public_key}" - ) - keyring.append(public_key) def maybe_read_from_path(key_path: Path) -> None: try: @@ -78,10 +67,23 @@ class KeyType(enum.Enum): except Exception as ex: log.warning(f"Could not read age keys from {key_path}", exc_info=ex) + if keys := os.environ.get("SOPS_AGE_KEY"): + # SOPS_AGE_KEY is fed into age.ParseIdentities by Sops, and + # reads identities line by line. See age/keysource.go in + # Sops, and age/parse.go in Age. + for private_key in keys.strip().splitlines(): + public_key = get_public_age_key(private_key) + log.info( + f"Found age public key from a private key " + f"in the environment (SOPS_AGE_KEY): {public_key}" + ) + keyring.append(public_key) + # Sops will try every location, see age/keysource.go - if key_path := os.environ.get("SOPS_AGE_KEY_FILE"): + elif key_path := os.environ.get("SOPS_AGE_KEY_FILE"): maybe_read_from_path(Path(key_path)) - maybe_read_from_path(user_config_dir() / "sops/age/keys.txt") + else: + maybe_read_from_path(user_config_dir() / "sops/age/keys.txt") return keyring diff --git a/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py b/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py index 3789fe901..86233cb1b 100644 --- a/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py +++ b/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py @@ -754,7 +754,7 @@ def test_secrets_key_generate_gpg( ] ) assert "age private key" not in output.out - assert re.match(r"PGP key.+is already set", output.out) is not None + assert re.match(r"PGP key.+is already set", output.err) is not None with capture_output as output: cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])