Age secret file store.

Store progfiguration secrets as files. Each secret is encrypted by an Age key.

Wrap the age binary to encrypt and decrypt secret values

Module Contents#



An age keypair


An age-encrypted secret value


A reference to a secret by name


Age secret file store for progfiguration inventories.


encrypt(value, pubkeys)

Encrypt a value to a list of public age keys

decrypt(→ str)

Decrypt an encrypted age value

exception progfiguration.sitehelpers.agesecrets.AgeParseException#

Bases: Exception

Common base class for all non-exit exceptions.

exception progfiguration.sitehelpers.agesecrets.MissingAgeKeyException#

Bases: Exception

Common base class for all non-exit exceptions.

class progfiguration.sitehelpers.agesecrets.AgeKey(secret: str, public: str, created: datetime.datetime)#

An age keypair


secret: The secret key public: The public key created: The datetime the key was created

classmethod from_output(output: str) AgeKey#

Parse the output of age-keygen into an AgeKey

Here’s example output from age-keygen -y:

# created: 2022-09-28T16:01:22-05:00 # public key: age14e42u048nehghjj3ch9mmnkdh4nsujn774klqxn02mznppx3gflsuj6y5m AGE-SECRET-KEY-1ASKGXED4DVGUH7SA50DHE2UHAYQ00PV87N2RQ5J5S6AUN9MLNSGQ3TKFGJ

This function parses that output and returns an AgeKey object.

classmethod generate(path: pathlib.Path | None = None) AgeKey#

Generate a new age keypair using the age-keygen binary

classmethod from_file(path: str) AgeKey#

Load an age keypair from a file

class progfiguration.sitehelpers.agesecrets.AgeSecret#

Bases: progfiguration.inventory.invstores.Secret

An age-encrypted secret value

secret: str#

The encrypted secret value

privkey_path: str | None#

The path to the private key.

If this is None, then the secret cannot be decrypted.

decrypt() str#

Decrypt the secret.

class progfiguration.sitehelpers.agesecrets.AgeSecretReference#

Bases: progfiguration.inventory.invstores.SecretReference

A reference to a secret by name

This is a wrapper type that allows us to pass around a reference to a secret without having to know the secret’s value.

name: str#

The name of the secret

dereference(nodename: str, hoststore: progfiguration.inventory.invstores.HostStore, secretstore: progfiguration.inventory.invstores.SecretStore) Any#

Get the final value of a role argument for a node.

Arguments to this method:

  • nodename: The name of the node that the argument is being applied to

  • hoststore: The inventory object

This function must retrieve or calculate the final value from its internal data and these arguments.

progfiguration.sitehelpers.agesecrets.encrypt(value: str, pubkeys: List[str])#

Encrypt a value to a list of public age keys

progfiguration.sitehelpers.agesecrets.decrypt(value: str, privkey_path: str | None) str#

Decrypt an encrypted age value

class progfiguration.sitehelpers.agesecrets.AgeSecretStore(controller_age_pubkey: str, decryption_age_privkey_path_list: List[str] | None = None)#

Bases: progfiguration.inventory.invstores.SecretStore

Age secret file store for progfiguration inventories.

Store progfiguration secrets as files in the site’s nodes/ and groups/ submodules. Each secret is encrypted by an Age key.

Look for a decryption key in the list passed to the initializer.

The CLI arguments it accepts:


The path to an age private key to decrypt secrets.

The InventoryNode sitedata it looks for:


The path on the node to an age private key to decrypt secrets. If this is not set, we try to use the decryption_age_privkey_path. If this is set, but the key does not exist, we raise FileNotFoundError.


The string of an age public key to encrypt secrets. If this is not set, encrypting secrets for the node (or any of its groups) will fail.

controller_age_pubkey: str#

The controller’s age public key

_cache: Dict[str, Dict[str, Dict[str, progfiguration.inventory.invstores.Secret]]]#

An in-memory cache.

self._cache[collection][name] = {secretname: Secret}

decryption_age_privkey_path: str = ''#

If this is not empty, use this path to an age private key to decrypt secrets.

During initialization, this is set to the first item in decryption_age_privkey_path_list that exists. Can be (re-)set after initialization.

TODO: how will the cli code in core allow setting this at runtime?

_get_secrets_file(collection: Literal[node, group, special], name: str) pathlib.Path#
_load_secrets(collection: Literal[node, group, special], name: str) Dict[str, progfiguration.inventory.invstores.Secret]#

Load a secrets from a secret store.

Cache them in memory for future use.

_save_secrets(collection: Literal[node, group, special], name: str)#

Save a secrets to a secret store.

Save them to disk.

list_secrets(collection: Literal[node, group, special], name: str) List[str]#

List secrets.

get_secret(collection: Literal[node, group, special], name: str, secret_name: str) progfiguration.inventory.invstores.Secret#

Retrieve a secret from the secret store.

This operation returns an opaque Secret type. The secret does not have to be decrypted yet, but can be, depending on the Secret implementation.

encrypt_secret(hoststore: progfiguration.inventory.invstores.HostStore, name: str, value: str, nodes: List[str], groups: List[str], controller_key: bool, store: bool = False) str#

Encrypt a secret for some list of nodes and groups.

Always encrypt for the controller so that it can decrypt too.

We assume this only ever happens on the controller.

apply_cli_arguments(args: Dict[str, str]) None#

Apply arguments from the command line

Arguments are passed as comma-separated key/value pairs, like --secret-store-arguments key1=value1,key2=value2.

find_node_key(node: progfiguration.inventory.nodes.InventoryNode)#

If called, this function should find the decryption key for a node.

It may be called by the progfigsite command-line program if the user specifies a node. The implementation may look up the key in the node’s sitedata.