agenix-shell
agenix-shell copied to clipboard
agenix-shell
Leveraging age and agenix,
this project allows you to inject variables containing secrets into your flakes' devShells.
This simplifies the onboarding process for new developers by enabling secure secret sharing (with access control) and making projects more self-contained by eliminating the need for external tools.
Usage
Basic knowledge of how agenix works is required.
It relies on the same setup as agenix:
- A
secretsdirectory containing all the encrypted secrets. - A
secrets.nixfile that lists the secrets and specifies which keys can decrypt each one.
Example of secrets/secrets.nix:
{
"foo.age".publicKeys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPDpVA+jisOuuNDeCJ67M11qUP8YY29cipajWzTFAobi"
];
}
While this is the format expected by agenix, you are not strictly bound to it, agenix-shell only requires you to specify the paths for the .age files, similar to agenix modules, following the secrets/secrets.nix structure is only useful if you want to use the agenix CLI.
agenix-shell injects two environment variables for each secret:
- One containing the cleartext secret itself.
- Another containing the path to the secret (automatically appending
_PATHto the variable name).
For example:
foo: Contains the secret.foo_PATH: Contains the path to the secret.
Basic Usage
{
devShells.${system}.default = let
installationScript = inputs.agenix-shell.lib.installationScript system {
secrets = {
foo.file = ./secrets/foo.age;
};
};
in pkgs.mkShell {
shellHook = ''
source ${lib.getExe installationScript}
'';
};
}
Check the basic example for a working setup. You'll need to delete the encrypted secret and encrypt your own using your key. Alternatively, you can use the provided key (not for production use).
Initialize with:
nix flake init -t github:aciceri/agenix-shell#basic
Internally, this approach uses flake-parts for argument evaluation. Refer to the flake.parts documentation for a full list of options.
With flake-parts
{
imports = [
inputs.agenix-shell.flakeModules.default
];
agenix-shell = {
secrets = {
foo.file = ./secrets/foo.age;
};
};
perSystem = {pkgs, config, lib, ...}: {
devShells.default = pkgs.mkShell {
shellHook = ''
source ${lib.getExe config.agenix-shell.installationScript}
'';
};
};
}
Check the flake-parts template for a working example. Initialize with:
nix flake init -t github:aciceri/agenix-shell#flake-parts
With devenv
Find a working template here.
Initialize with:
nix flake init -t github:aciceri/agenix-shell#devenv
How It Works
The functionality is straightforward:
agenix-shellexports a configurable script, which is sourced in thedevShell(e.g. via ashellHook).- The script:
- Decrypts secrets using the user's SSH keys (default:
$HOME/.ssh/id_rsaor$HOME/.ssh/id_ed25519). - Stores decrypted secrets in a secure location:
- Linux:
$XDG_RUNTIME_DIR/agenix-shell/<hash>(commonly mounted ontmpfs). - Darwin:
~/.agenix-shell/<hash>(mounted onhfs, similar totmpfs).
- Linux:
- Declares two variables per secret:
- One containing the secret itself.
- Another containing the path to the secret.
- Decrypts secrets using the user's SSH keys (default:
Everything is highly customizable via options. Refer to flake.parts for a complete list and defaults.
The script is hygienic:
- Intermediate variables are unset.
- A custom
PATHis used to isolate dependencies (on Linux).