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