Loading nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1309,6 +1309,7 @@ ./services/networking/netbird.nix ./services/networking/netbird/server.nix ./services/networking/netclient.nix ./services/networking/netfoil.nix ./services/networking/networkd-dispatcher.nix ./services/networking/networkmanager.nix ./services/networking/newt.nix Loading nixos/modules/services/networking/netfoil.nix 0 → 100644 +263 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let cfg = config.services.netfoil; in { options = { services.netfoil = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "Enable Netfoil, a minimal, filtering, DNS proxy"; }; listen = { port = lib.mkOption { type = lib.types.int; default = 53; description = "Port on which Netfoil listens for incoming connections"; }; ipAddress = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "IP address on which Netfoil listens for incoming connections"; }; }; logAllowed = lib.mkOption { type = lib.types.bool; default = false; description = "Log allowed DNS queries"; }; doHUrl = lib.mkOption { type = lib.types.str; default = "https://security.cloudflare-dns.com/dns-query"; description = "The DoH URL to use for upstream DNS queries"; }; doHIPs = lib.mkOption { type = lib.types.str; default = "1.1.1.2,1.0.0.2"; description = "The DoH IPs to use for upstream DNS queries"; }; logDenied = lib.mkOption { type = lib.types.bool; default = true; description = "Log denied DNS queries"; }; config = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; description = "Additional configuration options for Netfoil"; }; rules = { allow = { exact = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of exact domain names to allow"; }; ipv4 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv4 CIDR ranges to allow"; }; ipv6 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv6 CIDR ranges to allow"; }; tld = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of TLDs to allow"; }; }; deny = { exact = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of exact domain names to deny"; }; ipv4 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv4 CIDR ranges to deny"; }; ipv6 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv6 CIDR ranges to deny"; }; tld = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of TLDs to deny"; }; }; known = { knownTlds = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ".com" ".net" ".org" ".edu" ".gov" ".mil" ".int" ]; description = "List of known TLDs"; }; }; pin = { a = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of A records to pin <domain:ipv4>"; }; responseDomain = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of domains to pin <domain:domain>"; }; }; }; }; }; config = lib.mkIf cfg.enable ( let configFile = lib.concatStringsSep "\n" ( [ "LogAllowed=${lib.boolToString cfg.logAllowed}" "LogDenied=${lib.boolToString cfg.logDenied}" "DoHURL=${cfg.doHUrl}" "DoHIPs=${cfg.doHIPs}" ] ++ (map (key: "${key} = \"${cfg.config.${key}}\"") (lib.attrNames cfg.config)) ++ lib.optional ((lib.length cfg.rules.pin.responseDomain) != 0) "PinResponseDomain=true" ); configDir = pkgs.buildEnv { name = "netfoil-config"; paths = [ (pkgs.writeTextDir "config" configFile) (pkgs.writeTextDir "allow.exact" (lib.concatStringsSep "\n" cfg.rules.allow.exact)) (pkgs.writeTextDir "allow.ipv4" (lib.concatStringsSep "\n" cfg.rules.allow.ipv4)) (pkgs.writeTextDir "allow.ipv6" (lib.concatStringsSep "\n" cfg.rules.allow.ipv6)) (pkgs.writeTextDir "allow.suffix" (lib.concatStringsSep "\n" cfg.rules.allow.tld)) (pkgs.writeTextDir "allow.tld" (lib.concatStringsSep "\n" cfg.rules.allow.tld)) (pkgs.writeTextDir "deny.exact" (lib.concatStringsSep "\n" cfg.rules.deny.exact)) (pkgs.writeTextDir "deny.ipv4" (lib.concatStringsSep "\n" cfg.rules.deny.ipv4)) (pkgs.writeTextDir "deny.ipv6" (lib.concatStringsSep "\n" cfg.rules.deny.ipv6)) (pkgs.writeTextDir "deny.suffix" (lib.concatStringsSep "\n" cfg.rules.deny.tld)) (pkgs.writeTextDir "deny.tld" (lib.concatStringsSep "\n" cfg.rules.deny.tld)) (pkgs.writeTextDir "known.tld" (lib.concatStringsSep "\n" cfg.rules.known.knownTlds)) (pkgs.writeTextDir "pin.a" (lib.concatStringsSep "\n" cfg.rules.pin.a)) (pkgs.writeTextDir "pin.response-domain" (lib.concatStringsSep "\n" cfg.rules.pin.responseDomain)) ]; }; in { systemd = { services.netfoil = { enable = true; description = "Netfoil DNS proxy"; after = [ "network.target" ]; requires = [ "netfoil.socket" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.netfoil}/bin/netfoil --config-directory ${configDir}"; Restart = "always"; RestartSec = "5"; DynamicUser = true; BindReadOnlyPaths = [ "${pkgs.netfoil}" "${configDir}" "/etc/ssl" builtins.storeDir ]; Slice = "netfoil.slice"; AmbientCapabilities = ""; CapabilityBoundingSet = [ ]; SystenCallArchitectures = "native"; SystemCallFilter = [ "@basic-io" "@file-system" "@network-io" "@signal" "@process" "@io-event" "@system-service" "@resources" ]; RuntimeDirectory = "netfoil"; RuntimeDirectoryMode = "0755"; RootDirectory = "/run/netfoil"; RestrictAddressFamilies = "AF_INET AF_INET6"; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; # This might set AllowDevices=char-rtc r ProtectClock = true; ProtectKernelModules = true; ProtectKernelLogs = true; LockPersonality = true; MemoryDenyWriteExecute = true; RemoveIPC = true; UMask = "0077"; # IPC namespace PrivateIPC = true; # UTS namespace ProtectHostname = true; # Changes mounts (custom is more strict) # https://github.com/systemd/systemd/blob/main/src/core/namespace.c # ProtectControlGroups = true; ProtectHome = true; ProtectProc = "invisible"; ProcSubset = "pid"; ProtectSystem = "strict"; PrivateTmp = true; # # seccomp _sysctl (custom filter does not allow it anyway) # /proc and /sys mounts (custom is more strict) ProtectKernelTunables = true; # # seccomp @raw-io (custom filter does not allow it anyway) PrivateDevices = true; DevicePolicy = "closed"; SocketBindDeny = "any"; CPUQuota = "50%"; MemoryMax = "100M"; TasksMax = "100"; }; }; slices.netfoil = { description = "Slice for Netfoil DNS proxy"; }; sockets.netfoil = { description = "Netfoil DNS proxy socket"; wantedBy = [ "sockets.target" ]; socketConfig = { ListenDatagram = "${cfg.listen.ipAddress}:${toString cfg.listen.port}"; Service = "netfoil.service"; }; }; }; } ); } nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1068,6 +1068,7 @@ in netbox_4_4 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_4; }; netbox_4_5 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_5; }; netdata = runTest ./netdata.nix; netfoil = runTest ./netfoil.nix; networking.networkd = handleTest ./networking/networkd-and-scripted.nix { networkd = true; }; networking.networkmanager = handleTest ./networking/networkmanager.nix { }; networking.scripted = handleTest ./networking/networkd-and-scripted.nix { networkd = false; }; Loading nixos/tests/netfoil.nix 0 → 100644 +28 −0 Original line number Diff line number Diff line { lib, ... }: { name = "netfoil"; meta.maintainers = with lib.maintainers; [ marcusramberg sgo ]; nodes = { one = { config, ... }: { services.netfoil = { enable = true; listen.port = 6353; }; }; }; interactive.sshBackdoor.enable = true; testScript = '' start_all() with subtest("ensure netfoil starts and listens on 6353"): one.wait_for_unit("netfoil.service") one.wait_for_open_port(6353) ''; } pkgs/by-name/ne/netfoil/package.nix 0 → 100644 +39 −0 Original line number Diff line number Diff line { lib, buildGoModule, fetchFromGitHub, nix-update-script, }: buildGoModule rec { pname = "netfoil"; version = "0.2.1"; src = fetchFromGitHub { owner = "tinfoil-factory"; repo = "netfoil"; tag = "v${version}"; hash = "sha256-1JpnVaU17uxQu0O8R0kfl7lCE3YMd/XFmbq9KUMAKqY="; }; __structuredAttrs = true; env.CGO_ENABLED = 0; proxyVendor = true; vendorHash = "sha256-xtc1zCSLuez9POx/jEjre0uVmvWvCW0TpXPFVi2p+CY="; passthru.updateScript = nix-update-script { }; meta = with lib; { description = "Minimal, filtering, DNS proxy"; homepage = "https://github.com/tinfoil-factory/netfoil"; license = licenses.asl20; maintainers = with maintainers; [ sgo marcusramberg ]; mainProgram = "netfoil"; }; } Loading
nixos/modules/module-list.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1309,6 +1309,7 @@ ./services/networking/netbird.nix ./services/networking/netbird/server.nix ./services/networking/netclient.nix ./services/networking/netfoil.nix ./services/networking/networkd-dispatcher.nix ./services/networking/networkmanager.nix ./services/networking/newt.nix Loading
nixos/modules/services/networking/netfoil.nix 0 → 100644 +263 −0 Original line number Diff line number Diff line { config, lib, pkgs, ... }: let cfg = config.services.netfoil; in { options = { services.netfoil = { enable = lib.mkOption { type = lib.types.bool; default = false; description = "Enable Netfoil, a minimal, filtering, DNS proxy"; }; listen = { port = lib.mkOption { type = lib.types.int; default = 53; description = "Port on which Netfoil listens for incoming connections"; }; ipAddress = lib.mkOption { type = lib.types.str; default = "127.0.0.1"; description = "IP address on which Netfoil listens for incoming connections"; }; }; logAllowed = lib.mkOption { type = lib.types.bool; default = false; description = "Log allowed DNS queries"; }; doHUrl = lib.mkOption { type = lib.types.str; default = "https://security.cloudflare-dns.com/dns-query"; description = "The DoH URL to use for upstream DNS queries"; }; doHIPs = lib.mkOption { type = lib.types.str; default = "1.1.1.2,1.0.0.2"; description = "The DoH IPs to use for upstream DNS queries"; }; logDenied = lib.mkOption { type = lib.types.bool; default = true; description = "Log denied DNS queries"; }; config = lib.mkOption { type = lib.types.attrsOf lib.types.str; default = { }; description = "Additional configuration options for Netfoil"; }; rules = { allow = { exact = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of exact domain names to allow"; }; ipv4 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv4 CIDR ranges to allow"; }; ipv6 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv6 CIDR ranges to allow"; }; tld = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of TLDs to allow"; }; }; deny = { exact = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of exact domain names to deny"; }; ipv4 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv4 CIDR ranges to deny"; }; ipv6 = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of ipv6 CIDR ranges to deny"; }; tld = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of TLDs to deny"; }; }; known = { knownTlds = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ".com" ".net" ".org" ".edu" ".gov" ".mil" ".int" ]; description = "List of known TLDs"; }; }; pin = { a = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of A records to pin <domain:ipv4>"; }; responseDomain = lib.mkOption { type = lib.types.listOf lib.types.str; default = [ ]; description = "List of domains to pin <domain:domain>"; }; }; }; }; }; config = lib.mkIf cfg.enable ( let configFile = lib.concatStringsSep "\n" ( [ "LogAllowed=${lib.boolToString cfg.logAllowed}" "LogDenied=${lib.boolToString cfg.logDenied}" "DoHURL=${cfg.doHUrl}" "DoHIPs=${cfg.doHIPs}" ] ++ (map (key: "${key} = \"${cfg.config.${key}}\"") (lib.attrNames cfg.config)) ++ lib.optional ((lib.length cfg.rules.pin.responseDomain) != 0) "PinResponseDomain=true" ); configDir = pkgs.buildEnv { name = "netfoil-config"; paths = [ (pkgs.writeTextDir "config" configFile) (pkgs.writeTextDir "allow.exact" (lib.concatStringsSep "\n" cfg.rules.allow.exact)) (pkgs.writeTextDir "allow.ipv4" (lib.concatStringsSep "\n" cfg.rules.allow.ipv4)) (pkgs.writeTextDir "allow.ipv6" (lib.concatStringsSep "\n" cfg.rules.allow.ipv6)) (pkgs.writeTextDir "allow.suffix" (lib.concatStringsSep "\n" cfg.rules.allow.tld)) (pkgs.writeTextDir "allow.tld" (lib.concatStringsSep "\n" cfg.rules.allow.tld)) (pkgs.writeTextDir "deny.exact" (lib.concatStringsSep "\n" cfg.rules.deny.exact)) (pkgs.writeTextDir "deny.ipv4" (lib.concatStringsSep "\n" cfg.rules.deny.ipv4)) (pkgs.writeTextDir "deny.ipv6" (lib.concatStringsSep "\n" cfg.rules.deny.ipv6)) (pkgs.writeTextDir "deny.suffix" (lib.concatStringsSep "\n" cfg.rules.deny.tld)) (pkgs.writeTextDir "deny.tld" (lib.concatStringsSep "\n" cfg.rules.deny.tld)) (pkgs.writeTextDir "known.tld" (lib.concatStringsSep "\n" cfg.rules.known.knownTlds)) (pkgs.writeTextDir "pin.a" (lib.concatStringsSep "\n" cfg.rules.pin.a)) (pkgs.writeTextDir "pin.response-domain" (lib.concatStringsSep "\n" cfg.rules.pin.responseDomain)) ]; }; in { systemd = { services.netfoil = { enable = true; description = "Netfoil DNS proxy"; after = [ "network.target" ]; requires = [ "netfoil.socket" ]; wantedBy = [ "multi-user.target" ]; serviceConfig = { Type = "simple"; ExecStart = "${pkgs.netfoil}/bin/netfoil --config-directory ${configDir}"; Restart = "always"; RestartSec = "5"; DynamicUser = true; BindReadOnlyPaths = [ "${pkgs.netfoil}" "${configDir}" "/etc/ssl" builtins.storeDir ]; Slice = "netfoil.slice"; AmbientCapabilities = ""; CapabilityBoundingSet = [ ]; SystenCallArchitectures = "native"; SystemCallFilter = [ "@basic-io" "@file-system" "@network-io" "@signal" "@process" "@io-event" "@system-service" "@resources" ]; RuntimeDirectory = "netfoil"; RuntimeDirectoryMode = "0755"; RootDirectory = "/run/netfoil"; RestrictAddressFamilies = "AF_INET AF_INET6"; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; # This might set AllowDevices=char-rtc r ProtectClock = true; ProtectKernelModules = true; ProtectKernelLogs = true; LockPersonality = true; MemoryDenyWriteExecute = true; RemoveIPC = true; UMask = "0077"; # IPC namespace PrivateIPC = true; # UTS namespace ProtectHostname = true; # Changes mounts (custom is more strict) # https://github.com/systemd/systemd/blob/main/src/core/namespace.c # ProtectControlGroups = true; ProtectHome = true; ProtectProc = "invisible"; ProcSubset = "pid"; ProtectSystem = "strict"; PrivateTmp = true; # # seccomp _sysctl (custom filter does not allow it anyway) # /proc and /sys mounts (custom is more strict) ProtectKernelTunables = true; # # seccomp @raw-io (custom filter does not allow it anyway) PrivateDevices = true; DevicePolicy = "closed"; SocketBindDeny = "any"; CPUQuota = "50%"; MemoryMax = "100M"; TasksMax = "100"; }; }; slices.netfoil = { description = "Slice for Netfoil DNS proxy"; }; sockets.netfoil = { description = "Netfoil DNS proxy socket"; wantedBy = [ "sockets.target" ]; socketConfig = { ListenDatagram = "${cfg.listen.ipAddress}:${toString cfg.listen.port}"; Service = "netfoil.service"; }; }; }; } ); }
nixos/tests/all-tests.nix +1 −0 Original line number Diff line number Diff line Loading @@ -1068,6 +1068,7 @@ in netbox_4_4 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_4; }; netbox_4_5 = handleTest ./web-apps/netbox/default.nix { netbox = pkgs.netbox_4_5; }; netdata = runTest ./netdata.nix; netfoil = runTest ./netfoil.nix; networking.networkd = handleTest ./networking/networkd-and-scripted.nix { networkd = true; }; networking.networkmanager = handleTest ./networking/networkmanager.nix { }; networking.scripted = handleTest ./networking/networkd-and-scripted.nix { networkd = false; }; Loading
nixos/tests/netfoil.nix 0 → 100644 +28 −0 Original line number Diff line number Diff line { lib, ... }: { name = "netfoil"; meta.maintainers = with lib.maintainers; [ marcusramberg sgo ]; nodes = { one = { config, ... }: { services.netfoil = { enable = true; listen.port = 6353; }; }; }; interactive.sshBackdoor.enable = true; testScript = '' start_all() with subtest("ensure netfoil starts and listens on 6353"): one.wait_for_unit("netfoil.service") one.wait_for_open_port(6353) ''; }
pkgs/by-name/ne/netfoil/package.nix 0 → 100644 +39 −0 Original line number Diff line number Diff line { lib, buildGoModule, fetchFromGitHub, nix-update-script, }: buildGoModule rec { pname = "netfoil"; version = "0.2.1"; src = fetchFromGitHub { owner = "tinfoil-factory"; repo = "netfoil"; tag = "v${version}"; hash = "sha256-1JpnVaU17uxQu0O8R0kfl7lCE3YMd/XFmbq9KUMAKqY="; }; __structuredAttrs = true; env.CGO_ENABLED = 0; proxyVendor = true; vendorHash = "sha256-xtc1zCSLuez9POx/jEjre0uVmvWvCW0TpXPFVi2p+CY="; passthru.updateScript = nix-update-script { }; meta = with lib; { description = "Minimal, filtering, DNS proxy"; homepage = "https://github.com/tinfoil-factory/netfoil"; license = licenses.asl20; maintainers = with maintainers; [ sgo marcusramberg ]; mainProgram = "netfoil"; }; }