shinobu/router: Split configuration

This commit is contained in:
Simon Bruder 2023-09-24 14:41:22 +02:00
parent c48d626c68
commit 133bb85357
Signed by: simon
GPG key ID: 8D3C82F9F309F8EC
9 changed files with 498 additions and 440 deletions

View file

@ -5,7 +5,7 @@
../../modules ../../modules
./services/co2_exporter.nix ./services/co2_exporter.nix
./services/router.nix ./services/router
]; ];
sbruder = { sbruder = {

View file

@ -1,439 +0,0 @@
# Home network configuration
# (2.5GbE clients)
# | |
# +----------+ +----------+
# | | | | | | (1GbE clients)
# | | | | | +|-|-|-|-|+
# +---+----+ +-+-+-+-+-+ |5 4 3 2 1|
# |upstream| | 1 2 3 4 | |TL-SG105 |
# +--------+ | shinobu | +---------+
# +---------+
#
# It consists of shinobu as a router (this configuration),
# connected to a TP-LINK TL-SG105E “smart managed” (i.e., it can do VLANs) 5-port switch.
# The upstream comes (for now) from a PŸUR “WLAN-Kabelbox” (Compal CH7467CE).
# Sadly, I could not enable bridge mode on it, so the packets now go through (at least) four layers of NAT:
# device → NAT on shinobu (→ NAT on plastic router → PŸUR CGNAT) → NAT on VPN
#
# Because the switch only supports GbE,
# the two clients I currently have with support for 2.5GbE are connected
# directly to the two remaining network interfaces on shinobu.
# Once I have more devices with support for 2.5GbE
# or I find a good deal on a matching switch,
# I will change this.
#
# Wireless is configured by providing the whole hostapd configuration file as a secret.
# Once nixpkgs PR 222536 is merged, I will migrate to using the NixOS module.
# Thanks to Intels wisdom, its not possible to use 5GHz in AP mode.
{ config, lib, pkgs, ... }:
let
domain = "home.sbruder.de";
vpnBypassFwMark = 10000;
in
{
sops.secrets.wg-upstream-private-key = {
owner = config.users.users.systemd-network.name;
sopsFile = ../secrets.yaml;
};
sops.secrets.hostapd-config = {
sopsFile = ../secrets.yaml;
};
boot.kernel.sysctl = {
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
};
networking = {
# networkd handles this
useDHCP = false;
nftables = {
enable = true;
ruleset = ''
define NAT_LAN_IFACES = { "br-lan" }
define NAT_WAN_IFACES = { "wg-upstream" }
define PHYSICAL_WAN = "enp1s0"
define MASQUERADE_IFACES = { $NAT_WAN_IFACES, $PHYSICAL_WAN }
define VUEKO_V4 = 168.119.176.53
define VUEKO_V6 = 2a01:4f8:c012:2f4::1
define VUEKO_PORT = 51820
define WG_UPSTREAM_ENDPOINT = ${lib.elemAt (lib.splitString ":" (lib.elemAt config.systemd.network.netdevs.wg-upstream.wireguardPeers 0).wireguardPeerConfig.Endpoint) 0}
define PLASTIC_ROUTER_V4 = 192.168.0.1
define VPN_BYPASS_MARK = ${toString vpnBypassFwMark}
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop
# Use MSS clamping
# to avoid too large packets from client on the lan
# not going through the tunnel.
iifname wg-upstream tcp flags syn / syn,rst tcp option maxseg size set rt mtu
oifname wg-upstream tcp flags syn / syn,rst tcp option maxseg size set rt mtu
# allow traffic between lan and wan
iifname $NAT_LAN_IFACES oifname $NAT_WAN_IFACES counter accept
iifname $NAT_WAN_IFACES oifname $NAT_LAN_IFACES ct state established,related counter accept
# accept responses on physical wan
iifname $PHYSICAL_WAN oifname $NAT_LAN_IFACES ct state established,related counter accept
# allow selected destinations via physical wan
# plastic router
iifname $NAT_LAN_IFACES oifname $PHYSICAL_WAN ip daddr $PLASTIC_ROUTER_V4 counter accept
# all destinations configured via policy based routing
oifname $PHYSICAL_WAN mark $VPN_BYPASS_MARK counter accept
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority filter; policy accept
oifname $MASQUERADE_IFACES masquerade
}
}
# Bypass VPN by setting mark.
# This acts in two places that are handled separatly by nftables:
# Packets from the local host (output hook) and forwared packets (prerouting hook).
# To simplify the handling,
# there is a single chain that handles both,
# which is jumped to from the specific chains.
table inet vpn-bypass {
# This must be of type route, otherwise no route lookup will be performed
chain output {
type route hook output priority mangle
jump common
}
# This does not need to be of type route
chain prerouting {
type filter hook prerouting priority mangle
jump common
}
chain common {
ip daddr $VUEKO_V4 udp dport $VUEKO_PORT mark set $VPN_BYPASS_MARK counter
ip6 daddr $VUEKO_V6 udp dport $VUEKO_PORT mark set $VPN_BYPASS_MARK counter
}
}
# Only allow select connections from and to (physical) wan,
# overriding NixOS firewall in some cases.
table inet restrict-wan {
# Priorities must be higher than filter (0),
# which the NixOS firewall uses.
chain input {
type filter hook input priority -50; policy accept
# accept responses
iifname $PHYSICAL_WAN ct state established,related counter accept
# accept icmpv6
iifname $PHYSICAL_WAN icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# drop everything else
iifname $PHYSICAL_WAN counter drop
}
# This handles all packets (local and forwarded)
chain postrouting {
type filter hook postrouting priority 0; policy accept
# accept connections to plastic router
oifname $PHYSICAL_WAN ip daddr $PLASTIC_ROUTER_V4 counter accept
# accept icmpv6
oifname $PHYSICAL_WAN icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# accept connections to selected endpoints
# VPN (wg-upstream)
oifname $PHYSICAL_WAN ip daddr $WG_UPSTREAM_ENDPOINT counter accept # only this is used
# destinations configured in VPN bypass
oifname $PHYSICAL_WAN mark $VPN_BYPASS_MARK counter accept
# drop all other packets
oifname $PHYSICAL_WAN counter drop
}
}
# Tracing infrastructure, can be used for debugging (nft monitor trace)
table inet trace {
chain prerouting {
type filter hook prerouting priority raw - 1
jump common
}
chain output {
type filter hook output priority raw - 1
jump common
}
chain common {
# Add tracing rule here
# … meta nftrace set 1
# DO NOT COMMIT ANY TRACING RULES
}
}
'';
};
};
systemd.network = {
enable = true;
# not all interfaces need to be up
wait-online.extraArgs = [ "--any" ];
netdevs = {
br-lan = {
netdevConfig = {
Name = "br-lan";
Kind = "bridge";
};
};
wg-upstream = {
netdevConfig = {
Kind = "wireguard";
Name = "wg-upstream";
};
wireguardConfig = {
PrivateKeyFile = config.sops.secrets.wg-upstream-private-key.path;
FirewallMark = 51820;
};
wireguardPeers = lib.singleton {
wireguardPeerConfig = {
Endpoint = "193.32.248.71:51820";
PublicKey = "eprzkkkSbXCANngQDo305DIAvkKAnZaN71IpTNaOoTk=";
AllowedIPs = [ "0.0.0.0/0" "::0/0" ];
PersistentKeepalive = 25;
};
};
};
};
networks = {
wan = {
name = "enp1s0";
DHCP = "ipv4";
networkConfig = {
IPv6AcceptRA = "yes";
};
dhcpV4Config = {
UseDNS = "no";
};
ipv6AcceptRAConfig = {
# Only use RA
DHCPv6Client = false;
UseDNS = "no";
};
};
lan1 = {
name = "enp2s0";
bridge = [ "br-lan" ];
};
lan2 = {
name = "enp3s0";
bridge = [ "br-lan" ];
};
lan3 = {
name = "enp4s0";
bridge = [ "br-lan" ];
};
br-lan = {
name = "br-lan";
domains = [ domain ];
address = [ "10.80.1.1/24" "fd00:80:1::1/64" ];
};
wg-upstream = {
name = "wg-upstream";
address = [ "10.66.208.88/32" "fc00:bbbb:bbbb:bb01::3:d057/128" ];
routingPolicyRules = [
{
routingPolicyRuleConfig = {
Family = "both"; # default is only ipv4
FirewallMark = 51820;
InvertRule = "yes";
Table = 51820;
Priority = 10;
#SuppressPrefixLength = 0; # cant be used here (forwarding does not work with it)
};
}
# FIXME: those two shouldnt be necessary
# It should automatically detect those routes existing and prioritise them
# LAN (v4)
{
routingPolicyRuleConfig = {
To = "10.80.1.0/24";
Priority = 9;
};
}
# LAN (v6)
{
routingPolicyRuleConfig = {
To = "fd00:80:1::/64";
Priority = 9;
};
}
# wg-home
{
routingPolicyRuleConfig = {
To = "10.80.0.0/24";
Priority = 9;
};
}
# VPN bypass
{
routingPolicyRuleConfig = {
Family = "both"; # welcome in the year 2023, where ipv4 is the default
FirewallMark = vpnBypassFwMark;
Priority = 9;
};
}
# plastic router
{
routingPolicyRuleConfig = {
To = "192.168.0.0/24";
Priority = 9;
};
}
];
routes = [
{
routeConfig = {
Gateway = "0.0.0.0"; # point-to-point connection
Table = 51820;
};
}
{
routeConfig = {
Gateway = "::";
Table = 51820;
};
}
];
};
};
};
services.resolved.enable = false;
services.dnsmasq = {
enable = true;
settings = {
bogus-priv = true; # do not forward revese lookups of internal addresses
domain-needed = true; # do not forward names without domain
interface = "br-lan"; # only respond to queries from lan
no-hosts = true; # do not resolve hosts from /etc/hosts
no-resolv = true; # only use explicitly configured resolvers
cache-size = 10000;
inherit domain;
# Allow resolving the router
interface-name = [
"${config.networking.hostName}.${domain},br-lan"
"${config.networking.hostName},br-lan"
];
# DHCPv4
dhcp-range = [
"10.80.1.20,10.80.1.150,12h" # DHCPv4
"fd00:80:1::,ra-stateless,ra-names" # SLAAC (for addresses) / DHCPv6 (for DNS)
];
dhcp-option = [
"option:router,10.80.1.1"
"option6:dns-server,fd00:80:1::1"
];
# Despite its name, the switch does not have a “smart” configuration,
# that would allow me to tell it not to get DHCP from wan,
# but from lan instead.
# So it has to use static configuration.
host-record = "switchviech,switchviech.${domain},10.80.1.19";
server = [
"127.0.0.1#5053"
];
};
};
systemd.services.dnsmasq.after = [ "systemd-networkd.service" ];
services.prometheus.exporters.dnsmasq = {
enable = true;
listenAddress = config.sbruder.wireguard.home.address;
leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
};
networking.firewall.allowedUDPPorts = [ 53 67 ];
networking.firewall.allowedTCPPorts = [ 53 ];
# Wireless
boot.kernelModules = [ "nl80211" ];
environment.systemPackages = with pkgs; [
ethtool
iw
wirelesstools
];
# The service is mostly taken from nixpkgs pr 222536.
systemd.services.hostapd = {
path = with pkgs; [ hostapd ];
after = [ "sys-subsystem-net-devices-wlp5s0.device" ];
bindsTo = [ "sys-subsystem-net-devices-wlp5s0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.hostapd}/bin/hostapd ${config.sops.secrets.hostapd-config.path}";
Restart = "always";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
RuntimeDirectory = "hostapd";
# Hardening
LockPersonality = true;
MemoryDenyWriteExecute = true;
DevicePolicy = "closed";
DeviceAllow = "/dev/rfkill rw";
NoNewPrivileges = true;
PrivateUsers = false; # hostapd requires true root access.
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
"@chown"
];
UMask = "0077";
};
};
services.https-dns-proxy = {
enable = true;
provider = {
kind = "custom";
ips = [ "9.9.9.9" "149.112.112.112" ];
url = "https://dns.quad9.net/dns-query";
};
};
}

