vars/sops/shared: add machines key on demand

This commit is contained in:
DavHau
2024-09-12 19:54:33 +02:00
parent 2fc5572ff7
commit a1dd10f502
4 changed files with 96 additions and 51 deletions

View File

@@ -22,19 +22,19 @@ from .sops import read_key, write_key
from .types import public_or_private_age_key_type, secret_name_type
def add_machine(flake_dir: Path, name: str, key: str, force: bool) -> None:
path = sops_machines_folder(flake_dir) / name
def add_machine(flake_dir: Path, machine: str, key: str, force: bool) -> None:
path = sops_machines_folder(flake_dir) / machine
write_key(path, key, force)
paths = [path]
def filter_machine_secrets(secret: Path) -> bool:
return secret.joinpath("machines", name).exists()
return secret.joinpath("machines", machine).exists()
paths.extend(update_secrets(flake_dir, filter_secrets=filter_machine_secrets))
commit_files(
paths,
flake_dir,
f"Add machine {name} to secrets",
f"Add machine {machine} to secrets",
)
@@ -70,9 +70,9 @@ def list_sops_machines(flake_dir: Path) -> list[str]:
return list_objects(path, validate)
def add_secret(flake_dir: Path, machine: str, secret: str) -> None:
def add_secret(flake_dir: Path, machine: str, secret_path: Path) -> None:
paths = secrets.allow_member(
secrets.machines_folder(sops_secrets_folder(flake_dir) / secret),
secrets.machines_folder(secret_path),
sops_machines_folder(flake_dir),
machine,
)
@@ -128,7 +128,11 @@ def add_secret_command(args: argparse.Namespace) -> None:
if args.flake is None:
msg = "Could not find clan flake toplevel directory"
raise ClanError(msg)
add_secret(args.flake.path, args.machine, args.secret)
add_secret(
args.flake.path,
args.machine,
sops_secrets_folder(args.flake.path) / args.secret,
)
def remove_secret_command(args: argparse.Namespace) -> None:

View File

