blob: ff8ea002d4bf6d9921342c293ebbbb9c7b339eba [file] [log] [blame]
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +02001{ config, pkgs, ... }:
2
3let
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +02004 hw = builtins.fromJSON (builtins.readFile ./hw.json);
5 fw = import ./fw-7535.nix;
6 vuko-pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhaCaC/CVYv6hphqmEdKaPrIn+Q946+myvL9SSnzFZk vuko@eagle";
7 q3k-pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIG599UildOrAq+LIOQjKqtGMwjgjIxozI1jtQQRKHtCP q3k@mimeomia";
8 networks = {
9 uplink = {
10 description = "Hackerspace Internet Uplink";
11 hw_addr = builtins.elemAt fw.hw_addresses 0;
12 ipv4 = "185.236.240.5";
13 ipv6 = "2a0d:eb00:2137:1::3";
14 };
15 lan = {
16 description = "Hackerspace LAN";
17 hw_addr = builtins.elemAt fw.hw_addresses 1;
18 ipv4 = "10.8.1.2";
19 ipv6 = "2a0d:eb00:4242::1";
20 };
21 managment = {
22 description = "Management network (temporary routing)";
23 hw_addr = builtins.elemAt fw.hw_addresses 2;
24 };
25 lte = {
26 description = "temp LTE uplink";
27 hw_addr = builtins.elemAt fw.hw_addresses 3;
28 };
29 vpn = {
30 description = "Hackerspace members vpn";
31 ipv4 = "10.9.1.1";
32 };
33 };
34 hostname = "customs";
35 openvpn-auth = import ./openvpn-auth { inherit pkgs; };
36 secrets-path = "/etc/nixos/secrets/";
37 update_authorized_keys = pkgs.writeShellScriptBin "update_authorized_keys" ''
38 ${pkgs.python3.withPackages (pp: [ pp.ldap3 ])}/bin/python ${./update_authorized_keys.py} ${hostname} ${secrets-path}/ldap-password.txt
39 '';
40
41in {
42 imports =
43 [
44 ./ulogd2/service.nix
45 #./hardware-configuration.nix
46 <nixpkgs/nixos/modules/profiles/minimal.nix>
47 <nixpkgs/nixos/modules/profiles/all-hardware.nix>
Piotr Dobrowolski6f6187c2021-10-16 23:22:22 +020048 ../../../bgpwtf/machines/modules/routing.nix
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +020049 ./checkinator-tracker.nix
50 ./checkinator-web.nix
51 ./mikrotik-exporter.nix
52 ./netboot.nix
53 ./laserproxy/service.nix
54 ];
55
56
57 boot.loader.grub.enable = true;
58 boot.loader.grub.version = 2;
59 boot.loader.grub.device = "nodev";
60 boot.loader.grub.extraConfig = ''
61 serial --unit=0 --speed=115200
62 terminal_input serial
63 terminal_output serial
64 '';
65 boot.kernelParams = ["console=tty0" "console=ttyS0,115200"];
66
67 time.timeZone = "Europe/Warsaw";
68
69 fileSystems."/" = {
70 device = "/dev/disk/by-partuuid/${hw.rootUUID}";
71 fsType = "ext4";
72 };
73
74 services.postfix = let acme_dir = "/var/lib/acme"; in {
75 enable = true;
76 domain = "customs.hackerspace.pl";
77 hostname = "customs.hackerspace.pl";
78 destination = [ "localhost" ];
79 sslCert = "${acme_dir}/customs.hackerspace.pl/full.pem";
80 sslKey = "${acme_dir}/customs.hackerspace.pl/key.pem";
81 enableSmtp = true;
82 enableSubmission = false;
83 #relayHost = "hackerspace.pl";
84 extraConfig = ''
85 inet_interfaces = loopback-only
86 '';
87 };
88
89 fileSystems."/mnt/secrets" = {
90 fsType = "tmpfs";
91 options = [ "rw" "mode=755" "size=200M" "nosuid" "nodev" "relatime" "noexec" ];
92 };
93
94 networking.hostName = hostname;
95 networking.domain = "hackerspace.pl";
96
97 networking.useDHCP = false;
98 networking.vlans = {
99 laser = {
100 id = 4001;
101 interface = "lan";
102 };
103 bms = {
104 id = 4002;
105 interface = "lan";
106 };
107 };
108
109 systemd.services.secrets = {
110 enable = true;
111 description = "Copy secrets and fix permissions";
112 script = ''
113 ${pkgs.coreutils}/bin/install --owner=root --mode=700 --directory /mnt/secrets/nginx/
114 ${pkgs.coreutils}/bin/install --owner=root --mode=400 -t /mnt/secrets/nginx/ \
115 ${secrets-path}/nginx/at.hackerspace.pl.key \
116 ${secrets-path}/nginx/at.hackerspace.pl.crt
117 ${pkgs.acl}/bin/setfacl -m "u:nginx:rx" /mnt/secrets/nginx
118 ${pkgs.acl}/bin/setfacl -m "u:nginx:r" /mnt/secrets/nginx/*
119 '';
120 wantedBy = [ "nginx.service" ];
121 partOf = [ "nginx.service" ];
122 serviceConfig.Type = "oneshot";
123 serviceConfig.RemainAfterExit = "true";
124 serviceConfig.User = "root";
125 };
126
127 services.prometheus.exporters.node = {
128 enable = true;
129 listenAddress = "[::1]";
130 port = 9100;
131 enabledCollectors = [ "systemd" ];
132 };
133
134 systemd.network.links = builtins.listToAttrs (map (
135 name: { name = "10-link-${name}"; value = {
136 enable = true;
137 matchConfig = {
138 MACAddress = networks."${name}".hw_addr;
139 };
140 linkConfig = {
141 Name = "${name}";
142 };
143 }; }
144 ) (builtins.filter (name: builtins.hasAttr "hw_addr" networks."${name}") (builtins.attrNames networks)));
145
146 #networking.interfaces.vpn = {
147 # virtual = true;
148 # name = "vpn";
149 # #ipv4.addresses = [ { address = 10.9.1.1; prefixlen = 16; } ];
150 #};
151
152 boot.kernel.sysctl = {
153 "net.ipv4.ip_forward" = true;
154 "net.ipv6.conf.all.forwarding" = true;
155 };
156
157 # using nftables so firewall has to be disabled
158 networking.firewall.enable = false;
159 networking.nftables.enable = true;
160 networking.nftables.ruleset = ''
161 table inet filter {
162 chain input {
163 type filter hook input priority 0;
164
165 # accept any localhost traffic
166 iifname lo accept
167
168 # accept traffic originated from us
169 ct state {established, related} accept
170
171 # ICMP
172 ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, mld-listener-query, nd-router-solicit } accept
173 ip protocol icmp icmp type { destination-unreachable, router-advertisement, time-exceeded, parameter-problem } accept
174
175 # allow "ping"
176 ip6 nexthdr icmpv6 icmpv6 type echo-request accept
177 ip protocol icmp icmp type echo-request accept
178
179 # allow OSPFv3
180 ip6 nexthdr 89 accept
181
182 tcp dport 22 accept
183 tcp dport 53 accept
184 udp dport 53 accept
185 tcp dport 80 accept
186 tcp dport 443 accept
187 udp dport tftp accept
188
189 iifname managment udp dport tftp accept
190 iifname lan tcp dport 8080 accept
191
192 # openvpn-members
193 udp dport 20001 accept
194 tcp dport 20001 accept
195
196 # laserproxy
197 udp dport 40200 accept
198 udp dport 50200 accept
199
200 counter drop
201 }
202
203 # Allow all outgoing connections.
204 chain output {
205 type filter hook output priority 0; policy accept;
206 }
207
208 chain forward {
209 type filter hook forward priority 0; policy drop;
210 ct state {established, related} jump accepted
211 oifname "loop" jump accepted
212 ip saddr 10.8.0.0/16 iifname "lan" jump accepted
213 ip saddr 10.9.0.0/16 iifname "vpn" jump accepted
214 ip6 saddr 2a0d:eb00:4242::0/64 iifname "lan" jump accepted
215 ip6 saddr 2a0d:eb00:4242:1::0/64 iifname "vpn" jump accepted
216 ip6 saddr 2a0d:eb00:4242:1::1/128 iifname "loop" jump accepted
217 }
218
219 chain accepted {
220 # IMPORTANT
221 # Log all connections to the outside world from LAN interface, as we are
222 # required to do so
223 oifname != "uplink" accept
224 iifname "uplink" accept
225 ip daddr { 10.0.0.0/8, 225.225.225.225/32 } accept
226 ip6 daddr { 2a0d:eb00::/29, fe80::/8 } accept
227 log group 2 accept
228 }
229 }
230
231 table inet net {
232 chain postrouting {
233 type nat hook postrouting priority 100;
234 ip saddr 10.8.0.0/16 oifname uplink snat ${networks.uplink.ipv4}
235 ip saddr 10.9.0.0/16 oifname uplink snat ${networks.uplink.ipv4}
236 }
237
238 chain prerouting {
239 type nat hook prerouting priority -100;
240
241 # Access to staszkecoin from Internet
242 ip version 4 iifname "uplink" tcp dport 8333 dnat 10.8.1.49
243 }
244 }
245 '';
246
247 systemd.services."loop-netdev" = let n = "loop"; in {
248 description = "Dummy interface: loop";
249 wantedBy = [ "network-setup.service" "sys-subsystem-net-devices-${n}.device" ];
250 partOf = [ "network-setup.service" ];
251 after = [ "network-pre.target" ];
252 before = [ "network-setup.service" ];
253 serviceConfig.Type = "oneshot";
254 serviceConfig.RemainAfterExit = true;
255 path = [ pkgs.iproute ];
256 script = ''
257 # Remove Dead Interfaces
258 ip link show "${n}" >/dev/null 2>&1 && ip link delete "${n}"
259 ip link add "${n}" type dummy
260 ip link set "${n}" up
261 '';
262 postStop = ''
263 ip link delete "${n}"
264 '';
265 };
266
267
268 networking.interfaces = {
269 uplink = {
270 ipv4.addresses = [ { address = networks.uplink.ipv4; prefixLength = 31; } ];
271 ipv6.addresses = [
272 { address = networks.uplink.ipv6; prefixLength = 112; }
273 ];
274 };
275 lan = {
276 ipv4.addresses = [ { address = networks.lan.ipv4; prefixLength = 16; } ];
277 ipv6.addresses = [ { address = networks.lan.ipv6; prefixLength = 64; } ];
278 };
279 loop = {
280 ipv6.addresses = [ { address = "2a0d:eb00:4242:1::1"; prefixLength = 128; } ];
281 };
282 laser = {
283 ipv4.addresses = [ { address = "10.11.0.1"; prefixLength = 24; } ];
284 };
285 bms = {
286 ipv4.addresses = [ { address = "10.11.1.1"; prefixLength = 24; } ];
287 };
288 managment = {
289 ipv4.addresses = [ { address = "10.10.1.1"; prefixLength = 24; } ];
290 };
291 lte = {
292 ipv4.addresses = [ { address = "192.168.1.2"; prefixLength = 24; } ];
293 };
294 };
295
296 networking.defaultGateway = {
297 address = "185.236.240.4";
298 interface = "uplink";
299 };
300 networking.defaultGateway6 = {
301 address = "2a0d:eb00:2137:1::1";
302 interface = "uplink";
303 };
304
305
306 networking.nameservers = [ "1.0.0.1" "8.8.8.8" ];
307
308 services.openssh = {
309 enable = true;
310 passwordAuthentication = false;
311 logLevel = "INFO";
312 };
313
314 users.users.root.openssh.authorizedKeys.keys = [ vuko-pubkey q3k-pubkey ];
315
316 services.dhcpd4 = {
317 enable = true;
318 configFile = ./dhcpd.conf;
319 interfaces = ["lan"];
320 };
321
322 hscloud.routing = {
323 enable = true;
324 # TODO(q3k): make this optional in upstream
325 extra = "";
326 routerID = "185.236.240.5";
327 tables.master.program = true;
328 pipe.v6.aggregate_to_kernel = {
329 table = "master";
330 peerTable = "aggregate";
331 filterIn = ''
332 if source = RTS_OSPF then accept;
333 if source = RTS_OSPF_EXT2 then accept;
334 reject;
335 '';
336 };
337 ospf.v6.upstream = {
338 table = "aggregate";
339 area."0.0.0.0" = {
340 interfaces.uplink = { type = "bcast"; };
341 interfaces.lan = { type = "bcast"; stub = true; };
342 interfaces.loop = { type = "ptp"; stub = true; };
343 };
344 };
345 };
346
347 services.radvd = {
348 enable = true;
349 config = ''
350 interface lan {
351 AdvSendAdvert on;
352 prefix 2a0d:eb00:4242::/64 {
353 };
354 route 0::/0 { };
355 };
356 interface vpn {
357 AdvSendAdvert on;
358 prefix 2a0d:eb00:4242:1::/64 {
359 AdvRouterAddr on;
360 };
361 route 0::/0 { };
362 };
363 '';
364 };
365
366 services.logrotate = {
367 enable = true;
368 paths = {
369 ulogd = {
370 enable = true;
371 frequency = "weekly";
372 path = "/var/log/ulogd.pcap";
373 extraConfig = ''
374 postrotate
375 ${pkgs.killall}/bin/killall -HUP ulogd
376 endscript
377 '';
378 keep = 55;
379 };
380 };
381 };
382
383 services.cron = let
384 log-neigh = pkgs.writeShellScript "log-neigh" ''
385 mkdir -p /var/log/arptables
386 chmod 700 /var/log/arptables
387
388 # Larger than 10MB? rotate.
389 if [[ $(find /var/log/arptables/arptables.log -type f -size +10485760c 2>/dev/null) ]]; then
390 f=/var/log/arptables/$(date "+%s").log
391 cp /var/log/arptables/arptables.log $f
392 gzip -9 $f
393 rm /var/log/arptables/arptables.log
394 fi
395
396 ip neigh >> /var/log/arptables/arptables.log
397 date --iso-8601=seconds >> /var/log/arptables/arptables.log
398 '';
399 in {
400 mailto = "vuko@hackerspace.pl";
401 enable = true;
402 systemCronJobs = [
403 "*/5 * * * * root ${log-neigh}"
404 "0 3 * * * root ${update_authorized_keys}/bin/update_authorized_keys"
405 ];
406 };
407
408 services.knot = {
409 enable = true;
410 extraConfig = ''
411 server:
412 listen: ${networks.uplink.ipv4}@53
413 listen: ${networks.uplink.ipv6}@53
414
415 zone:
416 - domain: waw.hackerspace.pl
417 storage: ${./zones}
418 file: waw.hackerspace.pl
419 - domain: i
420 storage: ${./zones}
421 file: i
422 - domain: api.ustream.tv
423 storage: ${./zones}
424 file: api.ustream.tv
425 - domain: api.eye.fi
426 storage: ${./zones}
427 file: api.eye.fi
428 log:
429 - target: syslog
430 any: info
431 '';
432 };
433
434 services.nginx.enable = true;
435 services.nginx.mapHashBucketSize = 64;
436 services.nginx.appendHttpConfig = ''
437 server_names_hash_bucket_size 64;
438 '';
439
440 security.acme = {
441 email = "bofh@hackerspace.pl";
442 acceptTerms = true;
443 };
444
445 services.nginx.virtualHosts."customs.hackerspace.pl" = {
446 default = true;
447 enableACME = true;
448
449 locations."/" = {
450 extraConfig = ''
451 return 302 https://isztar.mf.gov.pl;
452 '';
453 };
454 locations."/metrics/luftdaten" = {
455 proxyPass = "http://10.8.0.146";
456 };
457 locations."/metrics/spejsiot" = {
458 proxyPass = "http://spejsiot.waw.hackerspace.pl/metrics";
459 };
460 locations."/metrics/apm" = {
461 proxyPass = "http://10.8.1.40:5000/metrics";
462 };
463 locations."/metrics/vending" = {
464 proxyPass = "http://10.8.1.32:8000/";
465 };
466 locations."/metrics/sztancarka" = {
467 proxyPass = "http://10.8.0.96:8888/";
468 };
469 locations."/metrics/mikrotik" = {
470 proxyPass = "http://127.0.0.1:9436/metrics";
471 extraConfig = ''
472 allow 209.250.231.127;
473 deny all;
474 '';
475 };
476 locations."/metrics/node" = {
477 proxyPass = "http://[::1]:9100/metrics";
478 extraConfig = ''
479 allow 209.250.231.127;
480 deny all;
481 '';
482 };
483 locations."/stats/sztancarka-ppm" = {
484 proxyPass = "http://10.8.0.96:9090/api/v1/query?query=rate%28cut_count_total%5B15m%5D%29+*+60";
485 };
486 locations."/stats/sztancarka-last-24h" = {
487 proxyPass = "http://10.8.0.96:9090/api/v1/query?query=round(increase(cut_count_total[24h]))";
488 };
489 };
490
491 services.unbound = {
492 enable = true;
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200493
Piotr Dobrowolskib6bc3e62021-10-16 21:56:59 +0200494 settings = {
495 server = {
496 interface = [
497 networks.lan.ipv4
498 "127.0.0.1"
499 "::1"
500 # networks.lan.ipv6 TODO
501 ];
502 access-control = [
503 "127.0.0.1/8 allow"
504 "10.0.0.0/8 allow"
505 ];
506 };
507
508 stub-zone = map (name: {
509 name = name;
510 stub-addr = networks.uplink.ipv4;
511 }) [ "waw.hackerspace.pl" "api.eye.fi" "api.ustream.tv" "i" ];
512 forward-zone = {
513 name = ".";
514 forward-addr = "185.236.240.1";
515 };
516 };
517 };
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200518
519 # Public VPN access for Hackerspace members
520 services.openvpn.servers.members.config = ''
521 script-security 3
522 auth-user-pass-verify ${openvpn-auth}/bin/openvpn-auth-member via-env
523 verify-client-cert none
524 username-as-common-name
Piotr Dobrowolskib6bc3e62021-10-16 21:56:59 +0200525
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200526 #user _openvpn
527 #group _openvpn
528 multihome
Piotr Dobrowolskib6bc3e62021-10-16 21:56:59 +0200529
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200530 port 20001
531 proto udp
532 proto udp6
533 dev vpn
534 dev-type tun
535 ca ${secrets-path}/openvpn-public/ca.crt
536 cert ${secrets-path}/openvpn-public/server.crt
537 key ${secrets-path}/openvpn-public/server.key
538 dh ${secrets-path}/openvpn-public/dh.pem
539 server 10.9.1.0 255.255.255.0
Piotr Dobrowolskib6bc3e62021-10-16 21:56:59 +0200540
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200541 push "route 10.8.0.0 255.255.0.0"
542 push "route 10.9.0.0 255.255.0.0"
543 push "route 10.10.0.0 255.255.0.0"
544 push "route 10.11.0.0 255.255.0.0"
545 push "dhcp-option DNS ${networks.lan.ipv4}"
546 push "dhcp-option DOMAIN waw.hackerspace.pl"
Piotr Dobrowolskib6bc3e62021-10-16 21:56:59 +0200547
Piotr Dobrowolskia01905a2021-10-16 18:22:46 +0200548 ifconfig-pool-persist /var/lib/openvpn-public/ipp.txt
549 #client-config-dir /var/lib/openvpn-public/ccd
550 client-to-client
551 keepalive 10 120
552 comp-lzo
553 persist-key
554 persist-tun
555 '';
556
557 environment.systemPackages = with pkgs; [
558 vim tcpdump htop nmon tmux git file procps parted dmidecode ack utillinux nmap mosh ncdu tree lz4 bind
559 rxvt_unicode.terminfo update_authorized_keys
560 ];
561 programs.mtr.enable = true;
562
563 environment.variables = {
564 EDITOR = "vim";
565 };
566
567 system.stateVersion = "20.03";
568
569 boot.vesa = false;
570 boot.loader.grub.splashImage = null;
571}
572