diff options
Diffstat (limited to 'nixos/modules/services/networking/nsd.nix')
-rw-r--r-- | nixos/modules/services/networking/nsd.nix | 687 |
1 files changed, 338 insertions, 349 deletions
diff --git a/nixos/modules/services/networking/nsd.nix b/nixos/modules/services/networking/nsd.nix index db8cb122871..36d9f5d2f16 100644 --- a/nixos/modules/services/networking/nsd.nix +++ b/nixos/modules/services/networking/nsd.nix @@ -9,17 +9,25 @@ let stateDir = "/var/lib/nsd"; pidFile = stateDir + "/var/nsd.pid"; + nsdPkg = pkgs.nsd.override { + bind8Stats = cfg.bind8Stats; + ipv6 = cfg.ipv6; + ratelimit = cfg.ratelimit.enable; + rootServer = cfg.rootServer; + zoneStats = length (collect (x: (x.zoneStats or null) != null) cfg.zones) > 0; + }; + zoneFiles = pkgs.stdenv.mkDerivation { - preferLocalBuild = true; - name = "nsd-env"; - buildCommand = concatStringsSep "\n" - [ "mkdir -p $out" - (concatStrings (mapAttrsToList (zoneName: zoneOptions: '' - cat > "$out/${zoneName}" <<_EOF_ - ${zoneOptions.data} - _EOF_ - '') zoneConfigs)) - ]; + preferLocalBuild = true; + name = "nsd-env"; + buildCommand = concatStringsSep "\n" + [ "mkdir -p $out" + (concatStrings (mapAttrsToList (zoneName: zoneOptions: '' + cat > "$out/${zoneName}" <<_EOF_ + ${zoneOptions.data} + _EOF_ + '') zoneConfigs)) + ]; }; configFile = pkgs.writeText "nsd.conf" '' @@ -33,7 +41,6 @@ let # the list of dynamically added zones. zonelistfile: "${stateDir}/var/zone.list" database: "${stateDir}/var/nsd.db" - logfile: "${stateDir}/var/nsd.log" pidfile: "${pidFile}" xfrdfile: "${stateDir}/var/xfrd.state" xfrdir: "${stateDir}/tmp" @@ -105,21 +112,21 @@ let zoneConfigFile = name: zone: '' - zone: - name: "${name}" - zonefile: "${stateDir}/zones/${name}" - ${maybeString "outgoing-interface: " zone.outgoingInterface} - ${forEach " rrl-whitelist: " zone.rrlWhitelist} - - ${forEach " allow-notify: " zone.allowNotify} - ${forEach " request-xfr: " zone.requestXFR} - allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback} - - ${forEach " notify: " zone.notify} - notify-retry: ${toString zone.notifyRetry} - ${forEach " provide-xfr: " zone.provideXFR} - - ''; + zone: + name: "${name}" + zonefile: "${stateDir}/zones/${name}" + ${maybeString "zonestats: " zone.zoneStats} + ${maybeString "outgoing-interface: " zone.outgoingInterface} + ${forEach " rrl-whitelist: " zone.rrlWhitelist} + + ${forEach " allow-notify: " zone.allowNotify} + ${forEach " request-xfr: " zone.requestXFR} + allow-axfr-fallback: ${yesOrNo zone.allowAXFRFallback} + + ${forEach " notify: " zone.notify} + notify-retry: ${toString zone.notifyRetry} + ${forEach " provide-xfr: " zone.provideXFR} + ''; zoneConfigs = zoneConfigs' {} "" { children = cfg.zones; }; @@ -130,8 +137,8 @@ let # fork -> pattern else zipAttrsWith (name: head) ( - mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child) - zone.children + mapAttrsToList (name: child: zoneConfigs' (parent // zone // { children = {}; }) name child) + zone.children ); # fighting infinite recursion @@ -145,138 +152,148 @@ let childConfig = x: v: { options.children = { type = types.attrsOf x; visible = v; }; }; - zoneOptionsRaw = types.submodule ( - { options, ... }: - { options = { - children = mkOption { - default = {}; - description = '' - Children zones inherit all options of their parents. Attributes - defined in a child will overwrite the ones of its parent. Only - leaf zones will be actually served. This way it's possible to - define maybe zones which share most attributes without - duplicating everything. This mechanism replaces nsd's patterns - in a save and functional way. - ''; - }; + zoneOptionsRaw = types.submodule { + options = { + children = mkOption { + default = {}; + description = '' + Children zones inherit all options of their parents. Attributes + defined in a child will overwrite the ones of its parent. Only + leaf zones will be actually served. This way it's possible to + define maybe zones which share most attributes without + duplicating everything. This mechanism replaces nsd's patterns + in a save and functional way. + ''; + }; - allowNotify = mkOption { - type = types.listOf types.str; - default = [ ]; - example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name" - "10.0.3.4&255.255.0.0 BLOCKED" - ]; - description = '' - Listed primary servers are allowed to notify this secondary server. - <screen><![CDATA[ - Format: <ip> <key-name | NOKEY | BLOCKED> - - <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges: - * 10.0.0.0/24 # via subnet size - * 10.0.0.0&255.255.255.0 # via subnet mask - * 10.0.0.1-10.0.0.254 # via range - - A optional port number could be added with a '@': - * 2001:1234::1@1234 - - <key-name | NOKEY | BLOCKED> - * <key-name> will use the specified TSIG key - * NOKEY no TSIG signature is required - * BLOCKED notifies from non-listed or blocked IPs will be ignored - * ]]></screen> - ''; - }; + allowNotify = mkOption { + type = types.listOf types.str; + default = [ ]; + example = [ "192.0.2.0/24 NOKEY" "10.0.0.1-10.0.0.5 my_tsig_key_name" + "10.0.3.4&255.255.0.0 BLOCKED" + ]; + description = '' + Listed primary servers are allowed to notify this secondary server. + <screen><![CDATA[ + Format: <ip> <key-name | NOKEY | BLOCKED> + + <ip> either a plain IPv4/IPv6 address or range. Valid patters for ranges: + * 10.0.0.0/24 # via subnet size + * 10.0.0.0&255.255.255.0 # via subnet mask + * 10.0.0.1-10.0.0.254 # via range + + A optional port number could be added with a '@': + * 2001:1234::1@1234 + + <key-name | NOKEY | BLOCKED> + * <key-name> will use the specified TSIG key + * NOKEY no TSIG signature is required + * BLOCKED notifies from non-listed or blocked IPs will be ignored + * ]]></screen> + ''; + }; - requestXFR = mkOption { - type = types.listOf types.str; - default = []; - example = []; - description = '' - Format: <code>[AXFR|UDP] <ip-address> <key-name | NOKEY></code> - ''; - }; + requestXFR = mkOption { + type = types.listOf types.str; + default = []; + example = []; + description = '' + Format: <code>[AXFR|UDP] <ip-address> <key-name | NOKEY></code> + ''; + }; - allowAXFRFallback = mkOption { - type = types.bool; - default = true; - description = '' - If NSD as secondary server should be allowed to AXFR if the primary - server does not allow IXFR. - ''; - }; + allowAXFRFallback = mkOption { + type = types.bool; + default = true; + description = '' + If NSD as secondary server should be allowed to AXFR if the primary + server does not allow IXFR. + ''; + }; - notify = mkOption { - type = types.listOf types.str; - default = []; - example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ]; - description = '' - This primary server will notify all given secondary servers about - zone changes. - <screen><![CDATA[ - Format: <ip> <key-name | NOKEY> + notify = mkOption { + type = types.listOf types.str; + default = []; + example = [ "10.0.0.1@3721 my_key" "::5 NOKEY" ]; + description = '' + This primary server will notify all given secondary servers about + zone changes. + <screen><![CDATA[ + Format: <ip> <key-name | NOKEY> - <ip> a plain IPv4/IPv6 address with on optional port number (ip@port) + <ip> a plain IPv4/IPv6 address with on optional port number (ip@port) - <key-name | NOKEY> - * <key-name> sign notifies with the specified key - * NOKEY don't sign notifies - ]]></screen> - ''; - }; + <key-name | NOKEY> + * <key-name> sign notifies with the specified key + * NOKEY don't sign notifies + ]]></screen> + ''; + }; - notifyRetry = mkOption { - type = types.int; - default = 5; - description = '' - Specifies the number of retries for failed notifies. Set this along with notify. - ''; - }; + notifyRetry = mkOption { + type = types.int; + default = 5; + description = '' + Specifies the number of retries for failed notifies. Set this along with notify. + ''; + }; - provideXFR = mkOption { - type = types.listOf types.str; - default = []; - example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ]; - description = '' - Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED - address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40 - ''; - }; + provideXFR = mkOption { + type = types.listOf types.str; + default = []; + example = [ "192.0.2.0/24 NOKEY" "192.0.2.0/24 my_tsig_key_name" ]; + description = '' + Allow these IPs and TSIG to transfer zones, addr TSIG|NOKEY|BLOCKED + address range 192.0.2.0/24, 1.2.3.4&255.255.0.0, 3.0.2.20-3.0.2.40 + ''; + }; - outgoingInterface = mkOption { - type = types.nullOr types.str; - default = null; - example = "2000::1@1234"; - description = '' - This address will be used for zone-transfere requests if configured - as a secondary server or notifications in case of a primary server. - Supply either a plain IPv4 or IPv6 address with an optional port - number (ip@port). - ''; - }; + outgoingInterface = mkOption { + type = types.nullOr types.str; + default = null; + example = "2000::1@1234"; + description = '' + This address will be used for zone-transfere requests if configured + as a secondary server or notifications in case of a primary server. + Supply either a plain IPv4 or IPv6 address with an optional port + number (ip@port). + ''; + }; - rrlWhitelist = mkOption { - type = types.listOf types.str; - default = []; - description = '' - Whitelists the given rrl-types. - The RRL classification types are: nxdomain, error, referral, any, - rrsig, wildcard, nodata, dnskey, positive, all - ''; - }; + rrlWhitelist = mkOption { + type = types.listOf types.str; + default = []; + description = '' + Whitelists the given rrl-types. + The RRL classification types are: nxdomain, error, referral, any, + rrsig, wildcard, nodata, dnskey, positive, all + ''; + }; - data = mkOption { - type = types.str; - default = ""; - example = ""; - description = '' - The actual zone data. This is the content of your zone file. - Use imports or pkgs.lib.readFile if you don't want this data in your config file. - ''; - }; + data = mkOption { + type = types.str; + default = ""; + example = ""; + description = '' + The actual zone data. This is the content of your zone file. + Use imports or pkgs.lib.readFile if you don't want this data in your config file. + ''; + }; + zoneStats = mkOption { + type = types.nullOr types.str; + default = null; + example = "%s"; + description = '' + When set to something distinct to null NSD is able to collect + statistics per zone. All statistics of this zone(s) will be added + to the group specified by this given name. Use "%s" to use the zones + name as the group. The groups are output from nsd-control stats + and stats_noreset. + ''; }; - } - ); + }; + }; in { @@ -291,6 +308,15 @@ in ''; }; + bind8Stats = mkOption { + type = types.bool; + default = false; + example = true; + description = '' + Wheter to enable BIND8 like statisics. + ''; + }; + rootServer = mkOption { type = types.bool; default = false; @@ -456,191 +482,162 @@ in }; - ratelimit = mkOption { - type = types.submodule ( - { options, ... }: - { options = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Enable ratelimit capabilities. - ''; - }; - - size = mkOption { - type = types.int; - default = 1000000; - description = '' - Size of the hashtable. More buckets use more memory but lower - the chance of hash hash collisions. - ''; - }; + ratelimit = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Enable ratelimit capabilities. + ''; + }; - ratelimit = mkOption { - type = types.int; - default = 200; - description = '' - Max qps allowed from any query source. - 0 means unlimited. With an verbosity of 2 blocked and - unblocked subnets will be logged. - ''; - }; + size = mkOption { + type = types.int; + default = 1000000; + description = '' + Size of the hashtable. More buckets use more memory but lower + the chance of hash hash collisions. + ''; + }; - whitelistRatelimit = mkOption { - type = types.int; - default = 2000; - description = '' - Max qps allowed from whitelisted sources. - 0 means unlimited. Set the rrl-whitelist option for specific - queries to apply this limit instead of the default to them. - ''; - }; + ratelimit = mkOption { + type = types.int; + default = 200; + description = '' + Max qps allowed from any query source. + 0 means unlimited. With an verbosity of 2 blocked and + unblocked subnets will be logged. + ''; + }; - slip = mkOption { - type = types.nullOr types.int; - default = null; - description = '' - Number of packets that get discarded before replying a SLIP response. - 0 disables SLIP responses. 1 will make every response a SLIP response. - ''; - }; + whitelistRatelimit = mkOption { + type = types.int; + default = 2000; + description = '' + Max qps allowed from whitelisted sources. + 0 means unlimited. Set the rrl-whitelist option for specific + queries to apply this limit instead of the default to them. + ''; + }; - ipv4PrefixLength = mkOption { - type = types.nullOr types.int; - default = null; - description = '' - IPv4 prefix length. Addresses are grouped by netblock. - ''; - }; + slip = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Number of packets that get discarded before replying a SLIP response. + 0 disables SLIP responses. 1 will make every response a SLIP response. + ''; + }; - ipv6PrefixLength = mkOption { - type = types.nullOr types.int; - default = null; - description = '' - IPv6 prefix length. Addresses are grouped by netblock. - ''; - }; + ipv4PrefixLength = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + IPv4 prefix length. Addresses are grouped by netblock. + ''; + }; - }; - }); - default = { + ipv6PrefixLength = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + IPv6 prefix length. Addresses are grouped by netblock. + ''; }; - example = {}; - description = '' - ''; }; - remoteControl = mkOption { - type = types.submodule ( - { config, options, ... }: - { options = { - - enable = mkOption { - type = types.bool; - default = false; - description = '' - Wheter to enable remote control via nsd-control(8). - ''; - }; - - interfaces = mkOption { - type = types.listOf types.str; - default = [ "127.0.0.1" "::1" ]; - description = '' - Which interfaces NSD should bind to for remote control. - ''; - }; - - port = mkOption { - type = types.int; - default = 8952; - description = '' - Port number for remote control operations (uses TLS over TCP). - ''; - }; + remoteControl = { + enable = mkOption { + type = types.bool; + default = false; + description = '' + Wheter to enable remote control via nsd-control(8). + ''; + }; - serverKeyFile = mkOption { - type = types.path; - default = "/etc/nsd/nsd_server.key"; - description = '' - Path to the server private key, which is used by the server - but not by nsd-control. This file is generated by nsd-control-setup. - ''; - }; + interfaces = mkOption { + type = types.listOf types.str; + default = [ "127.0.0.1" "::1" ]; + description = '' + Which interfaces NSD should bind to for remote control. + ''; + }; - serverCertFile = mkOption { - type = types.path; - default = "/etc/nsd/nsd_server.pem"; - description = '' - Path to the server self signed certificate, which is used by the server - but and by nsd-control. This file is generated by nsd-control-setup. - ''; - }; + port = mkOption { + type = types.int; + default = 8952; + description = '' + Port number for remote control operations (uses TLS over TCP). + ''; + }; - controlKeyFile = mkOption { - type = types.path; - default = "/etc/nsd/nsd_control.key"; - description = '' - Path to the client private key, which is used by nsd-control - but not by the server. This file is generated by nsd-control-setup. - ''; - }; + serverKeyFile = mkOption { + type = types.path; + default = "/etc/nsd/nsd_server.key"; + description = '' + Path to the server private key, which is used by the server + but not by nsd-control. This file is generated by nsd-control-setup. + ''; + }; - controlCertFile = mkOption { - type = types.path; - default = "/etc/nsd/nsd_control.pem"; - description = '' - Path to the client certificate signed with the server certificate. - This file is used by nsd-control and generated by nsd-control-setup. - ''; - }; + serverCertFile = mkOption { + type = types.path; + default = "/etc/nsd/nsd_server.pem"; + description = '' + Path to the server self signed certificate, which is used by the server + but and by nsd-control. This file is generated by nsd-control-setup. + ''; + }; - }; + controlKeyFile = mkOption { + type = types.path; + default = "/etc/nsd/nsd_control.key"; + description = '' + Path to the client private key, which is used by nsd-control + but not by the server. This file is generated by nsd-control-setup. + ''; + }; - }); - default = { + controlCertFile = mkOption { + type = types.path; + default = "/etc/nsd/nsd_control.pem"; + description = '' + Path to the client certificate signed with the server certificate. + This file is used by nsd-control and generated by nsd-control-setup. + ''; }; - example = {}; - description = '' - ''; }; keys = mkOption { - type = types.attrsOf (types.submodule ( - { options, ... }: - { options = { - - algorithm = mkOption { - type = types.str; - default = "hmac-sha256"; - description = '' - Authentication algorithm for this key. - ''; - }; - - keyFile = mkOption { - type = types.path; - description = '' - Path to the file which contains the actual base64 encoded - key. The key will be copied into "${stateDir}/private" before - NSD starts. The copied file is only accessibly by the NSD - user. - ''; - }; + type = types.attrsOf (types.submodule { + options = { + algorithm = mkOption { + type = types.str; + default = "hmac-sha256"; + description = '' + Authentication algorithm for this key. + ''; + }; + keyFile = mkOption { + type = types.path; + description = '' + Path to the file which contains the actual base64 encoded + key. The key will be copied into "${stateDir}/private" before + NSD starts. The copied file is only accessibly by the NSD + user. + ''; }; - })); - default = { - }; + }; + }); + default = {}; example = { - "tsig.example.org" = { - algorithm = "hmac-md5"; - secret = "aaaaaabbbbbbccccccdddddd"; - }; + "tsig.example.org" = { + algorithm = "hmac-md5"; + secret = "aaaaaabbbbbbccccccdddddd"; + }; }; description = '' Define your TSIG keys here. @@ -651,32 +648,32 @@ in type = types.attrsOf zoneOptions; default = {}; example = { - "serverGroup1" = { - provideXFR = [ "10.1.2.3 NOKEY" ]; - children = { - "example.com." = { - data = '' - $ORIGIN example.com. - $TTL 86400 - @ IN SOA a.ns.example.com. admin.example.com. ( - ... - ''; - }; - "example.org." = { - data = '' - $ORIGIN example.org. - $TTL 86400 - @ IN SOA a.ns.example.com. admin.example.com. ( - ... - ''; - }; - }; + "serverGroup1" = { + provideXFR = [ "10.1.2.3 NOKEY" ]; + children = { + "example.com." = { + data = '' + $ORIGIN example.com. + $TTL 86400 + @ IN SOA a.ns.example.com. admin.example.com. ( + ... + ''; + }; + "example.org." = { + data = '' + $ORIGIN example.org. + $TTL 86400 + @ IN SOA a.ns.example.com. admin.example.com. ( + ... + ''; + }; }; + }; - "example.net." = { - provideXFR = [ "10.3.2.1 NOKEY" ]; - data = ''...''; - }; + "example.net." = { + provideXFR = [ "10.3.2.1 NOKEY" ]; + data = ''...''; + }; }; description = '' Define your zones here. Zones can cascade other zones and therefore @@ -693,25 +690,18 @@ in config = mkIf cfg.enable { - # this is not working :( - nixpkgs.config.nsd = { - ipv6 = cfg.ipv6; - ratelimit = cfg.ratelimit.enable; - rootServer = cfg.rootServer; - }; - users.extraGroups = singleton { - name = username; - gid = config.ids.gids.nsd; + name = username; + gid = config.ids.gids.nsd; }; users.extraUsers = singleton { - name = username; - description = "NSD service user"; - home = stateDir; - createHome = true; - uid = config.ids.uids.nsd; - group = username; + name = username; + description = "NSD service user"; + home = stateDir; + createHome = true; + uid = config.ids.uids.nsd; + group = username; }; systemd.services.nsd = { @@ -720,10 +710,9 @@ in after = [ "network.target" ]; serviceConfig = { - Type = "forking"; PIDFile = pidFile; Restart = "always"; - ExecStart = "${pkgs.nsd}/sbin/nsd -c ${configFile}"; + ExecStart = "${nsdPkg}/sbin/nsd -d -c ${configFile}"; }; preStart = '' |