1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.spiped;
7in
8{
9 options = {
10 services.spiped = {
11 enable = mkOption {
12 type = types.bool;
13 default = false;
14 description = lib.mdDoc "Enable the spiped service module.";
15 };
16
17 config = mkOption {
18 type = types.attrsOf (types.submodule (
19 {
20 options = {
21 encrypt = mkOption {
22 type = types.bool;
23 default = false;
24 description = lib.mdDoc ''
25 Take unencrypted connections from the
26 `source` socket and send encrypted
27 connections to the `target` socket.
28 '';
29 };
30
31 decrypt = mkOption {
32 type = types.bool;
33 default = false;
34 description = lib.mdDoc ''
35 Take encrypted connections from the
36 `source` socket and send unencrypted
37 connections to the `target` socket.
38 '';
39 };
40
41 source = mkOption {
42 type = types.str;
43 description = lib.mdDoc ''
44 Address on which spiped should listen for incoming
45 connections. Must be in one of the following formats:
46 `/absolute/path/to/unix/socket`,
47 `host.name:port`,
48 `[ip.v4.ad.dr]:port` or
49 `[ipv6::addr]:port` - note that
50 hostnames are resolved when spiped is launched and are
51 not re-resolved later; thus if DNS entries change
52 spiped will continue to connect to the expired
53 address.
54 '';
55 };
56
57 target = mkOption {
58 type = types.str;
59 description = lib.mdDoc "Address to which spiped should connect.";
60 };
61
62 keyfile = mkOption {
63 type = types.path;
64 description = lib.mdDoc ''
65 Name of a file containing the spiped key. As the
66 daemon runs as the `spiped` user, the
67 key file must be somewhere owned by that user. By
68 default, we recommend putting the keys for any spipe
69 services in `/var/lib/spiped`.
70 '';
71 };
72
73 timeout = mkOption {
74 type = types.int;
75 default = 5;
76 description = lib.mdDoc ''
77 Timeout, in seconds, after which an attempt to connect to
78 the target or a protocol handshake will be aborted (and the
79 connection dropped) if not completed
80 '';
81 };
82
83 maxConns = mkOption {
84 type = types.int;
85 default = 100;
86 description = lib.mdDoc ''
87 Limit on the number of simultaneous connections allowed.
88 '';
89 };
90
91 waitForDNS = mkOption {
92 type = types.bool;
93 default = false;
94 description = lib.mdDoc ''
95 Wait for DNS. Normally when `spiped` is
96 launched it resolves addresses and binds to its source
97 socket before the parent process returns; with this option
98 it will daemonize first and retry failed DNS lookups until
99 they succeed. This allows `spiped` to
100 launch even if DNS isn't set up yet, but at the expense of
101 losing the guarantee that once `spiped` has
102 finished launching it will be ready to create pipes.
103 '';
104 };
105
106 disableKeepalives = mkOption {
107 type = types.bool;
108 default = false;
109 description = lib.mdDoc "Disable transport layer keep-alives.";
110 };
111
112 weakHandshake = mkOption {
113 type = types.bool;
114 default = false;
115 description = lib.mdDoc ''
116 Use fast/weak handshaking: This reduces the CPU time spent
117 in the initial connection setup, at the expense of losing
118 perfect forward secrecy.
119 '';
120 };
121
122 resolveRefresh = mkOption {
123 type = types.int;
124 default = 60;
125 description = lib.mdDoc ''
126 Resolution refresh time for the target socket, in seconds.
127 '';
128 };
129
130 disableReresolution = mkOption {
131 type = types.bool;
132 default = false;
133 description = lib.mdDoc "Disable target address re-resolution.";
134 };
135 };
136 }
137 ));
138
139 default = {};
140
141 example = literalExpression ''
142 {
143 pipe1 =
144 { keyfile = "/var/lib/spiped/pipe1.key";
145 encrypt = true;
146 source = "localhost:6000";
147 target = "endpoint.example.com:7000";
148 };
149 pipe2 =
150 { keyfile = "/var/lib/spiped/pipe2.key";
151 decrypt = true;
152 source = "0.0.0.0:7000";
153 target = "localhost:3000";
154 };
155 }
156 '';
157
158 description = lib.mdDoc ''
159 Configuration for a secure pipe daemon. The daemon can be
160 started, stopped, or examined using
161 `systemctl`, under the name
162 `spiped@foo`.
163 '';
164 };
165 };
166 };
167
168 config = mkIf cfg.enable {
169 assertions = mapAttrsToList (name: c: {
170 assertion = (c.encrypt -> !c.decrypt) || (c.decrypt -> c.encrypt);
171 message = "A pipe must either encrypt or decrypt";
172 }) cfg.config;
173
174 users.groups.spiped.gid = config.ids.gids.spiped;
175 users.users.spiped = {
176 description = "Secure Pipe Service user";
177 group = "spiped";
178 uid = config.ids.uids.spiped;
179 };
180
181 systemd.services."spiped@" = {
182 description = "Secure pipe '%i'";
183 after = [ "network.target" ];
184
185 serviceConfig = {
186 Restart = "always";
187 User = "spiped";
188 PermissionsStartOnly = true;
189 };
190
191 preStart = ''
192 cd /var/lib/spiped
193 chmod -R 0660 *
194 chown -R spiped:spiped *
195 '';
196 scriptArgs = "%i";
197 script = "exec ${pkgs.spiped}/bin/spiped -F `cat /etc/spiped/$1.spec`";
198 };
199
200 system.activationScripts.spiped = optionalString (cfg.config != {})
201 "mkdir -p /var/lib/spiped";
202
203 # Setup spiped config files
204 environment.etc = mapAttrs' (name: cfg: nameValuePair "spiped/${name}.spec"
205 { text = concatStringsSep " "
206 [ (if cfg.encrypt then "-e" else "-d") # Mode
207 "-s ${cfg.source}" # Source
208 "-t ${cfg.target}" # Target
209 "-k ${cfg.keyfile}" # Keyfile
210 "-n ${toString cfg.maxConns}" # Max number of conns
211 "-o ${toString cfg.timeout}" # Timeout
212 (optionalString cfg.waitForDNS "-D") # Wait for DNS
213 (optionalString cfg.weakHandshake "-f") # No PFS
214 (optionalString cfg.disableKeepalives "-j") # Keepalives
215 (if cfg.disableReresolution then "-R"
216 else "-r ${toString cfg.resolveRefresh}")
217 ];
218 }) cfg.config;
219 };
220}