@@ -210,15 +210,18 @@ def allow_member(
msg += list_directory(source_folder)
raise ClanError(msg)
group_folder.mkdir(parents=True, exist_ok=True)
user_target = group_folder / name
if user_target.exists():
if not user_target.is_symlink():
msg = f"Cannot add user '{name}' to {group_folder.parent.name} secret. {user_target} exists but is not a symlink"
member = group_folder / name
if member.exists():
if not member.is_symlink():
msg = f"Cannot add user '{name}' to {group_folder.parent.name} secret. {member} exists but is not a symlink"
raise ClanError(msg)
user_target.unlink()
# return early if the symlink already points to the correct target
if member.resolve() == source:
return []
member.unlink()
user_target.symlink_to(os.path.relpath(source, user_target.parent))
changed = [user_target]
member.symlink_to(os.path.relpath(source, member.parent))
changed = [member]
if do_update_keys:
changed.extend(
update_keys(

View File

@@ -2,7 +2,7 @@ from pathlib import Path
from clan_cli.machines.machines import Machine
from clan_cli.secrets.folders import sops_secrets_folder
from clan_cli.secrets.machines import add_machine, has_machine
from clan_cli.secrets.machines import add_machine, add_secret, has_machine
from clan_cli.secrets.secrets import decrypt_secret, encrypt_secret, has_secret
from clan_cli.secrets.sops import generate_private_key
@@ -80,4 +80,9 @@ class SecretStore(SecretStoreBase):
(output_dir / "key.txt").write_text(key)
def exists(self, generator_name: str, name: str, shared: bool = False) -> bool:
return (self.directory(generator_name, name, shared) / "secret").exists()
secret_folder = self.secret_path(generator_name, name, shared)
if not (secret_folder / "secret").exists():
return False
# add_secret will be a no-op if the machine is already added
add_secret(self.machine.flake_dir, self.machine.name, secret_folder)
return True

View File

@@ -17,69 +17,102 @@ def test_vm_deployment(
temporary_home: Path,
sops_setup: SopsSetup,
) -> None:
config = nested_dict()
config["clan"]["virtualisation"]["graphics"] = False
config["services"]["getty"]["autologinUser"] = "root"
config["services"]["openssh"]["enable"] = True
config["networking"]["firewall"]["enable"] = False
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
my_generator["files"]["my_secret"]["secret"] = True
my_generator["script"] = """
# machine 1
machine1_config = nested_dict()
machine1_config["clan"]["virtualisation"]["graphics"] = False
machine1_config["services"]["getty"]["autologinUser"] = "root"
machine1_config["services"]["openssh"]["enable"] = True
machine1_config["networking"]["firewall"]["enable"] = False
m1_generator = machine1_config["clan"]["core"]["vars"]["generators"]["m1_generator"]
m1_generator["files"]["my_secret"]["secret"] = True
m1_generator["script"] = """
echo hello > $out/my_secret
"""
my_shared_generator = config["clan"]["core"]["vars"]["generators"][
m1_shared_generator = machine1_config["clan"]["core"]["vars"]["generators"][
"my_shared_generator"
]
my_shared_generator["share"] = True
my_shared_generator["files"]["shared_secret"]["secret"] = True
my_shared_generator["files"]["no_deploy_secret"]["secret"] = True
my_shared_generator["files"]["no_deploy_secret"]["deploy"] = False
my_shared_generator["script"] = """
m1_shared_generator["share"] = True
m1_shared_generator["files"]["shared_secret"]["secret"] = True
m1_shared_generator["files"]["no_deploy_secret"]["secret"] = True
m1_shared_generator["files"]["no_deploy_secret"]["deploy"] = False
m1_shared_generator["script"] = """
echo hello > $out/shared_secret
echo hello > $out/no_deploy_secret
"""
# machine 2
machine2_config = nested_dict()
machine2_config["clan"]["virtualisation"]["graphics"] = False
machine2_config["services"]["getty"]["autologinUser"] = "root"
machine2_config["services"]["openssh"]["enable"] = True
machine2_config["users"]["users"]["root"]["openssh"]["authorizedKeys"]["keys"] = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDuhpzDHBPvn8nv8RH1MRomDOaXyP4GziQm7r3MZ1Syk grmpf"
]
machine2_config["networking"]["firewall"]["enable"] = False
machine2_config["clan"]["core"]["vars"]["generators"]["my_shared_generator"] = (
m1_shared_generator.copy()
)
flake = generate_flake(
temporary_home,
flake_template=CLAN_CORE / "templates" / "minimal",
machine_configs={"my_machine": config},
machine_configs={"m1_machine": machine1_config, "m2_machine": machine2_config},
monkeypatch=monkeypatch,
)
monkeypatch.chdir(flake.path)
sops_setup.init()
cli.run(["vars", "generate", "my_machine"])
cli.run(["vars", "generate"])
# check sops secrets not empty
sops_secrets = json.loads(
run(
nix_eval(
[
f"{flake.path}#nixosConfigurations.my_machine.config.sops.secrets",
]
)
).stdout.strip()
)
assert sops_secrets != {}
for machine in ["m1_machine", "m2_machine"]:
sops_secrets = json.loads(
run(
nix_eval(
[
f"{flake.path}#nixosConfigurations.{machine}.config.sops.secrets",
]
)
).stdout.strip()
)
assert sops_secrets != {}
my_secret_path = run(
nix_eval(
[
f"{flake.path}#nixosConfigurations.my_machine.config.clan.core.vars.generators.my_generator.files.my_secret.path",
f"{flake.path}#nixosConfigurations.m1_machine.config.clan.core.vars.generators.m1_generator.files.my_secret.path",
]
)
).stdout.strip()
assert "no-such-path" not in my_secret_path
vm = run_vm_in_thread("my_machine")
qga = qga_connect("my_machine", vm)
for machine in ["m1_machine", "m2_machine"]:
shared_secret_path = run(
nix_eval(
[
f"{flake.path}#nixosConfigurations.{machine}.config.clan.core.vars.generators.my_shared_generator.files.shared_secret.path",
]
)
).stdout.strip()
assert "no-such-path" not in shared_secret_path
vm_m1 = run_vm_in_thread("m1_machine")
vm_m2 = run_vm_in_thread("m2_machine", ssh_port=2222)
qga_m1 = qga_connect("m1_machine", vm_m1)
qga_m2 = qga_connect("m2_machine", vm_m2)
# check my_secret is deployed
_, out, _ = qga.run("cat /run/secrets/vars/my_generator/my_secret", check=True)
_, out, _ = qga_m1.run("cat /run/secrets/vars/m1_generator/my_secret", check=True)
assert out == "hello\n"
# check shared_secret is deployed
_, out, _ = qga.run(
# check shared_secret is deployed on m1
_, out, _ = qga_m1.run(
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
)
assert out == "hello\n"
# check shared_secret is deployed on m2
_, out, _ = qga_m2.run(
"cat /run/secrets/vars/my_shared_generator/shared_secret", check=True
)
assert out == "hello\n"
# check no_deploy_secret is not deployed
returncode, out, _ = qga.run(
returncode, out, _ = qga_m1.run(
"test -e /run/secrets/vars/my_shared_generator/no_deploy_secret", check=False
)
assert returncode != 0
qga.exec_cmd("poweroff")
wait_vm_down("my_machine", vm)
qga_m1.exec_cmd("poweroff")
qga_m2.exec_cmd("poweroff")
wait_vm_down("m1_machine", vm_m1)
wait_vm_down("m2_machine", vm_m2)