diff --git a/README.md b/README.md index 67718f60..de5f8d5c 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 @@ -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 @@ -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: @@ -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 diff --git a/doc/tutorial.md b/doc/tutorial.md index 83441214..751afa9c 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -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/): diff --git a/example/armored-secret.age b/example/armored-secret.age new file mode 100644 index 00000000..0e8bff1e --- /dev/null +++ b/example/armored-secret.age @@ -0,0 +1,7 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IFYzWG1FQSBpZkZW +aFpLNnJxc0VUMHRmZ2dZS0pjMGVENnR3OHd5K0RiT1RjRUhibFZBCnN5UG5vUjA3 +SXpsNGtiVUw4T0tIVFo5Wkk5QS9NQlBndzVvektiQ0ozc0kKLS0tIGxyY1Q4dEZ1 +VGZEanJyTFNta2JNRmpZb2FnK2JyS1hSVml1UGdMNWZKQXMKYla+wTXcRedyZoEb +LVWaSx49WoUTU0KBPJg9RArxaeC23GoCDzR/aM/1DvYU +-----END AGE ENCRYPTED FILE----- diff --git a/example/secrets.nix b/example/secrets.nix index 2910329e..84927f40 100644 --- a/example/secrets.nix +++ b/example/secrets.nix @@ -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; + }; } diff --git a/pkgs/agenix.sh b/pkgs/agenix.sh index 3d0415ea..21446e6b 100644 --- a/pkgs/agenix.sh +++ b/pkgs/agenix.sh @@ -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 @@ -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")" @@ -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 diff --git a/test/integration.nix b/test/integration.nix index e0ee85a0..d2bfaaa1 100644 --- a/test/integration.nix +++ b/test/integration.nix @@ -63,6 +63,9 @@ pkgs.nixosTest { file = ../example/secret2.age; path = "/home/user1/secret2"; }; + secrets.armored-secret = { + file = ../example/armored-secret.age; + }; }; }; }; @@ -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'") @@ -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}'"