nix-configs/hosts/wormhole/router.nix
2022-03-01 23:53:11 +01:00

422 lines
15 KiB
Nix
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{ config, lib, pkgs, ... }:
with lib;
with lib.my;
let
wgEnabled = hasAttrByPath [ "hosts" config.networking.hostName ] (importTOML ../../data/wireguard.toml);
cfg = config.ragon.networking.router;
waninterface = cfg.waninterface;
laninterface = cfg.laninterface;
prefixSize = cfg.prefixSize;
statics = cfg.statics;
domain = cfg.domain;
disableFirewallFor = cfg.disableFirewallFor;
lan = {
name = "lan";
internet = true;
allowipv6 = true;
ipv4addr = "10.0.0.1";
netipv4addr = "10.0.0.0";
dhcpv4start = "10.0.10.1";
dhcpv4end = "10.0.255.240";
routes = [
{ address = "10.12.0.0"; prefixLength = 16; via = "10.0.1.2"; }
{ address = "10.13.0.0"; prefixLength = 16; via = "10.0.1.2"; }
];
ipv4size = 16;
vlan = 4;
};
iot = {
name = "iot";
internet = false;
allowipv6 = false;
ipv4addr = "10.1.0.1";
netipv4addr = "10.1.0.0";
dhcpv4start = "10.1.1.1";
dhcpv4end = "10.1.255.240";
routes = [ ];
ipv4size = 16;
vlan = 2;
};
guest = {
name = "guest";
internet = true;
allowipv6 = false;
ipv4addr = "192.168.2.1";
netipv4addr = "192.168.2.0";
dhcpv4start = "192.168.2.10";
dhcpv4end = "192.168.2.240";
routes = [ ];
ipv4size = 24;
vlan = 3;
};
nets = [ lan iot guest ];
ipv6nets = builtins.filter (a: a.allowipv6) nets;
interfaceGenerator = obj: {
"${obj.name}".ipv4 = {
addresses = [{
address = obj.ipv4addr;
prefixLength = obj.ipv4size;
}];
routes = [{
address = obj.netipv4addr;
prefixLength = obj.ipv4size;
}] ++ obj.routes;
};
};
in
{
options.ragon.networking.router.enable = mkBoolOpt false;
options.ragon.networking.router.waninterface =
lib.mkOption {
type = lib.types.str;
default = "eth1";
};
options.ragon.networking.router.laninterface =
lib.mkOption {
type = lib.types.str;
default = "eth0";
};
options.ragon.networking.router.domain =
lib.mkOption {
type = lib.types.str;
default = "hailsatan.eu";
};
options.ragon.networking.router.prefixSize =
lib.mkOption {
type = lib.types.int;
default = 59;
};
options.ragon.networking.router.statics =
lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [
{ name = "j.hailsatan.eu"; ip = "10.0.0.2"; }
{ name = "paperless.hailsatan.eu"; ip = "10.0.0.2"; }
{ name = "unifi.hailsatan.eu"; ip = "10.0.0.2"; }
{ name = "nix.hailsatan.eu"; ip = "10.0.0.2"; }
{ name = "h.hailsatan.eu"; ip = "10.0.0.1"; }
{ name = "grafana.hailsatan.eu"; ip = "10.0.0.2"; }
{ name = "nzbr.de"; ip = "10.0.1.2"; }
{ name = "storm.nzbr.de"; ip = "45.9.63.165"; }
{ name = "avalanche.nzbr.de"; ip = "202.61.247.0"; }
];
};
options.ragon.networking.router.disableFirewallFor =
lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [
{ hostname = "enterprise"; mac = "d8:cb:8a:76:09:0a"; tcpports = [ 22 ]; udpports = [ ]; }
{ hostname = "earthquake"; mac = "78:24:af:bc:0c:07"; tcpports = [ 22 22000 ]; udpports = [ 22000 51820 ]; }
];
};
options.ragon.networking.router.staticDHCPs =
lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [
# ragon - machines
{ name = "enterprise"; ip = "10.0.0.9"; mac = "d8:cb:8a:76:09:0a"; }
{ name = "ds9"; ip = "10.0.0.2"; mac = "f4:b5:20:0e:21:d5"; }
# ragon - vms
{ name = "homeassistant"; ip = "10.0.0.20"; mac = "52:54:00:a1:04:14"; }
{ name = "enterprise-win"; ip = "10.0.0.201"; mac = "52:54:00:f3:ab:dd"; }
# ragon - iot
{ name = "zbbridge"; ip = "10.1.0.5"; mac = "98:f4:ab:e2:b6:a3"; }
{ name = "wled-Schrank-Philipp"; ip = "10.1.0.10"; mac = "2c:f4:32:20:74:60"; }
{ name = "wled-Betthintergrund-Phi"; ip = "10.1.0.11"; mac = "2c:3a:e8:0e:ab:71"; }
# nzbr - machines
{ name = "earthquake"; ip = "10.0.1.2"; mac = "78:24:af:bc:0c:07"; }
{ name = "comet"; ip = "10.0.1.4"; mac = "0c:98:38:d3:16:8f"; }
{ name = "meteor"; ip = "10.0.1.8"; mac = "54:27:1e:5c:1f:ed"; } # Wireless
{ name = "meteor"; ip = "10.0.1.16"; mac = "00:21:cc:5c:f5:dc"; } # Wired
{ name = "hurricane"; ip = "10.0.1.32"; mac = "f0:2f:74:1b:af:e0"; }
# nzbr - vms
{ name = "earthquake-macos"; ip = "10.0.1.201"; mac = "52:54:00:8e:e2:66"; }
{ name = "earthquake-win"; ip = "10.0.1.202"; mac = "52:54:00:97:37:69"; }
# nzbr - consoles
{ name = "xbox"; ip = "10.0.2.1"; mac = "58:82:a8:30:2d:1c"; }
{ name = "wii"; ip = "10.0.2.2"; mac = "00:23:cc:50:78:00"; }
{ name = "switch"; ip = "10.0.2.3"; mac = "dc:68:eb:bb:01:fc"; } # Wireless
];
};
options.ragon.networking.router.forwardedPorts =
lib.mkOption {
type = lib.types.listOf lib.types.attrs;
default = [
#{ proto = "tcp"; sourcePort = "5060-5061"; destination = "10.0.0.11"; }
];
};
config = {
# https://www.willghatch.net/blog/2020/06/22/nixos-raspberry-pi-4-google-fiber-router/
# Youd better forward packets if you actually want a router.
boot.kernel.sysctl = {
"net.ipv4.ip_forward" = 1;
"net.ipv6.conf.all.forwarding" = 1;
"net.ipv6.conf.default.forwarding" = 1;
"net.ipv6.conf.6rdtun.forwarding" = 1;
};
networking.vlans =
let
genVlan = obj: {
"${obj.name}" = {
id = obj.vlan;
interface = laninterface;
};
};
in
lib.foldl (a: b: a // b) { } (map genVlan nets);
networking.interfaces =
let
genVlanConf = lib.foldl (a: b: a // b) { } (map interfaceGenerator nets);
in
{
"${waninterface}" = {
useDHCP = true;
};
} // genVlanConf;
networking.dhcpcd = {
enable = true;
allowInterfaces = [
"${waninterface}"
] ++ (map (a: a.name) ipv6nets);
extraConfig =
let
genDesc = obj: ''
# We dont want dhcpcd to give us an address on the ${obj.name} interface.
interface ${obj.name}
noipv4
'';
allGenIntDescs = builtins.concatStringsSep "\n" (map genDesc ipv6nets);
in
''
# The man page says that ipv6rs should be disabled globally when
# using a prefix delegation.
noipv6rs
interface ${waninterface}
# On the wan interface, we want to ask for a prefix delegation.
iaid 0
ipv6rs
ia_pd 0/::/${toString prefixSize} lan/0/${toString prefixSize}
${allGenIntDescs}
'';
runHook = ''
if [[ "$reason" == "BOUND6" ]] || [[ "$reason" == "REBIND6" ]]; then
${pkgs.python3}/bin/python3 ${pkgs.writeScript "dhcpcd-runHook.py" ''
import json
import sys
import subprocess
import os
prefix = os.environ.get("new_dhcp6_ia_pd1_prefix1")[:-1]
# https://stackoverflow.com/a/37316533/12852285
def mac2ipv6(mac):
# only accept MACs separated by a colon
parts = mac.split(":")
# modify parts to match IPv6 value
parts.insert(3, "ff")
parts.insert(4, "fe")
parts[0] = "%x" % (int(parts[0], 16) ^ 2)
# format output
ipv6Parts = []
for i in range(0, len(parts), 2):
ipv6Parts.append("".join(parts[i:i+2]))
ipv6 = "%s%s" % (prefix, ":".join(ipv6Parts))
return ipv6
data = json.loads("""${builtins.toJSON disableFirewallFor}""")
for host in data:
print('setting firewall rules for ' + host["hostname"])
IP = mac2ipv6(host["mac"])
if len(host["tcpports"]) > 0:
subprocess.run(["${pkgs.nftables}/bin/nft", "insert", "rule", "inet", "filter", "forward", "ip6", "daddr", IP, "tcp", "dport", f'{{ {", ".join(map(str, host["tcpports"]))} }}', "accept" ])
if len(host["udpports"]) > 0:
subprocess.run(["${pkgs.nftables}/bin/nft", "insert", "rule", "inet", "filter", "forward", "ip6", "daddr", IP, "udp", "dport", f'{{ {", ".join(map(str, host["udpports"]))} }}', "accept" ])
subprocess.run(["${pkgs.nftables}/bin/nft", "insert", "rule", "inet", "filter", "forward", "ip6", "daddr", IP, "icmpv6", "type", "{ destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, echo-reply, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert }", "accept"])
''}
fi
'';
};
networking.firewall.enable = false; # disable iptables cause it's ass to set up
networking.nftables.enable = true;
networking.nftables.ruleset =
let
unsafeInterfaces = (map (x: x.name) (filter (x: x.internet == false) nets));
safeInterfaces = (map (x: x.name) (filter (x: x.internet == true) nets)) ++ [ "lo" ] ++ (optionals (wgEnabled) [ "wg0" ]);
allInternalInterfaces = (map (x: x.name) nets) ++ [ "lo" ];
portForwards = concatStringsSep "\n" (map (x: "iifname ${waninterface} ${x.proto} dport ${toString x.sourcePort} dnat ${x.destination}") cfg.forwardedPorts);
dropUnsafe = concatStringsSep "\n" (map (x: "iifname ${x} drop") unsafeInterfaces);
allowSafe = concatStringsSep "\n" (map (x: "iifname ${x} accept") safeInterfaces);
allowSafeOif = concatStringsSep "\n" (map (x: "oifname ${x} ct state { established, related } accept") safeInterfaces);
allowAll = concatStringsSep "\n" (map (x: "iifname ${x} accept") (allInternalInterfaces ++ (optionals (wgEnabled) [ "wg0" ])));
in
''
define unsafe_interfaces = {
${concatStringsSep ",\n" unsafeInterfaces}
}
define safe_interfaces = {
lo,
${concatStringsSep ",\n" safeInterfaces}
}
define all_interfaces = {
lo,
${concatStringsSep ",\n" allInternalInterfaces}
}
table inet filter {
chain input {
type filter hook input priority 0;
# allow established/related connections
ct state { established, related } accept
# early drop of invalid connections
ct state invalid drop
# allow from loopback and internal nic
${allowAll}
# allow icmp
ip protocol icmp icmp type echo-request limit rate over 1/second burst 5 packets drop
ip6 nexthdr icmpv6 icmpv6 type echo-request limit rate over 1/second burst 5 packets drop
ip protocol icmp icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } accept
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, echo-reply, echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert, packet-too-big, parameter-problem, time-exceeded } accept
# open port 22, but only allow 2 new connections per minute from each ip
tcp dport 22 ct state new flow table ssh-ftable { ip saddr limit rate 2/minute } accept
tcp dport 80 accept
tcp dport 443 accept
udp dport 51820 accept
# everything else
reject with icmp type port-unreachable
}
chain forward {
type filter hook forward priority 0;
# allow from loopback and internal nic
${allowSafe}
# allow established/related connections
${allowSafeOif}
# Drop everything else
drop
}
chain output {
type filter hook output priority 0
# dont allow any trafic from iot and stuff to escape to the wild
${dropUnsafe}
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority 0
${portForwards}
}
chain postrouting {
type nat hook postrouting priority 0
oifname ${waninterface} masquerade
}
}
'';
services.dnsmasq = {
enable = true;
alwaysKeepRunning = true;
extraConfig =
let
inherit (pkgs) runCommand;
gen = obj: ''
interface=${obj.name}
dhcp-range=${obj.name},${obj.dhcpv4start},${obj.dhcpv4end},12h
'';
genHosts = obj: ''
dhcp-host=${obj.mac},${obj.ip},${obj.name}
'';
genall = builtins.concatStringsSep "\n" (map gen nets);
genallHosts = builtins.concatStringsSep "\n" (map genHosts cfg.staticDHCPs);
genstatics = builtins.concatStringsSep "\n" (map (a: "address=/${a.name}/${a.ip}") statics);
netbootxyz = builtins.fetchurl {
url = "https://github.com/netbootxyz/netboot.xyz/releases/download/2.0.40/netboot.xyz.efi";
sha256 = "1gvgvlaxhjkr9i0b2bjq85h12ni9h5fn6r8nphsag3il9kificcc";
};
netbootxyzpath = runCommand "netbootpath" { } ''
mkdir $out
ln -s ${netbootxyz} $out/netbootxyz.efi
'';
in
''
no-resolv
# unbound broke
# server=127.0.0.1#5353 # unbound
server=1.1.1.1
server=1.0.0.1
# https://hveem.no/using-dnsmasq-for-dhcpv6
# don't ever listen to anything on wan and stuff
except-interface=${waninterface},${laninterface}
listen-address=0.0.0.0,::
# don't send bogus requests out on the internets
bogus-priv
# enable IPv6 Route Advertisements
enable-ra
# Construct a valid IPv6 range from reading the address set on the interface. The :: part refers to the ifid in dhcp6c.conf. Make sure you get this right or dnsmasq will get confused.
dhcp-range=lan,::,constructor:lan, ra-names,slaac, 12h
# ra-names enables a mode which gives DNS names to dual-stack hosts which do SLAAC for IPv6.
# Add your local-only LAN domain
local=/${domain}/
# have your simple hosts expanded to domain
expand-hosts
# set your domain for expand-hosts
domain=${domain}
# forward .kube domains to coredns
server=/kube/10.13.0.10
${genall}
'' +
optionalString wgEnabled ''
interface=wg0
no-dhcp-interface=wg0
addn-hosts=/run/wireguard-hosts
'' + ''
interface=lo # otherwise localhost dns does not work
${genstatics}
${genallHosts}
dhcp-boot=netbootxyz.efi
enable-tftp
tftp-root=${netbootxyzpath}
# set authoritative mode
dhcp-authoritative
'';
};
};
}