about summary refs log tree commit diff
path: root/modules/unbound.nix
diff options
context:
space:
mode:
Diffstat (limited to 'modules/unbound.nix')
-rw-r--r--modules/unbound.nix209
1 files changed, 150 insertions, 59 deletions
diff --git a/modules/unbound.nix b/modules/unbound.nix
index c38c25b..10807a9 100644
--- a/modules/unbound.nix
+++ b/modules/unbound.nix
@@ -18,6 +18,20 @@ in
       type = lib.types.str;
       default = config.networking.domain;
     };
+
+    zone = {
+      whitelist = lib.mkOption {
+        description = "List of domains that always will be allowed.";
+        type = with lib.types; listOf str;
+        default = [ ];
+      };
+
+      blacklist = lib.mkOption {
+        description = "List of domains that always will be denied.";
+        type = with lib.types; listOf str;
+        default = [ ];
+      };
+    };
   };
 
   config = lib.mkIf cfg.enable {
@@ -48,55 +62,70 @@ in
             ];
 
             local-zone =
+              let
+                mapAs = t: map (x: ''"${x}" ${t}'');
+              in
+              (
+                lib.my.configurations
+                |> lib.mapAttrsToList (x: _: [ ''"${x}.${cfg.domain}" redirect'' ])
+                |> lib.concatLists
+              )
+              ++ mapAs "always_transparent" cfg.zone.whitelist
+              ++ mapAs "always_nxdomain" cfg.zone.blacklist;
+            local-data =
               lib.my.configurations
-              |> lib.mapAttrsToList (x: _: [ ''"${x}.${cfg.domain}" redirect'' ])
-              |> lib.concatLists;
-            local-data = lib.concatLists (
-              lib.mapAttrsToList (
+              |> lib.mapAttrsToList (
                 hostname:
                 let
                   domain = "${hostname}.${cfg.domain}";
                 in
                 attr:
                 (lib.optionals (lib.hasAttr "wireguard" attr) (
-                  with attr.wireguard;
+                  let
+                    inherit (attr.wireguard) ipv4 ipv6;
+                  in
                   [
-                    "\"${domain} 604800 IN A ${ipv4.address}\""
-                    "\"${domain} 604800 IN AAAA ${ipv6.address}\""
-                    "\"${domain}. A ${ipv4.address}\""
-                    "\"${domain}. AAAA ${ipv6.address}\""
+                    ''"${domain} 604800 IN A ${ipv4.address}"''
+                    ''"${domain} 604800 IN AAAA ${ipv6.address}"''
+                    ''"${domain}. A ${ipv4.address}"''
+                    ''"${domain}. AAAA ${ipv6.address}"''
                   ]
                   ++ (lib.optionals (lib.hasAttr "domains" attr) (
-                    lib.concatMap (domain: [
-                      "\"${domain}. A ${ipv4.address}\""
-                      "\"${domain}. AAAA ${ipv6.address}\""
-                    ]) attr.domains
+                    attr.domains
+                    |> lib.concatMap (domain: [
+                      ''"${domain}. A ${ipv4.address}"''
+                      ''"${domain}. AAAA ${ipv6.address}"''
+                    ])
                   ))
                 ))
-              ) lib.my.configurations
-            );
-            local-data-ptr = lib.concatLists (
-              lib.mapAttrsToList (
+              )
+              |> lib.concatLists;
+            local-data-ptr =
+              lib.my.configurations
+              |> lib.mapAttrsToList (
                 hostname:
                 let
                   domain = "${hostname}.${cfg.domain}";
                 in
                 attr:
                 (lib.optionals (lib.hasAttr "wireguard" attr) (
-                  with attr.wireguard;
+                  let
+                    inherit (attr.wireguard) ipv4 ipv6;
+                  in
                   [
-                    "\"${ipv4.address} ${domain}\""
-                    "\"${ipv6.address} ${domain}\""
+                    ''"${ipv4.address} ${domain}"''
+                    ''"${ipv6.address} ${domain}"''
                   ]
                   ++ (lib.optionals (lib.hasAttr "domains" attr) (
-                    lib.concatMap (domain: [
-                      "\"${ipv4.address} ${domain}\""
-                      "\"${ipv6.address} ${domain}\""
-                    ]) attr.domains
+                    attr.domains
+                    |> lib.concatMap (domain: [
+                      ''"${ipv4.address} ${domain}"''
+                      ''"${ipv6.address} ${domain}"''
+                    ])
                   ))
                 ))
-              ) lib.my.configurations
-            );
+              )
+              |> lib.concatLists;
 
             private-domain = map (domain: "${domain}.") [
               cfg.domain
@@ -137,13 +166,11 @@ in
             verbosity = 1;
           };
 
