feat[server]: first working microvm

This commit is contained in:
Leon Schwarzäugl 2025-12-24 14:48:27 +01:00 committed by Leon Schwarzäugl
parent e00defbd83
commit 2f4ebcba44
40 changed files with 759 additions and 194 deletions

View file

@ -0,0 +1,76 @@
{ lib, config, globals, confLib, ... }:
let
inherit (confLib.gen { name = "kea"; dir = "/var/lib/private/kea"; }) serviceName serviceDir;
in
{
options = {
swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
};
config = lib.mkIf config.swarselmodules.server.${serviceName} {
environment.persistence."/persist".directories = lib.mkIf config.swarselsystems.isImpermanence [
{ directory = serviceDir; mode = "0700"; }
];
services.kea.dhcp4 = {
enable = true;
settings = {
lease-database = {
name = "/var/lib/kea/dhcp4.leases";
persist = true;
type = "memfile";
};
valid-lifetime = 86400;
renew-timer = 3600;
interfaces-config = {
# XXX: BUG: why does this bind other macvtaps?
interfaces = map (name: "me-${name}") (builtins.attrNames globals.networks.home-lan.vlans);
service-sockets-max-retries = -1;
};
subnet4 = lib.flip lib.mapAttrsToList globals.networks.home-lan.vlans (
vlanName: vlanCfg: {
inherit (vlanCfg) id;
interface = "me-${vlanName}";
subnet = vlanCfg.cidrv4;
pools = [
{
pool = "${lib.net.cidr.host 20 vlanCfg.cidrv4} - ${lib.net.cidr.host (-6) vlanCfg.cidrv4}";
}
];
option-data =
[
{
name = "routers";
data = vlanCfg.hosts.hintbooth.ipv4; # FIXME: how to advertise v6 address also?
}
];
# Advertise DNS server for VLANS that have internet access
# ++
# lib.optional
# (lib.elem vlanName [
# "services"
# "home"
# "devices"
# "guests"
# ])
# {
# name = "domain-name-servers";
# data = globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv4;
# };
reservations = lib.concatLists (
lib.forEach (builtins.attrValues vlanCfg.hosts) (
hostCfg:
lib.optional (hostCfg.mac != null) {
hw-address = hostCfg.mac;
ip-address = hostCfg.ipv4;
}
)
);
}
);
};
};
};
}

View file

@ -0,0 +1,74 @@
{ lib, config, confLib, ... }:
let
inherit (confLib.gen { name = "nftables"; }) serviceName;
in
{
options = {
swarselmodules.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
};
config = lib.mkIf config.swarselmodules.${serviceName} {
networking.nftables = {
stopRuleset = lib.mkDefault ''
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state invalid drop
ct state {established, related} accept
iifname lo accept
meta l4proto ipv6-icmp accept
meta l4proto icmp accept
ip protocol igmp accept
tcp dport ${toString (lib.head config.services.openssh.ports)} accept
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
'';
firewall = {
enable = true;
localZoneName = "local";
snippets = {
nnf-common.enable = false;
nnf-conntrack.enable = true;
nnf-drop.enable = true;
nnf-loopback.enable = true;
nnf-ssh.enable = true;
};
rules.untrusted-to-local = {
from = [ "untrusted" ];
to = [ "local" ];
inherit (config.networking.firewall)
allowedTCPPorts
allowedTCPPortRanges
allowedUDPPorts
allowedUDPPortRanges
;
};
rules.icmp-and-igmp = {
after = [
"ct"
"ssh"
];
from = "all";
to = [ "local" ];
extraLines = [
"meta l4proto ipv6-icmp accept"
"meta l4proto icmp accept"
"ip protocol igmp accept"
];
};
};
};
};
}

View file

