at master 11 kB view raw
1{ 2 config, 3 lib, 4 pkgs, 5 ... 6}: 7let 8 9 cfg = config.services.bind; 10 11 bindPkg = config.services.bind.package; 12 13 bindUser = "named"; 14 15 bindZoneCoerce = 16 list: 17 builtins.listToAttrs ( 18 lib.forEach list (zone: { 19 name = zone.name; 20 value = zone; 21 }) 22 ); 23 24 bindZoneOptions = 25 { name, config, ... }: 26 { 27 options = { 28 name = lib.mkOption { 29 type = lib.types.str; 30 default = name; 31 description = "Name of the zone."; 32 }; 33 master = lib.mkOption { 34 description = "Master=false means slave server"; 35 type = lib.types.bool; 36 }; 37 file = lib.mkOption { 38 type = lib.types.either lib.types.str lib.types.path; 39 description = "Zone file resource records contain columns of data, separated by whitespace, that define the record."; 40 }; 41 masters = lib.mkOption { 42 type = lib.types.listOf lib.types.str; 43 description = "List of servers for inclusion in stub and secondary zones."; 44 }; 45 slaves = lib.mkOption { 46 type = lib.types.listOf lib.types.str; 47 description = "Addresses who may request zone transfers."; 48 default = [ ]; 49 }; 50 allowQuery = lib.mkOption { 51 type = lib.types.listOf lib.types.str; 52 description = '' 53 List of address ranges allowed to query this zone. Instead of the address(es), this may instead 54 contain the single string "any". 55 ''; 56 default = [ "any" ]; 57 }; 58 extraConfig = lib.mkOption { 59 type = lib.types.lines; 60 description = "Extra zone config to be appended at the end of the zone section."; 61 default = ""; 62 }; 63 }; 64 }; 65 66 confFile = pkgs.writeText "named.conf" '' 67 include "/etc/bind/rndc.key"; 68 controls { 69 inet 127.0.0.1 allow {localhost;} keys {"rndc-key";}; 70 }; 71 72 acl cachenetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} }; 73 acl badnetworks { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} }; 74 75 options { 76 listen-on port ${toString cfg.listenOnPort} { ${ 77 lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOn 78 } }; 79 listen-on-v6 port ${toString cfg.listenOnIpv6Port} { ${ 80 lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6 81 } }; 82 allow-query-cache { cachenetworks; }; 83 blackhole { badnetworks; }; 84 forward ${cfg.forward}; 85 forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.forwarders} }; 86 directory "${cfg.directory}"; 87 pid-file "/run/named/named.pid"; 88 ${cfg.extraOptions} 89 }; 90 91 ${cfg.extraConfig} 92 93 ${lib.concatMapStrings ( 94 { 95 name, 96 file, 97 master ? true, 98 slaves ? [ ], 99 masters ? [ ], 100 allowQuery ? [ ], 101 extraConfig ? "", 102 }: 103 '' 104 zone "${name}" { 105 type ${if master then "master" else "slave"}; 106 file "${file}"; 107 ${ 108 if master then 109 '' 110 allow-transfer { 111 ${lib.concatMapStrings (ip: "${ip};\n") slaves} 112 }; 113 '' 114 else 115 '' 116 masters { 117 ${lib.concatMapStrings (ip: "${ip};\n") masters} 118 }; 119 '' 120 } 121 allow-query { ${lib.concatMapStrings (ip: "${ip}; ") allowQuery}}; 122 ${extraConfig} 123 }; 124 '' 125 ) (lib.attrValues cfg.zones)} 126 ''; 127 128in 129 130{ 131 132 ###### interface 133 134 options = { 135 136 services.bind = { 137 138 enable = lib.mkEnableOption "BIND domain name server"; 139 140 package = lib.mkPackageOption pkgs "bind" { }; 141 142 cacheNetworks = lib.mkOption { 143 default = [ 144 "127.0.0.0/24" 145 "::1/128" 146 ]; 147 type = lib.types.listOf lib.types.str; 148 description = '' 149 What networks are allowed to use us as a resolver. Note 150 that this is for recursive queries -- all networks are 151 allowed to query zones configured with the `zones` option 152 by default (although this may be overridden within each 153 zone's configuration, via the `allowQuery` option). 154 It is recommended that you limit cacheNetworks to avoid your 155 server being used for DNS amplification attacks. 156 ''; 157 }; 158 159 blockedNetworks = lib.mkOption { 160 default = [ ]; 161 type = lib.types.listOf lib.types.str; 162 description = '' 163 What networks are just blocked. 164 ''; 165 }; 166 167 ipv4Only = lib.mkOption { 168 default = false; 169 type = lib.types.bool; 170 description = '' 171 Only use ipv4, even if the host supports ipv6. 172 ''; 173 }; 174 175 forwarders = lib.mkOption { 176 default = config.networking.nameservers; 177 defaultText = lib.literalExpression "config.networking.nameservers"; 178 type = lib.types.listOf lib.types.str; 179 description = '' 180 List of servers we should forward requests to. 181 ''; 182 }; 183 184 forward = lib.mkOption { 185 default = "first"; 186 type = lib.types.enum [ 187 "first" 188 "only" 189 ]; 190 description = '' 191 Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'. 192 ''; 193 }; 194 195 listenOn = lib.mkOption { 196 default = [ "any" ]; 197 type = lib.types.listOf lib.types.str; 198 description = '' 199 Interfaces to listen on. 200 ''; 201 }; 202 203 listenOnPort = lib.mkOption { 204 default = 53; 205 type = lib.types.port; 206 description = '' 207 Port to listen on. 208 ''; 209 }; 210 211 listenOnIpv6 = lib.mkOption { 212 default = [ "any" ]; 213 type = lib.types.listOf lib.types.str; 214 description = '' 215 Ipv6 interfaces to listen on. 216 ''; 217 }; 218 219 listenOnIpv6Port = lib.mkOption { 220 default = 53; 221 type = lib.types.port; 222 description = '' 223 Ipv6 port to listen on. 224 ''; 225 }; 226 227 directory = lib.mkOption { 228 type = lib.types.str; 229 default = "/run/named"; 230 description = "Working directory of BIND."; 231 }; 232 233 zones = lib.mkOption { 234 default = [ ]; 235 type = 236 with lib.types; 237 coercedTo (listOf attrs) bindZoneCoerce (attrsOf (lib.types.submodule bindZoneOptions)); 238 description = '' 239 List of zones we claim authority over. 240 ''; 241 example = { 242 "example.com" = { 243 master = false; 244 file = "/var/dns/example.com"; 245 masters = [ "192.168.0.1" ]; 246 slaves = [ ]; 247 extraConfig = ""; 248 }; 249 }; 250 }; 251 252 extraConfig = lib.mkOption { 253 type = lib.types.lines; 254 default = ""; 255 description = '' 256 Extra lines to be added verbatim to the generated named configuration file. 257 ''; 258 }; 259 260 extraOptions = lib.mkOption { 261 type = lib.types.lines; 262 default = ""; 263 description = '' 264 Extra lines to be added verbatim to the options section of the 265 generated named configuration file. 266 ''; 267 }; 268 269 extraArgs = lib.mkOption { 270 type = lib.types.listOf lib.types.str; 271 default = [ ]; 272 description = '' 273 Additional command-line arguments to pass to named. 274 ''; 275 example = [ 276 "-n" 277 "4" 278 ]; 279 }; 280 281 configFile = lib.mkOption { 282 type = lib.types.path; 283 default = confFile; 284 defaultText = lib.literalExpression "confFile"; 285 description = '' 286 Overridable config file to use for named. By default, that 287 generated by nixos. 288 ''; 289 }; 290 291 }; 292 293 }; 294 295 ###### implementation 296 297 config = lib.mkIf cfg.enable { 298 299 networking.resolvconf.useLocalResolver = lib.mkDefault true; 300 301 users.users.${bindUser} = { 302 group = bindUser; 303 description = "BIND daemon user"; 304 isSystemUser = true; 305 }; 306 users.groups.${bindUser} = { }; 307 308 systemd.tmpfiles.settings."bind" = lib.mkIf (cfg.directory != "/run/named") { 309 ${cfg.directory} = { 310 d = { 311 user = bindUser; 312 group = bindUser; 313 age = "-"; 314 }; 315 }; 316 }; 317 systemd.services.bind = { 318 description = "BIND Domain Name Server"; 319 after = [ "network.target" ]; 320 wantedBy = [ "multi-user.target" ]; 321 322 preStart = '' 323 if ! [ -f "/etc/bind/rndc.key" ]; then 324 ${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -a -A hmac-sha256 2>/dev/null 325 fi 326 ''; 327 328 serviceConfig = { 329 Type = "forking"; # Set type to forking, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788 330 ExecStart = "${bindPkg.out}/sbin/named ${lib.optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile} ${lib.concatStringsSep " " cfg.extraArgs}"; 331 ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload"; 332 ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop"; 333 User = bindUser; 334 RuntimeDirectory = "named"; 335 RuntimeDirectoryPreserve = "yes"; 336 ConfigurationDirectory = "bind"; 337 ReadWritePaths = [ 338 (lib.mapAttrsToList ( 339 name: config: if (lib.hasPrefix "/" config.file) then ("-${dirOf config.file}") else "" 340 ) cfg.zones) 341 cfg.directory 342 ]; 343 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; 344 AmbientCapabilities = "CAP_NET_BIND_SERVICE"; 345 # Security 346 NoNewPrivileges = true; 347 # Sandboxing 348 ProtectSystem = "strict"; 349 ReadOnlyPaths = "/sys"; 350 ProtectHome = true; 351 PrivateTmp = true; 352 PrivateDevices = true; 353 PrivateMounts = true; 354 ProtectHostname = true; 355 ProtectClock = true; 356 ProtectKernelTunables = true; 357 ProtectKernelModules = true; 358 ProtectKernelLogs = true; 359 ProtectControlGroups = true; 360 ProtectProc = "invisible"; 361 ProcSubset = "pid"; 362 RemoveIPC = true; 363 RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6 AF_NETLINK" ]; 364 LockPersonality = true; 365 MemoryDenyWriteExecute = true; 366 RestrictRealtime = true; 367 RestrictSUIDSGID = true; 368 RestrictNamespaces = true; 369 # System Call Filtering 370 SystemCallArchitectures = "native"; 371 SystemCallFilter = "~@mount @debug @clock @reboot @resources @privileged @obsolete acct modify_ldt add_key adjtimex clock_adjtime delete_module fanotify_init finit_module get_mempolicy init_module io_destroy io_getevents iopl ioperm io_setup io_submit io_cancel kcmp kexec_load keyctl lookup_dcookie migrate_pages move_pages open_by_handle_at perf_event_open process_vm_readv process_vm_writev ptrace remap_file_pages request_key set_mempolicy swapoff swapon uselib vmsplice"; 372 }; 373 374 unitConfig.Documentation = "man:named(8)"; 375 }; 376 }; 377}