refactor vlan and firewall config into modules

fix bug with vlans module

fix vlans
This commit is contained in:
Joakim Repomaa
2025-02-03 15:32:09 +02:00
committed by Joakim Repomaa
parent 6026e064fd
commit 2a36964fcc
5 changed files with 255 additions and 112 deletions

View File

@@ -49,19 +49,19 @@
freun-dev = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [ ./hosts/freun.dev ];
modules = [ ./modules ./hosts/freun.dev ];
};
radish = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [ ./hosts/radish ];
modules = [ ./modules ./hosts/radish ];
};
apu = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = { inherit inputs; };
modules = [ ./hosts/apu ];
modules = [ ./modules ./hosts/apu ];
};
};
};

View File

@@ -4,100 +4,6 @@
{ lib, pkgs, config, ... }:
let
services = {
ssh = { tcp = [ 22 ]; };
dhcp = { udp = [ 67 68 ]; };
dns = { udp = [ 53 ]; };
web = { tcp = [ 80 443 ]; };
};
vlans = {
koti = {
id = 10;
ipv6 = true;
availableServices = with services; [ dhcp dns ssh ];
};
gast = {
id = 20;
ipv6 = true;
staticLeases = [
{ macAddress = "dc:a6:32:05:08:5d"; address = "10.20.1.235"; }
];
availableServices = with services; [ dhcp dns ];
};
iot = {
id = 30;
availableServices = with services; [ dhcp dns ];
};
cfg = {
id = 40;
staticLeases = [
{ macAddress = "8c:3b:ad:c5:b8:ee"; address = "10.40.0.10"; }
];
availableServices = with services; [ dhcp dns ];
};
};
pvid = vlans.cfg.id;
vlanIds = lib.map ({ id, ... }: id) (lib.attrValues vlans);
vlanRange = {
min = lib.foldr lib.min 999 vlanIds;
max = lib.foldr lib.max 0 vlanIds;
};
buildVlanNetdev = name: { id, ... }: {
name = "20-${name}";
value = {
netdevConfig = {
Name = name;
Kind = "vlan";
MACAddress = "00:0d:b9:49:d2:${toString id}";
};
vlanConfig = { Id = id; };
};
};
buildStaticLease = { macAddress, address }: ''
[DHCPServerStaticLease]
MACAddress=${macAddress}
Address=${address}
'';
buildBridgeVLANConfig = id: { VLAN = id; };
buildVlanNetwork = name: { id, ipv6 ? false, staticLeases ? [ ], ... }: {
name = "30-${name}";
value = {
matchConfig = {
Name = name;
};
networkConfig = {
Address = "10.${toString id}.0.1/23";
IPMasquerade = "ipv4";
DHCPServer = true;
IPv6AcceptRA = false;
IPv6SendRA = ipv6;
DHCPPrefixDelegation = ipv6;
};
dhcpServerConfig = {
PoolOffset = 255;
DNS = "10.${toString id}.0.1";
};
extraConfig = lib.concatLines (lib.map buildStaticLease staticLeases);
};
};
buildFirewallRules = name: { availableServices ? [ ], ... }: {
allowedUDPPorts = lib.flatten (lib.map ({ udp ? [ ], ... }: udp) availableServices);
allowedTCPPorts = lib.flatten (lib.map ({ tcp ? [ ], ... }: tcp) availableServices);
};
vlanNetdevs = lib.mapAttrs' buildVlanNetdev vlans;
vlanNetworks = lib.mapAttrs' buildVlanNetwork vlans;
firewallRules = lib.mapAttrs buildFirewallRules (
vlans // { "tailscale*".availableServices = with services; [ ssh web ]; }
);
nginxVhost = options: {
http2 = true;
forceSSL = true;
@@ -129,6 +35,51 @@ in
};
};
modules.vlans = {
enable = true;
networks = {
koti = {
id = 10;
ipv6 = true;
};
gast = {
id = 20;
ipv6 = true;
staticLeases = {
"dc:a6:32:05:08:5d" = "10.20.1.235";
};
};
iot = {
id = 30;
};
cfg = {
id = 40;
staticLeases = {
"8c:3b:ad:c5:b8:ee" = "10.40.0.10";
};
};
};
bridge = {
enable = true;
pvid = config.modules.vlans.networks.cfg.id;
netdev = "20-lan";
network = "30-lan";
bindNetwork = "30-bind-lan";
};
};
modules.firewall.rules = {
koti = [ "dhcp" "dns" "ssh" ];
gast = [ "dhcp" "dns" ];
iot = [ "dhcp" "dns" ];
cfg = [ "dhcp" "dns" ];
"tailscale*" = [ "ssh" "web" ];
};
systemd.network = {
enable = true;
config.networkConfig.IPv6Forwarding = true;
@@ -147,18 +98,16 @@ in
linkConfig.Name = "intern1";
};
};
netdevs = {
"20-lan" = {
netdevConfig = {
Name = "lan";
Kind = "bridge";
};
bridgeConfig = {
VLANFiltering = true;
DefaultPVID = pvid;
};
};
} // vlanNetdevs;
};
networks = {
"30-bind-lan" = {
matchConfig = {
@@ -167,13 +116,8 @@ in
networkConfig = {
Bridge = "lan";
};
extraConfig = ''
[BridgeVLAN]
VLAN=${toString vlanRange.min}-${toString vlanRange.max}
EgressUntagged=${toString pvid}
PVID=${toString pvid}
'';
};
"30-lan" = {
matchConfig = {
Name = "lan";
@@ -182,9 +126,8 @@ in
IPv6AcceptRA = false;
ConfigureWithoutCarrier = true;
};
vlan = lib.attrNames vlans;
bridgeVLANs = lib.map buildBridgeVLANConfig vlanIds;
};
"30-wan" = {
matchConfig = {
Name = "extern0";
@@ -202,7 +145,7 @@ in
Use6RD = true;
};
};
} // vlanNetworks;
};
};
services.networkd-dispatcher = {
@@ -316,7 +259,6 @@ in
networking.nftables.enable = true;
networking.firewall.enable = true;
networking.useDHCP = false;
networking.firewall.interfaces = firewallRules;
system.stateVersion = "24.05";
}

