1{
2 config,
3 lib,
4 pkgs,
5 buildEnv,
6 ...
7}:
8
9let
10 cfg = config.services.peering-manager;
11
12 pythonFmt = pkgs.formats.pythonVars { };
13 settingsFile = pythonFmt.generate "peering-manager-settings.py" cfg.settings;
14 extraConfigFile = pkgs.writeTextFile {
15 name = "peering-manager-extraConfig.py";
16 text = cfg.extraConfig;
17 };
18 configFile = pkgs.concatText "configuration.py" [
19 settingsFile
20 extraConfigFile
21 ];
22
23 pkg =
24 (pkgs.peering-manager.overrideAttrs (old: {
25 postInstall =
26 ''
27 ln -s ${configFile} $out/opt/peering-manager/peering_manager/configuration.py
28 ''
29 + lib.optionalString cfg.enableLdap ''
30 ln -s ${cfg.ldapConfigPath} $out/opt/peering-manager/peering_manager/ldap_config.py
31 ''
32 + lib.optionalString cfg.enableOidc ''
33 ln -s ${cfg.oidcConfigPath} $out/opt/peering-manager/peering_manager/oidc_config.py
34 '';
35 })).override
36 {
37 inherit (cfg) plugins;
38 };
39 peeringManagerManageScript = pkgs.writeScriptBin "peering-manager-manage" ''
40 #!${pkgs.stdenv.shell}
41 export PYTHONPATH=${pkg.pythonPath}
42 sudo -u peering-manager ${pkg}/bin/peering-manager "$@"
43 '';
44
45in
46{
47 options.services.peering-manager = with lib; {
48 enable = mkOption {
49 type = types.bool;
50 default = false;
51 description = ''
52 Enable Peering Manager.
53
54 This module requires a reverse proxy that serves `/static` separately.
55 See this [example](https://github.com/peering-manager/contrib/blob/main/nginx.conf) on how to configure this.
56 '';
57 };
58
59 enableScheduledTasks = mkOption {
60 type = types.bool;
61 default = true;
62 description = ''
63 Set up [scheduled tasks](https://peering-manager.readthedocs.io/en/stable/setup/8-scheduled-tasks/)
64 '';
65 };
66
67 listenAddress = mkOption {
68 type = types.str;
69 default = "[::1]";
70 description = ''
71 Address the server will listen on.
72 '';
73 };
74
75 port = mkOption {
76 type = types.port;
77 default = 8001;
78 description = ''
79 Port the server will listen on.
80 '';
81 };
82
83 plugins = mkOption {
84 type = types.functionTo (types.listOf types.package);
85 default = _: [ ];
86 defaultText = literalExpression ''
87 python3Packages: with python3Packages; [];
88 '';
89 description = ''
90 List of plugin packages to install.
91 '';
92 };
93
94 secretKeyFile = mkOption {
95 type = types.path;
96 description = ''
97 Path to a file containing the secret key.
98 '';
99 };
100
101 peeringdbApiKeyFile = mkOption {
102 type = with types; nullOr path;
103 default = null;
104 description = ''
105 Path to a file containing the PeeringDB API key.
106 '';
107 };
108
109 settings = lib.mkOption {
110 description = ''
111 Configuration options to set in `configuration.py`.
112 See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
113 '';
114
115 default = { };
116
117 type = lib.types.submodule {
118 freeformType = pythonFmt.type;
119
120 options = {
121 ALLOWED_HOSTS = lib.mkOption {
122 type = with lib.types; listOf str;
123 default = [ "*" ];
124 description = ''
125 A list of valid fully-qualified domain names (FQDNs) and/or IP
126 addresses that can be used to reach the peering manager service.
127 '';
128 };
129 };
130 };
131 };
132
133 extraConfig = mkOption {
134 type = types.lines;
135 default = "";
136 description = ''
137 Additional lines of configuration appended to the `configuration.py`.
138 See the [documentation](https://peering-manager.readthedocs.io/en/stable/configuration/optional-settings/) for more possible options.
139 '';
140 };
141
142 enableLdap = mkOption {
143 type = types.bool;
144 default = false;
145 description = ''
146 Enable LDAP-Authentication for Peering Manager.
147
148 This requires a configuration file being pass through `ldapConfigPath`.
149 '';
150 };
151
152 ldapConfigPath = mkOption {
153 type = types.path;
154 description = ''
155 Path to the Configuration-File for LDAP-Authentication, will be loaded as `ldap_config.py`.
156 See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6-ldap/#configuration) for possible options.
157 '';
158 };
159
160 enableOidc = mkOption {
161 type = types.bool;
162 default = false;
163 description = ''
164 Enable OIDC-Authentication for Peering Manager.
165
166 This requires a configuration file being pass through `oidcConfigPath`.
167 '';
168 };
169
170 oidcConfigPath = mkOption {
171 type = types.path;
172 description = ''
173 Path to the Configuration-File for OIDC-Authentication, will be loaded as `oidc_config.py`.
174 See the [documentation](https://peering-manager.readthedocs.io/en/stable/setup/6b-oidc/#configuration) for possible options.
175 '';
176 };
177 };
178
179 config = lib.mkIf cfg.enable {
180 services.peering-manager = {
181 settings = {
182 DATABASE = {
183 NAME = "peering-manager";
184 USER = "peering-manager";
185 HOST = "/run/postgresql";
186 };
187
188 # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate
189 # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended
190 # to use two separate database IDs.
191 REDIS = {
192 tasks = {
193 UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
194 DATABASE = 0;
195 };
196 caching = {
197 UNIX_SOCKET_PATH = config.services.redis.servers.peering-manager.unixSocket;
198 DATABASE = 1;
199 };
200 };
201 };
202
203 extraConfig =
204 ''
205 with open("${cfg.secretKeyFile}", "r") as file:
206 SECRET_KEY = file.readline()
207 ''
208 + lib.optionalString (cfg.peeringdbApiKeyFile != null) ''
209 with open("${cfg.peeringdbApiKeyFile}", "r") as file:
210 PEERINGDB_API_KEY = file.readline()
211 '';
212
213 plugins = (
214 ps:
215 (lib.optionals cfg.enableLdap [ ps.django-auth-ldap ])
216 ++ (lib.optionals cfg.enableOidc (
217 with ps;
218 [
219 mozilla-django-oidc
220 pyopenssl
221 josepy
222 ]
223 ))
224 );
225 };
226
227 system.build.peeringManagerPkg = pkg;
228
229 services.redis.servers.peering-manager.enable = true;
230
231 services.postgresql = {
232 enable = true;
233 ensureDatabases = [ "peering-manager" ];
234 ensureUsers = [
235 {
236 name = "peering-manager";
237 ensureDBOwnership = true;
238 }
239 ];
240 };
241
242 environment.systemPackages = [ peeringManagerManageScript ];
243
244 systemd.targets.peering-manager = {
245 description = "Target for all Peering Manager services";
246 wantedBy = [ "multi-user.target" ];
247 wants = [ "network-online.target" ];
248 after = [
249 "network-online.target"
250 "redis-peering-manager.service"
251 ];
252 };
253
254 systemd.services =
255 let
256 defaults = {
257 environment = {
258 PYTHONPATH = pkg.pythonPath;
259 };
260 serviceConfig = {
261 WorkingDirectory = "/var/lib/peering-manager";
262 User = "peering-manager";
263 Group = "peering-manager";
264 StateDirectory = "peering-manager";
265 StateDirectoryMode = "0750";
266 Restart = "on-failure";
267 };
268 };
269 in
270 {
271 peering-manager-migration = lib.recursiveUpdate defaults {
272 description = "Peering Manager migrations";
273 wantedBy = [ "peering-manager.target" ];
274 serviceConfig = {
275 Type = "oneshot";
276 ExecStart = "${pkg}/bin/peering-manager migrate";
277 };
278 };
279
280 peering-manager = lib.recursiveUpdate defaults {
281 description = "Peering Manager WSGI Service";
282 wantedBy = [ "peering-manager.target" ];
283 after = [ "peering-manager-migration.service" ];
284
285 preStart = ''
286 ${pkg}/bin/peering-manager remove_stale_contenttypes --no-input
287 '';
288
289 serviceConfig = {
290 ExecStart = ''
291 ${pkg.python.pkgs.gunicorn}/bin/gunicorn peering_manager.wsgi \
292 --bind ${cfg.listenAddress}:${toString cfg.port} \
293 --pythonpath ${pkg}/opt/peering-manager
294 '';
295 };
296 };
297
298 peering-manager-rq = lib.recursiveUpdate defaults {
299 description = "Peering Manager Request Queue Worker";
300 wantedBy = [ "peering-manager.target" ];
301 after = [ "peering-manager.service" ];
302 serviceConfig.ExecStart = "${pkg}/bin/peering-manager rqworker high default low";
303 };
304
305 peering-manager-housekeeping = lib.recursiveUpdate defaults {
306 description = "Peering Manager housekeeping job";
307 after = [ "peering-manager.service" ];
308 serviceConfig = {
309 Type = "oneshot";
310 ExecStart = "${pkg}/bin/peering-manager housekeeping";
311 };
312 };
313
314 peering-manager-peeringdb-sync = lib.recursiveUpdate defaults {
315 description = "PeeringDB sync";
316 after = [ "peering-manager.service" ];
317 serviceConfig = {
318 Type = "oneshot";
319 ExecStart = "${pkg}/bin/peering-manager peeringdb_sync";
320 };
321 };
322
323 peering-manager-prefix-fetch = lib.recursiveUpdate defaults {
324 description = "Fetch IRR AS-SET prefixes";
325 after = [ "peering-manager.service" ];
326 serviceConfig = {
327 Type = "oneshot";
328 ExecStart = "${pkg}/bin/peering-manager grab_prefixes";
329 };
330 };
331
332 peering-manager-configuration-deployment = lib.recursiveUpdate defaults {
333 description = "Push configuration to routers";
334 after = [ "peering-manager.service" ];
335 serviceConfig = {
336 Type = "oneshot";
337 ExecStart = "${pkg}/bin/peering-manager configure_routers";
338 };
339 };
340
341 peering-manager-session-poll = lib.recursiveUpdate defaults {
342 description = "Poll peering sessions from routers";
343 after = [ "peering-manager.service" ];
344 serviceConfig = {
345 Type = "oneshot";
346 ExecStart = "${pkg}/bin/peering-manager poll_bgp_sessions --all";
347 };
348 };
349 };
350
351 systemd.timers = {
352 peering-manager-housekeeping = {
353 description = "Run Peering Manager housekeeping job";
354 wantedBy = [ "timers.target" ];
355 timerConfig.OnCalendar = "daily";
356 };
357
358 peering-manager-peeringdb-sync = {
359 enable = lib.mkDefault cfg.enableScheduledTasks;
360 description = "Sync PeeringDB at 2:30";
361 wantedBy = [ "timers.target" ];
362 timerConfig.OnCalendar = "02:30:00";
363 };
364
365 peering-manager-prefix-fetch = {
366 enable = lib.mkDefault cfg.enableScheduledTasks;
367 description = "Fetch IRR AS-SET prefixes at 4:30";
368 wantedBy = [ "timers.target" ];
369 timerConfig.OnCalendar = "04:30:00";
370 };
371
372 peering-manager-configuration-deployment = {
373 enable = lib.mkDefault cfg.enableScheduledTasks;
374 description = "Push router configuration every hour 5 minutes before full hour";
375 wantedBy = [ "timers.target" ];
376 timerConfig.OnCalendar = "*:55:00";
377 };
378
379 peering-manager-session-poll = {
380 enable = lib.mkDefault cfg.enableScheduledTasks;
381 description = "Poll peering sessions from routers every hour";
382 wantedBy = [ "timers.target" ];
383 timerConfig.OnCalendar = "*:00:00";
384 };
385 };
386
387 users.users.peering-manager = {
388 home = "/var/lib/peering-manager";
389 isSystemUser = true;
390 group = "peering-manager";
391 };
392 users.groups.peering-manager = { };
393 users.groups."${config.services.redis.servers.peering-manager.user}".members = [
394 "peering-manager"
395 ];
396 };
397
398 meta.maintainers = with lib.maintainers; [ yuka ];
399}