Update tests for clan secrets

This commit is contained in:
Louis Opter
2024-10-01 20:08:00 -07:00
committed by Mic92
parent ab46e3c1e2
commit 81f162d4e6

View File

@@ -1,8 +1,12 @@
import functools
import json import json
import logging import logging
import os import os
import re
import subprocess
from collections.abc import Iterator from collections.abc import Iterator
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import pytest import pytest
@@ -228,7 +232,7 @@ def test_groups(
@contextmanager @contextmanager
def use_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"]
monkeypatch.delenv("SOPS_AGE_KEY_FILE") monkeypatch.delenv("SOPS_AGE_KEY_FILE")
monkeypatch.setenv("SOPS_AGE_KEY", key) monkeypatch.setenv("SOPS_AGE_KEY", key)
@@ -239,29 +243,95 @@ def use_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
monkeypatch.setenv("SOPS_AGE_KEY_FILE", old_key) monkeypatch.setenv("SOPS_AGE_KEY_FILE", old_key)
@contextmanager
def use_gpg_key(key: str, monkeypatch: pytest.MonkeyPatch) -> Iterator[None]:
old_key_file = os.environ.get("SOPS_AGE_KEY_FILE")
old_key = os.environ.get("SOPS_AGE_KEY")
monkeypatch.delenv("SOPS_AGE_KEY_FILE", raising=False)
monkeypatch.delenv("SOPS_AGE_KEY", raising=False)
monkeypatch.setenv("SOPS_PGP_FP", key)
try:
yield
finally:
monkeypatch.delenv("SOPS_PGP_FP")
if old_key_file is not None:
monkeypatch.setenv("SOPS_AGE_KEY_FILE", old_key_file)
if old_key is not None:
monkeypatch.setenv("SOPS_AGE_KEY", old_key)
@pytest.fixture
def gpg_key(
tmp_path: Path,
monkeypatch: pytest.MonkeyPatch,
) -> str:
gpg_home = tmp_path / "gnupghome"
gpg_home.mkdir(mode=0o700)
gpg_environ = os.environ.copy()
gpg_environ["GNUPGHOME"] = str(gpg_home)
run = functools.partial(
subprocess.run,
encoding="utf-8",
check=True,
env=gpg_environ,
)
key_parameters = "\n".join(
(
"%no-protection",
"%transient-key",
"Key-Type: rsa",
"Key-Usage: cert encrypt",
"Name-Real: Foo Bar",
"Name-Comment: Test user",
"Name-Email: test@clan.lol",
"%commit",
)
)
run(["gpg", "--batch", "--quiet", "--generate-key"], input=key_parameters)
details = run(["gpg", "--list-keys", "--with-colons"], capture_output=True)
fingerprint = None
for line in details.stdout.strip().split(os.linesep):
if not line.startswith("fpr"):
continue
fingerprint = line.split(":")[9]
break
assert fingerprint is not None, "Could not generate test GPG key"
log.info(f"Created GPG key under {gpg_home}")
monkeypatch.setenv("GNUPGHOME", str(gpg_home))
return fingerprint
def test_secrets( def test_secrets(
test_flake: FlakeForTest, test_flake: FlakeForTest,
capture_output: CaptureOutput, capture_output: CaptureOutput,
monkeypatch: pytest.MonkeyPatch, monkeypatch: pytest.MonkeyPatch,
gpg_key: str,
age_keys: list["KeyPair"], age_keys: list["KeyPair"],
) -> None: ) -> None:
with capture_output as output: with capture_output as output:
cli.run(["secrets", "list", "--flake", str(test_flake.path)]) cli.run(["secrets", "list", "--flake", str(test_flake.path)])
assert output.out == "" assert output.out == ""
monkeypatch.setenv("SOPS_NIX_SECRET", "foo") # Generate a new key for the clan
monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key")) monkeypatch.setenv("SOPS_AGE_KEY_FILE", str(test_flake.path / ".." / "age.key"))
cli.run(["secrets", "key", "generate", "--flake", str(test_flake.path)]) with capture_output as output:
cli.run(["secrets", "key", "generate", "--flake", str(test_flake.path)])
assert "age private key" in output.out
# Read the key that was generated
with capture_output as output: with capture_output as output:
cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)]) cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])
key = json.loads(output.out)["key"] key = json.loads(output.out)["publickey"]
assert key.startswith("age1") assert key.startswith("age1")
# Add testuser with the key that was generated for the clan
cli.run( cli.run(
["secrets", "users", "add", "--flake", str(test_flake.path), "testuser", key] ["secrets", "users", "add", "--flake", str(test_flake.path), "testuser", key]
) )
with pytest.raises(ClanError): # does not exist yet with pytest.raises(ClanError): # does not exist yet
cli.run(["secrets", "get", "--flake", str(test_flake.path), "nonexisting"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "nonexisting"])
monkeypatch.setenv("SOPS_NIX_SECRET", "foo")
cli.run(["secrets", "set", "--flake", str(test_flake.path), "initialkey"]) cli.run(["secrets", "set", "--flake", str(test_flake.path), "initialkey"])
with capture_output as output: with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "initialkey"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "initialkey"])
@@ -290,6 +360,8 @@ def test_secrets(
cli.run(["secrets", "list", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "list", "--flake", str(test_flake.path), "key"])
assert output.out == "key\n" assert output.out == "key\n"
# using the `age_keys` KeyPair, add a machine and rotate its key
cli.run( cli.run(
[ [
"secrets", "secrets",
@@ -316,7 +388,7 @@ def test_secrets(
cli.run(["secrets", "machines", "list", "--flake", str(test_flake.path)]) cli.run(["secrets", "machines", "list", "--flake", str(test_flake.path)])
assert output.out == "machine1\n" assert output.out == "machine1\n"
with use_key(age_keys[1].privkey, monkeypatch): with use_age_key(age_keys[1].privkey, monkeypatch):
with capture_output as output: with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
assert output.out == "foo" assert output.out == "foo"
@@ -336,7 +408,7 @@ def test_secrets(
) )
# should also rotate the encrypted secret # should also rotate the encrypted secret
with use_key(age_keys[0].privkey, monkeypatch): with use_age_key(age_keys[0].privkey, monkeypatch):
with capture_output as output: with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
assert output.out == "foo" assert output.out == "foo"
@@ -375,7 +447,7 @@ def test_secrets(
"key", "key",
] ]
) )
with capture_output as output, use_key(age_keys[1].privkey, monkeypatch): with capture_output as output, use_age_key(age_keys[1].privkey, monkeypatch):
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
assert output.out == "foo" assert output.out == "foo"
cli.run( cli.run(
@@ -448,12 +520,12 @@ def test_secrets(
] ]
) )
with use_key(age_keys[1].privkey, monkeypatch): with use_age_key(age_keys[1].privkey, monkeypatch):
with capture_output as output: with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
assert output.out == "foo" assert output.out == "foo"
# extend group will update secrets # Add an user with a GPG key
cli.run( cli.run(
[ [
"secrets", "secrets",
@@ -461,10 +533,13 @@ def test_secrets(
"add", "add",
"--flake", "--flake",
str(test_flake.path), str(test_flake.path),
"--pgp-key",
gpg_key,
"user2", "user2",
age_keys[2].pubkey,
] ]
) )
# Extend group will update secrets
cli.run( cli.run(
[ [
"secrets", "secrets",
@@ -477,7 +552,7 @@ def test_secrets(
] ]
) )
with use_key(age_keys[2].privkey, monkeypatch): # user2 with use_gpg_key(gpg_key, monkeypatch): # user2
with capture_output as output: with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"]) cli.run(["secrets", "get", "--flake", str(test_flake.path), "key"])
assert output.out == "foo" assert output.out == "foo"
@@ -495,7 +570,7 @@ def test_secrets(
) )
with ( with (
pytest.raises(ClanError), pytest.raises(ClanError),
use_key(age_keys[2].privkey, monkeypatch), use_gpg_key(gpg_key, monkeypatch),
capture_output as output, capture_output as output,
): ):
# user2 is not in the group anymore # user2 is not in the group anymore
@@ -520,3 +595,66 @@ def test_secrets(
with capture_output as output: with capture_output as output:
cli.run(["secrets", "list", "--flake", str(test_flake.path)]) cli.run(["secrets", "list", "--flake", str(test_flake.path)])
assert output.out == "" assert output.out == ""
def test_secrets_key_generate_gpg(
test_flake: FlakeForTest,
capture_output: CaptureOutput,
monkeypatch: pytest.MonkeyPatch,
gpg_key: str,
) -> None:
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:
cli.run(
[
"secrets",
"key",
"generate",
"--flake",
str(test_flake.path),
]
)
assert "age private key" not in output.out
assert re.match(r"PGP key.+is already set", output.out) is not None
with capture_output as output:
cli.run(["secrets", "key", "show", "--flake", str(test_flake.path)])
key = json.loads(output.out)
assert key["type"] == "pgp"
assert key["publickey"] == gpg_key
# Add testuser with the key that was (not) generated for the clan:
cli.run(
[
"secrets",
"users",
"add",
"--flake",
str(test_flake.path),
"--pgp-key",
gpg_key,
"testuser",
]
)
with capture_output as output:
cli.run(
[
"secrets",
"users",
"get",
"--flake",
str(test_flake.path),
"testuser",
]
)
key = json.loads(output.out)
assert key["type"] == "pgp"
assert key["publickey"] == gpg_key
monkeypatch.setenv("SOPS_NIX_SECRET", "secret-value")
cli.run(["secrets", "set", "--flake", str(test_flake.path), "secret-name"])
with capture_output as output:
cli.run(["secrets", "get", "--flake", str(test_flake.path), "secret-name"])
assert output.out == "secret-value"