Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add an option to output secrets in armor #276

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 13 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

`agenix` is a small and convenient Nix library for securely managing and deploying secrets using common public-private SSH key pairs:
You can encrypt a secret (password, access-token, etc.) on a source machine using a number of public SSH keys,
and deploy that encrypted secret to any another target machine that has the corresponding private SSH key of one of those public keys.
This project contains two parts:
and deploy that encrypted secret to any another target machine that has the corresponding private SSH key of one of those public keys.
This project contains two parts:
1. An `agenix` commandline app (CLI) to encrypt secrets into secured `.age` files that can be copied into the Nix store.
2. An `agenix` NixOS module to conveniently
* add those encrypted secrets (`.age` files) into the Nix store so that they can be deployed like any other Nix package using `nixos-rebuild` or similar tools.
Expand Down Expand Up @@ -250,7 +250,7 @@ e.g. inside your `flake.nix` file:
$ cd secrets
$ touch secrets.nix
```
This `secrets.nix` file is **not** imported into your NixOS configuration.
This `secrets.nix` file is **not** imported into your NixOS configuration.
It's only used for the `agenix` CLI tool (example below) to know which public keys to use for encryption.
3. Add public keys to your `secrets.nix` file:
```nix
Expand All @@ -266,10 +266,15 @@ e.g. inside your `flake.nix` file:
{
"secret1.age".publicKeys = [ user1 system1 ];
"secret2.age".publicKeys = users ++ systems;
"armored-secret.age" = {
publicKeys = [ user1 ];
armor = true;
};
}
```
These are the users and systems that will be able to decrypt the `.age` files later with their corresponding private keys.
You can obtain the public keys from
The armor option may also be supplied here to ensure files are output in Base64 PEM text which is useful for more readable diffs.
You can obtain the public keys from
* your local computer usually in `~/.ssh`, e.g. `~/.ssh/id_ed25519.pub`.
* from a running target machine with `ssh-keyscan`:
```ShellSession
Expand All @@ -290,7 +295,7 @@ e.g. inside your `flake.nix` file:
age.secrets.secret1.file = ../secrets/secret1.age;
}
```
When the `age.secrets` attribute set contains a secret, the `agenix` NixOS module will later automatically decrypt and mount that secret under the default path `/run/agenix/secret1`.
When the `age.secrets` attribute set contains a secret, the `agenix` NixOS module will later automatically decrypt and mount that secret under the default path `/run/agenix/secret1`.
Here the `secret1.age` file becomes part of your NixOS deployment, i.e. moves into the Nix store.

6. Reference the secrets' mount path in your config:
Expand All @@ -306,14 +311,14 @@ e.g. inside your `flake.nix` file:
So `config.age.secrets.secret1.path` will contain the path `/run/agenix/secret1` by default.
7. Use `nixos-rebuild` or [another deployment tool](https://nixos.wiki/wiki/Applications#Deployment") of choice as usual.

The `secret1.age` file will be copied over to the target machine like any other Nix package.
The `secret1.age` file will be copied over to the target machine like any other Nix package.
Then it will be decrypted and mounted as described before.
8. Edit secret files:
```ShellSession
$ agenix -e secret1.age
```
It assumes your SSH private key is in `~/.ssh/`.
In order to decrypt and open a `.age` file for editing you need the private key of one of the public keys
It assumes your SSH private key is in `~/.ssh/`.
In order to decrypt and open a `.age` file for editing you need the private key of one of the public keys
it was encrypted with. You can pass the private key you want to use explicitly with `-i`, e.g.
```ShellSession
$ agenix -e secret1.age -i ~/.ssh/id_ed25519
Expand Down
4 changes: 4 additions & 0 deletions doc/tutorial.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
{
"secret1.age".publicKeys = [ user1 system1 ];
"secret2.age".publicKeys = users ++ systems;
"armored-secret.age" = {
publicKeys = [ user1 ];
armor = true;
};
}
```
4. Edit secret files (these instructions assume your SSH private key is in ~/.ssh/):
Expand Down
7 changes: 7 additions & 0 deletions example/armored-secret.age
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFYzWG1FQSBpZkZW
aFpLNnJxc0VUMHRmZ2dZS0pjMGVENnR3OHd5K0RiT1RjRUhibFZBCnN5UG5vUjA3
SXpsNGtiVUw4T0tIVFo5Wkk5QS9NQlBndzVvektiQ0ozc0kKLS0tIGxyY1Q4dEZ1
VGZEanJyTFNta2JNRmpZb2FnK2JyS1hSVml1UGdMNWZKQXMKYla+wTXcRedyZoEb
LVWaSx49WoUTU0KBPJg9RArxaeC23GoCDzR/aM/1DvYU
-----END AGE ENCRYPTED FILE-----
4 changes: 4 additions & 0 deletions example/secrets.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ in {
"secret1.age".publicKeys = [user1 system1];
"secret2.age".publicKeys = [user1];
"passwordfile-user1.age".publicKeys = [user1 system1];
"armored-secret.age" = {
publicKeys = [user1];
armor = true;
};
}
8 changes: 8 additions & 0 deletions pkgs/agenix.sh
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ function keys {
(@nixInstantiate@ --json --eval --strict -E "(let rules = import $RULES; in rules.\"$1\".publicKeys)" | @jqBin@ -r .[]) || exit 1
}

function armor {
(@nixInstantiate@ --json --eval --strict -E "(let rules = import $RULES; in (builtins.hasAttr \"armor\" rules.\"$1\" && rules.\"$1\".armor))") || exit 1
}

function decrypt {
FILE=$1
KEYS=$2
Expand Down Expand Up @@ -148,6 +152,7 @@ function decrypt {
function edit {
FILE=$1
KEYS=$(keys "$FILE") || exit 1
ARMOR=$(armor "$FILE") || exit 1

CLEARTEXT_DIR=$(@mktempBin@ -d)
CLEARTEXT_FILE="$CLEARTEXT_DIR/$(basename "$FILE")"
Expand All @@ -169,6 +174,9 @@ function edit {
[ -f "$FILE" ] && [ "$EDITOR" != ":" ] && @diffBin@ -q "$CLEARTEXT_FILE.before" "$CLEARTEXT_FILE" && warn "$FILE wasn't changed, skipping re-encryption." && return

ENCRYPT=()
if [[ "$ARMOR" == "true" ]]; then
ENCRYPT+=(--armor)
fi
while IFS= read -r key
do
if [ -n "$key" ]; then
Expand Down
7 changes: 7 additions & 0 deletions test/integration.nix
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ pkgs.nixosTest {
file = ../example/secret2.age;
path = "/home/user1/secret2";
};
secrets.armored-secret = {
file = ../example/armored-secret.age;
};
};
};
};
Expand All @@ -71,6 +74,7 @@ pkgs.nixosTest {
user = "user1";
password = "password1234";
secret2 = "world!";
armored-secret = "Hello World!";
in ''
system1.wait_for_unit("multi-user.target")
system1.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
Expand All @@ -91,6 +95,9 @@ pkgs.nixosTest {
system1.send_chars("cat /run/user/$(id -u)/agenix/secret2 > /tmp/2\n")
system1.wait_for_file("/tmp/2")
assert "${secret2}" in system1.succeed("cat /tmp/2")
system1.send_chars("cat /run/user/$(id -u)/agenix/armored-secret > /tmp/3\n")
system1.wait_for_file("/tmp/3")
assert "${armored-secret}" in system1.succeed("cat /tmp/3")

userDo = lambda input : f"sudo -u user1 -- bash -c 'set -eou pipefail; cd /tmp/secrets; {input}'"

Expand Down
Loading