-          forward-zone = [
-            {
-              name = ".";
-              forward-tls-upstream = true;
-              forward-addr = lib.dns.mkDoT lib.dns.const.quad9.ecs;
-            }
-          ];
+          forward-zone = {
+            name = ".";
+            forward-tls-upstream = true;
+            forward-addr = lib.dns.mkDoT lib.dns.const.quad9.ecs;
+          };
 
           cachedb = with config.services.redis.servers.default; {
             backend = "redis";
@@ -154,33 +181,83 @@ in
           dnstap = {
             dnstap-enable = true;
             dnstap-socket-path = "/run/dnstap-unbound/read.sock";
-            dnstap-send-identity = true;
-            dnstap-send-version = true;
-            dnstap-log-resolver-query-messages = true;
-            dnstap-log-resolver-response-messages = true;
-            dnstap-log-client-query-messages = true;
+            dnstap-send-identity = false;
+            dnstap-send-version = false;
+            dnstap-log-resolver-query-messages = false;
+            dnstap-log-resolver-response-messages = false;
+            dnstap-log-forwarder-query-messages = false;
+            dnstap-log-forwarder-response-messages = false;
+            dnstap-log-client-query-messages = false;
             dnstap-log-client-response-messages = true;
-            dnstap-log-forwarder-query-messages = true;
-            dnstap-log-forwarder-response-messages = true;
           };
 
-          rpz = [
-            {
-              name = "hagezi-ultimate";
-              zonefile = "hagezi-ultimate";
-              url = "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/ultimate.txt";
-            }
-            {
-              name = "big-osid";
-              zonefile = "big-osid";
-              url = "https://big.oisd.nl/rpz";
-            }
-            {
-              name = "nsfw-osid";
-              zonefile = "nsfw-osid";
-              url = "https://nsfw.oisd.nl/rpz";
-            }
-          ];
+          rpz =
+            let
+              # https://unbound.docs.nlnetlabs.nl/en/latest/topics/filtering/rpz.html
+              mkRpz =
+                {
+                  name,
+                  url ? null,
+                  zonefile ? null,
+                }:
+                {
+                  inherit name;
+                  zonefile = lib.mkIf (zonefile != null) zonefile;
+                  url = lib.mkIf (url != null) url;
+                  rpz-log = true;
+                  rpz-log-name = name;
+                };
+
+              # https://unbound.docs.nlnetlabs.nl/en/latest/manpages/unbound.conf.html#response-policy-zone-options
+              mkLocalZonefile =
+                {
+                  name,
+                  action,
+                  list,
+                }:
+                [
+                  ''
+                    $TTL 30
+                    @ SOA localhost. root.localhost. 1 43200 3600 86400 300
+                      NS  localhost.
+                  ''
+                ]
+                ++ (cfg.zone.${name} |> map (x: "${x} CNAME ${action}"))
+                |> lib.concatLines
+                |> pkgs.writeText "${name}.zone"
+                |> toString;
+            in
+            [
+              {
+                name = "whitelist";
+                zonefile = mkLocalZonefile {
+                  name = "whitelist";
+                  action = "rpz-passthru.";
+                  list = cfg.zone.whitelist;
+                };
+              }
+              {
+                name = "blacklist";
+                zonefile = mkLocalZonefile {
+                  name = "blacklist";
+                  action = ".";
+                  list = cfg.zone.whitelist;
+                };
+              }
+              {
+                name = "hagezi-ultimate";
+                url = "https://raw.githubusercontent.com/hagezi/dns-blocklists/main/rpz/ultimate.txt";
+              }
+              {
+                name = "big-osid";
+                url = "https://big.oisd.nl/rpz";
+              }
+              {
+                name = "nsfw-osid";
+                url = "https://nsfw.oisd.nl/rpz";
+              }
+            ]
+            |> map mkRpz;
         };
 
         enableRootTrustAnchor = true;
@@ -190,7 +267,7 @@ in
 
       prometheus.exporters.unbound = {
         enable = true;
-        listenAddress = lib.mkDefault this.wireguard.ipv4.address;
+        listenAddress = "127.0.0.1";
         port = 9167;
         inherit (config.services.unbound) group user;
         unbound.host = "unix://${config.services.unbound.localControlSocketPath}";
@@ -209,16 +286,30 @@ in
 
           dnstap-unbound = {
             serviceConfig = {
-              ExecStart = "${lib.getExe pkgs.dnstap} -u ${config.services.unbound.settings.dnstap.dnstap-socket-path}";
+              ExecStart = "${lib.getExe pkgs.dnstap} -j -u ${config.services.unbound.settings.dnstap.dnstap-socket-path}";
               User = config.services.unbound.user;
               Group = config.services.unbound.group;
               RuntimeDirectory = "dnstap-unbound";
             };
             wantedBy = [ "multi-user.target" ];
           };
+
+          alloy.reloadTriggers = [ config.environment.etc."alloy/unbound.alloy".source ];
         };
       };
 
+    environment.etc."alloy/unbound.alloy".text = with config.services.prometheus.exporters.unbound; ''
+      prometheus.scrape "unbound" {
+        targets = [
+          {
+            __address__ = "${listenAddress}:${toString port}",
+            instance    = "${config.networking.hostName}",
+          },
+        ]
+        forward_to = [prometheus.relabel.default.receiver]
+      }
+    '';
+
     boot.kernel.sysctl."net.ipv4.tcp_fastopen" = lib.mkOverride 200 3;
 
     topology.nodes.${this.hostname}.services.unbound = {

Consider giving Nix/NixOS a try! <3