@ -1,4 +1,4 @@
{ lib, config, pkgs, ... }:
{ lib, config, pkgs, withHomeManager, ... }:
{
options.swarselmodules.server.packages = lib.mkEnableOption "enable packages on server";
config = lib.mkIf config.swarselmodules.server.packages {
@ -14,6 +14,7 @@
tmux
busybox
swarsel-deploy
] ++ lib.optionals withHomeManager [
swarsel-gens
swarsel-switch
];

View file

@ -1,56 +1,142 @@
{ lib, config, ... }:
{ lib, config, globals, ... }:
let
serviceName = "router";
in
{
options.swarselmodules.server.${serviceName} = lib.mkEnableOption "enable ${serviceName} on server";
config = lib.mkIf config.swarselmodules.server.${serviceName} {
config = lib.mkIf config.swarselmodules.server.${serviceName}
{
services.avahi.reflector = true;
systemd.network = {
wait-online.anyInterface = true;
networks = {
"30-lan0" = {
matchConfig.Name = "lan0";
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
ConfigureWithoutCarrier = true;
networking.nftables = {
firewall = {
zones = {
untrusted.interfaces = [ "lan" ];
wgHome.interfaces = [ "wgHome" ];
adguardhome.ipv4Addresses = [ globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv4 ];
adguardhome.ipv6Addresses = [ globals.networks.home-lan.vlans.services.hosts.hintbooth-adguardhome.ipv6 ];
}
// lib.flip lib.concatMapAttrs globals.networks.home-lan.vlans (
vlanName: _: {
"vlan-${vlanName}".interfaces = [ "me-${vlanName}" ];
}
);
rules = {
masquerade-internet = {
from = map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans);
to = [ "untrusted" ];
# masquerade = true; NOTE: custom rule below for ip4 + ip6
late = true; # Only accept after any rejects have been processed
verdict = "accept";
};
# Allow access to the AdGuardHome DNS server from any VLAN that has internet access
access-adguardhome-dns = {
from = map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans);
to = [ "adguardhome" ];
verdict = "accept";
};
# Allow devices in the home VLAN to talk to any of the services or home devices.
access-services = {
from = [ "vlan-home" ];
to = [
"vlan-services"
"vlan-devices"
];
late = true;
verdict = "accept";
};
# Allow the services VLAN to talk to our wireguard server
services-to-local = {
from = [ "vlan-services" ];
to = [ "local" ];
allowedUDPPorts = [ 52829 ];
};
# Forward traffic between wireguard participants
forward-proxy-home-vpn-traffic = {
from = [ "wgHome" ];
to = [ "wgHome" ];
verdict = "accept";
};
};
};
"30-lan1" = {
matchConfig.Name = "lan1";
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
ConfigureWithoutCarrier = true;
chains.postrouting = {
masquerade-internet = {
after = [ "hook" ];
late = true;
rules =
lib.forEach
(map (name: "vlan-${name}") (builtins.attrNames globals.networks.home-lan.vlans))
(
zone:
lib.concatStringsSep " " [
"meta protocol { ip, ip6 }"
(lib.head config.networking.nftables.firewall.zones.${zone}.ingressExpression)
(lib.head config.networking.nftables.firewall.zones.untrusted.egressExpression)
"masquerade random"
]
);
};
};
"30-lan2" = {
matchConfig.Name = "lan2";
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
ConfigureWithoutCarrier = true;
};
};
"30-lan3" = {
matchConfig.Name = "lan3";
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
ConfigureWithoutCarrier = true;
};
};
"10-wan" = {
matchConfig.Name = "wan";
networkConfig = {
# start a DHCP Client for IPv4 Addressing/Routing
DHCP = "ipv4";
DNSOverTLS = true;
DNSSEC = true;
IPv6PrivacyExtensions = false;
IPForward = true;
};
# make routing on this interface a dependency for network-online.target
linkConfig.RequiredForOnline = "routable";
};
};
boot.kernel.sysctl = {
"net.ipv4.ip_forward" = 1;
"net.ipv4.conf.all.forwarding" = true;
"net.ipv6.conf.all.forwarding" = true;
};
systemd.network = {
wait-online.anyInterface = true;
networks = {
"30-lan1" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan1.mac;
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
Bridge = "br";
ConfigureWithoutCarrier = true;
};
};
"30-lan2" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan2.mac;
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
Bridge = "br";
ConfigureWithoutCarrier = true;
};
};
"30-lan3" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan3.mac;
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
Bridge = "br";
ConfigureWithoutCarrier = true;
};
};
"30-lan4" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan4.mac;
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
Bridge = "br";
ConfigureWithoutCarrier = true;
};
};
"30-lan5" = {
matchConfig.MACAddress = config.repo.secrets.local.networking.networks.lan5.mac;
linkConfig.RequiredForOnline = "enslaved";
networkConfig = {
Bridge = "br";
ConfigureWithoutCarrier = true;
};
};
};
};
};
};
}

View file

@ -22,16 +22,18 @@ in
}
config.swarselsystems.shellAliases;
nixpkgs.config.permittedInsecurePackages = [
# matrix
"olm-3.2.16"
# sonarr
"aspnetcore-runtime-wrapped-6.0.36"
"aspnetcore-runtime-6.0.36"
"dotnet-sdk-wrapped-6.0.428"
"dotnet-sdk-6.0.428"
#
"SDL_ttf-2.0.11"
];
nixpkgs.config = lib.mkIf (!config.swarselsystems.isMicroVM) {
perittedInsecurePackages = [
# matrix
"olm-3.2.16"
# sonarr
"aspnetcore-runtime-wrapped-6.0.36"
"aspnetcore-runtime-6.0.36"
"dotnet-sdk-wrapped-6.0.428"
"dotnet-sdk-6.0.428"
#
"SDL_ttf-2.0.11"
];
};
};
}

View file

@ -1,4 +1,4 @@
{ self, lib, config, ... }:
{ self, lib, config, withHomeManager, ... }:
{
options.swarselmodules.server.ssh = lib.mkEnableOption "enable ssh on server";
config = lib.mkIf config.swarselmodules.server.ssh {
@ -21,16 +21,18 @@
}
];
};
users.users."${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = [
(self + /secrets/public/ssh/yubikey.pub)
(self + /secrets/public/ssh/magicant.pub)
# (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
];
users.users.root.openssh.authorizedKeys.keyFiles = [
(self + /secrets/public/ssh/yubikey.pub)
(self + /secrets/public/ssh/magicant.pub)
# (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
];
users.users = {
"${config.swarselsystems.mainUser}".openssh.authorizedKeys.keyFiles = lib.mkIf withHomeManager [
(self + /secrets/public/ssh/yubikey.pub)
(self + /secrets/public/ssh/magicant.pub)
# (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
];
root.openssh.authorizedKeys.keyFiles = [
(self + /secrets/public/ssh/yubikey.pub)
(self + /secrets/public/ssh/magicant.pub)
# (lib.mkIf config.swarselsystems.isBastionTarget (self + /secrets/public/ssh/jump.pub))
];
};
security.sudo.extraConfig = ''
Defaults env_keep+=SSH_AUTH_SOCK
'';