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 (lib.mdDoc "PowerDNS Recursor, a recursive DNS server");
31
32 dns.address = mkOption {
33 type = oneOrMore types.str;
34 default = [ "::" "0.0.0.0" ];
35 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 = lib.mdDoc ''
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 services.pdns-recursor.settings = mkDefaultAttrs {
163 local-address = cfg.dns.address;
164 local-port = cfg.dns.port;
165 allow-from = cfg.dns.allowFrom;
166
167 webserver-address = cfg.api.address;
168 webserver-port = cfg.api.port;
169 webserver-allow-from = cfg.api.allowFrom;
170
171 forward-zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
172 forward-zones-recurse = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZonesRecurse;
173 export-etc-hosts = cfg.exportHosts;
174 dnssec = cfg.dnssecValidation;
175 serve-rfc1918 = cfg.serveRFC1918;
176 lua-config-file = pkgs.writeText "recursor.lua" cfg.luaConfig;
177
178 daemon = false;
179 write-pid = false;
180 log-timestamp = false;
181 disable-syslog = true;
182 };
183
184 systemd.packages = [ pkgs.pdns-recursor ];
185
186 systemd.services.pdns-recursor = {
187 wantedBy = [ "multi-user.target" ];
188
189 serviceConfig = {
190 ExecStart = [ "" "${pkgs.pdns-recursor}/bin/pdns_recursor --config-dir=${configDir}" ];
191 };
192 };
193
194 users.users.pdns-recursor = {
195 isSystemUser = true;
196 group = "pdns-recursor";
197 description = "PowerDNS Recursor daemon user";
198 };
199
200 users.groups.pdns-recursor = {};
201
202 };
203
204 imports = [
205 (mkRemovedOptionModule [ "services" "pdns-recursor" "extraConfig" ]
206 "To change extra Recursor settings use services.pdns-recursor.settings instead.")
207 ];
208
209 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
210
211}