1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 dataDir = "/var/lib/pdns-recursor";
7 username = "pdns-recursor";
8
9 cfg = config.services.pdns-recursor;
10 zones = mapAttrsToList (zone: uri: "${zone}.=${uri}") cfg.forwardZones;
11
12 configFile = pkgs.writeText "recursor.conf" ''
13 local-address=${cfg.dns.address}
14 local-port=${toString cfg.dns.port}
15 allow-from=${concatStringsSep "," cfg.dns.allowFrom}
16
17 webserver-address=${cfg.api.address}
18 webserver-port=${toString cfg.api.port}
19 webserver-allow-from=${concatStringsSep "," cfg.api.allowFrom}
20
21 forward-zones=${concatStringsSep "," zones}
22 export-etc-hosts=${if cfg.exportHosts then "yes" else "no"}
23 dnssec=${cfg.dnssecValidation}
24 serve-rfc1918=${if cfg.serveRFC1918 then "yes" else "no"}
25
26 ${cfg.extraConfig}
27 '';
28
29in {
30 options.services.pdns-recursor = {
31 enable = mkEnableOption "PowerDNS Recursor, a recursive DNS server";
32
33 dns.address = mkOption {
34 type = types.str;
35 default = "0.0.0.0";
36 description = ''
37 IP address Recursor DNS server will bind to.
38 '';
39 };
40
41 dns.port = mkOption {
42 type = types.int;
43 default = 53;
44 description = ''
45 Port number Recursor DNS server will bind to.
46 '';
47 };
48
49 dns.allowFrom = mkOption {
50 type = types.listOf types.str;
51 default = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/16" ];
52 example = [ "0.0.0.0/0" ];
53 description = ''
54 IP address ranges of clients allowed to make DNS queries.
55 '';
56 };
57
58 api.address = mkOption {
59 type = types.str;
60 default = "0.0.0.0";
61 description = ''
62 IP address Recursor REST API server will bind to.
63 '';
64 };
65
66 api.port = mkOption {
67 type = types.int;
68 default = 8082;
69 description = ''
70 Port number Recursor REST API server will bind to.
71 '';
72 };
73
74 api.allowFrom = mkOption {
75 type = types.listOf types.str;
76 default = [ "0.0.0.0/0" ];
77 description = ''
78 IP address ranges of clients allowed to make API requests.
79 '';
80 };
81
82 exportHosts = mkOption {
83 type = types.bool;
84 default = false;
85 description = ''
86 Whether to export names and IP addresses defined in /etc/hosts.
87 '';
88 };
89
90 forwardZones = mkOption {
91 type = types.attrs;
92 example = { eth = "127.0.0.1:5353"; };
93 default = {};
94 description = ''
95 DNS zones to be forwarded to other servers.
96 '';
97 };
98
99 dnssecValidation = mkOption {
100 type = types.enum ["off" "process-no-validate" "process" "log-fail" "validate"];
101 default = "validate";
102 description = ''
103 Controls the level of DNSSEC processing done by the PowerDNS Recursor.
104 See https://doc.powerdns.com/md/recursor/dnssec/ for a detailed explanation.
105 '';
106 };
107
108 serveRFC1918 = mkOption {
109 type = types.bool;
110 default = true;
111 description = ''
112 Whether to directly resolve the RFC1918 reverse-mapping domains:
113 <literal>10.in-addr.arpa</literal>,
114 <literal>168.192.in-addr.arpa</literal>,
115 <literal>16-31.172.in-addr.arpa</literal>
116 This saves load on the AS112 servers.
117 '';
118 };
119
120 extraConfig = mkOption {
121 type = types.lines;
122 default = "";
123 description = ''
124 Extra options to be appended to the configuration file.
125 '';
126 };
127 };
128
129 config = mkIf cfg.enable {
130
131 users.extraUsers."${username}" = {
132 home = dataDir;
133 createHome = true;
134 uid = config.ids.uids.pdns-recursor;
135 description = "PowerDNS Recursor daemon user";
136 };
137
138 systemd.services.pdns-recursor = {
139 unitConfig.Documentation = "man:pdns_recursor(1) man:rec_control(1)";
140 description = "PowerDNS recursive server";
141 wantedBy = [ "multi-user.target" ];
142 after = [ "network.target" ];
143
144 serviceConfig = {
145 User = username;
146 Restart ="on-failure";
147 RestartSec = "5";
148 PrivateTmp = true;
149 PrivateDevices = true;
150 AmbientCapabilities = "cap_net_bind_service";
151 ExecStart = ''${pkgs.pdns-recursor}/bin/pdns_recursor \
152 --config-dir=${dataDir} \
153 --socket-dir=${dataDir} \
154 --disable-syslog
155 '';
156 };
157
158 preStart = ''
159 # Link configuration file into recursor home directory
160 configPath=${dataDir}/recursor.conf
161 if [ "$(realpath $configPath)" != "${configFile}" ]; then
162 rm -f $configPath
163 ln -s ${configFile} $configPath
164 fi
165 '';
166 };
167 };
168}