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