2020-12-18 18:49:50 +01:00
# agenix - [age](https://github.com/FiloSottile/age)-encrypted secrets for NixOS
2020-09-03 21:03:01 +02:00
2020-12-18 18:49:50 +01:00
`agenix` is a commandline tool for managing secrets encrypted with your existing SSH keys. This project also includes the NixOS module `age` for adding encrypted secrets into the Nix store and decrypting them.
2020-09-03 21:03:01 +02:00
2021-12-29 19:13:26 +01:00
## Contents
* [Problem and solution ](#problem-and-solution )
* [Features ](#features )
* [Installation ](#installation )
* [niv ](#install-via-niv ) (Current recommendation)
* [module ](#install-module-via-niv )
* [CLI ](#install-cli-via-niv )
* [nix-channel ](#install-via-nix-channel )
* [module ](#install-module-via-nix-channel )
* [CLI ](#install-cli-via-nix-channel )
* [fetchTarball ](#install-via-fetchtarball )
* [module ](#install-module-via-fetchtarball )
* [CLI ](#install-cli-via-fetchTarball )
* [flakes ](#install-via-flakes )
* [module ](#install-module-via-flakes )
* [CLI ](#install-cli-via-flakes )
* [Tutorial ](#tutorial )
* [Rekeying ](#rekeying )
* [Don't symlink secret ](#dont-symlink-secret )
* [Use other implementations ](#use-other-implementations )
* [Threat model/Warnings ](#threat-modelwarnings )
* [Acknowledgements ](#acknowledgements )
2020-12-18 18:49:50 +01:00
## Problem and solution
All files in the Nix store are readable by any system user, so it is not a suitable place for including cleartext secrets. Many existing tools (like NixOps deployment.keys) deploy secrets separately from `nixos-rebuild` , making deployment, caching, and auditing more difficult. Out-of-band secret management is also less reproducible.
`agenix` solves these issues by using your pre-existing SSH key infrastructure and `age` to encrypt secrets into the Nix store. Secrets are decrypted using an SSH host private key during NixOS system activation.
2020-09-03 22:16:44 +02:00
## Features
2020-09-03 21:03:01 +02:00
* Secrets are encrypted with SSH keys
2020-09-03 22:18:21 +02:00
* system public keys via `ssh-keyscan`
* can use public keys available on GitHub for users (for example, https://github.com/ryantm.keys)
2020-09-03 21:03:01 +02:00
* No GPG
* Very little code, so it should be easy for you to audit
2020-09-04 06:12:02 +02:00
* Encrypted secrets are stored in the Nix store, so a separate distribution mechanism is not necessary
2020-09-03 21:03:01 +02:00
2020-12-18 19:09:17 +01:00
## Notices
2020-12-19 00:40:34 +01:00
* Password-protected ssh keys: since the underlying tool age/rage do not support ssh-agent, password-protected ssh keys do not work well. For example, if you need to rekey 20 secrets you will have to enter your password 20 times.
2020-12-18 19:09:17 +01:00
2020-09-03 22:16:44 +02:00
## Installation
2020-09-03 21:03:01 +02:00
Choose one of the following methods:
2021-12-29 19:13:26 +01:00
* [niv ](#install-via-niv ) (Current recommendation)
* [nix-channel ](#install-via-nix-channel )
* [fetchTarball ](#install-via-fetchTarball )
* [flakes ](#install-via-flakes )
### Install via [niv](https://github.com/nmattia/niv)
2020-09-03 21:03:01 +02:00
First add it to niv:
2020-12-18 18:49:50 +01:00
```ShellSession
2020-09-03 21:03:01 +02:00
$ niv add ryantm/agenix
```
2021-12-29 19:13:26 +01:00
#### Install module via niv
2020-09-03 22:16:44 +02:00
2021-12-29 19:13:26 +01:00
Then add the following to your `configuration.nix` in the `imports` list:
2020-09-03 21:03:01 +02:00
```nix
{
2021-12-01 00:08:57 +01:00
imports = [ "${(import ./nix/sources.nix).agenix}/modules/age.nix" ];
2020-09-03 21:03:01 +02:00
}
```
2021-12-29 19:13:26 +01:00
#### Install CLI via niv
To install the `agenix` binary:
```nix
{
2021-12-29 19:20:00 +01:00
environment.systemPackages = [ (pkgs.callPackage "${(import ./nix/sources.nix).agenix}/pkgs/agenix.nix" {}) ];
2021-12-29 19:13:26 +01:00
}
```
### Install via nix-channel
2020-09-03 21:03:01 +02:00
2021-12-29 19:13:26 +01:00
As root run:
2020-09-03 21:03:01 +02:00
2020-12-18 18:49:50 +01:00
```ShellSession
2021-12-29 19:13:26 +01:00
$ sudo nix-channel --add https://github.com/ryantm/agenix/archive/main.tar.gz agenix
$ sudo nix-channel --update
2020-09-03 21:03:01 +02:00
```
2021-12-29 19:13:26 +01:00
#### Install module via nix-channel
Then add the following to your `configuration.nix` in the `imports` list:
2020-09-03 21:03:01 +02:00
```nix
{
2021-08-01 13:26:50 +02:00
imports = [ < agenix / modules / age . nix > ];
2020-09-03 21:03:01 +02:00
}
```
2021-12-29 19:13:26 +01:00
#### Install CLI via nix-channel
2021-10-16 18:04:16 +02:00
2021-12-29 19:13:26 +01:00
To install the `agenix` binary:
2021-10-16 18:04:16 +02:00
```nix
{
2021-12-29 19:13:26 +01:00
environment.systemPackages = [ (pkgs.callPackage < agenix / pkgs / agenix . nix > {}) ];
2021-10-16 18:04:16 +02:00
}
```
2021-12-29 19:13:26 +01:00
### Install via fetchTarball
#### Install module via fetchTarball
2020-09-03 21:03:01 +02:00
2021-12-29 19:13:26 +01:00
Add the following to your configuration.nix:
2020-09-03 21:03:01 +02:00
2020-09-03 22:16:44 +02:00
```nix
2020-09-03 21:03:01 +02:00
{
2021-11-21 02:30:45 +01:00
imports = [ "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/modules/age.nix" ];
2020-09-03 21:03:01 +02:00
}
```
or with pinning:
```nix
{
imports = let
# replace this with an actual commit id or tag
commit = "298b235f664f925b433614dc33380f0662adfc3f";
in [
"${builtins.fetchTarball {
url = "https://github.com/ryantm/agenix/archive/${commit}.tar.gz";
# replace this with an actual hash
sha256 = "0000000000000000000000000000000000000000000000000000";
2021-12-01 00:08:57 +01:00
}}/modules/age.nix"
2020-09-03 21:03:01 +02:00
];
}
```
2021-12-29 19:13:26 +01:00
#### Install CLI via fetchTarball
To install the `agenix` binary:
```nix
{
environment.systemPackages = [ (pkgs.callPackage "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/pkgs/agenix.nix" {}) ];
}
```
### Install via Flakes
2020-09-03 22:16:44 +02:00
2021-12-29 19:13:26 +01:00
#### Install module via Flakes
2020-09-03 21:03:01 +02:00
2020-09-03 22:16:44 +02:00
```nix
2020-09-03 21:03:01 +02:00
{
inputs.agenix.url = "github:ryantm/agenix";
# optional, not necessary for the module
#inputs .agenix.inputs.nixpkgs.follows = "nixpkgs";
outputs = { self, nixpkgs, agenix }: {
# change `yourhostname` to your actual hostname
nixosConfigurations.yourhostname = nixpkgs.lib.nixosSystem {
# change to your system:
system = "x86_64-linux";
modules = [
./configuration.nix
agenix.nixosModules.age
];
};
};
}
```
2021-12-29 19:13:26 +01:00
#### Install CLI via Flakes
2020-09-03 22:16:44 +02:00
2020-09-04 16:13:03 +02:00
You don't need to install it,
2020-09-03 22:16:44 +02:00
2020-12-18 18:49:50 +01:00
```ShellSession
2020-09-03 22:16:44 +02:00
nix run github:ryantm/agenix -- --help
```
2020-09-04 16:13:03 +02:00
but, if you want to (change the system based on your system):
2020-09-04 06:12:02 +02:00
```nix
{
environment.systemPackages = [ agenix.defaultPackage.x86_64-linux ];
}
```
2020-09-03 22:16:44 +02:00
## Tutorial
2021-05-13 05:37:06 +02:00
1. The system you want to deploy secrets to should already exist and
have `sshd` running on it so that it has generated SSH host keys in
`/etc/ssh/` .
2. Make a directory to store secrets and `secrets.nix` file for listing secrets and their public keys:
2020-09-03 22:16:44 +02:00
2020-12-18 18:49:50 +01:00
```ShellSession
2020-09-03 22:16:44 +02:00
$ mkdir secrets
2020-12-18 20:37:23 +01:00
$ cd secrets
2020-09-04 00:18:20 +02:00
$ touch secrets.nix
2020-09-03 22:16:44 +02:00
```
2021-05-13 05:37:06 +02:00
3. Add public keys to `secrets.nix` file (hint: use `ssh-keyscan` or GitHub (for example, https://github.com/ryantm.keys)):
2020-09-04 00:18:20 +02:00
```nix
let
user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
2020-12-18 18:49:50 +01:00
user2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILI6jSq53F/3hEmSs+oq9L4TwOo1PrDMAgcA1uo1CCV/";
users = [ user1 user2 ];
2020-09-04 00:18:20 +02:00
system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
2020-12-18 18:49:50 +01:00
system2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzxQgondgEYcLpcPdJLrTdNgZ2gznOHCAxMdaceTUT1";
systems = [ system1 system2 ];
2020-09-04 00:18:20 +02:00
in
{
2020-09-04 06:13:10 +02:00
"secret1.age".publicKeys = [ user1 system1 ];
2020-12-18 18:49:50 +01:00
"secret2.age".publicKeys = users ++ systems;
2020-09-04 00:18:20 +02:00
}
2020-09-03 22:16:44 +02:00
```
2021-05-13 05:37:06 +02:00
4. Edit secret files (these instructions assume your SSH private key is in ~/.ssh/):
2020-12-18 18:49:50 +01:00
```ShellSession
2020-09-03 22:16:44 +02:00
$ agenix -e secret1.age
```
2021-05-13 05:37:06 +02:00
5. Add secret to a NixOS module config:
2020-09-03 22:16:44 +02:00
```nix
2020-09-04 06:12:02 +02:00
age.secrets.secret1.file = ../secrets/secret1.age;
2020-09-03 22:16:44 +02:00
```
2021-05-13 05:37:06 +02:00
6. NixOS rebuild or use your deployment tool like usual.
2020-09-03 22:16:44 +02:00
2021-11-21 15:13:56 +01:00
The secret will be decrypted to the value of `config.age.secrets.secret1.path` (`/run/agenix/secret1` by default). For per-secret options controlling ownership etc, see [modules/age.nix ](modules/age.nix ).
2021-04-08 20:47:48 +02:00
2020-09-03 22:16:44 +02:00
## Rekeying
2020-09-04 00:18:20 +02:00
If you change the public keys in `secrets.nix` , you should rekey your
2020-09-03 22:16:44 +02:00
secrets:
2020-12-18 18:49:50 +01:00
```ShellSession
2020-09-03 22:16:44 +02:00
$ 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
2020-12-18 18:49:50 +01:00
when rekeyed, even if the identities do not. (This eventually could be
improved upon by reading the identities from the age file.)
2020-09-03 22:16:44 +02:00
2021-11-08 18:45:34 +01:00
## Don't symlink secret
If your secret cannot be a symlink, you should set the `symlink` option to `false` :
```nix
{
age.secrets.some-secret = {
file = ./secret;
path = "/var/lib/some-service/some-secret";
symlink = false;
};
}
```
Instead of first decrypting the secret to `/run/agenix` and then symlinking to its `path` , the secret will instead be forcibly moved to its `path` . Please note that, currently, there are no cleanup mechanisms for secrets that are not symlinked by agenix.
2021-12-06 00:18:47 +01:00
## Use other implementations
This project uses the Rust implementation of age, [rage ](https://github.com/str4d/rage ), by default. You can change it to use the [official implementation ](https://github.com/FiloSottile/age ).
### Module
```nix
{
age.ageBin = "${pkgs.age}/bin/age";
}
```
### CLI
```nix
{
environment.systemPackages = [
(agenix.defaultPackage.x86_64-linux.override { ageBin = "${pkgs.age}/bin/age"; })
];
}
```
2020-09-03 22:16:44 +02:00
## Threat model/Warnings
2022-02-02 22:53:46 +01:00
This project has not been audited by a security professional.
2020-09-03 22:16:44 +02:00
People unfamiliar with `age` might be surprised that secrets are not
authenticated. This means that every attacker that has write access to
2020-12-18 18:49:50 +01:00
the secret files can modify secrets because public keys are exposed.
2020-09-03 22:16:44 +02:00
This seems like not a problem on the first glance because changing the
2020-12-18 18:49:50 +01:00
configuration itself could expose secrets easily. However, reviewing
configuration changes is easier than reviewing random secrets (for
example, 4096-bit rsa keys). This would be solved by having a message
2020-09-03 22:16:44 +02:00
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
2020-09-03 21:03:01 +02:00
2020-12-18 18:49:50 +01:00
This project is based off of [sops-nix ](https://github.com/Mic92/sops-nix ) created Mic92. Thank you to Mic92 for inspiration and advice.