vuls: init at 0.27.0
[NixPkgs.git] / nixos / modules / services / networking / rosenpass.nix
blob92ecc1cb31a369fc28aa97a44d8a1ac455ce325b
1 { config
2 , lib
3 , options
4 , pkgs
5 , ...
6 }:
7 let
8   inherit (lib)
9     attrValues
10     concatLines
11     concatMap
12     filter
13     filterAttrsRecursive
14     flatten
15     getExe
16     mkIf
17     optional
18     ;
20   cfg = config.services.rosenpass;
21   opt = options.services.rosenpass;
22   settingsFormat = pkgs.formats.toml { };
25   options.services.rosenpass =
26     let
27       inherit (lib)
28         literalExpression
29         mkOption
30         ;
31       inherit (lib.types)
32         enum
33         listOf
34         nullOr
35         path
36         str
37         submodule
38         ;
39     in
40     {
41       enable = lib.mkEnableOption "Rosenpass";
43       package = lib.mkPackageOption pkgs "rosenpass" { };
45       defaultDevice = mkOption {
46         type = nullOr str;
47         description = "Name of the network interface to use for all peers by default.";
48         example = "wg0";
49       };
51       settings = mkOption {
52         type = submodule {
53           freeformType = settingsFormat.type;
55           options = {
56             public_key = mkOption {
57               type = path;
58               description = "Path to a file containing the public key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
59             };
61             secret_key = mkOption {
62               type = path;
63               description = "Path to a file containing the secret key of the local Rosenpass peer. Generate this by running {command}`rosenpass gen-keys`.";
64             };
66             listen = mkOption {
67               type = listOf str;
68               description = "List of local endpoints to listen for connections.";
69               default = [ ];
70               example = literalExpression "[ \"0.0.0.0:10000\" ]";
71             };
73             verbosity = mkOption {
74               type = enum [ "Verbose" "Quiet" ];
75               default = "Quiet";
76               description = "Verbosity of output produced by the service.";
77             };
79             peers =
80               let
81                 peer = submodule {
82                   freeformType = settingsFormat.type;
84                   options = {
85                     public_key = mkOption {
86                       type = path;
87                       description = "Path to a file containing the public key of the remote Rosenpass peer.";
88                     };
90                     endpoint = mkOption {
91                       type = nullOr str;
92                       default = null;
93                       description = "Endpoint of the remote Rosenpass peer.";
94                     };
96                     device = mkOption {
97                       type = str;
98                       default = cfg.defaultDevice;
99                       defaultText = literalExpression "config.${opt.defaultDevice}";
100                       description = "Name of the local WireGuard interface to use for this peer.";
101                     };
103                     peer = mkOption {
104                       type = str;
105                       description = "WireGuard public key corresponding to the remote Rosenpass peer.";
106                     };
107                   };
108                 };
109               in
110               mkOption {
111                 type = listOf peer;
112                 description = "List of peers to exchange keys with.";
113                 default = [ ];
114               };
115           };
116         };
117         default = { };
118         description = "Configuration for Rosenpass, see <https://rosenpass.eu/> for further information.";
119       };
120     };
122   config = mkIf cfg.enable {
123     warnings =
124       let
125         # NOTE: In the descriptions below, we tried to refer to e.g.
126         # options.systemd.network.netdevs."<name>".wireguardPeers.*.PublicKey
127         # directly, but don't know how to traverse "<name>" and * in this path.
128         extractions = [
129           {
130             relevant = config.systemd.network.enable;
131             root = config.systemd.network.netdevs;
132             peer = (x: x.wireguardPeers);
133             key = x: x.PublicKey or null;
134             description = "${options.systemd.network.netdevs}.\"<name>\".wireguardPeers.*.PublicKey";
135           }
136           {
137             relevant = config.networking.wireguard.enable;
138             root = config.networking.wireguard.interfaces;
139             peer = (x: x.peers);
140             key = (x: x.publicKey);
141             description = "${options.networking.wireguard.interfaces}.\"<name>\".peers.*.publicKey";
142           }
143           rec {
144             relevant = root != { };
145             root = config.networking.wg-quick.interfaces;
146             peer = (x: x.peers);
147             key = (x: x.publicKey);
148             description = "${options.networking.wg-quick.interfaces}.\"<name>\".peers.*.publicKey";
149           }
150         ];
151         relevantExtractions = filter (x: x.relevant) extractions;
152         extract = { root, peer, key, ... }:
153           filter (x: x != null) (flatten (concatMap (x: (map key (peer x))) (attrValues root)));
154         configuredKeys = flatten (map extract relevantExtractions);
155         itemize = xs: concatLines (map (x: " - ${x}") xs);
156         descriptions = map (x: "`${x.description}`");
157         missingKeys = filter (key: !builtins.elem key configuredKeys) (map (x: x.peer) cfg.settings.peers);
158         unusual = ''
159           While this may work as expected, e.g. you want to manually configure WireGuard,
160           such a scenario is unusual. Please double-check your configuration.
161         '';
162       in
163       (optional (relevantExtractions != [ ] && missingKeys != [ ]) ''
164         You have configured Rosenpass peers with the WireGuard public keys:
165         ${itemize missingKeys}
166         But there is no corresponding active Wireguard peer configuration in any of:
167         ${itemize (descriptions relevantExtractions)}
168         ${unusual}
169       '')
170       ++
171       optional (relevantExtractions == [ ]) ''
172         You have configured Rosenpass, but you have not configured Wireguard via any of:
173         ${itemize (descriptions extractions)}
174         ${unusual}
175       '';
177     environment.systemPackages = [ cfg.package pkgs.wireguard-tools ];
179     systemd.services.rosenpass =
180       let
181         filterNonNull = filterAttrsRecursive (_: v: v != null);
182         config = settingsFormat.generate "config.toml" (
183           filterNonNull (cfg.settings
184             //
185             (
186               let
187                 credentialPath = id: "$CREDENTIALS_DIRECTORY/${id}";
188                 # NOTE: We would like to remove all `null` values inside `cfg.settings`
189                 # recursively, since `settingsFormat.generate` cannot handle `null`.
190                 # This would require to traverse both attribute sets and lists recursively.
191                 # `filterAttrsRecursive` only recurses into attribute sets, but not
192                 # into values that might contain other attribute sets (such as lists,
193                 # e.g. `cfg.settings.peers`). Here, we just specialize on `cfg.settings.peers`,
194                 # and this may break unexpectedly whenever a `null` value is contained
195                 # in a list in `cfg.settings`, other than `cfg.settings.peers`.
196                 peersWithoutNulls = map filterNonNull cfg.settings.peers;
197               in
198               {
199                 secret_key = credentialPath "pqsk";
200                 public_key = credentialPath "pqpk";
201                 peers = peersWithoutNulls;
202               }
203             )
204           )
205         );
206       in
207       rec {
208         wantedBy = [ "multi-user.target" ];
209         wants = [ "network-online.target" ];
210         after = [ "network-online.target" ];
211         path = [ cfg.package pkgs.wireguard-tools ];
213         serviceConfig = {
214           User = "rosenpass";
215           Group = "rosenpass";
216           RuntimeDirectory = "rosenpass";
217           DynamicUser = true;
218           AmbientCapabilities = [ "CAP_NET_ADMIN" ];
219           LoadCredential = [
220             "pqsk:${cfg.settings.secret_key}"
221             "pqpk:${cfg.settings.public_key}"
222           ];
223         };
225         # See <https://www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers>
226         environment.CONFIG = "%t/${serviceConfig.RuntimeDirectory}/config.toml";
228         script = ''
229           ${getExe pkgs.envsubst} -i ${config} -o "$CONFIG"
230           rosenpass exchange-config "$CONFIG"
231         '';
232       };
233   };