7
modules/default.nix Normal file
View File

@@ -0,0 +1,7 @@
{ ... }:
{
imports = [
./vlans.nix
./firewall.nix
];
}

33
modules/firewall.nix Normal file
View File

@@ -0,0 +1,33 @@
{ lib, config, ... }:
let
services = {
ssh = { tcp = [ 22 ]; };
dhcp = { udp = [ 67 68 ]; };
dns = { udp = [ 53 ]; };
web = { tcp = [ 80 443 ]; };
};
rulesForServices = enabledServices:
lib.foldr
(service: { allowedUDPPorts, allowedTCPPorts }: {
allowedUDPPorts = allowedUDPPorts ++ services.${service}.udp or [ ];
allowedTCPPorts = allowedTCPPorts ++ services.${service}.tcp or [ ];
})
{ allowedUDPPorts = [ ]; allowedTCPPorts = [ ]; }
enabledServices;
cfg = config.modules.firewall.rules;
in
{
options.modules.firewall.rules = lib.mkOption {
type = lib.types.attrsOf (lib.types.listOf (lib.types.enum (lib.attrNames services)));
default = { };
};
config = lib.mkIf (lib.length (lib.attrNames cfg) > 0) {
networking.firewall = {
enable = lib.mkDefault true;
interfaces = lib.mapAttrs (_: enabledServices: rulesForServices enabledServices) cfg;
};
};
}

161
modules/vlans.nix Normal file
View File

