{ lib, pkgs, config, ... }: let cfg = config.skynet_dns; # reads that date to a string (will need to be fixed in 2038) current_date = toString builtins.currentTime; # base config for domains we own (skynet.ie, csn.ul.ie, ulcompsoc.ie) get_config_file = (domain: '' $TTL 60 ; 1 minute ; hostmaster@${domain} is an email address that recieves stuff related to dns @ IN SOA ${cfg.own.nameserver}.${domain}. hostmaster.${domain}. ( ; Serial (YYYYMMDDCC) this has to be updated for each time the record is updated ${current_date} 600 ; Refresh (10 minutes) 300 ; Retry (5 minutes) 604800 ; Expire (1 week) 3600 ; Minimum (1 hour) ) @ NS ns1.${domain}. @ NS ns2.${domain}. ; @ stands for teh root domain so teh A record below is where ${domain} points to ;@ A 193.1.99.76 ;@ MX 5 ${domain}. ; can have multiple mailserves ;@ MX 20 mail2.${domain}. ; ------------------------------------------ ; Server Names ; ------------------------------------------ ; External addresses ; ------------------------------------------ ${lib.strings.concatMapStrings (x: x + "\n") cfg.records.external} ; this is fixed for now wintermute A 193.1.101.148 ; internal addresses ; ------------------------------------------ ; May come back to this idea in teh future ; agentjones.int A 172.20.20.1 ; cname's ; ------------------------------------------ ${lib.strings.concatMapStrings (x: x + "\n") cfg.records.cname} '' ); # https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/4/html/reference_guide/s2-bind-configuration-zone-reverse # config for our reverse dnspointers (not properly working) get_config_file_rev = (domain: '' $ORIGIN 99.1.193.in-addr.arpa. $TTL 60 ; 1 minute ; hostmaster@${domain} is an email address that recieves stuff related to dns @ IN SOA ${cfg.own.nameserver}.${domain}. hostmaster.${domain}. ( ; Serial (YYYYMMDDCC) this has to be updated for each time the record is updated ${current_date} 600 ; Refresh (10 minutes) 300 ; Retry (5 minutes) 604800 ; Expire (1 week) 3600 ; Minimum (1 hour) ) @ NS ns1.${domain}. @ NS ns2.${domain}. ${lib.strings.concatMapStrings (x: x + "." + domain + ".\n") cfg.records.reverse} '' ); # domains we dont have proper ownship over, only here to ensure the logs dont get cluttered. get_config_file_old_domains = (domain: '' $TTL 60 ; 1 minute ; hostmaster@skynet.ie is an email address that recieves stuff related to dns @ IN SOA ${cfg.own.nameserver}.skynet.ie. hostmaster.skynet.ie. ( ; Serial (YYYYMMDDCC) this has to be updated for each time the record is updated ${current_date} 600 ; Refresh (10 minutes) 300 ; Retry (5 minutes) 604800 ; Expire (1 week) 3600 ; Minimum (1 hour) ) @ NS ns1.skynet.ie. @ NS ns2.skynet.ie. '' ); # arrys of teh two nameservers tmp1 = ["193.1.99.109"]; tmp2 = ["193.1.99.120"]; primaries = (if cfg.primary then # primary servers have no primaries (ones they listen to) [] else if builtins.elem cfg.own.ip tmp1 then tmp2 else tmp1 ); secondaries = (if cfg.primary then if builtins.elem cfg.own.ip tmp1 then tmp2 else tmp1 else [] ); # small function to tidy up the spam of the cache networks, would use teh subnet except all external traffic has the ip of teh router create_cache_networks = (map (x: "193.1.99.${toString x}/32" ) (lib.lists.range 71 126) ); # standard function to create the etc file, pass in the text and domain and it makes it create_entry_etc_sub = domain: text: { # Creates /etc/skynet/dns/domain "skynet/dns/${domain}" = { user = "named"; group = "named"; # The UNIX file mode bits mode = "0664"; text = text; }; }; # (text.owned "csn.ul.ie") # standard function to create the etc file, pass in the text and domain and it makes it create_entry_etc = domain: type: if type == "owned" then create_entry_etc_sub domain (text.owned domain) else if type == "reverse" then create_entry_etc_sub domain (text.reverse domain) else if type == "old" then create_entry_etc_sub domain (text.old domain) else {}; create_entry_zone = (domain: extraConfig: { "${domain}" = { extraConfig = '' ${extraConfig} // for bumping the config // ${current_date} ''; # really wish teh nixos config didnt use master/slave master = cfg.primary; masters = primaries; slaves = secondaries; # need to write this to a file # using the date in it so it will trigger a restart file = "/etc/skynet/dns/${domain}"; # no leading whitespace for first line }; }); text = { owned = domain: get_config_file domain; reverse = domain: get_config_file_rev domain; old = domain: get_config_file_old_domains domain; }; extraConfig = { owned = if cfg.primary then '' allow-update { key rfc2136key.skynet.ie.; }; dnssec-policy default; inline-signing yes; '' else ""; # no extra config for reverse reverse = ""; old = ""; }; in { imports = [ ../applications/firewall.nix ]; options = { skynet_dns = { enable = lib.mkEnableOption { default = false; example = true; description = "Skynet DNS"; type = lib.types.bool; }; primary = lib.mkOption { type = lib.types.bool; default = false; }; own = { ip = lib.mkOption { type = lib.types.str; description = '' ip of this server ''; }; nameserver = lib.mkOption { default = "ns1"; type = lib.types.str; description = '' the hostname of this nameserver, eg ns1, ns2 ''; }; external = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: agentjones A 193.1.99.72 ''; }; cname = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: ns1 CNAME ns1 ''; }; reverse = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: 20 IN PTR vigil ''; }; }; records = { external = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: agentjones A 193.1.99.72 ''; }; cname = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: ns1 CNAME ns1 ''; }; reverse = lib.mkOption { default = [ ]; type = lib.types.listOf lib.types.str; description = '' External records like: 20 IN PTR vigil ''; }; }; }; }; config = lib.mkIf cfg.enable { # open the firewall for this skynet_firewall.forward = [ "ip daddr ${cfg.own.ip} tcp dport 53 counter packets 0 bytes 0 accept" "ip daddr ${cfg.own.ip} udp dport 53 counter packets 0 bytes 0 accept" ]; services.bind.zones = (create_entry_zone "csn.ul.ie" extraConfig.owned ) // (create_entry_zone "skynet.ie" extraConfig.owned ) // (create_entry_zone "ulcompsoc.ie" extraConfig.owned ) // (create_entry_zone "99.1.193.in-addr.arpa" extraConfig.reverse )// (create_entry_zone "conradcollins.net" extraConfig.old )// (create_entry_zone "edelharty.net" extraConfig.old ); environment.etc = (create_entry_etc "csn.ul.ie" "owned") // (create_entry_etc "skynet.ie" "owned") // (create_entry_etc "ulcompsoc.ie" "owned") // (create_entry_etc "99.1.193.in-addr.arpa" "reverse") // (create_entry_etc "conradcollins.net" "old") // (create_entry_etc "edelharty.net" "old"); # secrets required age.secrets.dns_dnskeys = { file = ../secrets/dns_dnskeys.conf.age; owner = "named"; group = "named"; }; networking.firewall = { allowedTCPPorts = [53]; allowedUDPPorts = [53]; }; services.bind = { enable = true; ipv4Only = true; # need to take a look at https://nixos.org/manual/nixos/unstable/#module-security-acme-config-dns extraConfig = '' include "/run/agenix/dns_dnskeys"; ''; # piles of no valid RRSIG resolving 'com/DS/IN' errors extraOptions = '' dnssec-validation yes; ''; # set the upstream dns servers # overrides the default dns servers forwarders = [ # Cloudflare "1.1.1.1" # Google "8.8.8.8" # Quad9 "9.9.9.9" ]; cacheNetworks = [ # this server itself "127.0.0.0/24" # all of skynet can use this as a resolver /* Origianl idea, however all external traffic had the ip of the router "193.1.99.64/26" So to fix this we need to allow smaller ranges? - Didnt work Fallback is explisitly listing each ip we have Now have a function for it */ ] ++ create_cache_networks; }; # creates a folder in /etc for the dns to use users.users.named = { createHome = true; home = "/etc/skynet/dns"; }; }; }