diff --git a/pkgs/clan-cli/clan_cli/secrets/key.py b/pkgs/clan-cli/clan_cli/secrets/key.py index c57b12c42..bb2df9a7c 100644 --- a/pkgs/clan-cli/clan_cli/secrets/key.py +++ b/pkgs/clan-cli/clan_cli/secrets/key.py @@ -13,22 +13,25 @@ from .sops import ( default_admin_private_key_path, generate_private_key, load_age_plugins, - maybe_get_admin_public_keys, ) log = logging.getLogger(__name__) def generate_key() -> sops.SopsKey: - keys = maybe_get_admin_public_keys() - if keys is not None: - key = keys[0] - print(f"{key.key_type.name} key {key.pubkey} is already set", file=sys.stderr) - return key + """ + Generate a new age key and return it as a SopsKey. + + This function does not check if the key already exists. + It will generate a new key every time it is called. + + Use 'check_key_exists' to check if a key already exists. + Before calling this function if you dont want to generate a new key. + """ path = default_admin_private_key_path() _, pub_key = generate_private_key(out_file=path) - print( + log.info( f"Generated age private key at '{path}' for your user.\nPlease back it up on a secure location or you will lose access to your secrets." ) return sops.SopsKey( @@ -37,13 +40,21 @@ 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:", file=sys.stderr) - print( - f"clan secrets users add --{key_type}-key {key.pubkey}", - file=sys.stderr, - ) + pub_keys = sops.maybe_get_admin_public_keys() + if not pub_keys or args.new: + key = generate_key() + pub_keys = [key] + + for key in pub_keys: + key_type = key.key_type.name.lower() + print(f"{key.key_type.name} key {key.pubkey} is already set", file=sys.stderr) + 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: @@ -89,6 +100,14 @@ def register_key_parser(parser: argparse.ArgumentParser) -> None: "`clan secrets users add --help'" ), ) + parser_generate.add_argument( + "--new", + help=( + "Generate a new key, without checking if a key already exists. " + " This will not overwrite an existing key." + ), + action="store_true", + ) parser_generate.set_defaults(func=generate_command) parser_show = subparser.add_parser("show", help="show public key") 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 2fac4f9ca..084101b0e 100644 --- a/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py +++ b/pkgs/clan-cli/clan_cli/tests/test_secrets_cli.py @@ -7,8 +7,10 @@ import string from collections.abc import Iterator from contextlib import contextmanager from typing import TYPE_CHECKING +from unittest.mock import patch import pytest +from clan_cli.secrets.key import generate_private_key from clan_cli.tests.age_keys import assert_secrets_file_recipients from clan_cli.tests.fixtures_flakes import FlakeForTest from clan_cli.tests.gpg_keys import GpgKey @@ -629,11 +631,14 @@ def test_secrets( monkeypatch.setenv( "SOPS_AGE_KEY_FILE", str(test_flake_with_core.path / ".." / "age.key") ) - with capture_output as output: + with patch( + "clan_cli.secrets.key.generate_private_key", wraps=generate_private_key + ) as spy: cli.run( ["secrets", "key", "generate", "--flake", str(test_flake_with_core.path)] ) - assert "age private key" in output.out + assert spy.call_count == 1 + # Read the key that was generated with capture_output as output: cli.run(["secrets", "key", "show", "--flake", str(test_flake_with_core.path)]) @@ -971,7 +976,12 @@ def test_secrets_key_generate_gpg( with use_gpg_key(gpg_key, monkeypatch): # Make sure clan secrets key generate recognizes # the PGP key and does nothing: - with capture_output as output: + with ( + capture_output as output, + patch( + "clan_cli.secrets.key.generate_private_key", wraps=generate_private_key + ) as spy_sops, + ): cli.run( [ "secrets", @@ -981,7 +991,8 @@ def test_secrets_key_generate_gpg( str(test_flake_with_core.path), ] ) - assert "age private key" not in output.out + assert spy_sops.call_count == 0 + # assert "age private key" not in output.out assert re.match(r"PGP key.+is already set", output.err), ( f"expected /PGP key.+is already set/ =~ {output.err}"