infra/nix/os/devices/router0-dmz0/configuration.nix

1220 lines
33 KiB
Nix
Raw Normal View History

# TODO: don't pull in bluez (or any bluetooth components)
2024-02-08 20:53:22 +01:00
{
repoFlake,
pkgs,
lib,
config,
nodeFlake,
nodeName,
localDomainName,
system,
...
}: let
inherit
(nodeFlake.inputs)
nixos-nftables-firewall
nixos-sbc
;
vlanRangeStart = builtins.head vlanRange;
vlanRangeEnd = builtins.elemAt vlanRange ((builtins.length vlanRange) - 1);
vlanRange = builtins.map (vlanid: (lib.strings.toInt vlanid)) (builtins.attrNames vlans);
2024-02-08 20:53:22 +01:00
vlanRangeWith0 = [0] ++ vlanRange;
mkVlanIpv4HostAddr = {
vlanid,
host,
thirdIpv4SegmentMin ? 20,
cidr ? true,
}: let
# reserve the first subnet for vlanid == 0
# number the other subnets continously from there
offset =
if vlanid == 0
then thirdIpv4SegmentMin
else thirdIpv4SegmentMin + 1 - vlanRangeStart;
in
builtins.concatStringsSep "."
2024-02-08 20:53:22 +01:00
["192" "168" (toString (vlanid + offset)) "${toString host}${lib.strings.optionalString cidr "/24"}"];
defaultVlan = {
name = "${localDomainName}";
packet_priority = 0;
};
vlans = {
"10".name = "mgmt";
"10".packet_priority = 0;
"11".name = "dmz";
"11".packet_priority = -5;
"12".name = "iot";
"12".packet_priority = -5;
"13".name = "office";
"13".packet_priority = -10;
"14".name = "guests";
"14".packet_priority = 10;
"15".name = "iot2";
"15".packet_priority = -10;
};
2024-02-08 20:53:22 +01:00
vlansByName =
lib.attrsets.mapAttrs'
(
vlanid': attrs:
lib.attrsets.nameValuePair
attrs.name
2024-02-08 20:53:22 +01:00
(attrs
// {
id = lib.strings.toInt vlanid';
id' = vlanid';
})
)
vlans;
2024-02-08 20:53:22 +01:00
getVlanDomain = {vlanid}:
if vlanid == 0
2024-02-08 20:53:22 +01:00
then defaultVlan.name
else vlans."${toString vlanid}".name + "." + defaultVlan.name;
bridgeInterfaceName = "br-lan";
2024-02-08 20:53:22 +01:00
mkInterfaceName = {vlanid}:
if vlanid == 0
then bridgeInterfaceName
2024-02-08 20:53:22 +01:00
else "${bridgeInterfaceName}.${toString vlanid}";
dmzExposedHost = "sj-srv1.dmz.internal";
dmzExposedHostIpv4 = mkVlanIpv4HostAddr {
vlanid = vlansByName.dmz.id;
host = 99;
cidr = false;
};
# "sj-srv1.dmz.internal";
2024-02-08 20:53:22 +01:00
in {
imports = [
nixos-sbc.nixosModules.default
nixos-sbc.nixosModules.boards.bananapi.bpir3
{
sbc.version = "0.2";
sbc.bootstrap.rootFilesystem = "btrfs";
sbc.wireless.wifi.acceptRegulatoryResponsibility = true;
}
repoFlake.inputs.sops-nix.nixosModules.sops
../../profiles/common/user.nix
../../snippets/nix-settings.nix
nixos-nftables-firewall.nixosModules.default
{
services.openssh.enable = true;
services.openssh.settings.PermitRootLogin = "yes";
users.commonUsers = {
enable = true;
enableNonRoot = false;
rootPasswordFile = config.sops.secrets.passwords-root.path;
};
sops.defaultSopsFile = ../../../../secrets/${nodeName}/secrets.yaml;
sops.defaultSopsFormat = "yaml";
sops.secrets.passwords-root.neededForUsers = true;
2024-06-12 22:18:52 +02:00
# sops.secrets.wlan0_saePasswordsFile = {};
2024-02-08 20:53:22 +01:00
sops.secrets.wlan0_wpaPskFile = {};
}
];
# sops.secrets.ssh_host_ed25519_key = {
# sopsFile = ../../../../secrets/${nodeName}/secrets.yaml;
# format = "yaml";
# path = "/etc/ssh/ssh_host_ed25519_key";
# mode = "0600";
# };
# sops.secrets.ssh_host_ed25519_key_pub = {
# sopsFile = ../../../../secrets/${nodeName}/secrets.yaml;
# format = "yaml";
# path = "/etc/ssh/ssh_host_ed25519_key.pub";
# mode = "0600";
# };
# sops.secrets.ssh_host_rsa_key = {
# sopsFile = ../../../../secrets/${nodeName}/secrets.yaml;
# format = "yaml";
# path = "/etc/ssh/ssh_host_rsa_key";
# mode = "0600";
# };
# sops.secrets.ssh_host_rsa_key_pub = {
# sopsFile = ../../../../secrets/${nodeName}/secrets.yaml;
# format = "yaml";
# path = "/etc/ssh/ssh_host_rsa_key.pub";
# mode = "0644";
# };
boot = {
kernel = {
sysctl = {
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
};
};
};
networking = {
hostName = nodeName;
useNetworkd = true;
useDHCP = false;
# these will be configured via nftables
nat.enable = lib.mkForce false;
firewall.enable = lib.mkForce false;
# Use the nftables firewall instead of the base nixos scripted rules.
# This flake provides a similar utility to the base nixos scripting.
# https://github.com/thelegy/nixos-nftables-firewall/tree/main
# TODO: configure packet_priority for VLANs (see https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_priority, https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation#packet_priority)
2024-02-08 20:53:22 +01:00
nftables = {
enable = true;
stopRuleset = "";
chains = {
prerouting = {
"exposeHost" = {
after = ["hook"];
rules = let
wanInterfaces = builtins.concatStringsSep ", " config.networking.nftables.firewall.zones.wan.interfaces;
in [
"iifname { ${wanInterfaces} } tcp dport 220 redirect to 22"
"iifname { ${wanInterfaces} } dnat ip to ${dmzExposedHostIpv4}"
];
2023-12-28 13:56:57 +01:00
};
};
2024-02-08 20:53:22 +01:00
};
2023-12-28 13:56:57 +01:00
2024-02-08 20:53:22 +01:00
firewall = {
enable = true;
zones =
{
lan.interfaces = [(mkInterfaceName {vlanid = 0;})];
vlan.interfaces = builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange;
# lan.ipv4Addresses = ["192.168.0.0/16"];
2024-02-08 20:53:22 +01:00
wan.interfaces = ["wan" "lan0"];
vpn.interfaces = ["wg0" "wg1" "wg2"];
2024-02-08 20:53:22 +01:00
}
//
# generate a zone for each vlan
lib.attrsets.mapAttrs
2024-02-08 20:53:22 +01:00
(key: value: {
interfaces = [(mkInterfaceName {vlanid = value.id;})];
})
vlansByName;
rules = let
ipv6IcmpTypes = [
"destination-unreachable"
"echo-reply"
"echo-request"
"packet-too-big"
"parameter-problem"
"time-exceeded"
# Without the nd-* ones ipv6 will not work.
"nd-neighbor-solicit"
"nd-router-advert"
"nd-neighbor-advert"
];
ipv4IcmpTypes = [
"destination-unreachable"
"echo-reply"
"echo-request"
"source-quench"
"time-exceeded"
"router-advertisement"
];
allowIcmpLines = [
"ip protocol icmp icmp type { ${builtins.concatStringsSep ", " ipv4IcmpTypes} } accept"
"ip6 nexthdr icmpv6 icmpv6 type { ${builtins.concatStringsSep ", " ipv6IcmpTypes} } accept"
];
in {
fw = {
from = ["fw"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
office-to-dmz = {
from = ["office"];
to = ["dmz"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
lan-to-fw = {
from = ["lan"];
to = ["fw" "lan"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
lan-to-wan = {
from = ["lan"];
to = ["wan"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
vlan-to-wan = {
from = ["vlan"];
to = ["wan"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
vlan-to-fw = {
allowedUDPPortRanges = [
{
from = 53;
to = 53;
}
2024-02-08 20:53:22 +01:00
{
from = 67;
to = 68;
}
{
from = 5201;
to = 5201;
2024-02-08 20:53:22 +01:00
}
];
allowedTCPPortRanges = [
{
from = 22;
to = 22;
}
{
from = 53;
to = 53;
}
{
from = 5201;
to = 5201;
}
];
from = ["vlan"];
to = ["fw"];
extraLines =
allowIcmpLines
++ [
"drop"
];
};
2024-02-08 20:53:22 +01:00
to-wan-nat = {
from = ["lan" "vlan"];
to = ["wan"];
masquerade = true;
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
wan-to-dmz = {
from = ["wan"];
to = ["dmz"];
verdict = "accept";
};
2024-02-08 20:53:22 +01:00
wan-to-fw = {
from = ["wan"];
to = ["fw"];
allowedTCPPortRanges = [
{
from = 22;
to = 22;
}
];
extraLines =
allowIcmpLines
++ [
"drop"
];
};
to-vpn-nat = {
from = ["lan" "vlan"];
to = ["vpn"];
masquerade = false;
verdict = "accept";
};
};
};
2024-02-08 20:53:22 +01:00
};
};
sops.secrets.wg0-privatekey = {
mode = "440";
group = "systemd-network";
};
sops.secrets.wg1-privatekey = {
mode = "440";
group = "systemd-network";
};
sops.secrets.wg0-peer0-psk = {
mode = "440";
group = "systemd-network";
};
sops.secrets.wg1-peer0-psk = {
mode = "440";
group = "systemd-network";
};
systemd.network = {
wait-online.anyInterface = true;
netdevs = let
router0-ifog_wg0Endpoint = "${repoFlake.colmena.router0-ifog.deployment.targetHost}:${
builtins.toString
repoFlake
.nixosConfigurations
.router0-ifog
.config
.systemd
.network
.netdevs
.wg0
.wireguardConfig
.ListenPort
}";
router0-ifog_wg1Endpoint = "${repoFlake.colmena.router0-ifog.deployment.targetHost}:${
builtins.toString
repoFlake
.nixosConfigurations
.router0-ifog
.config
.systemd
.network
.netdevs
.wg1
.wireguardConfig
.ListenPort
}";
router0-hosthatch_wg0Endpoint = "${repoFlake.colmena.router0-hosthatch.deployment.targetHost}:${
builtins.toString
repoFlake
.nixosConfigurations
.router0-hosthatch
.config
.systemd
.network
.netdevs
.wg0
.wireguardConfig
.ListenPort
}";
in
2024-02-08 20:53:22 +01:00
{
# Create the bridge interface
"20-${bridgeInterfaceName}" = {
netdevConfig = {
Kind = "bridge";
Name = bridgeInterfaceName;
};
2024-02-08 20:53:22 +01:00
extraConfig = ''
[Bridge]
STP=yes
VLANFiltering=yes
VLANProtocol=802.1q
DefaultPVID=0
'';
};
wg0 = {
enable = true;
netdevConfig = {
Name = "wg0";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = builtins.toString config.sops.secrets.wg0-privatekey.path;
FirewallMark = 100;
};
wireguardPeers = [
{
wireguardPeerConfig = {
AllowedIPs = [
# this allows all traffic to be routed through this interface
"0.0.0.0/0"
# # alternatively, specific destinations could be allowed
# # remote peer wg addr
# "10.0.0.0/32"
# "1.1.1.1/32"
# # ifconfig.co.
# "172.67.168.106"
# "104.21.54.91"
];
PersistentKeepalive = 15;
PresharedKeyFile = builtins.toString config.sops.secrets.wg0-peer0-psk.path;
PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM=";
Endpoint = router0-ifog_wg0Endpoint;
};
}
];
};
wg1 = {
enable = true;
netdevConfig = {
Name = "wg1";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = builtins.toString config.sops.secrets.wg1-privatekey.path;
FirewallMark = 101;
};
wireguardPeers = [
{
wireguardPeerConfig = {
AllowedIPs = [
# this allows all traffic to be routed through this interface
"0.0.0.0/0"
];
PersistentKeepalive = 15;
PresharedKeyFile = builtins.toString config.sops.secrets.wg1-peer0-psk.path;
PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM=";
Endpoint = router0-ifog_wg1Endpoint;
};
}
];
};
wg2 = {
enable = true;
netdevConfig = {
Name = "wg2";
Kind = "wireguard";
};
wireguardConfig = {
PrivateKeyFile = builtins.toString config.sops.secrets.wg0-privatekey.path;
FirewallMark = 102;
};
wireguardPeers = [
{
wireguardPeerConfig = {
AllowedIPs = [
# this allows all traffic to be routed through this interface
"0.0.0.0/0"
# # alternatively, specific destinations could be allowed
# # remote peer wg addr
# "10.0.0.0/32"
# "1.1.1.1/32"
# # ifconfig.co.
# "172.67.168.106"
# "104.21.54.91"
];
PersistentKeepalive = 15;
PresharedKeyFile = builtins.toString config.sops.secrets.wg0-peer0-psk.path;
PublicKey = "/RPDdqPzr9iRc7zR0bRkt9aS2QCt+b2K3WbsNg8XamM=";
Endpoint = router0-hosthatch_wg0Endpoint;
};
}
];
};
2024-02-08 20:53:22 +01:00
}
# generate the vlan devices. these will be tagged on the main bridge
// builtins.foldl'
(acc: cur: acc // cur)
2024-02-08 20:53:22 +01:00
{}
(
builtins.map
({
vlanid,
vlanid',
}: {
"20-${mkInterfaceName {inherit vlanid;}}" = {
netdevConfig = {
Kind = "vlan";
2024-02-08 20:53:22 +01:00
Name = "${mkInterfaceName {inherit vlanid;}}";
};
vlanConfig.Id = vlanid;
};
})
2024-02-08 20:53:22 +01:00
(
builtins.map
(vlanid: {
inherit vlanid;
vlanid' = builtins.toString vlanid;
})
vlanRange
)
2024-02-08 20:53:22 +01:00
);
networks =
{
# places options here that should always exist
"lo" = {
matchConfig.Name = "lo";
# these are roughly equivalent to:
# ip rule add fwmark 100 priority 0 table 100
# ip rule add fwmark 100 priority 1 prohibit
# ip rule add fwmark 101 priority 0 table 101
# ip rule add fwmark 101 priority 1 prohibit
routingPolicyRules = [
{
routingPolicyRuleConfig = {
FirewallMark = 100;
Priority = 30000;
Table = 100;
};
}
{
routingPolicyRuleConfig = {
FirewallMark = 100;
Priority = 30001;
Table = 100;
Type = "prohibit";
};
}
{
routingPolicyRuleConfig = {
FirewallMark = 101;
Priority = 30000;
Table = 101;
};
}
{
routingPolicyRuleConfig = {
FirewallMark = 101;
Priority = 30001;
Table = 101;
Type = "prohibit";
};
}
{
routingPolicyRuleConfig = {
FirewallMark = 102;
Priority = 30000;
Table = 102;
};
}
{
routingPolicyRuleConfig = {
FirewallMark = 102;
Priority = 30001;
Table = 102;
Type = "prohibit";
};
}
];
};
2024-02-08 20:53:22 +01:00
# use lan0 as secondary WAN interface
"10-lan0-wan" = {
matchConfig.Name = "lan0";
networkConfig = {
2024-06-12 22:18:52 +02:00
# start a DHCP Client for IPv4/6 Addressing/Routing
DHCP = true;
2024-02-08 20:53:22 +01:00
# accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC)
IPv6AcceptRA = true;
DNSOverTLS = true;
DNSSEC = true;
IPv6PrivacyExtensions = false;
IPForward = true;
};
linkConfig.RequiredForOnline = "no";
# similar to
# ip route add default via 172.16.0.1 table 101
routes = [
{
routeConfig = {
Gateway = "_dhcp4";
Table = 101;
};
}
];
};
2024-02-08 20:53:22 +01:00
"10-wan" = {
matchConfig.Name = "wan";
networkConfig = {
2024-06-12 22:18:52 +02:00
# start a DHCP Client for IPv4/6 Addressing/Routing
DHCP = true;
2024-02-08 20:53:22 +01:00
# accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC)
IPv6AcceptRA = true;
DNSOverTLS = true;
DNSSEC = true;
IPv6PrivacyExtensions = false;
IPForward = true;
};
# make routing on this interface a dependency for network-online.target
# linkConfig.RequiredForOnline = "routable";
linkConfig.RequiredForOnline = "no";
# similar to
# ip route add default via 192.168.0.1 table 100
routes = [
{
routeConfig = {
Gateway = "_dhcp4";
Table = 100;
};
}
{
routeConfig = {
Gateway = "_dhcp4";
Table = 102;
};
}
];
};
2024-02-08 20:53:22 +01:00
# Connect the bridge ports to the bridge
"30-lan1" = {
matchConfig.Name = "lan1";
networkConfig = {
Bridge = bridgeInterfaceName;
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "enslaved";
2024-02-08 20:53:22 +01:00
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = vlansByName.dmz.id;
PVID = vlansByName.dmz.id;
EgressUntagged = vlansByName.dmz.id;
};
}
];
};
2024-02-08 20:53:22 +01:00
"30-lan2" = {
matchConfig.Name = "lan2";
networkConfig = {
Bridge = bridgeInterfaceName;
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "enslaved";
2024-02-08 20:53:22 +01:00
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = vlansByName.office.id;
PVID = vlansByName.office.id;
EgressUntagged = vlansByName.office.id;
};
}
];
};
2024-02-08 20:53:22 +01:00
"30-lan3" = {
matchConfig.Name = "lan3";
networkConfig = {
Bridge = bridgeInterfaceName;
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "enslaved";
2024-02-08 20:53:22 +01:00
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}";
};
}
];
};
# Configure the bridge for its desired function
"40-${bridgeInterfaceName}" = {
matchConfig.Name = bridgeInterfaceName;
bridgeConfig = {};
address = [
(mkVlanIpv4HostAddr {
vlanid = 0;
host = 1;
})
];
networkConfig = {
ConfigureWithoutCarrier = true;
};
# Don't wait for it as it also would wait for wlan and DFS which takes around 5 min
linkConfig.RequiredForOnline = "no";
linkConfig.ActivationPolicy = "always-up";
2024-02-08 20:53:22 +01:00
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}";
};
}
];
2024-02-08 20:53:22 +01:00
vlan = (
builtins.map
(vlanid: (mkInterfaceName {inherit vlanid;}))
vlanRange
);
};
"50-wg0" = {
enable = true;
matchConfig.Name = "wg0";
address = [
"10.0.0.1/31"
];
routes = [
2024-07-26 14:01:59 +02:00
# {
# routeConfig = {
# # test the set uprouting to a specific IP
# Destination = "${repoFlake.colmena.sj-bm-hostkey0.deployment.targetHost}/32";
# MultiPathRoute = "10.0.0.0 1";
# };
# }
];
};
"50-wg1" = {
enable = true;
matchConfig.Name = "wg1";
address = [
"10.0.0.3/31"
];
routes = [
2024-07-26 14:01:59 +02:00
# {
# routeConfig = {
# Destination = "${repoFlake.colmena.sj-bm-hostkey0.deployment.targetHost}/32";
# MultiPathRoute = "10.0.0.2 1";
# };
# }
];
};
"50-wg2" = {
enable = true;
matchConfig.Name = "wg2";
address = [
"10.0.1.1/31"
];
routes = [
# TODO: add a testing route here
];
};
2024-02-08 20:53:22 +01:00
}
# configuration for the hostapd dynamic interfaces
# * netdev type vlan
# * host address for vlan
# * vlan config for wlan interface
// builtins.foldl'
(acc: cur: acc // cur)
2024-02-08 20:53:22 +01:00
{}
(builtins.map
2024-02-08 20:53:22 +01:00
({
vlanid,
vlanid',
}: {
# configure the tagged vlan device with an address and vlan filtering.
# dnsmasq is configured to serve the respective /24 range on each tagged device.
# this device only receives traffic for the given vlanid and sends tagged traffic to the bridge.
2024-02-08 20:53:22 +01:00
"41-${mkInterfaceName {inherit vlanid;}}" = {
matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}";
address = [
2024-02-08 20:53:22 +01:00
(mkVlanIpv4HostAddr {
inherit vlanid;
host = 1;
})
];
networkConfig = {
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "no";
linkConfig.ActivationPolicy = "always-up";
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = vlanid;
};
}
];
};
# configure the wlan interface as a bridge member that
# * only gets traffic for vid 15
# * untags traffic after receiving it
# * tags traffic that comes out of it
"41-wlan0.${vlanid'}" = {
matchConfig.Name = "wlan0.${vlanid'}";
networkConfig = {
Bridge = bridgeInterfaceName;
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "no";
bridgeVLANs = [
{
bridgeVLANConfig = {
VLAN = vlanid;
PVID = vlanid;
EgressUntagged = vlanid;
};
}
];
};
2024-02-08 20:53:22 +01:00
"50-${mkInterfaceName {inherit vlanid;}}" = {
matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}";
address = [
2024-02-08 20:53:22 +01:00
(mkVlanIpv4HostAddr {
inherit vlanid;
host = 1;
})
];
networkConfig = {
ConfigureWithoutCarrier = true;
};
linkConfig.RequiredForOnline = "no";
};
})
2024-02-08 20:53:22 +01:00
(
builtins.map
(vlanid: {
inherit vlanid;
vlanid' = builtins.toString vlanid;
})
vlanRange
2024-02-08 20:53:22 +01:00
));
};
# wireless access point
services.hostapd = {
enable = true;
# package = nodeFlake.packages.${system}.hostapd_patched;
2024-02-08 20:53:22 +01:00
radios = let
# generated with https://miniwebtool.com/mac-address-generator/
mkBssid = i: "34:56:ce:0f:ed:4${toString i}";
in {
wlan0 = {
band = "2g";
2024-07-26 14:01:59 +02:00
# FIXME: apparently setting this could cause bugs, testing disabling it for a while.
# countryCode = "CH";
channel = 0; # 0 would mean Automatic Channel Selection
settings = {
# TODO: this would be faster but x13s on windows can't connect when it's enabled.
# ieee80211n = 1;
# Exclude DFS channels from ACS
# This option can be used to exclude all DFS channels from the ACS channel list
# in cases where the driver supports DFS channels.
acs_exclude_dfs = 0;
# Disassociate stations based on excessive transmission failures or other
# indications of connection loss. This depends on the driver capabilities and
# may not be available with all drivers.
disassoc_low_ack = 0;
skip_inactivity_poll = 1;
# TODO: check if this is required. multicast can be more efficient so it'd be nice to disable this.
multicast_to_unicast = 1;
};
2024-02-08 20:53:22 +01:00
# use 'iw phy#1 info' to determine your VHT capabilities
wifi4 = {
enable = true;
require = false;
capabilities = [
"HT20"
"HT40+"
"LDPC"
"SHORT-GI-20"
"SHORT-GI-40"
"TX-STBC"
"RX-STBC1"
"MAX-AMSDU-7935"
# "DELAYED-BA"
# "DSSS_CCK-40"
"40-INTOLERANT"
];
};
wifi5 = {
enable = false;
require = false;
2024-02-08 20:53:22 +01:00
};
wifi6 = {
enable = false;
require = false;
};
2024-02-08 20:53:22 +01:00
networks = {
wlan0 = let
iface = "wlan0";
in {
ssid = "mlsia";
bssid = mkBssid 0;
# enables debug logging
logLevel = 0;
2024-06-12 22:18:52 +02:00
authentication.mode =
"wpa2-sha256"
# "wpa3-sae-transition"
# "wpa3-sae"
;
2024-02-08 20:53:22 +01:00
authentication.wpaPskFile = config.sops.secrets."${iface}_wpaPskFile".path;
2024-06-12 22:18:52 +02:00
# TODO: unfortunately SAE passwords don't work per VLAN like PSKs do
# authentication.saePasswordsFile = config.sops.secrets."${iface}_saePasswordsFile".path;
2024-02-08 20:53:22 +01:00
# see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf for reference
settings = {
# disable syslog because it duplicates stdout
logger_syslog = lib.mkForce 0;
2024-02-08 20:53:22 +01:00
# bridge = bridgeInterfaceName;
2024-02-08 20:53:22 +01:00
# wpa_psk_file = config.sops.secrets.wlan0_wpaPskFile.path;
# not yet supported on hostapd 2.10
# sae_password_file = config.sops.secrets.wlan0_saePasswordsFile.path;
2024-02-08 20:53:22 +01:00
# resources on vlan tagging
# https://wireless.wiki.kernel.org/en/users/Documentation/hostapd#dynamic_vlan_tagging
# https://forum.openwrt.org/t/individual-per-passphrase-wifi-vlans-using-wpa-psk-file-no-radius-required/161696/4
2024-02-08 20:53:22 +01:00
dynamic_vlan = 1;
# this option currently requires a patch to hostapd
vlan_no_bridge = 1;
2024-02-08 20:53:22 +01:00
/*
not used due to the above vlan_no_bridge setting
vlan_tagged_interface = bridgeInterfaceName;
vlan_naming = 1;
vlan_bridge = "br-${iface}.";
2024-02-08 20:53:22 +01:00
*/
vlan_file = let
generated =
builtins.map
(
vlanid: "${builtins.toString vlanid} ${iface}.${builtins.toString vlanid}"
)
vlanRange;
wildcard = [
# Optional wildcard entry matching all VLAN IDs. The first # in the interface
# name will be replaced with the VLAN ID. The network interfaces are created
# (and removed) dynamically based on the use.
# see https://w1.fi/cgit/hostap/tree/hostapd/hostapd.vlan
"* ${iface}.#"
];
file =
pkgs.writeText "hostapd.vlan"
(builtins.concatStringsSep "\n" (generated ++ wildcard));
filePath = toString file;
in
filePath;
2024-02-08 20:53:22 +01:00
wpa_key_mgmt = lib.mkForce (builtins.concatStringsSep " " [
"WPA-PSK"
# TODO: the printer can't connect when this is on
# "WPA-PSK-SHA256"
# unfortunately SAE doesn't support VLAN passwords in the way i'd like to use them
# "SAE"
]);
# wpa_psk_radius = 0;
wpa_pairwise = "CCMP";
wmm_enabled = 1;
# IEEE 802.11i (authentication) related configuration
# Encrypt management frames to protect against deauthentication and similar attacks.
# 0 := disabled; 1 := optional; 2 := required
ieee80211w = 1;
# sae_require_mfp = 1;
2024-06-12 22:18:52 +02:00
# sae_groups = "19 20 21";
2024-02-08 20:53:22 +01:00
# [ENABLE-TLSv1.3] = enable TLSv1.3 (experimental - disabled by default)
tls_flags = "[ENABLE-TLSv1.3]";
ieee8021x = 0;
eap_server = 0;
2024-02-08 20:53:22 +01:00
};
};
2024-02-08 20:53:22 +01:00
};
};
2024-02-08 20:53:22 +01:00
};
};
services.resolved.enable = false;
services.dnsmasq = {
enable = true;
settings = {
domain-needed = true;
bogus-priv = true;
no-resolv = true;
localise-queries = true;
proxy-dnssec = true;
conntrack = true;
2023-12-28 13:56:57 +01:00
# enable for debugging
# log-debug = true;
# log-queries = true;
# disable negative caching
no-negcache = true;
local-ttl = 0;
dhcp-ttl = 0;
2024-02-08 20:53:22 +01:00
dhcp-range = let
mkDhcpRange = {
tag,
vlanid,
}:
builtins.concatStringsSep "," [
tag
2024-02-08 20:53:22 +01:00
(mkVlanIpv4HostAddr {
inherit vlanid;
host = 100;
cidr = false;
})
(mkVlanIpv4HostAddr {
inherit vlanid;
host = 199;
cidr = false;
})
"12h"
];
2024-02-08 20:53:22 +01:00
in
builtins.map
2024-02-08 20:53:22 +01:00
(
vlanid:
mkDhcpRange {
tag = mkInterfaceName {inherit vlanid;};
inherit vlanid;
}
)
vlanRangeWith0;
# TODO: double-check that this works
dhcp-host = "1c:69:7a:07:08:5f,${dmzExposedHostIpv4},${dmzExposedHost}";
expand-hosts = true;
2023-08-22 10:20:16 +02:00
# don't use /etc/hosts as this would advertise ${nodeName} as localhost
no-hosts = true;
server = [
# upstream DNS servers
# https://dnsforge.de/
"176.9.93.198"
"176.9.1.117"
"2a01:4f8:151:34aa::198"
"2a01:4f8:141:316d::117"
# cloudflare and google
# "9.9.9.9" "8.8.8.8" "1.1.1.1"
];
2024-02-08 20:53:22 +01:00
domain =
[
"/${getVlanDomain {vlanid = 0;}}/,local"
]
++ builtins.map
(
vlanid: "${getVlanDomain {inherit vlanid;}},${mkVlanIpv4HostAddr {
inherit vlanid;
host = 0;
cidr = true;
}},local"
)
2024-02-08 20:53:22 +01:00
vlanRangeWith0;
# TODO: compare this to using `interface-name`
2024-02-08 20:53:22 +01:00
dynamic-host =
[
]
++ builtins.map
(
vlanid:
builtins.concatStringsSep "," [
# "${getVlanDomain{inherit vlanid;}}" "0.0.0.1" (mkInterfaceName {inherit vlanid;})
"${nodeName}.${getVlanDomain {inherit vlanid;}}"
"0.0.0.1"
(mkInterfaceName {inherit vlanid;})
]
)
2024-02-08 20:53:22 +01:00
vlanRangeWith0;
2024-02-08 20:53:22 +01:00
dhcp-option-force =
builtins.map
(vlanid: "${mkInterfaceName {inherit vlanid;}},option:domain-search,${getVlanDomain {inherit vlanid;}}")
vlanRangeWith0;
# auth-server = [
# (builtins.concatStringsSep "," [
# "www.stefanjunker.de"
# # (mkInterfaceName { vlanid = vlansByName.dmz.id; })
# # (mkInterfaceName { vlanid = vlansByName.office.id; })
# ])
# ];
cname = [
"mailserver.svc.stefanjunker.de,${dmzExposedHost}"
"www.stefanjunker.de,${dmzExposedHost}"
"hedgedoc.www.stefanjunker.de,${dmzExposedHost}"
"jitsi.www.stefanjunker.de,${dmzExposedHost}"
"lldap.www.stefanjunker.de,${dmzExposedHost}"
"forgejo.www.stefanjunker.de,${dmzExposedHost}"
];
};
};
system.stateVersion = "24.05";
# boot.kernelPackages = pkgs.linuxPackages_bpir3_6_6;
environment.systemPackages = [
pkgs.ethtool
pkgs.vim
pkgs.wireguard-tools
pkgs.tshark
pkgs.tmux
(pkgs.writeShellScriptBin "dbg-ip" ''
echo links:
ip -br -c l
echo
echo addresses:
ip -br -c a
echo
echo vlans:
bridge -c vlan
'')
(pkgs.writeShellScriptBin "dbg-dnsmasq" ''
# get the rendered in-use config
pgrep -a dnsmasq | grep -Eo '[^ ]*conf' | xargs cat | grep -Eo '[^=]*conf' | xargs cat
'')
];
}