at 25.11-pre 10 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 { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOn} }; 77 listen-on-v6 { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} }; 78 allow-query-cache { cachenetworks; }; 79 blackhole { badnetworks; }; 80 forward ${cfg.forward}; 81 forwarders { ${lib.concatMapStrings (entry: " ${entry}; ") cfg.forwarders} }; 82 directory "${cfg.directory}"; 83 pid-file "/run/named/named.pid"; 84 ${cfg.extraOptions} 85 }; 86 87 ${cfg.extraConfig} 88 89 ${lib.concatMapStrings ( 90 { 91 name, 92 file, 93 master ? true, 94 slaves ? [ ], 95 masters ? [ ], 96 allowQuery ? [ ], 97 extraConfig ? "", 98 }: 99 '' 100 zone "${name}" { 101 type ${if master then "master" else "slave"}; 102 file "${file}"; 103 ${ 104 if master then 105 '' 106 allow-transfer { 107 ${lib.concatMapStrings (ip: "${ip};\n") slaves} 108 }; 109 '' 110 else 111 '' 112 masters { 113 ${lib.concatMapStrings (ip: "${ip};\n") masters} 114 }; 115 '' 116 } 117 allow-query { ${lib.concatMapStrings (ip: "${ip}; ") allowQuery}}; 118 ${extraConfig} 119 }; 120 '' 121 ) (lib.attrValues cfg.zones)} 122 ''; 123 124in 125 126{ 127 128 ###### interface 129 130 options = { 131 132 services.bind = { 133 134 enable = lib.mkEnableOption "BIND domain name server"; 135 136 package = lib.mkPackageOption pkgs "bind" { }; 137 138 cacheNetworks = lib.mkOption { 139 default = [ 140 "127.0.0.0/24" 141 "::1/128" 142 ]; 143 type = lib.types.listOf lib.types.str; 144 description = '' 145 What networks are allowed to use us as a resolver. Note 146 that this is for recursive queries -- all networks are 147 allowed to query zones configured with the `zones` option 148 by default (although this may be overridden within each 149 zone's configuration, via the `allowQuery` option). 150 It is recommended that you limit cacheNetworks to avoid your 151 server being used for DNS amplification attacks. 152 ''; 153 }; 154 155 blockedNetworks = lib.mkOption { 156 default = [ ]; 157 type = lib.types.listOf lib.types.str; 158 description = '' 159 What networks are just blocked. 160 ''; 161 }; 162 163 ipv4Only = lib.mkOption { 164 default = false; 165 type = lib.types.bool; 166 description = '' 167 Only use ipv4, even if the host supports ipv6. 168 ''; 169 }; 170 171 forwarders = lib.mkOption { 172 default = config.networking.nameservers; 173 defaultText = lib.literalExpression "config.networking.nameservers"; 174 type = lib.types.listOf lib.types.str; 175 description = '' 176 List of servers we should forward requests to. 177 ''; 178 }; 179 180 forward = lib.mkOption { 181 default = "first"; 182 type = lib.types.enum [ 183 "first" 184 "only" 185 ]; 186 description = '' 187 Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'. 188 ''; 189 }; 190 191 listenOn = lib.mkOption { 192 default = [ "any" ]; 193 type = lib.types.listOf lib.types.str; 194 description = '' 195 Interfaces to listen on. 196 ''; 197 }; 198 199 listenOnIpv6 = lib.mkOption { 200 default = [ "any" ]; 201 type = lib.types.listOf lib.types.str; 202 description = '' 203 Ipv6 interfaces to listen on. 204 ''; 205 }; 206 207 directory = lib.mkOption { 208 type = lib.types.str; 209 default = "/run/named"; 210 description = "Working directory of BIND."; 211 }; 212 213 zones = lib.mkOption { 214 default = [ ]; 215 type = 216 with lib.types; 217 coercedTo (listOf attrs) bindZoneCoerce (attrsOf (lib.types.submodule bindZoneOptions)); 218 description = '' 219 List of zones we claim authority over. 220 ''; 221 example = { 222 "example.com" = { 223 master = false; 224 file = "/var/dns/example.com"; 225 masters = [ "192.168.0.1" ]; 226 slaves = [ ]; 227 extraConfig = ""; 228 }; 229 }; 230 }; 231 232 extraConfig = lib.mkOption { 233 type = lib.types.lines; 234 default = ""; 235 description = '' 236 Extra lines to be added verbatim to the generated named configuration file. 237 ''; 238 }; 239 240 extraOptions = lib.mkOption { 241 type = lib.types.lines; 242 default = ""; 243 description = '' 244 Extra lines to be added verbatim to the options section of the 245 generated named configuration file. 246 ''; 247 }; 248 249 configFile = lib.mkOption { 250 type = lib.types.path; 251 default = confFile; 252 defaultText = lib.literalExpression "confFile"; 253 description = '' 254 Overridable config file to use for named. By default, that 255 generated by nixos. 256 ''; 257 }; 258 259 }; 260 261 }; 262 263 ###### implementation 264 265 config = lib.mkIf cfg.enable { 266 267 networking.resolvconf.useLocalResolver = lib.mkDefault true; 268 269 users.users.${bindUser} = { 270 group = bindUser; 271 description = "BIND daemon user"; 272 isSystemUser = true; 273 }; 274 users.groups.${bindUser} = { }; 275 276 systemd.tmpfiles.settings."bind" = lib.mkIf (cfg.directory != "/run/named") { 277 ${cfg.directory} = { 278 d = { 279 user = bindUser; 280 group = bindUser; 281 age = "-"; 282 }; 283 }; 284 }; 285 systemd.services.bind = { 286 description = "BIND Domain Name Server"; 287 after = [ "network.target" ]; 288 wantedBy = [ "multi-user.target" ]; 289 290 preStart = '' 291 if ! [ -f "/etc/bind/rndc.key" ]; then 292 ${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -a -A hmac-sha256 2>/dev/null 293 fi 294 ''; 295 296 serviceConfig = { 297 Type = "forking"; # Set type to forking, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788 298 ExecStart = "${bindPkg.out}/sbin/named ${lib.optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile}"; 299 ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload"; 300 ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop"; 301 User = bindUser; 302 RuntimeDirectory = "named"; 303 RuntimeDirectoryPreserve = "yes"; 304 ConfigurationDirectory = "bind"; 305 ReadWritePaths = [ 306 (lib.mapAttrsToList ( 307 name: config: if (lib.hasPrefix "/" config.file) then ("-${dirOf config.file}") else "" 308 ) cfg.zones) 309 cfg.directory 310 ]; 311 CapabilityBoundingSet = "CAP_NET_BIND_SERVICE"; 312 AmbientCapabilities = "CAP_NET_BIND_SERVICE"; 313 # Security 314 NoNewPrivileges = true; 315 # Sandboxing 316 ProtectSystem = "strict"; 317 ReadOnlyPaths = "/sys"; 318 ProtectHome = true; 319 PrivateTmp = true; 320 PrivateDevices = true; 321 PrivateMounts = true; 322 ProtectHostname = true; 323 ProtectClock = true; 324 ProtectKernelTunables = true; 325 ProtectKernelModules = true; 326 ProtectKernelLogs = true; 327 ProtectControlGroups = true; 328 ProtectProc = "invisible"; 329 ProcSubset = "pid"; 330 RemoveIPC = true; 331 RestrictAddressFamilies = [ "AF_UNIX AF_INET AF_INET6 AF_NETLINK" ]; 332 LockPersonality = true; 333 MemoryDenyWriteExecute = true; 334 RestrictRealtime = true; 335 RestrictSUIDSGID = true; 336 RestrictNamespaces = true; 337 # System Call Filtering 338 SystemCallArchitectures = "native"; 339 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"; 340 }; 341 342 unitConfig.Documentation = "man:named(8)"; 343 }; 344 }; 345}