View file

@ -0,0 +1,13 @@
{
domain = "home.sbruder.de";
vpnBypassFwMark = 10000;
wg-upstream = {
endpoint = rec {
address = "193.32.248.71";
port = 51820;
full = "${address}:${toString port}";
};
publicKey = "eprzkkkSbXCANngQDo305DIAvkKAnZaN71IpTNaOoTk=";
addresses = [ "10.66.208.88/32" "fc00:bbbb:bbbb:bb01::3:d057/128" ];
};
}

View file

@ -0,0 +1,48 @@
# Home network configuration
# (2.5GbE clients)
# | |
# +----------+ +----------+
# | | | | | | (1GbE clients)
# | | | | | +|-|-|-|-|+
# +---+----+ +-+-+-+-+-+ |5 4 3 2 1|
# |upstream| | 1 2 3 4 | |TL-SG105 |
# +--------+ | shinobu | +---------+
# +---------+
#
# It consists of shinobu as a router (this configuration),
# connected to a TP-LINK TL-SG105E “smart managed” (i.e., it can do VLANs) 5-port switch.
# The upstream comes (for now) from a PŸUR “WLAN-Kabelbox” (Compal CH7467CE).
# Sadly, I could not enable bridge mode on it, so the packets now go through (at least) four layers of NAT:
# device → NAT on shinobu (→ NAT on plastic router → PŸUR CGNAT) → NAT on VPN
#
# Because the switch only supports GbE,
# the two clients I currently have with support for 2.5GbE are connected
# directly to the two remaining network interfaces on shinobu.
# Once I have more devices with support for 2.5GbE
# or I find a good deal on a matching switch,
# I will change this.
#
# Wireless is configured by providing the whole hostapd configuration file as a secret.
# Once nixpkgs PR 222536 is merged, I will migrate to using the NixOS module.
# Thanks to Intels wisdom, its not possible to use 5GHz in AP mode.
{ config, lib, pkgs, ... }:
let
cfg = import ./common.nix;
in
{
imports = [
./dnsmasq.nix
./networkd.nix
./nft.nix
./wlan.nix
];
boot.kernel.sysctl = {
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
};
environment.systemPackages = with pkgs; [
ethtool
];
}