@@ -0,0 +1,161 @@
{ lib, config, ... }:
let
types = {
portList = lib.types.listOf lib.types.int;
service = lib.types.submodule {
options = {
tcp = lib.mkOption {
type = types.portList;
default = [ ];
};
udp = lib.mkOption {
type = types.portList;
default = [ ];
};
};
};
vlan = lib.types.submodule {
options = {
id = lib.mkOption {
type = lib.types.int;
};
staticLeases = lib.mkOption {
type = lib.types.attrsOf lib.types.str;
default = { };
};
ipv6 = lib.mkEnableOption "ipv6";
};
};
bridge = lib.types.submodule {
options = {
enable = lib.mkEnableOption "Enable bridge";
pvid = lib.mkOption {
type = lib.types.int;
};
bindNetwork = lib.mkOption {
type = lib.types.str;
};
network = lib.mkOption {
type = lib.types.str;
};
netdev = lib.mkOption {
type = lib.types.str;
};
};
};
};
cfg = config.modules.vlans;
vlans = lib.mapAttrsToList (attrName: { name ? attrName, ... }@vlan: ({ inherit name; } // vlan)) cfg.networks;
vlanAttrs = lib.listToAttrs (lib.map ({ name, ... }@value: { inherit name value; }) vlans);
buildNetdev = name: { id, ... }: {
name = "20-${name}";
value = {
netdevConfig = {
Name = name;
Kind = "vlan";
MACAddress = "00:0d:b9:49:d2:${toString id}";
};
vlanConfig = { Id = id; };
};
};
buildStaticLease = macAddress: address: ''
[DHCPServerStaticLease]
MACAddress=${macAddress}
Address=${address}
'';
buildNetwork = name: { id, ipv6, staticLeases, ... }: {
name = "30-${name}";
value = {
matchConfig = {
Name = name;
};
networkConfig = {
Address = "10.${toString id}.0.1/23";
IPMasquerade = "ipv4";
DHCPServer = true;
IPv6AcceptRA = false;
IPv6SendRA = ipv6;
DHCPPrefixDelegation = ipv6;
};
dhcpServerConfig = {
PoolOffset = 255;
DNS = "10.${toString id}.0.1";
};
extraConfig = lib.concatLines (lib.mapAttrsToList buildStaticLease staticLeases);
};
};
vlanIds = lib.mapAttrsToList (_: { id, ... }: id) vlanAttrs;
vlanRange = {
min = lib.foldr lib.min 999 vlanIds;
max = lib.foldr lib.max 0 vlanIds;
};
bridgeVLANConfig = ''
[BridgeVLAN]
VLAN=${toString vlanRange.min}-${toString vlanRange.max}
EgressUntagged=${toString cfg.bridge.pvid}
PVID=${toString cfg.bridge.pvid}
'';
bridgeNetDev =
if (cfg.bridge.enable) then {
"${cfg.bridge.netdev}".bridgeConfig = {
VLANFiltering = true;
DefaultPVID = cfg.bridge.pvid;
};
} else { };
bridgeBindNetwork =
if (cfg.bridge.enable) then {
${cfg.bridge.bindNetwork}.extraConfig = bridgeVLANConfig;
} else { };
bridgeNetwork =
if (cfg.bridge.enable) then {
"${cfg.bridge.network}" = {
vlan = lib.map ({ name, ... }: name) vlans;
bridgeVLANs = lib.map ({ id, ... }: { VLAN = id; }) vlans;
};
} else { };
netdevs = lib.mergeAttrsList [
(lib.mapAttrs' buildNetdev vlanAttrs)
bridgeNetDev
];
networks = lib.mergeAttrsList [
(lib.mapAttrs' buildNetwork vlanAttrs)
bridgeBindNetwork
bridgeNetwork
];
in
{
options.modules.vlans = {
enable = lib.mkEnableOption "Enable VLANs";
networks = lib.mkOption {
type = lib.types.attrsOf types.vlan;
};
bridge = lib.mkOption {
type = types.bridge;
};
};
config = lib.mkIf cfg.enable {
systemd.network = {
enable = lib.mkDefault true;
inherit netdevs networks;
};
};
}