fix rotating keys with sops

This commit is contained in:
Jörg Thalheim
2023-08-08 13:44:15 +02:00
parent 5ac5e32214
commit 0533948085
6 changed files with 131 additions and 34 deletions

View File

@@ -11,8 +11,8 @@ from typing import IO
from .. import tty
from ..errors import ClanError
from ..nix import nix_shell
from .folders import list_objects, sops_secrets_folder
from .sops import SopsKey, encrypt_file, ensure_sops_key, read_key
from .folders import list_objects, sops_secrets_folder, sops_users_folder
from .sops import SopsKey, encrypt_file, ensure_sops_key, read_key, update_keys
from .types import VALID_SECRET_NAME, secret_name_type
@@ -54,6 +54,9 @@ def set_command(args: argparse.Namespace) -> None:
else:
encrypt_secret(key, sops_secrets_folder() / args.secret, sys.stdin)
# make sure we add ourselves to the key
allow_member(users_folder(args.secret), sops_users_folder(), key.username)
def remove_command(args: argparse.Namespace) -> None:
secret: str = args.secret
@@ -67,6 +70,51 @@ def add_secret_argument(parser: argparse.ArgumentParser) -> None:
parser.add_argument("secret", help="the name of the secret", type=secret_name_type)
def machines_folder(group: str) -> Path:
return sops_secrets_folder() / group / "machines"
def users_folder(group: str) -> Path:
return sops_secrets_folder() / group / "users"
def groups_folder(group: str) -> Path:
return sops_secrets_folder() / group / "groups"
def collect_keys_for_type(folder: Path) -> list[str]:
if not folder.exists():
return []
keys = []
for p in folder.iterdir():
if not p.is_symlink():
continue
try:
target = p.resolve()
except FileNotFoundError:
tty.warn(f"Ignoring broken symlink {p}")
continue
kind = target.parent.name
if folder.name != kind:
tty.warn(f"Expected {p} to point to {folder} but points to {target.parent}")
continue
keys.append(read_key(target))
return keys
def collect_keys_for_path(path: Path) -> list[str]:
keys = []
keys += collect_keys_for_type(path / "machines")
keys += collect_keys_for_type(path / "users")
groups = path / "groups"
if not groups.is_dir():
return keys
for group in groups.iterdir():
keys += collect_keys_for_type(group / "machines")
keys += collect_keys_for_type(group / "users")
return keys
def allow_member(group_folder: Path, source_folder: Path, name: str) -> None:
source = source_folder / name
if not source.exists():
@@ -80,12 +128,20 @@ def allow_member(group_folder: Path, source_folder: Path, name: str) -> None:
)
os.remove(user_target)
user_target.symlink_to(source)
update_keys(group_folder.parent, collect_keys_for_path(group_folder.parent))
def disallow_member(group_folder: Path, name: str) -> None:
target = group_folder / name
if not target.exists():
raise ClanError(f"{name} does not exist in group in {group_folder}")
keys = collect_keys_for_path(group_folder.parent)
if len(keys) < 2:
raise ClanError(
f"Cannot remove {name} from {group_folder.parent.name}. No keys left. Use 'clan secrets remove {name}' to remove the secret."
)
os.remove(target)
if len(os.listdir(group_folder)) == 0:
@@ -94,17 +150,7 @@ def disallow_member(group_folder: Path, name: str) -> None:
if len(os.listdir(group_folder.parent)) == 0:
os.rmdir(group_folder.parent)
def machines_folder(group: str) -> Path:
return sops_secrets_folder() / group / "machines"
def users_folder(group: str) -> Path:
return sops_secrets_folder() / group / "users"
def groups_folder(group: str) -> Path:
return sops_secrets_folder() / group / "groups"
update_keys(target.parent.parent, collect_keys_for_path(group_folder.parent))
def register_secrets_parser(subparser: argparse._SubParsersAction) -> None: