nixpkgs/nixos/modules/services/networking/bind.nix
Luflosi 8e945401d5
bind: make systemd service wait for BIND to be ready
Without this change, the systemd unit will be marked as ready even though BIND has not finished starting yet.
This causes other units that depend on BIND to start even though BIND is not ready yet.
From https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788: "Bind9 will daemonize itself _when it is ready_."

Also modify the NixOS test. With this change, waiting for the unit alone will ensure that BIND is ready to accept queries. I would have expected to see the test failing without this commit but with the `machine.wait_for_open_port(53)` line removed but I found this to not be the case most of the time. This is probably the case because the situation is inherently racy and on my machine BIND happens to start in time most of the time.
2024-05-31 13:33:29 +02:00

284 lines
8.1 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.bind;
bindPkg = config.services.bind.package;
bindUser = "named";
bindZoneCoerce = list: builtins.listToAttrs (lib.forEach list (zone: { name = zone.name; value = zone; }));
bindZoneOptions = { name, config, ... }: {
options = {
name = mkOption {
type = types.str;
default = name;
description = "Name of the zone.";
};
master = mkOption {
description = "Master=false means slave server";
type = types.bool;
};
file = mkOption {
type = types.either types.str types.path;
description = "Zone file resource records contain columns of data, separated by whitespace, that define the record.";
};
masters = mkOption {
type = types.listOf types.str;
description = "List of servers for inclusion in stub and secondary zones.";
};
slaves = mkOption {
type = types.listOf types.str;
description = "Addresses who may request zone transfers.";
default = [ ];
};
allowQuery = mkOption {
type = types.listOf types.str;
description = ''
List of address ranges allowed to query this zone. Instead of the address(es), this may instead
contain the single string "any".
NOTE: This overrides the global-level `allow-query` setting, which is set to the contents
of `cachenetworks`.
'';
default = [ "any" ];
};
extraConfig = mkOption {
type = types.str;
description = "Extra zone config to be appended at the end of the zone section.";
default = "";
};
};
};
confFile = pkgs.writeText "named.conf"
''
include "/etc/bind/rndc.key";
controls {
inet 127.0.0.1 allow {localhost;} keys {"rndc-key";};
};
acl cachenetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.cacheNetworks} };
acl badnetworks { ${concatMapStrings (entry: " ${entry}; ") cfg.blockedNetworks} };
options {
listen-on { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOn} };
listen-on-v6 { ${concatMapStrings (entry: " ${entry}; ") cfg.listenOnIpv6} };
allow-query { cachenetworks; };
blackhole { badnetworks; };
forward ${cfg.forward};
forwarders { ${concatMapStrings (entry: " ${entry}; ") cfg.forwarders} };
directory "${cfg.directory}";
pid-file "/run/named/named.pid";
${cfg.extraOptions}
};
${cfg.extraConfig}
${ concatMapStrings
({ name, file, master ? true, slaves ? [], masters ? [], allowQuery ? [], extraConfig ? "" }:
''
zone "${name}" {
type ${if master then "master" else "slave"};
file "${file}";
${ if master then
''
allow-transfer {
${concatMapStrings (ip: "${ip};\n") slaves}
};
''
else
''
masters {
${concatMapStrings (ip: "${ip};\n") masters}
};
''
}
allow-query { ${concatMapStrings (ip: "${ip}; ") allowQuery}};
${extraConfig}
};
'')
(attrValues cfg.zones) }
'';
in
{
###### interface
options = {
services.bind = {
enable = mkEnableOption "BIND domain name server";
package = mkPackageOption pkgs "bind" { };
cacheNetworks = mkOption {
default = [ "127.0.0.0/24" "::1/128" ];
type = types.listOf types.str;
description = ''
What networks are allowed to use us as a resolver. Note
that this is for recursive queries -- all networks are
allowed to query zones configured with the `zones` option
by default (although this may be overridden within each
zone's configuration, via the `allowQuery` option).
It is recommended that you limit cacheNetworks to avoid your
server being used for DNS amplification attacks.
'';
};
blockedNetworks = mkOption {
default = [ ];
type = types.listOf types.str;
description = ''
What networks are just blocked.
'';
};
ipv4Only = mkOption {
default = false;
type = types.bool;
description = ''
Only use ipv4, even if the host supports ipv6.
'';
};
forwarders = mkOption {
default = config.networking.nameservers;
defaultText = literalExpression "config.networking.nameservers";
type = types.listOf types.str;
description = ''
List of servers we should forward requests to.
'';
};
forward = mkOption {
default = "first";
type = types.enum ["first" "only"];
description = ''
Whether to forward 'first' (try forwarding but lookup directly if forwarding fails) or 'only'.
'';
};
listenOn = mkOption {
default = [ "any" ];
type = types.listOf types.str;
description = ''
Interfaces to listen on.
'';
};
listenOnIpv6 = mkOption {
default = [ "any" ];
type = types.listOf types.str;
description = ''
Ipv6 interfaces to listen on.
'';
};
directory = mkOption {
type = types.str;
default = "/run/named";
description = "Working directory of BIND.";
};
zones = mkOption {
default = [ ];
type = with types; coercedTo (listOf attrs) bindZoneCoerce (attrsOf (types.submodule bindZoneOptions));
description = ''
List of zones we claim authority over.
'';
example = {
"example.com" = {
master = false;
file = "/var/dns/example.com";
masters = [ "192.168.0.1" ];
slaves = [ ];
extraConfig = "";
};
};
};
extraConfig = mkOption {
type = types.lines;
default = "";
description = ''
Extra lines to be added verbatim to the generated named configuration file.
'';
};
extraOptions = mkOption {
type = types.lines;
default = "";
description = ''
Extra lines to be added verbatim to the options section of the
generated named configuration file.
'';
};
configFile = mkOption {
type = types.path;
default = confFile;
defaultText = literalExpression "confFile";
description = ''
Overridable config file to use for named. By default, that
generated by nixos.
'';
};
};
};
###### implementation
config = mkIf cfg.enable {
networking.resolvconf.useLocalResolver = mkDefault true;
users.users.${bindUser} =
{
group = bindUser;
description = "BIND daemon user";
isSystemUser = true;
};
users.groups.${bindUser} = {};
systemd.services.bind = {
description = "BIND Domain Name Server";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
preStart = ''
mkdir -m 0755 -p /etc/bind
if ! [ -f "/etc/bind/rndc.key" ]; then
${bindPkg.out}/sbin/rndc-confgen -c /etc/bind/rndc.key -u ${bindUser} -a -A hmac-sha256 2>/dev/null
fi
${pkgs.coreutils}/bin/mkdir -p /run/named
chown ${bindUser} /run/named
${pkgs.coreutils}/bin/mkdir -p ${cfg.directory}
chown ${bindUser} ${cfg.directory}
'';
serviceConfig = {
Type = "forking"; # Set type to forking, see https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=900788
ExecStart = "${bindPkg.out}/sbin/named -u ${bindUser} ${optionalString cfg.ipv4Only "-4"} -c ${cfg.configFile}";
ExecReload = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' reload";
ExecStop = "${bindPkg.out}/sbin/rndc -k '/etc/bind/rndc.key' stop";
};
unitConfig.Documentation = "man:named(8)";
};
};
}