ALPHA: wire is a tool to deploy nixos systems wire.althaea.zone/
1--- 2comment: true 3title: Manage Secrets 4description: Manage keys, secrets, files, and other out-of-store paths with wire Tool. 5--- 6 7# {{ $frontmatter.title }} 8 9{{ $frontmatter.description }} 10 11## Introduction 12 13wire Tool is very unopinionated as to how you encrypt your secrets, wire only 14handles pushing and setting up permissions of your key files. 15 16The `source` of your key can be a literal string (unencrypted), a path 17(unencrypted), or a command that wire runs to evaluate the key. Programs that 18work well with wire keys include: 19 20- GPG 21- [Age](https://github.com/FiloSottile/age) 22- Anything that non-interactively decrypts to `stdout`. 23 24### Prerequisites 25 26wire uses a Rust binary to receive encrypted key data, so your deploying 27user must be trusted or you must add garnix as a trusted public key: 28 29```nix 30{ config, ... }: 31{ 32 nix.settings.trusted-users = [ 33 config.deployment.target.user # [!code ++] 34 ]; 35} 36``` 37 38Otherwise, you may see errors such as: 39 40``` 41error: cannot add path '/nix/store/...-wire-tool-agent-x86_64-linux-...' because it lacks a signature by a trusted key 42``` 43 44This is a requirement because `nix copy` is used to copy the binary. 45As a benefit to this approach, key deployments are significantly faster! 46 47### A Trivial "Key" 48 49```nix:line-numbers [hive.nix] 50let 51 sources = import ./npins; 52 wire = import sources.wire; 53in wire.makeHive { 54 meta.nixpkgs = import sources.nixpkgs { }; 55 56 node-1 = { 57 deployment.key."file.txt" = { 58 source = '' 59 Hello World! 60 ''; 61 }; 62 }; 63} 64``` 65 66```sh 67[user@node-1]$ cat /run/keys/file.txt 68Hello World! 69``` 70 71### Encrypting with GPG 72 73```nix:line-numbers [hive.nix] 74let 75 sources = import ./npins; 76 wire = import sources.wire; 77in wire.makeHive { 78 meta.nixpkgs = import sources.nixpkgs { }; 79 80 node-1 = { 81 deployment.key."file.txt" = { 82 source = [ 83 "gpg" 84 "--decrypt" 85 "${./secrets/file.txt.gpg}" 86 ]; 87 }; 88 }; 89} 90``` 91 92```sh 93[user@node-1]$ cat /run/keys/file.txt 94Hello World! 95``` 96 97### Encrypting with KeepassXC 98 99A simple example of extracting a KeepassXC attachment into a wire key. 100You must pass the password through stdin as the command must be non-interactive. 101Note that the `--stdout` is important as wire expects the command to output the key to stdout. 102 103```nix:line-numbers [hive.nix] 104let 105 sources = import ./npins; 106 wire = import sources.wire; 107in wire.makeHive { 108 meta.nixpkgs = import sources.nixpkgs { }; 109 110 node-1 = { 111 deployment.key."file.txt" = { 112 source = [ 113 "bash" 114 "-c" 115 ''cat ~/pass | keepassxc-cli attachment-export --stdout ~/.local/share/keepass/database.kdbx test 'file.txt''' 116 ]; 117 }; 118 }; 119} 120``` 121 122```sh 123[user@node-1]$ cat /run/keys/file.txt 124Hello World! 125``` 126 127### A Plain Text File 128 129```nix:line-numbers [hive.nix] 130let 131 sources = import ./npins; 132 wire = import sources.wire; 133in wire.makeHive { 134 meta.nixpkgs = import sources.nixpkgs { }; 135 136 node-1 = { 137 deployment.key."file.txt" = { 138 # using this syntax will enter the file into the store, readable by 139 # anyone! 140 source = ./file.txt; 141 }; 142 }; 143} 144``` 145 146## Persistence 147 148wire defaults `destDir` to `/run/keys`. `/run/` is held in memory and will not 149persist past reboot. Change 150[`deployment.key.<name>.destDir`](/reference/module#deployment-keys-name-destdir) 151to something like `/etc/keys` if you need secrets every time the machine boots. 152 153## Upload Order 154 155By default wire will upload keys before the system is activated. You can 156force wire to upload the key after the system is activated by setting 157[`deployment.keys.<name>.uploadAt`](/reference/module#deployment-keys-name-uploadat) 158to `post-activation`. 159 160## Permissions and Ownership 161 162wire secrets are owned by user & group `root` (`0600`). You can change these 163with the `user` and `group` option. 164 165```nix:line-numbers [hive.nix] 166let 167 sources = import ./npins; 168 wire = import sources.wire; 169in wire.makeHive { 170 meta.nixpkgs = import sources.nixpkgs { }; 171 172 node-1 = { 173 deployment.key."file.txt" = { 174 source = [ 175 "gpg" 176 "--decrypt" 177 "${./secrets/file.txt.gpg}" 178 ]; 179 180 user = "my-user"; 181 group = "my-group"; 182 }; 183 }; 184} 185``` 186 187## Further Examples 188 189### Using Keys With Services 190 191You can access the full absolute path of any key with 192`config.deployment.keys.<name>.path` (auto-generated and read-only). 193 194Keys also have a `config.deployment.keys.<name>.service` property 195(auto-generated and read-only), which represent systemd services that you can 196`require`, telling systemd there is a hard-dependency on that key for the 197service to run. 198 199Here's an example with the Tailscale service: 200 201```nix:line-numbers [hive.nix] 202let 203 sources = import ./npins; 204 wire = import sources.wire; 205in wire.makeHive { 206 meta.nixpkgs = import sources.nixpkgs { }; 207 208 node-1 = {config, ...}: { 209 services.tailscale = { 210 enable = true; 211 # use deployment key path directly 212 authKeyFile = config.deployment.keys."tailscale.key".path; 213 }; 214 215 deployment.keys."tailscale.key" = { 216 keyCommand = ["gpg" "--decrypt" "${./secrets/tailscale.key.gpg}"]; 217 }; 218 219 # The service will not start unless the key exists. 220 systemd.services.tailscaled-autoconnect.requires = [ 221 config.deployment.keys."tailscale.key".service 222 ]; 223 }; 224} 225``` 226 227### Scoping a Key to a service account 228 229Additionally you can scope the key to the user that the service runs under, to 230further reduce duplication using the `config` argument. Here's an example of 231providing a certificate that is only readable by the caddy service. 232 233```nix:line-numbers [hive.nix] 234let 235 sources = import ./npins; 236 wire = import sources.wire; 237in wire.makeHive { 238 meta.nixpkgs = import sources.nixpkgs { }; 239 240 some-web-server = {config, ...}: { 241 deployment.keys."some.host.pem" = { 242 keyCommand = ["gpg" "--decrypt" "${./some.host.pem.gpg}"]; 243 destDir = "/etc/keys"; 244 245 # inherit the user and group that caddy runs under 246 # the key will only readable by the caddy service 247 inherit (config.services.caddy) user group; 248 }; 249 250 # ^^ repeat for `some.host.key` 251 252 services.caddy = { 253 virtualHosts."https://some.host".extraConfig = '' 254 tls ${config.deployment.keys."some.host.pem".path} ${config.deployment.keys."some.host.key".path} 255 ''; 256 }; 257 }; 258} 259```