feat(clan-cli): support multiple keys for a user
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import dataclasses
|
||||
import json
|
||||
import os
|
||||
from collections.abc import Iterable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
@@ -7,18 +9,18 @@ from clan_cli.secrets.folders import sops_secrets_folder
|
||||
from clan_cli.tests.helpers import cli
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class KeyPair:
|
||||
def __init__(self, pubkey: str, privkey: str) -> None:
|
||||
self.pubkey = pubkey
|
||||
self.privkey = privkey
|
||||
pubkey: str
|
||||
privkey: str
|
||||
|
||||
|
||||
class SopsSetup:
|
||||
"""Hold a list of three key pairs and create an "admin" user in the clan.
|
||||
"""Hold a list of key pairs and create an "admin" user in the clan.
|
||||
|
||||
The first key in the list is used as the admin key and
|
||||
the private part of the key is exposed in the
|
||||
`SOPS_AGE_KEY` environment variable, the two others can
|
||||
`SOPS_AGE_KEY` environment variable, the others can
|
||||
be used to add machines or other users.
|
||||
"""
|
||||
|
||||
@@ -52,6 +54,14 @@ KEYS = [
|
||||
"age1dhuh9xtefhgpr2sjjf7gmp9q2pr37z92rv4wsadxuqdx48989g7qj552qp",
|
||||
"AGE-SECRET-KEY-169N3FT32VNYQ9WYJMLUSVTMA0TTZGVJF7YZWS8AHTWJ5RR9VGR7QCD8SKF",
|
||||
),
|
||||
KeyPair(
|
||||
"age1n58rxm8y6h9prmwn0qk7nggfsu9f9j4u35dxg7akpkjd5vgsavssfzmq9y",
|
||||
"AGE-SECRET-KEY-1YU2JVE445KT6S8UN3403NHH6EZU404RMEH9RTME9SPWXWMLJS0LQM5NWM7",
|
||||
),
|
||||
KeyPair(
|
||||
"age1eyyhln9g3cdwtrwpckugvqgtf5p8ugt0426sw38ra3wkc0t4rfhslq7txv",
|
||||
"AGE-SECRET-KEY-1567QKA63Y9P62SHF5TCHVCT5GZX2LZ8NS0E9RKA2QHDA662SF5LQ2VJJYX",
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@@ -73,7 +83,7 @@ def sops_setup(
|
||||
def assert_secrets_file_recipients(
|
||||
flake_path: Path,
|
||||
secret_name: str,
|
||||
expected_age_recipients_keypairs: list["KeyPair"],
|
||||
expected_age_recipients_keypairs: Iterable["KeyPair"],
|
||||
err_msg: str | None = None,
|
||||
) -> None:
|
||||
"""Checks that the recipients of a secrets file matches expectations.
|
||||
|
||||
@@ -59,7 +59,7 @@ def test_machine_delete(
|
||||
) -> None:
|
||||
flake = flake_with_sops
|
||||
|
||||
admin_key, machine_key, machine2_key = sops_setup.keys
|
||||
admin_key, machine_key, machine2_key, *xs = sops_setup.keys
|
||||
|
||||
# create a couple machines with their keys
|
||||
for name, key in (("my-machine", machine_key), ("my-machine2", machine2_key)):
|
||||
|
||||
@@ -159,6 +159,86 @@ def test_users(
|
||||
) -> None:
|
||||
_test_identities("users", test_flake, capture_output, age_keys, monkeypatch)
|
||||
|
||||
# some additional user-specific tests
|
||||
|
||||
admin_key = age_keys[2]
|
||||
sops_folder = test_flake.path / "sops"
|
||||
|
||||
user_keys = {
|
||||
"bob": [age_keys[0], age_keys[1]],
|
||||
"alice": [age_keys[2]],
|
||||
"charlie": [age_keys[3], age_keys[4]],
|
||||
}
|
||||
|
||||
for user, keys in user_keys.items():
|
||||
key_args = [f"--age-key={key.pubkey}" for key in keys]
|
||||
|
||||
# add the user keys
|
||||
cli.run(
|
||||
[
|
||||
"secrets",
|
||||
"users",
|
||||
"add",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
user,
|
||||
*key_args,
|
||||
]
|
||||
)
|
||||
assert (sops_folder / "users" / user / "key.json").exists()
|
||||
|
||||
# check they are returned in get
|
||||
with capture_output as output:
|
||||
cli.run(["secrets", "users", "get", "--flake", str(test_flake.path), user])
|
||||
|
||||
for key in keys:
|
||||
assert key.pubkey in output.out
|
||||
|
||||
# set a secret
|
||||
secret_name = f"{user}_secret"
|
||||
cli.run(
|
||||
[
|
||||
"secrets",
|
||||
"set",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
"--user",
|
||||
user,
|
||||
secret_name,
|
||||
]
|
||||
)
|
||||
|
||||
# check the secret has each of our user's keys as a recipient
|
||||
# in addition the admin key should be there
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[admin_key, *keys],
|
||||
)
|
||||
|
||||
if len(keys) == 1:
|
||||
continue
|
||||
|
||||
# remove one of the keys
|
||||
cli.run(
|
||||
[
|
||||
"secrets",
|
||||
"users",
|
||||
"remove-key",
|
||||
"--flake",
|
||||
str(test_flake.path),
|
||||
user,
|
||||
keys[0].pubkey,
|
||||
]
|
||||
)
|
||||
|
||||
# check the secret has been updated
|
||||
assert_secrets_file_recipients(
|
||||
test_flake.path,
|
||||
secret_name,
|
||||
expected_age_recipients_keypairs=[admin_key, *keys[1:]],
|
||||
)
|
||||
|
||||
|
||||
def test_machines(
|
||||
test_flake: FlakeForTest,
|
||||
@@ -786,7 +866,10 @@ def test_secrets_key_generate_gpg(
|
||||
"testuser",
|
||||
]
|
||||
)
|
||||
key = json.loads(output.out)
|
||||
keys = json.loads(output.out)
|
||||
assert len(keys) == 1
|
||||
|
||||
key = keys[0]
|
||||
assert key["type"] == "pgp"
|
||||
assert key["publickey"] == gpg_key.fingerprint
|
||||
|
||||
|
||||
Reference in New Issue
Block a user