diff options
| -rw-r--r-- | archetypes/profiles/router/default.nix | 28 | ||||
| -rw-r--r-- | archetypes/profiles/router/unbound.nix | 70 | ||||
| -rw-r--r-- | nixos/default.nix | 3 | ||||
| -rw-r--r-- | nixos/services/router/dns.nix | 171 | ||||
| -rw-r--r-- | nixos/services/router/unbound-blocklist.nix (renamed from nixos/unbound-blocklist.nix) | 0 |
5 files changed, 195 insertions, 77 deletions
diff --git a/archetypes/profiles/router/default.nix b/archetypes/profiles/router/default.nix index 0818a6b..646982b 100644 --- a/archetypes/profiles/router/default.nix +++ b/archetypes/profiles/router/default.nix @@ -1,12 +1,28 @@ { lib, pkgs, ... }: let mkRouter = lib.mkOverride 800; - # TODO pass mkRouter - #imports = [ - # ./unbound.nix - #]; - - nixosConfig = {}; + nixosConfig = { + services.unbound = { + _blocklists = { + enable = true; + blocklists = { + hageziNSFW = [ + "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/nsfw.txt" + "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/nsfw.txt" + "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/nsfw.txt" + ]; + hageziPro = [ + "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/pro.txt" + "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/pro.txt" + "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/pro.txt" + ]; + }; + }; + }; + services._router.dnsDhcpConfig = { + enable = mkRouter true; + }; + }; homeConfig = {}; in { diff --git a/archetypes/profiles/router/unbound.nix b/archetypes/profiles/router/unbound.nix deleted file mode 100644 index 1322193..0000000 --- a/archetypes/profiles/router/unbound.nix +++ /dev/null @@ -1,70 +0,0 @@ -{ - services.unbound = { - enable = true; - _blocklists = { - enable = true; - blocklists = { - hageziNSFW = [ - "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/nsfw.txt" - "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/nsfw.txt" - "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/nsfw.txt" - ]; - hageziPro = [ - "https://cdn.jsdelivr.net/gh/hagezi/dns-blocklists@latest/rpz/pro.txt" - "https://gitlab.com/hagezi/mirror/-/raw/main/dns-blocklists/rpz/pro.txt" - "https://codeberg.org/hagezi/mirror2/raw/branch/main/dns-blocklists/rpz/pro.txt" - ]; - }; - }; - settings = { - server = { - # Listen on all interfaces (or specify specific IPs) - interface = [ "0.0.0.0" "::0" ]; - - # Allow queries from local networks - access-control = [ - "127.0.0.0/8 allow" - "192.168.0.0/16 allow" - "10.0.0.0/8 allow" - "172.16.0.0/12 allow" - ]; - - ## Enable DNSSEC validation - #auto-trust-anchor-file: "/var/unbound/root.key" - - # Harden against out-of-zone data - harden-referral-path = true; - harden-dnssec-stripped = true; - - # Privacy options - qname-minimisation = true; - - # Cache settings - cache-min-ttl = 300; - cache-max-ttl = 86400; - - # Hide version - hide-identity = true; - hide-version = true; - - # Based on recommended settings in https://docs.pi-hole.net/guides/dns/unbound/#configure-unbound - harden-glue = true; - use-caps-for-id = false; - prefetch = true; - edns-buffer-size = 1232; - }; - # Forward unknown to public resolver via DoT - forward-zone = [ - { - name = "."; - forward-addr = [ - "9.9.9.9#dns.quad9.net" - "149.112.112.112#dns.quad9.net" - ]; - forward-tls-upstream = true; # Encrypted DNS - } - ]; - remote-control.control-enable = true; - }; - }; -} diff --git a/nixos/default.nix b/nixos/default.nix index 4b87741..76b5d6f 100644 --- a/nixos/default.nix +++ b/nixos/default.nix @@ -7,6 +7,8 @@ ./services/cgit.nix ./services/gitea.nix ./services/searxng.nix + ./services/router/dns.nix + ./services/router/unbound-blocklist.nix ./bootloader.nix ./doas.nix @@ -23,7 +25,6 @@ ./ssh.nix ./sudo.nix ./suspend.nix - ./unbound-blocklist.nix ./zshenv.nix ]; } diff --git a/nixos/services/router/dns.nix b/nixos/services/router/dns.nix new file mode 100644 index 0000000..2772a27 --- /dev/null +++ b/nixos/services/router/dns.nix @@ -0,0 +1,171 @@ +{ config, lib, ... }: let + #cfg = config.services._routing; + cfg = { + localDomain = "home.lan"; + defaultGateway = "127.0.0.1"; + dhcp = { + rangeStart = "192.168.1.50"; + rangeEnd = "192.168.1.150"; + subnetMask = "255.255.255.0"; + leaseTime = "12h"; + }; + log = { + dhcp = false; + dns = false; + }; + }; +in { + options.services._router.dnsDhcpConfig = { + enable = lib.mkEnableOption "enable pre-configured unbound(outbound) + dnsmasq(local) dns(+dhcp) server"; + # TODO + defaultGateway = lib.mkOption { + }; + localIPSubnet = lib.mkOption { + }; + dhcp = { + rangeStart = lib.mkOption { + }; + rangeEnd = lib.mkOption { + }; + rangeSubnetMask = lib.mkOption { + }; + leaseTime = lib.mkOption { + }; + staticLeases = lib.mkOption { + }; + }; + localDomain = lib.mkOption { + type = lib.types.str; + default = "home.lan"; + description = ""; + }; + #ipv6 = lib.mkEnableOption "enable ipv6"; # TODO + }; + + config = lib.mkIf cfg.enable { + # Unbound as a recursive DNS resolver. + # Preferred for its security/privacy features + performance + services.unbound = { + enable = true; + settings = { + server = { + # Listen on all interfaces (or specify specific IPs) + interface = [ "0.0.0.0" ]; # ++ lib.optionals cfg.ipv6 [ "::0" ]; + + # Allow queries from local networks + access-control = [ + "127.0.0.0/8 allow" + "192.168.0.0/16 allow" + "10.0.0.0/8 allow" + "172.16.0.0/12 allow" + ]; + + # Cache settings + cache-min-ttl = 300; # 5 min + cache-max-ttl = 60 * 60 * 24; # 1 day + + # Prefetch DNS records for better performance + prefetch-key = true; + prefetch = true; + + # Enable DNSSEC validation (signed DNS, prevents man-in-the-middle attacks) + # `auto-trust-anchor-file` is set by default to "/var/unbound/root.key" + trust-anchor-file = ''""''; + domain-insecure = cfg.localDomain; # Local domain is not DNSSEC-signed + harden-below-nxdomain = true; # Protect against non-existent domain response attacks + harden-glue = true; # Protect against incorrect DNS glue record attacks + harden-dnssec-stripped = true; # Ensures that DNSSEC signatures are not stripped from DNS responses + + # Allow queries to localhost for dnsmasq local domain resolution + do-not-query-localhost = false; + + # Privacy / Security + harden-referral-path = true; # Prevents receiving data from servers that are outside the zone in the DNS referral chain + qname-minimisation = true; # Minimizes the amount of information exposed in DNS queries by only sending the essential parts of the query + hide-identity = true; # Hide software identity in response + hide-version = true; # Hide unbound version in response + use-caps-for-id = false; # Disables using uppercase characters in the DNS transaction ID, for compatibility + edns-buffer-size = 1232; # Sets the EDNS (Extension mechanisms for DNS) buffer size to 1232 bytes, which is the default for many DNS resolvers + + # Logging + #verbosity = 3; + #log-queries = true; + #log-replies = true; + #log-local-actions = true; + }; + # Forward unknown to public resolver via DoT + forward-zone = [ + # Local DNS: forward to dnsmasq + { + name = ''"${cfg.localDomain}."''; # TODO mk config + forward-addr = "127.0.0.1@5353"; # TODO mk config + } + # Local reverse DNS: forward reverse lookups (PTR records) + { + name = ''"1.168.192.in-addr.arpa"''; # NOTE: 192.168.1.0 -> 1.168.192 + forward-addr = "127.0.0.1@5353"; + } + # Upstream DNS + { + name = ''"."''; + forward-addr = [ + "9.9.9.9#dns.quad9.net" + "149.112.112.112#dns.quad9.net" + ]; + forward-tls-upstream = true; # Encrypted DNS + } + ]; + remote-control.control-enable = true; + }; + }; + + # Configure dnsmasq for dhcp and local hostname resolution + services.dnsmasq = { + enable = true; + settings = let + mkDHCPRange = ipRangeStart: ipRangeEnd: subnetMask: leaseTime: "${ipRangeStart},${ipRangeEnd},${subnetMask},${leaseTime}"; + mkDHCPOption = option: value: "option:${option},${value}"; + mkDHCPStaticLease = macAddress: hostname: staticIp: "${macAddress},${hostname},${staticIp},infinite"; + #dhcpStaticLeases = builtins.map (); + in { + # General + no-resolv = true; # Do not read /etc/resolv.conf, resolve only the LAN + no-poll = true; # Do not poll /etc/resolv.conf for changes + # TODO config local domain + local = "/${cfg.localDomain}/"; # Use local-only for the defined domain (prevents upstream leaks) + domain = cfg.localDomain; # Define the local domain name + expand-hosts = true; # Create both fully-qualified and short-name entries from DHCP hostnames + + # DNS Server + port = 5353; # Use port 5353 for DNS server since unbound is the main DNS resolver + + # DHCP Server + # TODO config + #dhcp-range = mkDHCPRange "192.168.1.50" "192.168.1.150" "255.255.255.0" "12h"; # Enable DHCP on the LAN interface + dhcp-range = with cfg.dhcp; mkDHCPRange rangeStart rangeEnd subnetMask leaseTime; # Enable DHCP on the LAN interface + + # TODO config + #dhcp-host = [ mkDHCPStaticLease ... ]; # Setup static leases + #dhcp-host = dhcpStaticLeases; # Setup static leases + + dhcp-option = [ + (mkDHCPOption "router" cfg.defaultGateway) # Set default gateway for clients + #(mkDHCPOption "ntp-server" cfg.defaultGateway) # Set ntp server for clients + (mkDHCPOption "dns-server" cfg.defaultGateway) # Set dns server for clients + (mkDHCPOption "domain-search" cfg.localDomain) # Add search rule to clients so they can resolve hostnames w/o the local domain suffix + ]; + + # Logging + #log-dhcp = true; # Log DHCP events + #log-queries = true; # Log DNS queries + + # Cache + cache-size = 1000; # Small cache limit is fine since Unbound does the heavy caching + }; + }; + + # Search localDomain so host can resolve short names + # This is eq. to dnsmasq's dhcp-option "domain-search" for clients, it just adds a search rule to resolv.conf + networking.search = [ cfg.localDomain ]; + }; +} diff --git a/nixos/unbound-blocklist.nix b/nixos/services/router/unbound-blocklist.nix index 153f2c0..153f2c0 100644 --- a/nixos/unbound-blocklist.nix +++ b/nixos/services/router/unbound-blocklist.nix |
