Merge pull request 'Sops: generate key should always 'generate' a key pair when beeing called' (#4664) from sops-keys into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4664
This commit is contained in:
hsjobeki
2025-08-10 12:09:47 +00:00
2 changed files with 48 additions and 18 deletions

View File

@@ -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 <username> --{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 <username> --{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")

View File

@@ -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}"