1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.pdns-recursor;
7
8 oneOrMore = type: with types; either type (listOf type);
9 valueType = with types; oneOf [ int str bool path ];
10 configType = with types; attrsOf (nullOr (oneOrMore valueType));
11
12 toBool = val: if val then "yes" else "no";
13 serialize = val: with types;
14 if str.check val then val
15 else if int.check val then toString val
16 else if path.check val then toString val
17 else if bool.check val then toBool val
18 else if builtins.isList val then (concatMapStringsSep "," serialize val)
19 else "";
20
21 configDir = pkgs.writeTextDir "recursor.conf"
22 (concatStringsSep "\n"
23 (flip mapAttrsToList cfg.settings
24 (name: val: "${name}=${serialize val}")));
25
26 mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
27
28in {
29 options.services.pdns-recursor = {
30 enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
31
32 dns.address = mkOption {
33 type = oneOrMore types.str;
34 default = [ "::" "0.0.0.0" ];
35 description = ''
36 IP addresses Recursor DNS server will bind to.
37 '';
38 };
39
40 dns.port = mkOption {
41 type = types.port;
42 default = 53;
43 description = ''
44 Port number Recursor DNS server will bind to.
45 '';
46 };
47
48 dns.allowFrom = mkOption {
49 type = types.listOf types.str;
50 default = [
51 "127.0.0.0/8" "10.0.0.0/8" "100.64.0.0/10"
52 "169.254.0.0/16" "192.168.0.0/16" "172.16.0.0/12"
53 "::1/128" "fc00::/7" "fe80::/10"
54 ];
55 example = [ "0.0.0.0/0" "::/0" ];
56 description = ''
57 IP address ranges of clients allowed to make DNS queries.
58 '';
59 };
60
61 api.address = mkOption {
62 type = types.str;
63 default = "0.0.0.0";
64 description = ''
65 IP address Recursor REST API server will bind to.
66 '';
67 };
68
69 api.port = mkOption {
70 type = types.port;
71 default = 8082;
72 description = ''
73 Port number Recursor REST API server will bind to.
74 '';
75 };
76
77 api.allowFrom = mkOption {
78 type = types.listOf types.str;
79 default = [ "127.0.0.1" "::1" ];
80 example = [ "0.0.0.0/0" "::/0" ];
81 description = ''
82 IP address ranges of clients allowed to make API requests.
83 '';
84 };
85
86 exportHosts = mkOption {
87 type = types.bool;
88 default = false;
89 description = ''
90 Whether to export names and IP addresses defined in /etc/hosts.
91 '';
92 };
93
94 forwardZones = mkOption {
95 type = types.attrs;
96 default = {};
97 description = ''
98 DNS zones to be forwarded to other authoritative servers.
99 '';
100 };
101
102 forwardZonesRecurse = mkOption {
103 type = types.attrs;
104 example = { eth = "[::1]:5353"; };
105 default = {};
106 description = ''
107 DNS zones to be forwarded to other recursive servers.
108 '';
109 };
110
111 dnssecValidation = mkOption {
112 type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
113 default = "validate";
114 description = ''
115 Controls the level of DNSSEC processing done by the PowerDNS Recursor.
116 See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
117 '';
118 };
119
120 serveRFC1918 = mkOption {
121 type = types.bool;
122 default = true;
123 description = ''
124 Whether to directly resolve the RFC1918 reverse-mapping domains:
125 `10.in-addr.arpa`,
126 `168.192.in-addr.arpa`,
127 `16-31.172.in-addr.arpa`
128 This saves load on the AS112 servers.
129 '';
130 };
131
132 settings = mkOption {
133 type = configType;
134 default = { };
135 example = literalExpression ''
136 {
137 loglevel = 8;
138 log-common-errors = true;
139 }
140 '';
141 description = ''
142 PowerDNS Recursor settings. Use this option to configure Recursor
143 settings not exposed in a NixOS option or to bypass one.
144 See the full documentation at
145 <https://doc.powerdns.com/recursor/settings.html>
146 for the available options.
147 '';
148 };
149
150 luaConfig = mkOption {
151 type = types.lines;
152 default = "";
153 description = ''
154 The content Lua configuration file for PowerDNS Recursor. See
155 <https://doc.powerdns.com/recursor/lua-config/index.html>.
156 '';
157 };
158 };
159
160 config = mkIf cfg.enable {
161
162 environment.etc."pdns-recursor".source = configDir;
163
164 services.pdns-recursor.settings = mkDefaultAttrs {
165 local-address = cfg.dns.address;
166 local-port = cfg.dns.port;
167 allow-from = cfg.dns.allowFrom;
168
169 webserver-address = cfg.api.address;
170 webserver-port = cfg.api.port;
171 webserver-allow-from = cfg.api.allowFrom;
172
173 forward-zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
174 forward-zones-recurse = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZonesRecurse;
175 export-etc-hosts = cfg.exportHosts;
176 dnssec = cfg.dnssecValidation;
177 serve-rfc1918 = cfg.serveRFC1918;
178 lua-config-file = pkgs.writeText "recursor.lua" cfg.luaConfig;
179
180 daemon = false;
181 write-pid = false;
182 log-timestamp = false;
183 disable-syslog = true;
184 };
185
186 systemd.packages = [ pkgs.pdns-recursor ];
187
188 systemd.services.pdns-recursor = {
189 wantedBy = [ "multi-user.target" ];
190
191 serviceConfig = {
192 ExecStart = [ "" "${pkgs.pdns-recursor}/bin/pdns_recursor --config-dir=${configDir}" ];
193 };
194 };
195
196 users.users.pdns-recursor = {
197 isSystemUser = true;
198 group = "pdns-recursor";
199 description = "PowerDNS Recursor daemon user";
200 };
201
202 users.groups.pdns-recursor = {};
203
204 };
205
206 imports = [
207 (mkRemovedOptionModule [ "services" "pdns-recursor" "extraConfig" ]
208 "To change extra Recursor settings use services.pdns-recursor.settings instead.")
209 ];
210
211 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
212
213}