1295 lines
36 KiB
Nix
1295 lines
36 KiB
Nix
# TODO: don't pull in bluez (or any bluetooth components)
|
|
{
|
|
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);
|
|
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 "."
|
|
["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;
|
|
};
|
|
|
|
vlansByName =
|
|
lib.attrsets.mapAttrs'
|
|
(
|
|
vlanid': attrs:
|
|
lib.attrsets.nameValuePair
|
|
attrs.name
|
|
(attrs
|
|
// {
|
|
id = lib.strings.toInt vlanid';
|
|
id' = vlanid';
|
|
})
|
|
)
|
|
vlans;
|
|
|
|
getVlanDomain = {vlanid}:
|
|
if vlanid == 0
|
|
then defaultVlan.name
|
|
else vlans."${toString vlanid}".name + "." + defaultVlan.name;
|
|
|
|
bridgeInterfaceName = "br-lan";
|
|
mkInterfaceName = {vlanid}:
|
|
if vlanid == 0
|
|
then bridgeInterfaceName
|
|
else "${bridgeInterfaceName}.${toString vlanid}";
|
|
|
|
dmzExposedHost = "sj-srv1";
|
|
dmzExposedHostDomain = "dmz.internal";
|
|
dmzExposedHostFQDN = "${dmzExposedHost}.${dmzExposedHostDomain}";
|
|
dmzExposedHostIpv4 = mkVlanIpv4HostAddr {
|
|
vlanid = vlansByName.dmz.id;
|
|
host = 99;
|
|
cidr = false;
|
|
};
|
|
|
|
dmzExposedHostMACaddr = repoFlake.nixosConfigurations.${dmzExposedHost}.config.systemd.network.netdevs."10-dmz0".netdevConfig.MACAddress;
|
|
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;
|
|
|
|
# sops.secrets.wlan0_saePasswordsFile = {};
|
|
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)
|
|
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}"
|
|
];
|
|
};
|
|
};
|
|
};
|
|
|
|
firewall = {
|
|
enable = true;
|
|
snippets.nnf-common.enable = true;
|
|
# included in the above
|
|
# snippets.nnf-conntrack.enable = true;
|
|
zones =
|
|
{
|
|
lan.interfaces = [(mkInterfaceName {vlanid = 0;})];
|
|
vlan.interfaces = builtins.map (vlanid: (mkInterfaceName {inherit vlanid;})) vlanRange;
|
|
# lan.ipv4Addresses = ["192.168.0.0/16"];
|
|
wan.interfaces = ["wan" "lan0"];
|
|
vpn.interfaces = ["wg0" "wg1" "wg2"];
|
|
}
|
|
//
|
|
# generate a zone for each vlan
|
|
lib.attrsets.mapAttrs
|
|
(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";
|
|
};
|
|
|
|
office-to-dmz = {
|
|
from = ["office"];
|
|
to = ["dmz"];
|
|
verdict = "accept";
|
|
};
|
|
|
|
lan-to-fw = {
|
|
from = ["lan"];
|
|
to = ["fw" "lan"];
|
|
verdict = "accept";
|
|
};
|
|
|
|
lan-to-wan = {
|
|
from = ["lan"];
|
|
to = ["wan"];
|
|
verdict = "accept";
|
|
};
|
|
|
|
vlan-to-wan = {
|
|
from = ["vlan"];
|
|
to = ["wan"];
|
|
verdict = "accept";
|
|
};
|
|
|
|
vlan-to-fw = {
|
|
allowedUDPPortRanges = [
|
|
{
|
|
from = 53;
|
|
to = 53;
|
|
}
|
|
{
|
|
from = 67;
|
|
to = 68;
|
|
}
|
|
{
|
|
from = 5201;
|
|
to = 5201;
|
|
}
|
|
];
|
|
allowedTCPPortRanges = [
|
|
{
|
|
from = 22;
|
|
to = 22;
|
|
}
|
|
{
|
|
from = 53;
|
|
to = 53;
|
|
}
|
|
{
|
|
from = 5201;
|
|
to = 5201;
|
|
}
|
|
];
|
|
from = ["vlan"];
|
|
to = ["fw"];
|
|
extraLines =
|
|
allowIcmpLines
|
|
++ [
|
|
"drop"
|
|
];
|
|
};
|
|
|
|
to-wan-nat = {
|
|
from = ["lan" "vlan"];
|
|
to = ["wan"];
|
|
masquerade = true;
|
|
verdict = "accept";
|
|
};
|
|
|
|
wan-to-dmz = {
|
|
from = ["wan"];
|
|
to = ["dmz"];
|
|
verdict = "accept";
|
|
};
|
|
|
|
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";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
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
|
|
{
|
|
# Create the bridge interface
|
|
"20-${bridgeInterfaceName}" = {
|
|
netdevConfig = {
|
|
Kind = "bridge";
|
|
Name = bridgeInterfaceName;
|
|
};
|
|
|
|
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;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
}
|
|
# generate the vlan devices. these will be tagged on the main bridge
|
|
// builtins.foldl'
|
|
(acc: cur: acc // cur)
|
|
{}
|
|
(
|
|
builtins.map
|
|
({
|
|
vlanid,
|
|
vlanid',
|
|
}: {
|
|
"20-${mkInterfaceName {inherit vlanid;}}" = {
|
|
netdevConfig = {
|
|
Kind = "vlan";
|
|
Name = "${mkInterfaceName {inherit vlanid;}}";
|
|
};
|
|
vlanConfig.Id = vlanid;
|
|
};
|
|
})
|
|
(
|
|
builtins.map
|
|
(vlanid: {
|
|
inherit vlanid;
|
|
vlanid' = builtins.toString vlanid;
|
|
})
|
|
vlanRange
|
|
)
|
|
);
|
|
networks = let
|
|
commonWanOptions = {
|
|
networkConfig = {
|
|
# start a DHCP Client for IPv4/6 Addressing/Routing
|
|
DHCP = true;
|
|
DNSOverTLS = true;
|
|
DNSSEC = true;
|
|
IPForward = true;
|
|
|
|
# accept Router Advertisements for Stateless IPv6 Autoconfiguraton (SLAAC)
|
|
IPv6AcceptRA = true;
|
|
IPv6PrivacyExtensions = false;
|
|
DHCPPrefixDelegation = true;
|
|
};
|
|
dhcpV4Config = {
|
|
UseDNS = false;
|
|
UseDomains = false;
|
|
UseHostname = false;
|
|
};
|
|
dhcpV6Config = {
|
|
UseDNS = false;
|
|
UseDomains = false;
|
|
UseHostname = false;
|
|
PrefixDelegationHint = "::/56";
|
|
UseDelegatedPrefix = true;
|
|
WithoutRA = "solicit";
|
|
};
|
|
ipv6AcceptRAConfig = {
|
|
UseDNS = false;
|
|
UseDomains = false;
|
|
};
|
|
|
|
# TODO: enable these somehow
|
|
# extraConfig = ''
|
|
# [IPv6AcceptRA]
|
|
# # FIXME: supported in nixos-24.11
|
|
# DHCPv6Client=solicit
|
|
|
|
# # FIXME: not supported at all yet
|
|
# UsePREF64=true
|
|
# '';
|
|
};
|
|
in
|
|
{
|
|
# 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";
|
|
};
|
|
}
|
|
];
|
|
};
|
|
# use lan0 as secondary WAN interface
|
|
"10-lan0-wan" = lib.attrsets.recursiveUpdate commonWanOptions {
|
|
matchConfig.Name = "lan0";
|
|
# make routing on this interface a dependency for network-online.target
|
|
# linkConfig.RequiredForOnline = "routable";
|
|
linkConfig.RequiredForOnline = "no";
|
|
|
|
dhcpV4Config = {
|
|
RouteMetric = 2000;
|
|
};
|
|
|
|
# similar to
|
|
# ip route add default via 172.16.0.1 table 101
|
|
routes = [
|
|
{
|
|
routeConfig = {
|
|
Gateway = "_dhcp4";
|
|
Table = 101;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
"10-wan" = lib.attrsets.recursiveUpdate commonWanOptions {
|
|
matchConfig.Name = "wan";
|
|
# make routing on this interface a dependency for network-online.target
|
|
# linkConfig.RequiredForOnline = "routable";
|
|
linkConfig.RequiredForOnline = "no";
|
|
|
|
dhcpV4Config = {
|
|
RouteMetric = 1000;
|
|
};
|
|
|
|
# similar to
|
|
# ip route add default via 192.168.0.1 table 100
|
|
routes = [
|
|
{
|
|
routeConfig = {
|
|
Gateway = "_dhcp4";
|
|
Table = 100;
|
|
};
|
|
}
|
|
{
|
|
routeConfig = {
|
|
Gateway = "_dhcp4";
|
|
Table = 102;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
# Connect the bridge ports to the bridge
|
|
"30-lan1" = {
|
|
matchConfig.Name = "lan1";
|
|
networkConfig = {
|
|
Bridge = bridgeInterfaceName;
|
|
ConfigureWithoutCarrier = true;
|
|
};
|
|
linkConfig.RequiredForOnline = "enslaved";
|
|
|
|
bridgeVLANs = [
|
|
{
|
|
bridgeVLANConfig = {
|
|
VLAN = vlansByName.dmz.id;
|
|
PVID = vlansByName.dmz.id;
|
|
EgressUntagged = vlansByName.dmz.id;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
"30-lan2" = {
|
|
matchConfig.Name = "lan2";
|
|
networkConfig = {
|
|
Bridge = bridgeInterfaceName;
|
|
ConfigureWithoutCarrier = true;
|
|
};
|
|
linkConfig.RequiredForOnline = "enslaved";
|
|
|
|
bridgeVLANs = [
|
|
{
|
|
bridgeVLANConfig = {
|
|
VLAN = vlansByName.office.id;
|
|
PVID = vlansByName.office.id;
|
|
EgressUntagged = vlansByName.office.id;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
"30-lan3" = {
|
|
matchConfig.Name = "lan3";
|
|
networkConfig = {
|
|
Bridge = bridgeInterfaceName;
|
|
ConfigureWithoutCarrier = true;
|
|
};
|
|
linkConfig.RequiredForOnline = "enslaved";
|
|
|
|
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";
|
|
|
|
bridgeVLANs = [
|
|
{
|
|
bridgeVLANConfig = {
|
|
VLAN = "${toString vlanRangeStart}-${toString vlanRangeEnd}";
|
|
};
|
|
}
|
|
];
|
|
|
|
vlan = (
|
|
builtins.map
|
|
(vlanid: (mkInterfaceName {inherit vlanid;}))
|
|
vlanRange
|
|
);
|
|
};
|
|
|
|
"50-wg0" = {
|
|
enable = true;
|
|
matchConfig.Name = "wg0";
|
|
address = [
|
|
"10.0.0.1/31"
|
|
];
|
|
|
|
routes = [
|
|
# {
|
|
# 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 = [
|
|
# {
|
|
# 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
|
|
];
|
|
};
|
|
}
|
|
# configuration for the hostapd dynamic interfaces
|
|
# * netdev type vlan
|
|
# * host address for vlan
|
|
# * vlan config for wlan interface
|
|
// builtins.foldl'
|
|
(acc: cur: acc // cur)
|
|
{}
|
|
(builtins.map
|
|
({
|
|
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.
|
|
"41-${mkInterfaceName {inherit vlanid;}}" = {
|
|
matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}";
|
|
address = [
|
|
(mkVlanIpv4HostAddr {
|
|
inherit vlanid;
|
|
host = 1;
|
|
})
|
|
];
|
|
networkConfig = {
|
|
ConfigureWithoutCarrier = true;
|
|
|
|
# the client shouldn't be allowed to send us RAs, that would be weird.
|
|
IPv6AcceptRA = false;
|
|
|
|
DHCPPrefixDelegation = true;
|
|
IPv6SendRA = true;
|
|
};
|
|
|
|
dhcpPrefixDelegationConfig = {
|
|
UplinkInterface = "wan";
|
|
Assign = true;
|
|
SubnetId = vlanid;
|
|
Announce = 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;
|
|
};
|
|
}
|
|
];
|
|
};
|
|
|
|
# "50-${mkInterfaceName {inherit vlanid;}}" = {
|
|
# matchConfig.Name = "${mkInterfaceName {inherit vlanid;}}";
|
|
# address = [
|
|
# (mkVlanIpv4HostAddr {
|
|
# inherit vlanid;
|
|
# host = 1;
|
|
# })
|
|
# ];
|
|
# networkConfig = {
|
|
# ConfigureWithoutCarrier = true;
|
|
# };
|
|
# linkConfig.RequiredForOnline = "no";
|
|
# };
|
|
})
|
|
(
|
|
builtins.map
|
|
(vlanid: {
|
|
inherit vlanid;
|
|
vlanid' = builtins.toString vlanid;
|
|
})
|
|
vlanRange
|
|
));
|
|
};
|
|
|
|
# wireless access point
|
|
services.hostapd = {
|
|
enable = true;
|
|
# package = nodeFlake.packages.${system}.hostapd_patched;
|
|
radios = let
|
|
# generated with https://miniwebtool.com/mac-address-generator/
|
|
mkBssid = i: "34:56:ce:0f:ed:4${toString i}";
|
|
in {
|
|
wlan0 = {
|
|
band = "2g";
|
|
# 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;
|
|
};
|
|
|
|
# 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"
|
|
|
|
"40-INTOLERANT"
|
|
|
|
# not supported by BPI-R3 module
|
|
# "DELAYED-BA"
|
|
# "DSSS_CCK-40"
|
|
];
|
|
};
|
|
|
|
wifi5 = {
|
|
enable = false;
|
|
require = false;
|
|
};
|
|
|
|
wifi6 = {
|
|
enable = false;
|
|
require = false;
|
|
};
|
|
|
|
networks = {
|
|
wlan0 = let
|
|
iface = "wlan0";
|
|
in {
|
|
ssid = "mlsia";
|
|
bssid = mkBssid 0;
|
|
|
|
# enables debug logging
|
|
logLevel = 0;
|
|
|
|
authentication.mode =
|
|
"wpa2-sha256"
|
|
# "wpa3-sae-transition"
|
|
# "wpa3-sae"
|
|
;
|
|
|
|
authentication.wpaPskFile = config.sops.secrets."${iface}_wpaPskFile".path;
|
|
|
|
# TODO: unfortunately SAE passwords don't work per VLAN like PSKs do
|
|
# authentication.saePasswordsFile = config.sops.secrets."${iface}_saePasswordsFile".path;
|
|
|
|
# see https://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf for reference
|
|
settings = {
|
|
# disable syslog because it duplicates stdout
|
|
logger_syslog = lib.mkForce 0;
|
|
|
|
# bridge = bridgeInterfaceName;
|
|
|
|
# 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;
|
|
|
|
# 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
|
|
|
|
dynamic_vlan = 1;
|
|
# this option currently requires a patch to hostapd
|
|
vlan_no_bridge = 1;
|
|
|
|
/*
|
|
not used due to the above vlan_no_bridge setting
|
|
vlan_tagged_interface = bridgeInterfaceName;
|
|
vlan_naming = 1;
|
|
vlan_bridge = "br-${iface}.";
|
|
*/
|
|
|
|
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;
|
|
|
|
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;
|
|
# sae_groups = "19 20 21";
|
|
|
|
# [ENABLE-TLSv1.3] = enable TLSv1.3 (experimental - disabled by default)
|
|
tls_flags = "[ENABLE-TLSv1.3]";
|
|
|
|
# TODO: debugging for wifi drops happens below here
|
|
# Require IEEE 802.1X authorization
|
|
ieee8021x = 0;
|
|
|
|
# Optionally, hostapd can be configured to use an integrated EAP server
|
|
# to process EAP authentication locally without need for an external RADIUS
|
|
# server. This functionality can be used both as a local authentication server
|
|
# for IEEE 802.1X/EAPOL and as a RADIUS server for other devices.
|
|
|
|
# Use integrated EAP server instead of external RADIUS authentication
|
|
# server. This is also needed if hostapd is configured to act as a RADIUS
|
|
# authentication server.
|
|
eap_server = 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 = 0;
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
};
|
|
|
|
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;
|
|
|
|
# enable for debugging
|
|
# log-debug = true;
|
|
# log-queries = true;
|
|
|
|
# disable negative caching
|
|
no-negcache = true;
|
|
local-ttl = 0;
|
|
dhcp-ttl = 0;
|
|
|
|
# v6 config
|
|
enable-ra = true;
|
|
|
|
dhcp-range = let
|
|
mkDhcpRange = {
|
|
tag,
|
|
vlanid,
|
|
}:
|
|
builtins.concatStringsSep "," [
|
|
tag
|
|
(mkVlanIpv4HostAddr {
|
|
inherit vlanid;
|
|
host = 100;
|
|
cidr = false;
|
|
})
|
|
(mkVlanIpv4HostAddr {
|
|
inherit vlanid;
|
|
host = 199;
|
|
cidr = false;
|
|
})
|
|
"12h"
|
|
# "slaac"
|
|
# "ra-stateless"
|
|
# "ra-names"
|
|
];
|
|
in
|
|
builtins.map
|
|
(
|
|
vlanid:
|
|
mkDhcpRange {
|
|
tag = mkInterfaceName {inherit vlanid;};
|
|
inherit vlanid;
|
|
}
|
|
)
|
|
vlanRangeWith0;
|
|
|
|
dhcp-host = builtins.concatStringsSep "," [
|
|
dmzExposedHostMACaddr
|
|
dmzExposedHostIpv4
|
|
dmzExposedHostFQDN
|
|
];
|
|
|
|
expand-hosts = true;
|
|
|
|
# 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"
|
|
|
|
# https://dismail.de/info.html#dns
|
|
"116.203.32.217"
|
|
"2a01:4f8:1c1b:44aa::1"
|
|
"159.69.114.157"
|
|
"2a01:4f8:c17:739a::2"
|
|
];
|
|
|
|
domain =
|
|
[
|
|
"/${getVlanDomain {vlanid = 0;}}/,local"
|
|
]
|
|
++ builtins.map
|
|
(
|
|
vlanid: "${getVlanDomain {inherit vlanid;}},${mkVlanIpv4HostAddr {
|
|
inherit vlanid;
|
|
host = 0;
|
|
cidr = true;
|
|
}},local"
|
|
)
|
|
vlanRangeWith0;
|
|
|
|
# TODO: compare this to using `interface-name`
|
|
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;})
|
|
]
|
|
)
|
|
vlanRangeWith0;
|
|
|
|
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
|
|
'')
|
|
];
|
|
}
|