clan-cli: tests/age_keys.py add notes, move function to check sops recipients
This supports the new integration test for `clan machines delete`.
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from clan_cli.secrets.folders import sops_secrets_folder
|
||||||
from helpers import cli
|
from helpers import cli
|
||||||
|
|
||||||
|
|
||||||
@@ -12,13 +14,26 @@ class KeyPair:
|
|||||||
|
|
||||||
|
|
||||||
class SopsSetup:
|
class SopsSetup:
|
||||||
|
"""Hold a list of three 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
|
||||||
|
be used to add machines or other users.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, keys: list[KeyPair]) -> None:
|
def __init__(self, keys: list[KeyPair]) -> None:
|
||||||
self.keys = keys
|
self.keys = keys
|
||||||
|
self.user = os.environ.get("USER", "admin")
|
||||||
|
|
||||||
|
# louis@(2025-03-10): It is odd to have to call an init function on a
|
||||||
|
# fixture: the fixture should already be initialized when it is received in
|
||||||
|
# the test function. Maybe we can arrange for the `flake` fixtures, to take
|
||||||
|
# the `sops_setup` fixture as input and call its `init` function on the
|
||||||
|
# correct path.
|
||||||
def init(self, flake_path: Path | None = None) -> None:
|
def init(self, flake_path: Path | None = None) -> None:
|
||||||
if flake_path is None:
|
if flake_path is None:
|
||||||
flake_path = Path.cwd()
|
flake_path = Path.cwd()
|
||||||
self.user = os.environ.get("USER", "user")
|
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
"vars",
|
"vars",
|
||||||
@@ -49,9 +64,6 @@ KEYS = [
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def age_keys() -> list[KeyPair]:
|
def age_keys() -> list[KeyPair]:
|
||||||
"""
|
|
||||||
Root directory of the tests
|
|
||||||
"""
|
|
||||||
return KEYS
|
return KEYS
|
||||||
|
|
||||||
|
|
||||||
@@ -59,8 +71,33 @@ def age_keys() -> list[KeyPair]:
|
|||||||
def sops_setup(
|
def sops_setup(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
) -> SopsSetup:
|
) -> SopsSetup:
|
||||||
"""
|
|
||||||
Root directory of the tests
|
|
||||||
"""
|
|
||||||
monkeypatch.setenv("SOPS_AGE_KEY", KEYS[0].privkey)
|
monkeypatch.setenv("SOPS_AGE_KEY", KEYS[0].privkey)
|
||||||
return SopsSetup(KEYS)
|
return SopsSetup(KEYS)
|
||||||
|
|
||||||
|
|
||||||
|
# louis@(2025-03-10): right now this is specific to the `sops/secrets` folder,
|
||||||
|
# but we could make it generic to any sops file if the need arises.
|
||||||
|
def assert_secrets_file_recipients(
|
||||||
|
flake_path: Path,
|
||||||
|
secret_name: str,
|
||||||
|
expected_age_recipients_keypairs: list["KeyPair"],
|
||||||
|
err_msg: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Checks that the recipients of a secrets file matches expectations.
|
||||||
|
|
||||||
|
This looks up the `secret` file for `secret_name` in the `sops` directory
|
||||||
|
under `flake_path`.
|
||||||
|
|
||||||
|
:param err_msg: in case of failure, if you gave an error message then it
|
||||||
|
will be displayed, otherwise pytest will display the two different sets
|
||||||
|
of recipients.
|
||||||
|
"""
|
||||||
|
sops_file = sops_secrets_folder(flake_path) / secret_name / "secret"
|
||||||
|
with sops_file.open("rb") as fp:
|
||||||
|
sops_data = json.load(fp)
|
||||||
|
age_recipients = {each["recipient"] for each in sops_data["sops"]["age"]}
|
||||||
|
expected_age_recipients = {pair.pubkey for pair in expected_age_recipients_keypairs}
|
||||||
|
if not err_msg:
|
||||||
|
assert age_recipients == expected_age_recipients
|
||||||
|
return
|
||||||
|
assert age_recipients == expected_age_recipients, err_msg
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ from pathlib import Path
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from age_keys import assert_secrets_file_recipients
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.secrets.folders import sops_secrets_folder
|
|
||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest
|
||||||
from helpers import cli
|
from helpers import cli
|
||||||
from stdout import CaptureOutput
|
from stdout import CaptureOutput
|
||||||
@@ -91,7 +91,7 @@ def _test_identities(
|
|||||||
test_secret_name,
|
test_secret_name,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
test_secret_name,
|
test_secret_name,
|
||||||
expected_age_recipients_keypairs=[age_keys[0], admin_age_key],
|
expected_age_recipients_keypairs=[age_keys[0], admin_age_key],
|
||||||
@@ -111,7 +111,7 @@ def _test_identities(
|
|||||||
age_keys[1].privkey,
|
age_keys[1].privkey,
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
test_secret_name,
|
test_secret_name,
|
||||||
expected_age_recipients_keypairs=[age_keys[1], admin_age_key],
|
expected_age_recipients_keypairs=[age_keys[1], admin_age_key],
|
||||||
@@ -302,7 +302,7 @@ def test_groups(
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[
|
expected_age_recipients_keypairs=[
|
||||||
@@ -327,7 +327,7 @@ def test_groups(
|
|||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
||||||
@@ -349,7 +349,7 @@ def test_groups(
|
|||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[
|
expected_age_recipients_keypairs=[
|
||||||
@@ -370,7 +370,7 @@ def test_groups(
|
|||||||
"user1",
|
"user1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
expected_age_recipients_keypairs=[machine1_age_key, admin_age_key],
|
||||||
@@ -391,7 +391,7 @@ def test_groups(
|
|||||||
"machine1",
|
"machine1",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
assert_sops_file_recipients(
|
assert_secrets_file_recipients(
|
||||||
test_flake.path,
|
test_flake.path,
|
||||||
secret_name,
|
secret_name,
|
||||||
expected_age_recipients_keypairs=[admin_age_key],
|
expected_age_recipients_keypairs=[admin_age_key],
|
||||||
@@ -413,29 +413,6 @@ def test_groups(
|
|||||||
assert not group_symlink.exists(follow_symlinks=False), err_msg
|
assert not group_symlink.exists(follow_symlinks=False), err_msg
|
||||||
|
|
||||||
|
|
||||||
def assert_sops_file_recipients(
|
|
||||||
flake_path: Path,
|
|
||||||
secret_name: str,
|
|
||||||
expected_age_recipients_keypairs: list["KeyPair"],
|
|
||||||
err_msg: str | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""Checks that the recipients of a SOPS file matches expectations.
|
|
||||||
|
|
||||||
:param err_msg: in case of failure, if you gave an error message then it
|
|
||||||
will be displayed, otherwise pytest will display the two different sets
|
|
||||||
of recipients.
|
|
||||||
"""
|
|
||||||
sops_file = sops_secrets_folder(flake_path) / secret_name / "secret"
|
|
||||||
with sops_file.open("rb") as fp:
|
|
||||||
sops_data = json.load(fp)
|
|
||||||
age_recipients = {each["recipient"] for each in sops_data["sops"]["age"]}
|
|
||||||
expected_age_recipients = {pair.pubkey for pair in expected_age_recipients_keypairs}
|
|
||||||
if not err_msg:
|
|
||||||
assert age_recipients == expected_age_recipients
|
|
||||||
return
|
|
||||||
assert age_recipients == expected_age_recipients, err_msg
|
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def use_age_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
def use_age_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
|
||||||
old_key = os.environ["SOPS_AGE_KEY_FILE"]
|
old_key = os.environ["SOPS_AGE_KEY_FILE"]
|
||||||
|
|||||||
Reference in New Issue
Block a user