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