{ config, lib, pkgs, ... }: with lib; let cfg = config.clerie.forward-filter; startScript = pkgs.writeScriptBin "forward-filter-start" '' #! ${pkgs.runtimeShell} -e ip46tables() { iptables -w "$@" ip6tables -w "$@" } ip46tables -D FORWARD -j forward-filter 2> /dev/null || true ip46tables -F forward-filter 2> /dev/null || true ip46tables -X forward-filter 2> /dev/null || true ip46tables -N forward-filter ip46tables -A forward-filter -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ${concatStrings (mapAttrsToList (iface: ifacecfg: '' ${concatMapStrings (rule: if (rule.sourceAddress != null || rule.destinationAddress != null ) then '' iptables -A forward-filter -o ${iface} ${optionalString (rule.incomingInterface != null) "-i ${rule.incomingInterface}"} ${optionalString (rule.sourceAddress != null) "-s ${rule.sourceAddress}"} ${optionalString (rule.destinationAddress != null) "-d ${rule.destinationAddress}"} ${optionalString (rule.jump != null) "-j ${rule.jump}"} '' else if (rule.sourceAddress6 != null || rule.destinationAddress6 != null ) then '' ip6tables -A forward-filter -o ${iface} ${optionalString (rule.incomingInterface != null) "-i ${rule.incomingInterface}"} ${optionalString (rule.sourceAddress6 != null) "-s ${rule.sourceAddress6}"} ${optionalString (rule.destinationAddress6 != null) "-d ${rule.destinationAddress6}"} ${optionalString (rule.jump != null) "-j ${rule.jump}"} '' else '' ip46tables -A forward-filter -o ${iface} ${optionalString (rule.incomingInterface != null) "-i ${rule.incomingInterface}"} ${optionalString (rule.jump != null) "-j ${rule.jump}"} '' ) ifacecfg.rules} ${optionalString (ifacecfg.default != null) '' ip46tables -A forward-filter -o ${iface} -j ${ifacecfg.default} ''} '') cfg.interfaces)} ip46tables -A FORWARD -j forward-filter ''; stopScript = pkgs.writeScriptBin "forward-filter-stop" '' #! ${pkgs.runtimeShell} -e ip46tables() { iptables -w "$@" ip6tables -w "$@" } ip46tables -D FORWARD -j forward-filter 2> /dev/null || true ip46tables -F forward-filter 2> /dev/null || true ip46tables -X forward-filter 2> /dev/null || true ''; in { options = { clerie.forward-filter = { enable = mkOption { type = types.bool; default = false; description = '' Whether to enable the forward-filter. It gives basic control about blocking and allowing forward between interfaces. ''; }; interfaces = mkOption { default = { }; type = with types; attrsOf (submodule { options = { rules = mkOption { type = with types; listOf (submodule { options = { sourceAddress = mkOption { type = types.nullOr types.str; default = null; example = "192.168.0.0/24"; description = ""; }; sourceAddress6 = mkOption { type = types.nullOr types.str; default = null; example = "fd00::/64"; description = ""; }; destinationAddress = mkOption { type = types.nullOr types.str; default = null; example = "192.168.0.0/24"; description = ""; }; destinationAddress6 = mkOption { type = types.nullOr types.str; default = null; example = "fd00::/64"; description = ""; }; incomingInterface = mkOption { type = types.nullOr types.str; default = null; example = "ens18"; description = ""; }; jump = mkOption { type = types.nullOr types.str; default = "ACCEPT"; example = "DROP"; description = ""; }; }; }); description = "List of rules to filter forwarding."; }; default = mkOption { type = types.nullOr types.str; default = "DROP"; example = "ACCEPT"; description = ""; }; }; }); description = '' Interface filter options. ''; }; }; }; config = mkIf cfg.enable { environment.systemPackages = [ pkgs.iptables ]; systemd.services.forward-filter = { description = "Forward Filter"; wantedBy = [ "sysinit.target" ]; wants = [ "network-pre.target" ]; before = [ "network-pre.target" ]; after = [ "systemd-modules-load.service" ]; path = [ pkgs.iptables ]; unitConfig.ConditionCapability = "CAP_NET_ADMIN"; unitConfig.DefaultDependencies = false; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; ExecStart = "@${startScript}/bin/forward-filter-start forward-filter-start"; ExecStop = "@${stopScript}/bin/forward-filter-stop forward-filter-stop"; }; }; }; }