diff --git a/nixos/modules/networking/netns.nix b/nixos/modules/networking/netns.nix deleted file mode 100644 index 42eabea..0000000 --- a/nixos/modules/networking/netns.nix +++ /dev/null @@ -1,473 +0,0 @@ -{ - config, - lib, - pkgs, - mylib, - ... -}: -with lib; -let - allNetns = config.networking.netns; - nonDefaultNetns = filterAttrs (_name: cfg: cfg.netns != "default") allNetns; - - mkService = - cfg: - let - inherit (cfg) - netns - interface - address - ; - enableIPv4Forwarding = if cfg.enableIPv4Forwarding then "1" else "0"; - enableIPv6Forwarding = if cfg.enableIPv6Forwarding then "1" else "0"; - in - { - path = with pkgs; [ - coreutils - iproute2 - procps - ]; - script = '' - ip netns add ${netns} - ip -n ${netns} link add ${interface} type dummy - ip -n ${netns} link set lo up - ip -n ${netns} link set ${interface} up - ip netns exec ${netns} sysctl -w net.ipv4.conf.default.forwarding=${enableIPv4Forwarding} - ip netns exec ${netns} sysctl -w net.ipv4.conf.all.forwarding=${enableIPv4Forwarding} - ip netns exec ${netns} sysctl -w net.ipv6.conf.default.forwarding=${enableIPv6Forwarding} - ip netns exec ${netns} sysctl -w net.ipv6.conf.all.forwarding=${enableIPv6Forwarding} - ${concatMapStringsSep "\n" (addr: "ip -n ${netns} addr add ${addr} dev ${interface}") address} - ''; - preStop = '' - ip netns del ${netns} - ''; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - }; - - # https://flokli.de/posts/2022-11-18-nsncd - mkNscdService = - cfg: - let - inherit (cfg) netns netnsPath; - in - { - serviceConfig = mylib.misc.serviceHardened // { - NetworkNamespacePath = netnsPath; - BindReadOnlyPaths = [ - "/etc/netns/${netns}/resolv.conf:/etc/resolv.conf:norbind" - "/etc/netns/${netns}/nsswitch.conf:/etc/nsswitch.conf:norbind" - ]; - BindPaths = [ "/run/netns-${netns}/nscd:/run/nscd:norbind" ]; - Type = "notify"; - Restart = "always"; - RestartSec = 5; - DynamicUser = true; - RuntimeDirectory = "netns-${netns}/nscd"; - ExecStart = "${pkgs.nsncd}/bin/nsncd"; - }; - environment.LD_LIBRARY_PATH = config.system.nssModules.path; - after = [ - "netns-${netns}.service" - "network.target" - ]; - partOf = [ "netns-${netns}.service" ]; - wantedBy = [ - "multi-user.target" - "netns-${netns}.service" - ]; - }; - - mkAuxMntnsService = - cfg: - let - inherit (cfg) - netns - mntnsPath - ; - in - { - path = with pkgs; [ - coreutils - util-linux - ]; - script = '' - touch ${mntnsPath} || echo "${mntnsPath} already exists" - unshare --mount=${mntnsPath} --propagation slave mount --bind --read-only /etc/netns/${netns}/resolv.conf /etc/resolv.conf - nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${netns}/nsswitch.conf /etc/nsswitch.conf - nsenter --mount=${mntnsPath} mount --bind --read-only /run/netns-${netns}/nscd /run/nscd - ''; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - after = [ - "netns-${netns}.service" - "netns-${netns}-nscd.service" - "network.target" - ]; - partOf = [ "netns-${netns}.service" ]; - wantedBy = [ - "multi-user.target" - "netns-${netns}.service" - ]; - }; - - mkPortForwardService = - cfg: fp: - let - inherit (fp) protocol source target; - sourceNetns = fp.netns; - sourceNetnsPath = config.networking.netns.${sourceNetns}.netnsPath; - targetNetns = cfg.netns; - targetNetnsConfig = cfg.serviceConfig; - serviceDeps = map (ns: "netns-${ns}.service") ( - filter (ns: ns != "default") [ - sourceNetns - targetNetns - ] - ); - in - { - serviceConfig = - mylib.misc.serviceHardened - // targetNetnsConfig - // { - Type = "simple"; - Restart = "on-failure"; - RestartSec = 5; - DynamicUser = true; - ExecStart = "${pkgs.netns-proxy}/bin/netns-proxy ${sourceNetnsPath} ${source} -b ${target} -p ${protocol} -v"; - ProtectProc = false; - RestrictNamespaces = "net"; - AmbientCapabilities = [ - "CAP_SYS_ADMIN" - "CAP_SYS_PTRACE" - ]; - CapabilityBoundingSet = [ - "CAP_SYS_ADMIN" - "CAP_SYS_PTRACE" - ]; - }; - after = [ - "network.target" - ] ++ serviceDeps; - partOf = serviceDeps; - wantedBy = [ - "multi-user.target" - ] ++ serviceDeps; - }; - - mkExtraVethService = - cfg: ev: - let - inherit (ev) - sourceInterface - targetInterface - ; - sourceNetns = cfg.netns; - sourceNetnsPath = cfg.netnsPath; - targetNetns = ev.netns; - targetNetnsPath = config.networking.netns.${targetNetns}.netnsPath; - serviceDeps = map (ns: "netns-${ns}.service") ( - filter (ns: ns != "default") [ - sourceNetns - targetNetns - ] - ); - mkSetup = - netns: _netnsPath: interface: - if netns == "default" then "ip link set ${interface} up" else "ip -n ${netns} link set ${interface} up"; - mkDrop = - netns: _netnsPath: interface: - if netns == "default" then "ip link del ${interface}" else "ip -n ${netns} link del ${interface}"; - in - { - path = with pkgs; [ - coreutils - iproute2 - procps - ]; - script = '' - ip link add ${sourceInterface} mtu 1400 address 02:00:00:00:00:01 netns ${sourceNetnsPath} type veth \ - peer ${targetInterface} mtu 1400 address 02:00:00:00:00:00 netns ${targetNetnsPath} - ${mkSetup sourceNetns sourceNetnsPath sourceInterface} - ${mkSetup targetNetns targetNetnsPath targetInterface} - ''; - preStop = '' - ${mkDrop sourceNetns sourceNetnsPath sourceInterface} - ''; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - after = [ - "network.target" - ] ++ serviceDeps; - partOf = serviceDeps; - wantedBy = [ - "multi-user.target" - ] ++ serviceDeps; - }; - - defaultResolv = pkgs.writeText "netns-default-resolv-conf" '' - nameserver 2606:4700:4700::1111 - nameserver 2001:4860:4860::8888 - nameserver 1.1.1.1 - nameserver 8.8.8.8 - ''; - - defaultNsswitch = pkgs.writeText "netns-default-nsswitch-conf" '' - passwd: ${concatStringsSep " " config.system.nssDatabases.passwd} - group: ${concatStringsSep " " config.system.nssDatabases.group} - shadow: ${concatStringsSep " " config.system.nssDatabases.shadow} - sudoers: ${concatStringsSep " " config.system.nssDatabases.sudoers} - - hosts: ${concatStringsSep " " (remove "resolve [!UNAVAIL=return]" config.system.nssDatabases.hosts)} - networks: files - - ethers: files - services: ${concatStringsSep " " config.system.nssDatabases.services} - protocols: files - rpc: files - ''; -in -{ - options.networking.netns = mkOption { - type = types.attrsOf ( - types.submodule ( - { name, config, ... }: - let - netnsConfig = config; - in - { - options = { - netns = mkOption { - type = types.str; - default = name; - readOnly = true; - description = '' - Name of the network namespace. - ''; - }; - netnsPath = mkOption { - type = types.str; - default = if name == "default" then "/proc/1/ns/net" else "/run/netns/${name}"; - readOnly = true; - description = '' - Path to the network namespace. - ''; - }; - mntnsPath = mkOption { - type = types.str; - default = if name == "default" then "/proc/1/ns/mnt" else "/run/netns-${name}/mntns"; - readOnly = true; - description = '' - Path to the auxiliary mount namespace. - ''; - }; - serviceConfig = mkOption { - type = types.attrs; - default = - if config.netns == "default" then - { } - else - { - NetworkNamespacePath = config.netnsPath; - BindReadOnlyPaths = [ - "/etc/netns/${config.netns}/resolv.conf:/etc/resolv.conf:norbind" - "/etc/netns/${config.netns}/nsswitch.conf:/etc/nsswitch.conf:norbind" - "/run/netns-${config.netns}/nscd:/run/nscd:norbind" - ]; - }; - readOnly = true; - description = '' - Systemd service configuration for entering the network namespace. - ''; - }; - interface = mkOption { - type = types.str; - default = name; - description = '' - Name of the dummy interface to add the address. - ''; - }; - address = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - Address to be added into the network namespace as source address. - ''; - }; - enableIPv4Forwarding = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable IPv4 packet forwarding in the network namespace. - ''; - }; - enableIPv6Forwarding = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable IPv6 packet forwarding in the network namespace. - ''; - }; - forwardPorts = mkOption { - type = types.listOf ( - types.submodule { - options = { - protocol = mkOption { - type = types.enum [ - "tcp" - "udp" - ]; - default = "tcp"; - description = '' - The protocol specifier for port forwarding between network namespaces. - ''; - }; - netns = mkOption { - type = types.str; - default = "default"; - description = '' - The network namespace to forward ports from. - ''; - }; - source = mkOption { - type = types.str; - description = '' - The source endpoint in the specified network namespace to forward. - ''; - }; - target = mkOption { - type = types.str; - description = '' - The target endpoint in the current network namespace to listen on. - ''; - }; - }; - } - ); - default = [ ]; - description = '' - List of forwarded ports from another network namespace to this - network namespace. - ''; - }; - extraVeths = mkOption { - type = types.listOf ( - types.submodule ( - { config, ... }: - { - options = { - netns = mkOption { - type = types.str; - default = "default"; - description = '' - The network namespace to connect to. - ''; - }; - sourceInterface = mkOption { - type = types.str; - default = if config.netns == "default" then "host" else config.netns; - description = '' - The interface name in current network namespace; - ''; - }; - targetInterface = mkOption { - type = types.str; - default = if netnsConfig.netns == "default" then "host" else netnsConfig.netns; - description = '' - The interface name in the other network namespace; - ''; - }; - }; - } - ) - ); - default = [ ]; - description = '' - Extra veth-pairs to be created for enabling link-scope connectivity - between inter-network namespaces. - Note that a veth-pair only needs to be defined on one end. - ''; - }; - }; - } - ) - ); - description = '' - Network namespace configuration. - ''; - }; - - config = { - networking.netns.default = { }; - - systemd.services = listToAttrs ( - mapAttrsToList (_name: cfg: nameValuePair "netns-${cfg.netns}" (mkService cfg)) nonDefaultNetns - ++ mapAttrsToList ( - _name: cfg: nameValuePair "netns-${cfg.netns}-nscd" (mkNscdService cfg) - ) nonDefaultNetns - ++ mapAttrsToList ( - _name: cfg: nameValuePair "netns-${cfg.netns}-mntns" (mkAuxMntnsService cfg) - ) nonDefaultNetns - ++ flatten ( - mapAttrsToList ( - _name: cfg: - (imap ( - index: fp: - nameValuePair "netns-${cfg.netns}-port-forward-${toString index}-${fp.netns}-${fp.protocol}" ( - mkPortForwardService cfg fp - ) - ) cfg.forwardPorts) - ) allNetns - ) - ++ flatten ( - mapAttrsToList ( - _name: cfg: - (imap ( - index: ev: - nameValuePair "netns-${cfg.netns}-extra-veth-${toString index}-${ev.netns}" ( - mkExtraVethService cfg ev - ) - ) cfg.extraVeths) - ) allNetns - ) - ); - - environment.etc = listToAttrs ( - mapAttrsToList ( - _name: cfg: - nameValuePair "netns/${cfg.netns}/resolv.conf" { - source = mkDefault defaultResolv; - } - ) nonDefaultNetns - ++ mapAttrsToList ( - _name: cfg: - nameValuePair "netns/${cfg.netns}/nsswitch.conf" { - source = mkDefault defaultNsswitch; - } - ) nonDefaultNetns - ); - - environment.systemPackages = mapAttrsToList ( - name: cfg: - let - inherit (cfg) netns netnsPath mntnsPath; - in - pkgs.writeShellApplication { - name = "netns-run-${netns}"; - runtimeInputs = with pkgs; [ util-linux ]; - text = '' - pkexec nsenter -t $$ -e --mount=${mntnsPath} --net=${netnsPath} -S "$(id -u)" -G "$(id -g)" --wdns="$PWD" "$@" - ''; - } - ) allNetns; - }; -} diff --git a/nixos/modules/networking/netns/common.nix b/nixos/modules/networking/netns/common.nix new file mode 100644 index 0000000..ef5a851 --- /dev/null +++ b/nixos/modules/networking/netns/common.nix @@ -0,0 +1,187 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; +let + allNetns = config.networking.netns; + nonDefaultNetns = filterAttrs (name: _cfg: name != "default") allNetns; +in +{ + options.networking.netns = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, config, ... }: + { + options = { + netnsPath = mkOption { + type = types.str; + default = if name == "default" then "/proc/1/ns/net" else "/run/netns/${name}"; + readOnly = true; + description = '' + Path to the network namespace. + ''; + }; + mntnsPath = mkOption { + type = types.str; + default = if name == "default" then "/proc/1/ns/mnt" else "/run/netns-${name}/mntns/${name}"; + readOnly = true; + description = '' + Path to the auxiliary mount namespace. + ''; + }; + interface = mkOption { + type = types.str; + default = name; + description = '' + Name of the dummy interface to add the address. + ''; + }; + address = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Address to be added into the network namespace as source address. + ''; + }; + enableIPv4Forwarding = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable IPv4 packet forwarding in the network namespace. + ''; + }; + enableIPv6Forwarding = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable IPv6 packet forwarding in the network namespace. + ''; + }; + serviceConfig = mkOption { + type = types.attrs; + default = + if name == "default" then + { } + else + { + NetworkNamespacePath = config.netnsPath; + BindReadOnlyPaths = optionals config.enableDNSIsolation [ + "/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind" + "/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind" + "/run/netns-${name}/nscd:/run/nscd:norbind" + ]; + }; + readOnly = true; + description = '' + Systemd service configuration for entering the network namespace. + ''; + }; + }; + } + ) + ); + description = '' + Network namespace configuration. + ''; + }; + + config = { + networking.netns.default = { }; + + systemd.services = listToAttrs ( + mapAttrsToList ( + name: cfg: + let + inherit (cfg) interface address; + enableIPv4Forwarding = if cfg.enableIPv4Forwarding then "1" else "0"; + enableIPv6Forwarding = if cfg.enableIPv6Forwarding then "1" else "0"; + in + nameValuePair "netns-${name}" { + path = with pkgs; [ + coreutils + iproute2 + procps + ]; + script = '' + ip netns add ${name} + ip -n ${name} link add ${interface} type dummy + ip -n ${name} link set lo up + ip -n ${name} link set ${interface} up + ip netns exec ${name} sysctl -w net.ipv4.conf.default.forwarding=${enableIPv4Forwarding} + ip netns exec ${name} sysctl -w net.ipv4.conf.all.forwarding=${enableIPv4Forwarding} + ip netns exec ${name} sysctl -w net.ipv6.conf.default.forwarding=${enableIPv6Forwarding} + ip netns exec ${name} sysctl -w net.ipv6.conf.all.forwarding=${enableIPv6Forwarding} + ${concatMapStringsSep "\n" (addr: "ip -n ${name} addr add ${addr} dev ${interface}") address} + ''; + preStop = '' + ip netns del ${name} + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + } + ) nonDefaultNetns + ++ mapAttrsToList ( + name: cfg: + let + inherit (cfg) mntnsPath enableDNSIsolation; + in + nameValuePair "netns-${name}-mntns" { + path = with pkgs; [ + coreutils + util-linux + ]; + script = '' + touch ${mntnsPath} || echo "${mntnsPath} already exists" + unshare --mount=${mntnsPath} --propagation slave true + ${optionalString enableDNSIsolation '' + nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${name}/resolv.conf /etc/resolv.conf + nsenter --mount=${mntnsPath} mount --bind --read-only /etc/netns/${name}/nsswitch.conf /etc/nsswitch.conf + nsenter --mount=${mntnsPath} mount --bind --read-only /run/netns-${name}/nscd /run/nscd + ''} + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + RuntimeDirectory = "netns-${name}/mntns"; + }; + after = + [ + "netns-${name}.service" + "network.target" + ] + ++ optionals enableDNSIsolation [ + "netns-${name}-nscd.service" + ]; + partOf = [ "netns-${name}.service" ]; + wantedBy = [ + "multi-user.target" + "netns-${name}.service" + ]; + } + ) nonDefaultNetns + ); + + environment.systemPackages = mkIf (nonDefaultNetns != { }) ( + mapAttrsToList ( + name: cfg: + let + inherit (cfg) netnsPath mntnsPath; + in + pkgs.writeShellApplication { + name = "netns-run-${name}"; + runtimeInputs = with pkgs; [ util-linux ]; + text = '' + pkexec nsenter -t $$ -e --mount=${mntnsPath} --net=${netnsPath} -S "$(id -u)" -G "$(id -g)" --wdns="$PWD" "$@" + ''; + } + ) allNetns + ); + }; +} diff --git a/nixos/modules/networking/netns/extra-veth.nix b/nixos/modules/networking/netns/extra-veth.nix new file mode 100644 index 0000000..d62da85 --- /dev/null +++ b/nixos/modules/networking/netns/extra-veth.nix @@ -0,0 +1,127 @@ +{ + config, + lib, + pkgs, + ... +}: +with lib; +let + allNetns = config.networking.netns; + allExtraVeths = flatten (mapAttrsToList (_name: cfg: cfg.extraVeths) allNetns); +in +{ + options.networking.netns = mkOption { + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options.extraVeths = mkOption { + type = types.listOf ( + types.submodule ( + { config, ... }: + { + options = { + sourceNetns = mkOption { + type = types.str; + default = name; + readOnly = true; + description = '' + The current network namespace. + ''; + }; + targetNetns = mkOption { + type = types.str; + description = '' + The network namespace to connect to. + ''; + }; + sourceInterface = mkOption { + type = types.str; + default = if config.targetNetns == "default" then "host" else config.targetNetns; + description = '' + The interface name in the current network namespace; + ''; + }; + targetInterface = mkOption { + type = types.str; + default = if config.sourceNetns == "default" then "host" else config.sourceNetns; + description = '' + The interface name in the other network namespace; + ''; + }; + }; + } + ) + ); + default = [ ]; + description = '' + Extra veth-pairs to be created for enabling link-scope connectivity + between inter-network namespaces. + Note that a veth-pair only needs to be defined on one end. + ''; + }; + } + ) + ); + }; + + config = { + systemd.services = listToAttrs ( + map ( + ev: + let + inherit (ev) + sourceNetns + targetNetns + sourceInterface + targetInterface + ; + sourceNetnsPath = config.networking.netns.${sourceNetns}.netnsPath; + targetNetnsPath = config.networking.netns.${targetNetns}.netnsPath; + serviceDeps = map (ns: "netns-${ns}.service") ( + filter (ns: ns != "default") [ + sourceNetns + targetNetns + ] + ); + mkSetup = + netns: _netnsPath: interface: + if netns == "default" then + "ip link set ${interface} up" + else + "ip -n ${netns} link set ${interface} up"; + mkDrop = + netns: _netnsPath: interface: + if netns == "default" then "ip link del ${interface}" else "ip -n ${netns} link del ${interface}"; + in + nameValuePair "netns-extra-veth-1-${sourceNetns}-${targetNetns}" { + path = with pkgs; [ + coreutils + iproute2 + procps + ]; + script = '' + ip link add ${sourceInterface} mtu 1400 address 02:00:00:00:00:01 netns ${sourceNetnsPath} type veth \ + peer ${targetInterface} mtu 1400 address 02:00:00:00:00:00 netns ${targetNetnsPath} + ${mkSetup sourceNetns sourceNetnsPath sourceInterface} + ${mkSetup targetNetns targetNetnsPath targetInterface} + ''; + preStop = '' + ${mkDrop sourceNetns sourceNetnsPath sourceInterface} + ''; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + }; + after = [ + "network.target" + ] ++ serviceDeps; + partOf = serviceDeps; + wantedBy = [ + "multi-user.target" + ] ++ serviceDeps; + } + ) allExtraVeths + ); + }; +} diff --git a/nixos/modules/networking/netns/nscd.nix b/nixos/modules/networking/netns/nscd.nix new file mode 100644 index 0000000..aa567c4 --- /dev/null +++ b/nixos/modules/networking/netns/nscd.nix @@ -0,0 +1,115 @@ +{ + config, + lib, + pkgs, + mylib, + ... +}: +with lib; +let + allNetns = config.networking.netns; + dnsIsolatedNetns = filterAttrs (name: cfg: name != "default" && cfg.enableDNSIsolation) allNetns; +in +{ + options.networking.netns = mkOption { + type = types.attrsOf ( + types.submodule ( + { ... }: + { + options.enableDNSIsolation = mkOption { + type = types.bool; + default = true; + description = '' + Whether to enable DNS isolation between network namespaces. When disabled, + DNS requests in this namespace may be exposed to other namespaces. + ''; + }; + } + ) + ); + }; + + config = { + # https://flokli.de/posts/2022-11-18-nsncd + systemd.services = mapAttrs' ( + name: cfg: + let + inherit (cfg) netnsPath; + in + nameValuePair "netns-${name}-nscd" { + serviceConfig = mylib.misc.serviceHardened // { + NetworkNamespacePath = netnsPath; + BindReadOnlyPaths = [ + "/etc/netns/${name}/resolv.conf:/etc/resolv.conf:norbind" + "/etc/netns/${name}/nsswitch.conf:/etc/nsswitch.conf:norbind" + ]; + BindPaths = [ "/run/netns-${name}/nscd:/run/nscd:norbind" ]; + Type = "notify"; + Restart = "on-failure"; + RestartSec = 5; + User = "${name}-nscd"; + RuntimeDirectory = "netns-${name}/nscd"; + RuntimeDirectoryPreserve = true; + ExecStart = "${pkgs.nsncd}/bin/nsncd"; + }; + environment.LD_LIBRARY_PATH = config.system.nssModules.path; + after = [ + "netns-${name}.service" + "network.target" + ]; + partOf = [ "netns-${name}.service" ]; + wantedBy = [ + "multi-user.target" + "netns-${name}.service" + ]; + } + ) dnsIsolatedNetns; + + users.users = mapAttrs' ( + name: _cfg: + nameValuePair "${name}-nscd" { + isSystemUser = true; + group = "${name}-nscd"; + } + ) dnsIsolatedNetns; + + users.groups = mapAttrs' (name: _cfg: nameValuePair "${name}-nscd" { }) dnsIsolatedNetns; + + environment.etc = listToAttrs ( + mapAttrsToList ( + name: _cfg: + nameValuePair "netns/${name}/resolv.conf" { + source = mkDefault ( + pkgs.writeText "netns-default-resolv-conf" '' + nameserver 2606:4700:4700::1111 + nameserver 2001:4860:4860::8888 + nameserver 1.1.1.1 + nameserver 8.8.8.8 + '' + ); + } + ) dnsIsolatedNetns + ++ mapAttrsToList ( + name: _cfg: + nameValuePair "netns/${name}/nsswitch.conf" { + source = mkDefault ( + pkgs.writeText "netns-default-nsswitch-conf" '' + passwd: ${concatStringsSep " " config.system.nssDatabases.passwd} + group: ${concatStringsSep " " config.system.nssDatabases.group} + shadow: ${concatStringsSep " " config.system.nssDatabases.shadow} + sudoers: ${concatStringsSep " " config.system.nssDatabases.sudoers} + + hosts: ${concatStringsSep " " (remove "resolve [!UNAVAIL=return]" config.system.nssDatabases.hosts)} + networks: files + + ethers: files + services: ${concatStringsSep " " config.system.nssDatabases.services} + protocols: files + rpc: files + '' + ); + } + ) dnsIsolatedNetns + ); + }; +} diff --git a/nixos/modules/networking/netns/port-forward.nix b/nixos/modules/networking/netns/port-forward.nix new file mode 100644 index 0000000..60f5778 --- /dev/null +++ b/nixos/modules/networking/netns/port-forward.nix @@ -0,0 +1,122 @@ +{ + config, + lib, + pkgs, + mylib, + ... +}: +with lib; +let + allNetns = config.networking.netns; +in +{ + options.networking.netns = mkOption { + type = types.attrsOf ( + types.submodule ( + { ... }: + { + options.forwardPorts = mkOption { + type = types.listOf ( + types.submodule { + options = { + protocol = mkOption { + type = types.enum [ + "tcp" + "udp" + ]; + default = "tcp"; + description = '' + The protocol specifier for port forwarding between network namespaces. + ''; + }; + netns = mkOption { + type = types.str; + default = "default"; + description = '' + The network namespace to forward ports from. + ''; + }; + source = mkOption { + type = types.str; + description = '' + The source endpoint in the specified network namespace to forward. + ''; + }; + target = mkOption { + type = types.str; + description = '' + The target endpoint in the current network namespace to listen on. + ''; + }; + }; + } + ); + default = [ ]; + description = '' + List of forwarded ports from another network namespace to this + network namespace. + ''; + }; + } + ) + ); + }; + + config = { + systemd.services = listToAttrs ( + flatten ( + mapAttrsToList ( + name: cfg: + (imap ( + index: fp: + let + inherit (fp) + protocol + source + target + netns + ; + netnsPath = config.networking.netns.${netns}.netnsPath; + serviceDeps = map (ns: "netns-${ns}.service") ( + filter (ns: ns != "default") [ + name + netns + ] + ); + in + nameValuePair "netns-${name}-port-forward-${toString index}" { + serviceConfig = + mylib.misc.serviceHardened + // cfg.serviceConfig + // { + Type = "simple"; + Restart = "on-failure"; + RestartSec = 5; + DynamicUser = true; + User = "${name}-port-forward-${toString index}"; + ExecStart = "${pkgs.netns-proxy}/bin/netns-proxy ${netnsPath} ${source} -b ${target} -p ${protocol} -v"; + ProtectProc = false; + RestrictNamespaces = "net"; + AmbientCapabilities = [ + "CAP_SYS_ADMIN" + "CAP_SYS_PTRACE" + ]; + CapabilityBoundingSet = [ + "CAP_SYS_ADMIN" + "CAP_SYS_PTRACE" + ]; + }; + after = [ + "network.target" + ] ++ serviceDeps; + partOf = serviceDeps; + wantedBy = [ + "multi-user.target" + ] ++ serviceDeps; + } + ) cfg.forwardPorts) + ) allNetns + ) + ); + }; +} diff --git a/nixos/modules/networking/ports.nix b/nixos/modules/networking/ports.nix index a8ae365..416f44f 100644 --- a/nixos/modules/networking/ports.nix +++ b/nixos/modules/networking/ports.nix @@ -8,13 +8,17 @@ in options.networking.ports = mkOption { type = with types; attrsOf port; default = { + # standard ports http = 80; https = 443; + socks = 1080; ssh = 2222; - # enthalpy + # local ports + enthalpy-gost = 3000; + + # public ports enthalpy-ipsec = 13000; - enthalpy-gost = 1080; }; readOnly = true; description = '' diff --git a/nixos/modules/services/enthalpy/bird.nix b/nixos/modules/services/enthalpy/bird.nix index c93294d..f2d59ae 100644 --- a/nixos/modules/services/enthalpy/bird.nix +++ b/nixos/modules/services/enthalpy/bird.nix @@ -4,6 +4,7 @@ config, lib, pkgs, + mylib, ... }: with lib; @@ -15,7 +16,7 @@ in enable = mkEnableOption "bird for site-scope connectivity"; socket = mkOption { type = types.str; - default = "/run/enthalpy/bird2/bird.ctl"; + default = "/run/netns-${cfg.netns}/bird/bird.ctl"; description = '' Path to the bird control socket. ''; @@ -42,50 +43,54 @@ in }; config = mkIf (cfg.enable && cfg.bird.enable) { - environment.etc."enthalpy/bird2.conf".source = pkgs.writeTextFile { - name = "bird2"; + environment.etc."netns/${cfg.netns}/bird.conf".source = pkgs.writeTextFile { + name = "bird"; text = cfg.bird.config; checkPhase = optionalString cfg.bird.checkConfig '' - ln -s $out bird2.conf - ${pkgs.buildPackages.bird}/bin/bird -d -p -c bird2.conf + ln -s $out bird.conf + ${pkgs.buildPackages.bird}/bin/bird -d -p -c bird.conf ''; }; - systemd.services.enthalpy-bird2 = { - serviceConfig = { - Type = "forking"; - Restart = "on-failure"; - RestartSec = 5; - DynamicUser = true; - RuntimeDirectory = "enthalpy/bird2"; - ExecStart = "${pkgs.bird}/bin/bird -s ${cfg.bird.socket} -c /etc/enthalpy/bird2.conf"; - ExecReload = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} configure"; - ExecStop = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} down"; - CapabilityBoundingSet = [ - "CAP_NET_ADMIN" - "CAP_NET_BIND_SERVICE" - "CAP_NET_RAW" - ]; - AmbientCapabilities = [ - "CAP_NET_ADMIN" - "CAP_NET_BIND_SERVICE" - "CAP_NET_RAW" - ]; - ProtectSystem = "full"; - ProtectHome = "yes"; - ProtectKernelTunables = true; - ProtectControlGroups = true; - PrivateTmp = true; - PrivateDevices = true; - SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io"; - MemoryDenyWriteExecute = "yes"; - }; - wantedBy = [ "multi-user.target" ]; - reloadTriggers = [ config.environment.etc."enthalpy/bird2.conf".source ]; + systemd.services.enthalpy-bird = { + serviceConfig = + mylib.misc.serviceHardened + // config.networking.netns.${cfg.netns}.serviceConfig + // { + Type = "forking"; + Restart = "on-failure"; + RestartSec = 5; + DynamicUser = true; + RuntimeDirectory = "netns-${cfg.netns}/bird"; + ExecStart = "${pkgs.bird}/bin/bird -s ${cfg.bird.socket} -c /etc/netns/${cfg.netns}/bird.conf"; + ExecReload = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} configure"; + ExecStop = "${pkgs.bird}/bin/birdc -s ${cfg.bird.socket} down"; + CapabilityBoundingSet = [ + "CAP_NET_ADMIN" + "CAP_NET_BIND_SERVICE" + "CAP_NET_RAW" + ]; + AmbientCapabilities = [ + "CAP_NET_ADMIN" + "CAP_NET_BIND_SERVICE" + "CAP_NET_RAW" + ]; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + }; + after = [ "netns-${cfg.netns}.service" ]; + partOf = [ "netns-${cfg.netns}.service" ]; + wantedBy = [ + "multi-user.target" + "netns-${cfg.netns}.service" + ]; + reloadTriggers = [ config.environment.etc."netns/${cfg.netns}/bird.conf".source ]; }; - services.enthalpy.services.enthalpy-bird2 = { }; - services.enthalpy.bird.config = mkBefore '' router id ${toString cfg.bird.routerId}; ipv6 sadr table sadr6; diff --git a/nixos/modules/services/enthalpy/clat.nix b/nixos/modules/services/enthalpy/clat.nix index d2483fc..3b30fe2 100644 --- a/nixos/modules/services/enthalpy/clat.nix +++ b/nixos/modules/services/enthalpy/clat.nix @@ -11,6 +11,7 @@ with lib; let inherit (mylib.network) cidr; cfg = config.services.enthalpy; + interface = config.networking.netns.${cfg.netns}.interface; in { options.services.enthalpy.clat = { @@ -38,7 +39,7 @@ in ]; preStart = '' ip -6 route replace 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \ - segs ${concatStringsSep "," cfg.clat.segment} dev enthalpy mtu 1280 + segs ${concatStringsSep "," cfg.clat.segment} dev ${interface} mtu 1280 ''; script = '' exec tayga --config ${pkgs.writeText "tayga.conf" '' @@ -56,28 +57,32 @@ in ''; preStop = '' ip -6 route del 64:ff9b::/96 from ${cfg.clat.address} encap seg6 mode encap \ - segs ${concatStringsSep "," cfg.clat.segment} dev enthalpy mtu 1280 + segs ${concatStringsSep "," cfg.clat.segment} dev ${interface} mtu 1280 ''; - serviceConfig = { - Type = "forking"; - Restart = "on-failure"; - RestartSec = 5; - DynamicUser = true; - CapabilityBoundingSet = [ "CAP_NET_ADMIN" ]; - AmbientCapabilities = [ "CAP_NET_ADMIN" ]; - ProtectSystem = "full"; - ProtectHome = "yes"; - ProtectKernelTunables = true; - ProtectControlGroups = true; - PrivateTmp = true; - SystemCallFilter = "~@cpu-emulation @debug @keyring @module @mount @obsolete @raw-io"; - MemoryDenyWriteExecute = "yes"; - }; - wants = [ "network.target" ]; - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; + serviceConfig = + mylib.misc.serviceHardened + // config.networking.netns.${cfg.netns}.serviceConfig + // { + Type = "forking"; + Restart = "on-failure"; + RestartSec = 5; + DynamicUser = true; + CapabilityBoundingSet = [ "CAP_NET_ADMIN" ]; + AmbientCapabilities = [ "CAP_NET_ADMIN" ]; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + "AF_NETLINK" + ]; + PrivateDevices = false; + }; + after = [ "netns-${cfg.netns}.service" ]; + partOf = [ "netns-${cfg.netns}.service" ]; + wantedBy = [ + "multi-user.target" + "netns-${cfg.netns}.service" + ]; }; - - services.enthalpy.services.enthalpy-clat = { }; }; } diff --git a/nixos/modules/services/enthalpy/common.nix b/nixos/modules/services/enthalpy/common.nix index 14d4d59..76465c2 100644 --- a/nixos/modules/services/enthalpy/common.nix +++ b/nixos/modules/services/enthalpy/common.nix @@ -3,7 +3,6 @@ { config, lib, - pkgs, mylib, ... }: diff --git a/nixos/modules/services/enthalpy/gost.nix b/nixos/modules/services/enthalpy/gost.nix index d387798..05e21d3 100644 --- a/nixos/modules/services/enthalpy/gost.nix +++ b/nixos/modules/services/enthalpy/gost.nix @@ -10,7 +10,6 @@ with lib; let cfg = config.services.enthalpy; - gostPort = config.networking.ports.enthalpy-gost; in { options.services.enthalpy.gost = { @@ -21,22 +20,24 @@ in systemd.services.enthalpy-gost = { serviceConfig = mylib.misc.serviceHardened // { Type = "simple"; - Restart = "always"; + Restart = "on-failure"; RestartSec = 5; DynamicUser = true; - ExecStart = "${pkgs.gost}/bin/gost -L=socks5://[::1]:${toString gostPort}"; + ExecStart = "${pkgs.gost}/bin/gost -L=socks5://[::1]:${toString config.networking.ports.enthalpy-gost}"; }; - after = [ "network-online.target" ]; - wantedBy = [ "network-online.target" ]; + after = [ "netns-${cfg.netns}.service" ]; + partOf = [ "netns-${cfg.netns}.service" ]; + wantedBy = [ + "multi-user.target" + "netns-${cfg.netns}.service" + ]; }; - networking.netns."${cfg.netns}".forwardPorts = [ - { - protocol = "tcp"; - netns = "default"; - source = "[::1]:${toString gostPort}"; - target = "[::1]:${toString gostPort}"; - } - ]; + networking.netns."${cfg.netns}".forwardPorts = singleton { + protocol = "tcp"; + netns = "default"; + source = "[::1]:${toString config.networking.ports.enthalpy-gost}"; + target = "[::1]:${toString config.networking.ports.socks}"; + }; }; } diff --git a/nixos/modules/services/enthalpy/services.nix b/nixos/modules/services/enthalpy/services.nix index 8675e88..3a087b0 100644 --- a/nixos/modules/services/enthalpy/services.nix +++ b/nixos/modules/services/enthalpy/services.nix @@ -3,7 +3,6 @@ { config, lib, - pkgs, ... }: with lib;