View file

@ -0,0 +1,65 @@
{ config, ... }:
let
cfg = import ./common.nix;
in
{
services.dnsmasq = {
enable = true;
settings = {
bogus-priv = true; # do not forward revese lookups of internal addresses
domain-needed = true; # do not forward names without domain
interface = "br-lan"; # only respond to queries from lan
no-hosts = true; # do not resolve hosts from /etc/hosts
no-resolv = true; # only use explicitly configured resolvers
cache-size = 10000;
inherit (cfg) domain;
# Allow resolving the router
interface-name = [
"${config.networking.hostName}.${cfg.domain},br-lan"
"${config.networking.hostName},br-lan"
];
# DHCPv4
dhcp-range = [
"10.80.1.20,10.80.1.150,12h" # DHCPv4
"fd00:80:1::,ra-stateless,ra-names" # SLAAC (for addresses) / DHCPv6 (for DNS)
];
dhcp-option = [
"option:router,10.80.1.1"
"option6:dns-server,fd00:80:1::1"
];
# Despite its name, the switch does not have a “smart” configuration,
# that would allow me to tell it not to get DHCP from wan,
# but from lan instead.
# So it has to use static configuration.
host-record = "switchviech,switchviech.${cfg.domain},10.80.1.19";
server = [
"127.0.0.1#5053"
];
};
};
systemd.services.dnsmasq.after = [ "systemd-networkd.service" ];
networking.firewall.allowedUDPPorts = [ 53 67 ];
networking.firewall.allowedTCPPorts = [ 53 ];
services.prometheus.exporters.dnsmasq = {
enable = true;
listenAddress = config.sbruder.wireguard.home.address;
leasesPath = "/var/lib/dnsmasq/dnsmasq.leases";
};
services.https-dns-proxy = {
enable = true;
provider = {
kind = "custom";
ips = [ "9.9.9.9" "149.112.112.112" ];
url = "https://dns.quad9.net/dns-query";
};
};
}

