Draft: clan-cli: secrets: Add support for PGP keys with sops-nix

To use a PGP key instead of an age key you can set `SOPS_PGP_FP`. (You
can use `gpg -k --fingerprint --fingerprint` to get your PGP encryption
key fingerprint, remove spaces from it).

The internal manifest file already supported a type field, and so I built
from there.

With those changes, I was able to add my PGP key, and update all my
secrets with it, instead of the age key originally generated:

```
% clan secrets key show | jq
{
  "key": "ADB6276965590A096004F6D1E114CBAE8FA29165",
  "type": "pgp"
}
% clan secrets key update
% for s in $(clan secrets list) ; do clan secrets users add-secret kal-pgp-from-2022-12-to-2024-12 "$s"; done
% for s in $(clan secrets list) ; do clan secrets users remove-secret --debug kal "$s" ; done
```
This commit is contained in:
Louis Opter
2024-09-27 11:31:59 -07:00
committed by Mic92
parent b1af3d5d6d
commit 61ceb44a71
5 changed files with 106 additions and 57 deletions

View File

@@ -1,5 +1,7 @@
import argparse
import functools
import getpass
import operator
import os
import shutil
import sys
@@ -20,6 +22,7 @@ from clan_cli.completions import (
from clan_cli.errors import ClanError
from clan_cli.git import commit_files
from . import sops
from .folders import (
list_objects,
sops_groups_folder,
@@ -42,13 +45,13 @@ def update_secrets(
changed_files.extend(
update_keys(
secret_path,
sorted(collect_keys_for_path(secret_path)),
sorted_keys(collect_keys_for_path(secret_path)),
)
)
return changed_files
def collect_keys_for_type(folder: Path) -> set[str]:
def collect_keys_for_type(folder: Path) -> set[tuple[str, sops.KeyType]]:
if not folder.exists():
return set()
keys = set()
@@ -68,7 +71,7 @@ def collect_keys_for_type(folder: Path) -> set[str]:
return keys
def collect_keys_for_path(path: Path) -> set[str]:
def collect_keys_for_path(path: Path) -> set[tuple[str, sops.KeyType]]:
keys = set()
keys.update(collect_keys_for_type(path / "machines"))
keys.update(collect_keys_for_type(path / "users"))
@@ -132,8 +135,8 @@ def encrypt_secret(
recipient_keys = collect_keys_for_path(secret_path)
if key.pubkey not in recipient_keys:
recipient_keys.add(key.pubkey)
if (key.pubkey, key.type) not in recipient_keys:
recipient_keys.add((key.pubkey, key.type))
files_to_commit.extend(
allow_member(
users_folder(secret_path),
@@ -144,7 +147,7 @@ def encrypt_secret(
)
secret_path = secret_path / "secret"
encrypt_file(secret_path, value, sorted(recipient_keys))
encrypt_file(secret_path, value, sorted_keys(recipient_keys))
files_to_commit.append(secret_path)
if git_commit:
commit_files(
@@ -228,7 +231,7 @@ def allow_member(
changed.extend(
update_keys(
group_folder.parent,
sorted(collect_keys_for_path(group_folder.parent)),
sorted_keys(collect_keys_for_path(group_folder.parent)),
)
)
return changed
@@ -255,10 +258,13 @@ def disallow_member(group_folder: Path, name: str) -> list[Path]:
group_folder.parent.rmdir()
return update_keys(
target.parent.parent, sorted(collect_keys_for_path(group_folder.parent))
target.parent.parent, sorted_keys(collect_keys_for_path(group_folder.parent))
)
sorted_keys = functools.partial(sorted, key=operator.itemgetter(0))
def has_secret(secret_path: Path) -> bool:
return (secret_path / "secret").exists()