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```