1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.bind;
8
9 bindUser = "named";
10
11 bindZoneCoerce = list: builtins.listToAttrs (lib.forEach list (zone: { name = zone.name; value = zone; }));
12
13 bindZoneOptions = { name, config, ... }: {
14 options = {
15 name = mkOption {
16 type = types.str;
17 default = name;
18 description = "Name of the zone.";
19 };
20 master = mkOption {
21 description = "Master=false means slave server";
22 type = types.bool;
23 };
24 file = mkOption {
25 type = types.either types.str types.path;
26 description = "Zone file resource records contain columns of data, separated by whitespace, that define the record.";
27 };
28 masters = mkOption {
29 type = types.listOf types.str;
30 description = "List of servers for inclusion in stub and secondary zones.";
31 };
32 slaves = mkOption {
33 type = types.listOf types.str;
34 description = "Addresses who may request zone transfers.";
35 default = [ ];
36 };
37 extraConfig = mkOption {
38 type = types.str;
39 description = "Extra zone config to be appended at the end of the zone section.";
40 default = "";
41 };
42 };
43 };
44
45 confFile = pkgs.writeText "named.conf"
46 ''
47 include "/etc/bind/rndc.key";
48 controls {
49 inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
50 };
51
52 acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
53 acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
54
55 options {
56 listen-on { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
57 listen-on-v6 { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
58 allow-query { cachenetworks; };
59 blackhole { badnetworks; };
60 forward first;
61 forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
62 directory "/run/named";
63 pid-file "/run/named/named.pid";
64 ${cfg.extraOptions}
65 };
66
67 ${cfg.extraConfig}
68
69 ${ concatMapStrings
70 ({ name, file, master ? true, slaves ? [], masters ? [], extraConfig ? "" }:
71 ''
72 zone "${name}" {
73 type ${if master then "master" else "slave"};
74 file "${file}";
75 ${ if master then
76 ''
77 allow-transfer {
78 ${concatMapStrings (ip: "${ip};\n") slaves}
79 };
80 ''
81 else
82 ''
83 masters {
84 ${concatMapStrings (ip: "${ip};\n") masters}
85 };
86 ''
87 }
88 allow-query { any; };
89 ${extraConfig}
90 };
91 '')
92 (attrValues cfg.zones) }
93 '';
94
95in
96
97{
98
99 ###### interface
100
101 options = {
102
103 services.bind = {
104
105 enable = mkEnableOption "BIND domain name server";
106
107 cacheNetworks = mkOption {
108 default = [ "127.0.0.0/24" ];
109 type = types.listOf types.str;
110 description = "
111 What networks are allowed to use us as a resolver. Note
112 that this is for recursive queries -- all networks are
113 allowed to query zones configured with the `zones` option.
114 It is recommended that you limit cacheNetworks to avoid your
115 server being used for DNS amplification attacks.
116 ";
117 };
118
119 blockedNetworks = mkOption {
120 default = [ ];
121 type = types.listOf types.str;
122 description = "
123 What networks are just blocked.
124 ";
125 };
126
127 ipv4Only = mkOption {
128 default = false;
129 type = types.bool;
130 description = "
131 Only use ipv4, even if the host supports ipv6.
132 ";
133 };
134
135 forwarders = mkOption {
136 default = config.networking.nameservers;
137 type = types.listOf types.str;
138 description = "
139 List of servers we should forward requests to.
140 ";
141 };
142
143 listenOn = mkOption {
144 default = [ "any" ];
145 type = types.listOf types.str;
146 description = "
147 Interfaces to listen on.
148 ";
149 };
150
151 listenOnIpv6 = mkOption {
152 default = [ "any" ];
153 type = types.listOf types.str;
154 description = "
155 Ipv6 interfaces to listen on.
156 ";
157 };
158
159 zones = mkOption {
160 default = [ ];
161 type = with types; coercedTo (listOf attrs) bindZoneCoerce (attrsOf (types.submodule bindZoneOptions));
162 description = "
163 List of zones we claim authority over.
164 ";
165 example = {
166 "example.com" = {
167 master = false;
168 file = "/var/dns/example.com";
169 masters = [ "192.168.0.1" ];
170 slaves = [ ];
171 extraConfig = "";
172 };
173 };
174 };
175
176 extraConfig = mkOption {
177 type = types.lines;
178 default = "";
179 description = "
180 Extra lines to be added verbatim to the generated named configuration file.
181 ";
182 };
183
184 extraOptions = mkOption {
185 type = types.lines;
186 default = "";
187 description = ''
188 Extra lines to be added verbatim to the options section of the
189 generated named configuration file.
190 '';
191 };
192
193 configFile = mkOption {
194 type = types.path;
195 default = confFile;
196 defaultText = "confFile";
197 description = "
198 Overridable config file to use for named. By default, that
199 generated by nixos.
200 ";
201 };
202
203 };
204
205 };
206
207
208 ###### implementation
209
210 config = mkIf cfg.enable {
211
212 networking.resolvconf.useLocalResolver = mkDefault true;
213
214 users.users.${bindUser} =
215 {
216 uid = config.ids.uids.bind;
217 description = "BIND daemon user";
218 };
219
220 systemd.services.bind = {
221 description = "BIND Domain Name Server";
222 after = [ "network.target" ];
223 wantedBy = [ "multi-user.target" ];
224
225 preStart = ''
226 mkdir -m 0755 -p /etc/bind
227 if ! [ -f "/etc/bind/rndc.key" ]; then
228 ${pkgs.bind.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
229 fi
230
231 ${pkgs.coreutils}/bin/mkdir -p /run/named
232 chown ${bindUser} /run/named
233 '';
234
235 serviceConfig = {
236 ExecStart = "${pkgs.bind.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f";
237 ExecReload = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
238 ExecStop = "${pkgs.bind.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
239 };
240
241 unitConfig.Documentation = "man:named(8)";
242 };
243 };
244}