View file

@ -0,0 +1,147 @@
{ config, lib, ... }:
let
cfg = import ./common.nix;
in
{
sops.secrets.wg-upstream-private-key = {
owner = config.users.users.systemd-network.name;
sopsFile = ../../secrets.yaml;
};
networking.useDHCP = false;
systemd.network = {
enable = true;
# not all interfaces need to be up
wait-online.extraArgs = [ "--any" ];
netdevs = {
br-lan = {
netdevConfig = {
Name = "br-lan";
Kind = "bridge";
};
};
wg-upstream = {
netdevConfig = {
Kind = "wireguard";
Name = "wg-upstream";
};
wireguardConfig = {
PrivateKeyFile = config.sops.secrets.wg-upstream-private-key.path;
FirewallMark = 51820;
};
wireguardPeers = lib.singleton {
wireguardPeerConfig = with cfg.wg-upstream; {
Endpoint = endpoint.full;
PublicKey = publicKey;
AllowedIPs = [ "0.0.0.0/0" "::0/0" ];
PersistentKeepalive = 25;
};
};
};
};
networks = {
wan = {
name = "enp1s0";
DHCP = "ipv4";
networkConfig = {
IPv6AcceptRA = "yes";
};
dhcpV4Config = {
UseDNS = "no";
};
ipv6AcceptRAConfig = {
# Only use RA
DHCPv6Client = false;
UseDNS = "no";
};
};
lan1 = {
name = "enp2s0";
bridge = [ "br-lan" ];
};
lan2 = {
name = "enp3s0";
bridge = [ "br-lan" ];
};
lan3 = {
name = "enp4s0";
bridge = [ "br-lan" ];
};
br-lan = {
name = "br-lan";
domains = [ cfg.domain ];
address = [ "10.80.1.1/24" "fd00:80:1::1/64" ];
};
wg-upstream = {
name = "wg-upstream";
address = cfg.wg-upstream.addresses;
routingPolicyRules = [
{
routingPolicyRuleConfig = {
Family = "both"; # default is only ipv4
FirewallMark = 51820;
InvertRule = "yes";
Table = 51820;
Priority = 10;
#SuppressPrefixLength = 0; # cant be used here (forwarding does not work with it)
};
}
# FIXME: those two shouldnt be necessary
# It should automatically detect those routes existing and prioritise them
# LAN (v4)
{
routingPolicyRuleConfig = {
To = "10.80.1.0/24";
Priority = 9;
};
}
# LAN (v6)
{
routingPolicyRuleConfig = {
To = "fd00:80:1::/64";
Priority = 9;
};
}
# wg-home
{
routingPolicyRuleConfig = {
To = "10.80.0.0/24";
Priority = 9;
};
}
# VPN bypass
{
routingPolicyRuleConfig = {
Family = "both"; # welcome in the year 2023, where ipv4 is the default
FirewallMark = cfg.vpnBypassFwMark;
Priority = 9;
};
}
# plastic router
{
routingPolicyRuleConfig = {
To = "192.168.0.0/24";
Priority = 9;
};
}
];
routes = [
{
routeConfig = {
Gateway = "0.0.0.0"; # point-to-point connection
Table = 51820;
};
}
{
routeConfig = {
Gateway = "::";
Table = 51820;
};
}
];
};
};
};
services.resolved.enable = false;
}

