agenix/README.md

252 lines
7.5 KiB
Markdown
Raw Normal View History

2020-12-18 20:49:50 +03:00
# agenix - [age](https://github.com/FiloSottile/age)-encrypted secrets for NixOS
2020-09-03 22:03:01 +03:00
2020-12-18 20:49:50 +03: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 22:03:01 +03:00
2020-12-18 20:49:50 +03: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 23:16:44 +03:00
## Features
2020-09-03 22:03:01 +03:00
* Secrets are encrypted with SSH keys
2020-09-03 23:18:21 +03: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 22:03:01 +03:00
* No GPG
* Very little code, so it should be easy for you to audit
2020-09-04 07:12:02 +03:00
* Encrypted secrets are stored in the Nix store, so a separate distribution mechanism is not necessary
2020-09-03 22:03:01 +03:00
2020-12-18 21:09:17 +03:00
## Notices
* The `age` module will only work if you use NixOS with [commit e6b8587](https://github.com/NixOS/nixpkgs/commit/e6b8587b25a19528695c5c270e6ff1c209705c31) which is included in the latest `nixos-20.09` or `nixos-unstable` releases.
* 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 21:09:17 +03:00
2020-09-03 23:16:44 +03:00
## Installation
2020-09-03 22:03:01 +03:00
Choose one of the following methods:
2020-09-03 23:16:44 +03:00
### [niv](https://github.com/nmattia/niv) (Current recommendation)
2020-09-03 22:03:01 +03:00
First add it to niv:
2020-12-18 20:49:50 +03:00
```ShellSession
2020-09-03 22:03:01 +03:00
$ niv add ryantm/agenix
```
2020-09-03 23:16:44 +03:00
#### Module
Then add the following to your configuration.nix in the `imports` list:
2020-09-03 22:03:01 +03:00
```nix
{
2021-12-01 02:08:57 +03:00
imports = [ "${(import ./nix/sources.nix).agenix}/modules/age.nix" ];
2020-09-03 22:03:01 +03:00
}
```
2020-09-03 23:16:44 +03:00
### nix-channel
2020-09-03 22:03:01 +03:00
As root run:
2020-12-18 20:49:50 +03:00
```ShellSession
$ nix-channel --add https://github.com/ryantm/agenix/archive/main.tar.gz agenix
2020-09-03 22:03:01 +03:00
$ nix-channel --update
```
Then add the following to your configuration.nix in the `imports` list:
2020-09-03 22:03:01 +03:00
```nix
{
imports = [ <agenix/modules/age.nix> ];
2020-09-03 22:03:01 +03:00
}
```
#### CLI
To install the `agenix` binary:
```nix
{
environment.systemPackages = [ import <agenix> {}.agenix ];
}
```
2020-09-03 23:16:44 +03:00
### fetchTarball
2020-09-03 22:03:01 +03:00
Add the following to your configuration.nix:
2020-09-03 23:16:44 +03:00
```nix
2020-09-03 22:03:01 +03:00
{
2021-11-21 04:30:45 +03:00
imports = [ "${builtins.fetchTarball "https://github.com/ryantm/agenix/archive/main.tar.gz"}/modules/age.nix" ];
2020-09-03 22:03:01 +03: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 02:08:57 +03:00
}}/modules/age.nix"
2020-09-03 22:03:01 +03:00
];
}
```
2020-09-03 23:16:44 +03:00
### Flakes
#### Module
2020-09-03 22:03:01 +03:00
2020-09-03 23:16:44 +03:00
```nix
2020-09-03 22:03:01 +03: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
];
};
};
}
```
2020-09-03 23:16:44 +03:00
#### CLI
2020-09-04 17:13:03 +03:00
You don't need to install it,
2020-09-03 23:16:44 +03:00
2020-12-18 20:49:50 +03:00
```ShellSession
2020-09-03 23:16:44 +03:00
nix run github:ryantm/agenix -- --help
```
2020-09-04 17:13:03 +03:00
but, if you want to (change the system based on your system):
2020-09-04 07:12:02 +03:00
```nix
{
environment.systemPackages = [ agenix.defaultPackage.x86_64-linux ];
}
```
2020-09-03 23:16:44 +03:00
## Tutorial
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 23:16:44 +03:00
2020-12-18 20:49:50 +03:00
```ShellSession
2020-09-03 23:16:44 +03:00
$ mkdir secrets
2020-12-18 22:37:23 +03:00
$ cd secrets
2020-09-04 01:18:20 +03:00
$ touch secrets.nix
2020-09-03 23:16:44 +03: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 01:18:20 +03:00
```nix
let
user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL0idNvgGiucWgup/mP78zyC23uFjYq0evcWdjGQUaBH";
2020-12-18 20:49:50 +03:00
user2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILI6jSq53F/3hEmSs+oq9L4TwOo1PrDMAgcA1uo1CCV/";
users = [ user1 user2 ];
2020-09-04 01:18:20 +03:00
system1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPJDyIr/FSz1cJdcoW69R+NrWzwGK/+3gJpqD1t8L2zE";
2020-12-18 20:49:50 +03:00
system2 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKzxQgondgEYcLpcPdJLrTdNgZ2gznOHCAxMdaceTUT1";
systems = [ system1 system2 ];
2020-09-04 01:18:20 +03:00
in
{
"secret1.age".publicKeys = [ user1 system1 ];
2020-12-18 20:49:50 +03:00
"secret2.age".publicKeys = users ++ systems;
2020-09-04 01:18:20 +03:00
}
2020-09-03 23:16:44 +03:00
```
4. Edit secret files (these instructions assume your SSH private key is in ~/.ssh/):
2020-12-18 20:49:50 +03:00
```ShellSession
2020-09-03 23:16:44 +03:00
$ agenix -e secret1.age
```
5. Add secret to a NixOS module config:
2020-09-03 23:16:44 +03:00
```nix
2020-09-04 07:12:02 +03:00
age.secrets.secret1.file = ../secrets/secret1.age;
2020-09-03 23:16:44 +03:00
```
6. NixOS rebuild or use your deployment tool like usual.
2020-09-03 23:16:44 +03: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 21:47:48 +03:00
2020-09-03 23:16:44 +03:00
## Rekeying
2020-09-04 01:18:20 +03:00
If you change the public keys in `secrets.nix`, you should rekey your
2020-09-03 23:16:44 +03:00
secrets:
2020-12-18 20:49:50 +03:00
```ShellSession
2020-09-03 23:16:44 +03: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 20:49:50 +03: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 23:16:44 +03: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 02:18:47 +03: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 23:16:44 +03:00
## Threat model/Warnings
2020-09-03 23:35:15 +03:00
This project has not be audited by a security professional.
2020-09-03 23:16:44 +03: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 20:49:50 +03:00
the secret files can modify secrets because public keys are exposed.
2020-09-03 23:16:44 +03:00
This seems like not a problem on the first glance because changing the
2020-12-18 20:49:50 +03: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 23:16:44 +03: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 22:03:01 +03:00
2020-12-18 20:49:50 +03: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.