diff --git a/README.md b/README.md index 483369b..d92b485 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ [age](https://github.com/FiloSottile/age)-encrypted secrets for NixOS. -# Features +It consists of a NixOS module `age`, and a CLI tool called `agenix` +used for editing and rekeying the secret files. + +## Features * Secrets are encrypted with SSH keys ** system public keys via `ssh-keyscan` @@ -10,11 +13,11 @@ * No GPG * Very little code, so it should be easy for you to audit -# Installation +## Installation Choose one of the following methods: -#### [niv](https://github.com/nmattia/niv) (Current recommendation) +### [niv](https://github.com/nmattia/niv) (Current recommendation) First add it to niv: @@ -22,7 +25,9 @@ First add it to niv: $ niv add ryantm/agenix ``` - Than add the following to your configuration.nix in the `imports` list: +#### Module + +Then add the following to your configuration.nix in the `imports` list: ```nix { @@ -30,7 +35,7 @@ $ niv add ryantm/agenix } ``` -#### nix-channel +### nix-channel As root run: @@ -47,11 +52,11 @@ $ nix-channel --update } ``` -#### fetchTarball +### fetchTarball Add the following to your configuration.nix: -``` nix +```nix { imports = [ "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/master.tar.gz"}/modules/age" ]; } @@ -74,9 +79,11 @@ $ nix-channel --update } ``` -#### Flakes +### Flakes -``` nix +#### Module + +```nix { inputs.agenix.url = "github:ryantm/agenix"; # optional, not necessary for the module @@ -96,6 +103,82 @@ $ nix-channel --update } ``` -# Tutorial +#### CLI -# Threat model +You don't need to install it: + +```console +nix run github:ryantm/agenix -- --help +``` + + +## Tutorial + +1. Make a directory to store secrets and a YAML file for configuring encryption. + + ```console + $ mkdir secrets + $ cd secerts + $ touch secrets.yaml + ``` +2. Add public keys to `secrets.yaml` file (hint use `ssh-keyscan` or GitHub (for example, https://github.com/ryantm.keys): + ```yaml + public_keys: + # users + - &user1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH + # systems + - &system1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE + + secrets: + - name: secret1.age + public_keys: + - *user1 + - *system1 + - name: secret2.age + public_keys: + - *user1 + ``` +3. Edit secret files (assuming your SSH private key is in ~/.ssh/): + ```console + $ agenix -e secret1.age + ``` +4. Add secret to NixOS module config: + ```nix + age.secrets.secret1 = ../secrets/secret1.age; + ``` +5. NixOS rebuild or use your deployment too like usual. + +## Rekeying + +If you change the public keys in `secrets.yaml`, you should rekey your +secrets: + +```console +$ agenix --rekey +``` + +To rekey a secret, you have to be able to decrypt it. Because of +randomness in `age`'s encryption algorithms, the files always change +when rekeyed, even if the identities do not. This eventually could be +improved upon by reading the identities from the age file. + +## Threat model/Warnings + +This library has not be audited by a security professional. + +People unfamiliar with `age` might be surprised that secrets are not +authenticated. This means that every attacker that has write access to +the repository can modify secrets because public keys are exposed. +This seems like not a problem on the first glance because changing the +configuration itself could expose secrets easily. However it is easier +to review configuration changes rather than random secrets (for +example 4096-bit rsa keys). This would be solved by having a message +authentication code (MAC) like other implementations like GPG or +[sops](https://github.com/Mic92/sops-nix) have, however this was left +out for simplicity in `age`. + +## Acknowledgements + +This project is based off of +[sops-nix](https://github.com/Mic92/sops-nix) created Mic92. Thank you +to Mic92 for inspiration and help with making this. diff --git a/example.yaml b/example.yaml deleted file mode 100644 index 39c3850..0000000 --- a/example.yaml +++ /dev/null @@ -1,16 +0,0 @@ -public_keys: - # users - - &user1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFx3E0bHOWxRu91+XFzimbVA1mP19c5To/7szED1OUf9 user1@example.com - # hosts - # get these via ssh-keyscan - - &host1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKxk6NtiVv8L8R6/+lHgq4UP8P6JC7a6Wl2klCOOk8GP root@host1 - -secrets: - - name: secret.age - public_keys: - - *user1 - - *host1 - - name: other.age - public_keys: - - *user1 - - *host1 diff --git a/example/secret1.age b/example/secret1.age new file mode 100644 index 0000000..8cb6f5e Binary files /dev/null and b/example/secret1.age differ diff --git a/example/secret2.age b/example/secret2.age new file mode 100644 index 0000000..fd456b3 --- /dev/null +++ b/example/secret2.age @@ -0,0 +1,5 @@ +age-encryption.org/v1 +-> ssh-ed25519 V3XmEA OB4+1FbPhQ3r6iGksM7peWX5it8NClpXIq/o5nnP7GA +FmHVUj+A5i5+bDFgySQskmlvynnosJiWUTJmBRiNA9I +--- tP+3mFVtd7ogVu1Lkboh55zoi5a77Ht08Uc/QuIviv4 +¤¬Xæ{”ïOŠ£èätMXxÔvÓª(¬IÁmyPÇï¸è+3²S3i \ No newline at end of file diff --git a/example/secrets.yaml b/example/secrets.yaml new file mode 100644 index 0000000..dee86cd --- /dev/null +++ b/example/secrets.yaml @@ -0,0 +1,14 @@ +public_keys: + # users + - &user1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH + # systems + - &system1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE + +secrets: + - name: secret1.age + public_keys: + - *user1 + - *system1 + - name: secret2.age + public_keys: + - *user1 diff --git a/example_keys/system1 b/example_keys/system1 new file mode 100644 index 0000000..fca6581 --- /dev/null +++ b/example_keys/system1 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACDyQ8iK/xUs9XCXXKFuvUfja1s8Biv/t4Caag9bfC9sxAAAAJA3yvCWN8rw +lgAAAAtzc2gtZWQyNTUxOQAAACDyQ8iK/xUs9XCXXKFuvUfja1s8Biv/t4Caag9bfC9sxA +AAAEA+J2V6AG1NriAIvnNKRauIEh1JE9HSdhvKJ68a5Fm0w/JDyIr/FSz1cJdcoW69R+Nr +WzwGK/+3gJpqD1t8L2zEAAAADHJ5YW50bUBob21lMQE= +-----END OPENSSH PRIVATE KEY----- diff --git a/example_keys/system1.pub b/example_keys/system1.pub new file mode 100644 index 0000000..8b52dbe --- /dev/null +++ b/example_keys/system1.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE diff --git a/example_keys/user1 b/example_keys/user1 new file mode 100644 index 0000000..4314295 --- /dev/null +++ b/example_keys/user1 @@ -0,0 +1,7 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACC9InTb4BornFoLqf5j+/M8gtt7hY2KtHr3FnYxkFGgRwAAAJC2JJ8htiSf +IQAAAAtzc2gtZWQyNTUxOQAAACC9InTb4BornFoLqf5j+/M8gtt7hY2KtHr3FnYxkFGgRw +AAAEDxt5gC/s53IxiKAjfZJVCCcFIsdeERdIgbYhLO719+Kb0idNvgGiucWgup/mP78zyC +23uFjYq0evcWdjGQUaBHAAAADHJ5YW50bUBob21lMQE= +-----END OPENSSH PRIVATE KEY----- diff --git a/example_keys/user1.pub b/example_keys/user1.pub new file mode 100644 index 0000000..1000936 --- /dev/null +++ b/example_keys/user1.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH diff --git a/pkgs/agenix.nix b/pkgs/agenix.nix index 5c34237..a4b6aad 100644 --- a/pkgs/agenix.nix +++ b/pkgs/agenix.nix @@ -6,16 +6,19 @@ PACKAGE="agenix" function show_help () { echo "$PACKAGE - edit and rekey age secret files" echo " " - echo "$PACKAGE -e FILE" - echo "$PACKAGE -r" + echo "$PACKAGE -e FILE [-i PRIVATE_KEY]" + echo "$PACKAGE -r [-i PRIVATE_KEY]" echo ' ' echo 'options:' echo '-h, --help show help' echo '-e, --edit FILE edits FILE using $EDITOR' echo '-r, --rekey re-encrypts all secrets with specified recipients' + echo '-i, --identity identity to use when decrypting' echo ' ' echo 'FILE an age-encrypted file' echo ' ' + echo 'PRIVATE_KEY a path to a private SSH key used to decrypt file' + echo ' ' echo 'EDITOR environment variable of editor to use when editing FILE' echo ' ' echo 'RULES environment variable with path to YAML file specifying recipient public keys.' @@ -25,6 +28,7 @@ function show_help () { test $# -eq 0 && (show_help && exit 1) REKEY=0 +DECRYPT=(--decrypt) while test $# -gt 0; do case "$1" in @@ -37,7 +41,17 @@ while test $# -gt 0; do if test $# -gt 0; then export FILE=$1 else - echo "no file specified" + echo "no FILE specified" + exit 1 + fi + shift + ;; + -i|--identity) + shift + if test $# -gt 0; then + DECRYPT+=(--identity "$1") + else + echo "no PRIVATE_KEY specified" exit 1 fi shift @@ -81,7 +95,6 @@ function edit { if [ -f "$FILE" ] then - DECRYPT=(--decrypt) while IFS= read -r key do DECRYPT+=(--identity "$key") diff --git a/pkgs/agenix.sh b/pkgs/agenix.sh deleted file mode 100644 index 5714afe..0000000 --- a/pkgs/agenix.sh +++ /dev/null @@ -1,49 +0,0 @@ -#! /usr/bin/env nix-shell -#! nix-shell -i bash -p age yq-go moreutils - -while test $# -gt 0; do - case "$1" in - -h|--help) - echo "$package - attempt to capture frames" - echo " " - echo "$package [options] application [arguments]" - echo " " - echo "options:" - echo "-h, --help show brief help" - echo "-a, --action=ACTION specify an action to use" - echo "-o, --output-dir=DIR specify a directory to store output in" - exit 0 - ;; - -a) - shift - if test $# -gt 0; then - export PROCESS=$1 - else - echo "no process specified" - exit 1 - fi - shift - ;; - --action*) - export PROCESS=`echo $1 | sed -e 's/^[^=]*=//g'` - shift - ;; - -o) - shift - if test $# -gt 0; then - export OUTPUT=$1 - else - echo "no output dir specified" - exit 1 - fi - shift - ;; - --output-dir*) - export OUTPUT=`echo $1 | sed -e 's/^[^=]*=//g'` - shift - ;; - *) - break - ;; - esac -done