This forces sops to use our config file, otherwise if any of the
environment variables set to specify recipients is present then
`--config` will be ignored (see [env_check]).
That's simple enough, still I ended up refactoring how we call sops for
correctness, and to align with its behavior. The code now distinguishes
between public and private keys explicitly. `secrets.decrypt_secret`
does not try to lookup for public and private keys anymore.
With this changeset, some people might have to adjust their environment
as public age and PGP keys will be discovered like sops would do. In
particular if multiple public keys are discovered, then the user will
have to specify which one to use for the clan.
This also makes the following changes:
- try to use `/dev/shm` when swapping a secret (it's what [pass] does
fwiw);
- alias immediate values for readability;
- remove some float comparison that could never succeed, and use sops'
exit status instead;
- remove unused function `maybe_get_sops_key`.
[env_check]: 8c567aa8a7/cmd/sops/main.go (L2229)
[pass]: http://passwordstore.org/
80 lines
2.4 KiB
Python
80 lines
2.4 KiB
Python
import argparse
|
|
import json
|
|
import logging
|
|
import sys
|
|
|
|
from clan_cli.errors import ClanError
|
|
from clan_cli.git import commit_files
|
|
|
|
from . import sops
|
|
from .secrets import update_secrets
|
|
from .sops import (
|
|
default_admin_private_key_path,
|
|
generate_private_key,
|
|
maybe_get_admin_public_key,
|
|
)
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
def generate_key() -> sops.SopsKey:
|
|
key = maybe_get_admin_public_key()
|
|
if key is not None:
|
|
print(f"{key.key_type.name} key {key.pubkey} is already set")
|
|
return key
|
|
|
|
path = default_admin_private_key_path()
|
|
_, pub_key = generate_private_key(out_file=path)
|
|
print(
|
|
f"Generated age private key at '{path}' for your user. Please back it up on a secure location or you will lose access to your secrets."
|
|
)
|
|
return sops.SopsKey(pub_key, username="", key_type=sops.KeyType.AGE)
|
|
|
|
|
|
def generate_command(args: argparse.Namespace) -> None:
|
|
key = generate_key()
|
|
print("Also add your age public key to the repository with:")
|
|
key_type = key.key_type.name.lower()
|
|
print(f"clan secrets users add --{key_type}-key <username>")
|
|
|
|
|
|
def show_command(args: argparse.Namespace) -> None:
|
|
key = sops.maybe_get_admin_public_key()
|
|
if not key:
|
|
msg = "No public key found"
|
|
raise ClanError(msg)
|
|
json.dump(key.as_dict(), sys.stdout, indent=2, sort_keys=True)
|
|
|
|
|
|
def update_command(args: argparse.Namespace) -> None:
|
|
flake_dir = args.flake.path
|
|
commit_files(update_secrets(flake_dir), flake_dir, "Updated secrets with new keys.")
|
|
|
|
|
|
def register_key_parser(parser: argparse.ArgumentParser) -> None:
|
|
subparser = parser.add_subparsers(
|
|
title="command",
|
|
description="the command to run",
|
|
help="the command to run",
|
|
required=True,
|
|
)
|
|
|
|
parser_generate = subparser.add_parser(
|
|
"generate",
|
|
description=(
|
|
"Generate an age key for the Clan, if you already have an age "
|
|
"or PGP key, then use it to create your user, see: "
|
|
"`clan secrets users add --help'"
|
|
),
|
|
)
|
|
parser_generate.set_defaults(func=generate_command)
|
|
|
|
parser_show = subparser.add_parser("show", help="show public key")
|
|
parser_show.set_defaults(func=show_command)
|
|
|
|
parser_update = subparser.add_parser(
|
|
"update",
|
|
help="re-encrypt all secrets with current keys (useful when changing keys)",
|
|
)
|
|
parser_update.set_defaults(func=update_command)
|