at 23.11-pre 8.5 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 = lib.mdDoc "Name of the zone."; 21 }; 22 master = mkOption { 23 description = lib.mdDoc "Master=false means slave server"; 24 type = types.bool; 25 }; 26 file = mkOption { 27 type = types.either types.str types.path; 28 description = lib.mdDoc "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 = lib.mdDoc "List of servers for inclusion in stub and secondary zones."; 33 }; 34 slaves = mkOption { 35 type = types.listOf types.str; 36 description = lib.mdDoc "Addresses who may request zone transfers."; 37 default = [ ]; 38 }; 39 allowQuery = mkOption { 40 type = types.listOf types.str; 41 description = lib.mdDoc '' 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 = lib.mdDoc "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 (lib.mdDoc "BIND domain name server"); 119 120 121 package = mkOption { 122 type = types.package; 123 default = pkgs.bind; 124 defaultText = literalExpression "pkgs.bind"; 125 description = lib.mdDoc "The BIND package to use."; 126 }; 127 128 cacheNetworks = mkOption { 129 default = [ "127.0.0.0/24" ]; 130 type = types.listOf types.str; 131 description = lib.mdDoc '' 132 What networks are allowed to use us as a resolver. Note 133 that this is for recursive queries -- all networks are 134 allowed to query zones configured with the `zones` option 135 by default (although this may be overridden within each 136 zone's configuration, via the `allowQuery` option). 137 It is recommended that you limit cacheNetworks to avoid your 138 server being used for DNS amplification attacks. 139 ''; 140 }; 141 142 blockedNetworks = mkOption { 143 default = [ ]; 144 type = types.listOf types.str; 145 description = lib.mdDoc '' 146 What networks are just blocked. 147 ''; 148 }; 149 150 ipv4Only = mkOption { 151 default = false; 152 type = types.bool; 153 description = lib.mdDoc '' 154 Only use ipv4, even if the host supports ipv6. 155 ''; 156 }; 157 158 forwarders = mkOption { 159 default = config.networking.nameservers; 160 defaultText = literalExpression "config.networking.nameservers"; 161 type = types.listOf types.str; 162 description = lib.mdDoc '' 163 List of servers we should forward requests to. 164 ''; 165 }; 166 167 forward = mkOption { 168 default = "first"; 169 type = types.enum ["first" "only"]; 170 description = lib.mdDoc '' 171 Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'. 172 ''; 173 }; 174 175 listenOn = mkOption { 176 default = [ "any" ]; 177 type = types.listOf types.str; 178 description = lib.mdDoc '' 179 Interfaces to listen on. 180 ''; 181 }; 182 183 listenOnIpv6 = mkOption { 184 default = [ "any" ]; 185 type = types.listOf types.str; 186 description = lib.mdDoc '' 187 Ipv6 interfaces to listen on. 188 ''; 189 }; 190 191 directory = mkOption { 192 type = types.str; 193 default = "/run/named"; 194 description = lib.mdDoc "Working directory of BIND."; 195 }; 196 197 zones = mkOption { 198 default = [ ]; 199 type = with types; coercedTo (listOf attrs) bindZoneCoerce (attrsOf (types.submodule bindZoneOptions)); 200 description = lib.mdDoc '' 201 List of zones we claim authority over. 202 ''; 203 example = { 204 "example.com" = { 205 master = false; 206 file = "/var/dns/example.com"; 207 masters = [ "192.168.0.1" ]; 208 slaves = [ ]; 209 extraConfig = ""; 210 }; 211 }; 212 }; 213 214 extraConfig = mkOption { 215 type = types.lines; 216 default = ""; 217 description = lib.mdDoc '' 218 Extra lines to be added verbatim to the generated named configuration file. 219 ''; 220 }; 221 222 extraOptions = mkOption { 223 type = types.lines; 224 default = ""; 225 description = lib.mdDoc '' 226 Extra lines to be added verbatim to the options section of the 227 generated named configuration file. 228 ''; 229 }; 230 231 configFile = mkOption { 232 type = types.path; 233 default = confFile; 234 defaultText = literalExpression "confFile"; 235 description = lib.mdDoc '' 236 Overridable config file to use for named. By default, that 237 generated by nixos. 238 ''; 239 }; 240 241 }; 242 243 }; 244 245 246 ###### implementation 247 248 config = mkIf cfg.enable { 249 250 networking.resolvconf.useLocalResolver = mkDefault true; 251 252 users.users.${bindUser} = 253 { 254 group = bindUser; 255 description = "BIND daemon user"; 256 isSystemUser = true; 257 }; 258 users.groups.${bindUser} = {}; 259 260 systemd.services.bind = { 261 description = "BIND Domain Name Server"; 262 after = [ "network.target" ]; 263 wantedBy = [ "multi-user.target" ]; 264 265 preStart = '' 266 mkdir -m 0755 -p /etc/bind 267 if ! [ -f "/etc/bind/rndc.key" ]; then 268 ${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null 269 fi 270 271 ${pkgs.coreutils}/bin/mkdir -p /run/named 272 chown ${bindUser} /run/named 273 274 ${pkgs.coreutils}/bin/mkdir -p ${cfg.directory} 275 chown ${bindUser} ${cfg.directory} 276 ''; 277 278 serviceConfig = { 279 ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} -f"; 280 ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload"; 281 ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop"; 282 }; 283 284 unitConfig.Documentation = "man:named(8)"; 285 }; 286 }; 287}