View file

@ -0,0 +1,32 @@
{ config, lib, ... }:
let
cfg = import ./common.nix;
flattenAttrValues = attrs: lib.flatten (map (v: if lib.isAttrs v then flattenAttrValues v else v) (lib.attrValues attrs));
mkKeyValue = lib.generators.mkKeyValueDefault
rec {
# specifies the generated string for a subset of nix values
mkValueString = v:
if lib.isString v then ''"${v}"''
else if lib.isList v then ''{ ${lib.concatMapStringsSep ", " mkValueString v} }''
else lib.generators.mkValueStringDefault { } v;
} " = ";
defines = lib.concatStringsSep
"\n"
(flattenAttrValues
(lib.mapAttrsRecursive
(path: value: "define " + (mkKeyValue ''CFG_${lib.concatStringsSep "_" path}'' value))
cfg));
in
{
networking.nftables = {
enable = true;
ruleset = ''
${defines}
include "${./rules.nft}"
'';
};
}

View file

@ -0,0 +1,127 @@
define NAT_LAN_IFACES = { "br-lan" }
define NAT_WAN_IFACES = { "wg-upstream" }
define PHYSICAL_WAN = "enp1s0"
define MASQUERADE_IFACES = { $NAT_WAN_IFACES, $PHYSICAL_WAN }
define VUEKO_V4 = 168.119.176.53
define VUEKO_V6 = 2a01:4f8:c012:2f4::1
define VUEKO_PORT = 51820
define WG_UPSTREAM_ENDPOINT = $CFG_wg-upstream_endpoint_address
define PLASTIC_ROUTER_V4 = 192.168.0.1
define VPN_BYPASS_MARK = $CFG_vpnBypassFwMark
table inet filter {
chain forward {
type filter hook forward priority filter; policy drop
# Use MSS clamping
# to avoid too large packets from client on the lan
# not going through the tunnel.
iifname wg-upstream tcp flags syn / syn,rst tcp option maxseg size set rt mtu
oifname wg-upstream tcp flags syn / syn,rst tcp option maxseg size set rt mtu
# allow traffic between lan and wan
iifname $NAT_LAN_IFACES oifname $NAT_WAN_IFACES counter accept
iifname $NAT_WAN_IFACES oifname $NAT_LAN_IFACES ct state established,related counter accept
# accept responses on physical wan
iifname $PHYSICAL_WAN oifname $NAT_LAN_IFACES ct state established,related counter accept
# allow selected destinations via physical wan
# plastic router
iifname $NAT_LAN_IFACES oifname $PHYSICAL_WAN ip daddr $PLASTIC_ROUTER_V4 counter accept
# all destinations configured via policy based routing
oifname $PHYSICAL_WAN mark $VPN_BYPASS_MARK counter accept
}
}
table inet nat {
chain postrouting {
type nat hook postrouting priority filter; policy accept
oifname $MASQUERADE_IFACES masquerade
}
}
# Bypass VPN by setting mark.
# This acts in two places that are handled separatly by nftables:
# Packets from the local host (output hook) and forwared packets (prerouting hook).
# To simplify the handling,
# there is a single chain that handles both,
# which is jumped to from the specific chains.
table inet vpn-bypass {
# This must be of type route, otherwise no route lookup will be performed
chain output {
type route hook output priority mangle
jump common
}
# This does not need to be of type route
chain prerouting {
type filter hook prerouting priority mangle
jump common
}
chain common {
ip daddr $VUEKO_V4 udp dport $VUEKO_PORT mark set $VPN_BYPASS_MARK counter
ip6 daddr $VUEKO_V6 udp dport $VUEKO_PORT mark set $VPN_BYPASS_MARK counter
}
}
# Only allow select connections from and to (physical) wan,
# overriding NixOS firewall in some cases.
table inet restrict-wan {
# Priorities must be higher than filter (0),
# which the NixOS firewall uses.
chain input {
type filter hook input priority -50; policy accept
# accept responses
iifname $PHYSICAL_WAN ct state established,related counter accept
# accept icmpv6
iifname $PHYSICAL_WAN icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# drop everything else
iifname $PHYSICAL_WAN counter drop
}
# This handles all packets (local and forwarded)
chain postrouting {
type filter hook postrouting priority 0; policy accept
# accept connections to plastic router
oifname $PHYSICAL_WAN ip daddr $PLASTIC_ROUTER_V4 counter accept
# accept icmpv6
oifname $PHYSICAL_WAN icmpv6 type { nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept
# accept connections to selected endpoints
# VPN (wg-upstream)
oifname $PHYSICAL_WAN ip daddr $WG_UPSTREAM_ENDPOINT counter accept # only this is used
# destinations configured in VPN bypass
oifname $PHYSICAL_WAN mark $VPN_BYPASS_MARK counter accept
# drop all other packets
oifname $PHYSICAL_WAN counter drop
}
}
# Tracing infrastructure, can be used for debugging (nft monitor trace)
table inet trace {
chain prerouting {
type filter hook prerouting priority raw - 1
jump common
}
chain output {
type filter hook output priority raw - 1
jump common
}
chain common {
# Add tracing rule here
# … meta nftrace set 1
# DO NOT COMMIT ANY TRACING RULES
}
}

View file

@ -0,0 +1,65 @@
{ config, pkgs, ... }:
{
sops.secrets.hostapd-config = {
sopsFile = ../../secrets.yaml;
};
# The service is mostly taken from nixpkgs pr 222536.
systemd.services.hostapd = {
path = with pkgs; [ hostapd ];
after = [ "sys-subsystem-net-devices-wlp5s0.device" ];
bindsTo = [ "sys-subsystem-net-devices-wlp5s0.device" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${pkgs.hostapd}/bin/hostapd ${config.sops.secrets.hostapd-config.path}";
Restart = "always";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
RuntimeDirectory = "hostapd";
# Hardening
LockPersonality = true;
MemoryDenyWriteExecute = true;
DevicePolicy = "closed";
DeviceAllow = "/dev/rfkill rw";
NoNewPrivileges = true;
PrivateUsers = false; # hostapd requires true root access.
PrivateTmp = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectProc = "invisible";
ProcSubset = "pid";
ProtectSystem = "strict";
RestrictAddressFamilies = [
"AF_INET"
"AF_INET6"
"AF_NETLINK"
"AF_UNIX"
];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
SystemCallFilter = [
"@system-service"
"~@privileged"
"@chown"
];
UMask = "0077";
};
};
environment.systemPackages = with pkgs; [
iw
wirelesstools
];
# Wireless
boot.kernelModules = [ "nl80211" ];
}