diff --git a/hosts/servo/services/default.nix b/hosts/servo/services/default.nix index 0118594a..9839787c 100644 --- a/hosts/servo/services/default.nix +++ b/hosts/servo/services/default.nix @@ -18,7 +18,7 @@ ./postgres.nix ./prosody.nix ./transmission.nix - ./trust-dns + ./trust-dns.nix ./wikipedia.nix ]; } diff --git a/hosts/servo/services/trust-dns.nix b/hosts/servo/services/trust-dns.nix new file mode 100644 index 00000000..6dba75b0 --- /dev/null +++ b/hosts/servo/services/trust-dns.nix @@ -0,0 +1,167 @@ +{ config, pkgs, ... }: + +{ + sane.services.trust-dns.enable = true; + + sane.services.trust-dns.listenAddrsIPv4 = [ + # specify each address explicitly, instead of using "*". + # this ensures responses are sent from the address at which the request was received. + "192.168.0.5" + "10.0.1.5" + ]; + + sane.services.trust-dns.zones."uninsane.org".TTL = 900; + sane.services.trust-dns.zones."uninsane.org".SOA = '' + ; SOA record structure: + ; SOA MNAME RNAME (... rest) + ; MNAME = Master name server for this zone. this is where update requests should be sent. + ; RNAME = admin contact (encoded email address) + ; Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it. + ; Refresh = how frequently secondary NS should query master + ; Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh) + ; Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry) + @ IN SOA ns1.uninsane.org. admin-dns.uninsane.org. ( + 2022121207 ; Serial + 4h ; Refresh + 30m ; Retry + 7d ; Expire + 5m) ; Negative response TTL + ''; + # TODO: split into services + sane.services.trust-dns.zones."uninsane.org".records = '' + rev TXT "2022121402" + + ; @ A %NATIVE% + ; XXX: RFC's specify that the MX record CANNOT BE A CNAME + mx A 185.157.162.178 + ; XXX NS records must also not be CNAME + ; it's best that we keep this identical, or a superset of, what org. lists as our NS. + ; so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here. + ; ns1 A %NATIVE% + ns2 A 185.157.162.178 + ns3 A 185.157.162.178 + ; native A %NATIVE% + ovpns A 185.157.162.178 + + @ NS ns1.uninsane.org. + @ NS ns2.uninsane.org. + @ NS ns3.uninsane.org. + ;@ NS uninsane.port0.org. + ;@ NS uninsane.psybnc.org. + + @ MX 10 mx.uninsane.org. + + bt CNAME native + fed CNAME native + git CNAME native + imap CNAME native + ipfs CNAME native + jackett CNAME native + jelly CNAME native + matrix CNAME native + web.matrix CNAME native + music CNAME native + nixcache CNAME native + pl-dev CNAME native + rss CNAME native + sink CNAME native + w CNAME native + + xmpp CNAME native + conference.xmpp CNAME native + pubsub.xmpp CNAME native + upload.xmpp CNAME native + vjid.xmpp CNAME native + + ; _Service._Proto.Name TTL Class SRV Priority Weight Port Target + _xmpp-client._tcp SRV 0 0 5222 native + _xmpp-server._tcp SRV 0 0 5269 native + + ; Sender Policy Framework: + ; +mx => mail passes if it originated from the MX + ; +a => mail passes if it originated from the A address of this domain + ; +ip4:.. => mail passes if it originated from this IP + ; -all => mail fails if none of these conditions were met + @ TXT "v=spf1 a mx -all" + + ; DKIM public key: + mx._domainkey TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB" + + ; DMARC fields : + ; p=none|quarantine|reject: what to do with failures + ; sp = p but for subdomains + ; rua = where to send aggregrate reports + ; ruf = where to send individual failure reports + ; fo=0|1|d|s controls WHEN to send failure reports + ; (1=on bad alignment; d=on DKIM failure; s=on SPF failure); + ; Additionally: + ; adkim=r|s (is DKIM relaxed [default] or strict) + ; aspf=r|s (is SPF relaxed [default] or strict) + ; pct = sampling ratio for punishing failures (default 100 for 100%) + ; rf = report format + ; ri = report interval + _dmarc TXT "v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s" + $INCLUDE /var/lib/trust-dns/native.uninsane.org.zone + ''; + + systemd.services.ddns-trust-dns = { + description = "update dynamic DNS entries for self-hosted trust-dns"; + after = [ "network.target" ]; + wantedBy = [ "trust-dns.service" ]; + restartTriggers = [( + builtins.toJSON config.sane.services.trust-dns + )]; + serviceConfig.Type = "oneshot"; + script = let + sed = "${pkgs.gnused}/bin/sed"; + curl = "${pkgs.curl}/bin/curl -4"; + zone-dir = "/var/lib/trust-dns"; + zone-out = "${zone-dir}/native.uninsane.org.zone"; + diff = "${pkgs.diffutils}/bin/diff"; + systemctl = "${pkgs.systemd}/bin/systemctl"; + zone-template = pkgs.writeText "native.uninsane.org.zone.in" '' + @ A %NATIVE% + ns1 A %NATIVE% + native A %NATIVE% + ''; + in '' + set -ex + mkdir -p ${zone-dir} + ip=$(${curl} https://ipinfo.io/ip) + + # TODO: validate that this is really our IP! + # - i could host a service in ovpns which replies to pings + ${sed} s/%NATIVE%/$ip/ ${zone-template} > ${zone-out}.new + + # see if anything changed + # TODO: instead of diffing, we could `dig` against the actual deployment. + # - that could be more resilient to races. + touch ${zone-out} # in case it didn't exist yet + cp ${zone-out} ${zone-out}.old + mv ${zone-out}.new ${zone-out} + # if so, restart trust-dns + if [ ${diff} -u ${zone-out}.old ${zone-out} ] + then + echo "zone unchanged. ip: $ip" + else + echo "zone changed." + status=$(${systemctl} is-active trust-dns.service || true) + echo $status + if [ "$status" = "active" ] + then + echo "restarting trust-dns." + ${systemctl} restart trust-dns.service + fi + fi + ''; + }; + + systemd.timers.ddns-trust-dns = { + # wantedBy = [ "multi-user.target" ]; + wantedBy = [ "trust-dns.service" ]; + timerConfig = { + OnStartupSec = "10min"; + OnUnitActiveSec = "10min"; + }; + }; +} diff --git a/hosts/servo/services/trust-dns/default.nix b/hosts/servo/services/trust-dns/default.nix deleted file mode 100644 index 2b1643c2..00000000 --- a/hosts/servo/services/trust-dns/default.nix +++ /dev/null @@ -1,82 +0,0 @@ -{ pkgs, ... }: - -{ - networking.firewall.allowedTCPPorts = [ 53 ]; - networking.firewall.allowedUDPPorts = [ 53 ]; - - systemd.services.ddns-trust-dns = { - description = "update dynamic DNS entries for self-hosted trust-dns"; - after = [ "network.target" ]; - restartTriggers = [ ./uninsane.org.zone ]; - serviceConfig.Type = "oneshot"; - script = let - sed = "${pkgs.gnused}/bin/sed"; - curl = "${pkgs.curl}/bin/curl -4"; - sha256sum = "${pkgs.coreutils-full}/bin/sha256sum"; - cut = "${pkgs.coreutils-full}/bin/cut"; - zone-state = "/var/lib/trust-dns/uninsane.org.zone"; - diff = "${pkgs.diffutils}/bin/diff"; - systemctl = "${pkgs.systemd}/bin/systemctl"; - in '' - set -ex - mkdir -p /var/lib/trust-dns - ip=$(${curl} https://ipinfo.io/ip) - - # TODO: validate that this is really our IP! - # - i can host a service in ovpns which replies to pings - ${sed} s/%NATIVE%/$ip/ ${./uninsane.org.zone} > ${zone-state}.new - - # see if anything changed - # TODO: instead of diffing, we could `dig` against the actual deployment. - # - that could be more resilient to races. - touch ${zone-state} - old_sha=$(${sha256sum} ${zone-state} | ${cut} -f 1 -d' ' ) - new_sha=$(${sha256sum} ${zone-state}.new | ${cut} -f 1 -d' ' ) - cp ${zone-state} ${zone-state}.old - mv ${zone-state}.new ${zone-state} - # if so, restart trust-dns - if [ "$new_sha" != "$old_sha" ] - then - echo "zone changed." - ${diff} -u ${zone-state}.old ${zone-state} || true - status=$(${systemctl} is-active trust-dns.service || true) - echo $status - if [ "$status" = "active" ] - then - echo "restarting trust-dns." - ${systemctl} restart trust-dns.service - fi - else - echo "zone unchanged. ip: $ip" - fi - ''; - }; - - systemd.timers.ddns-trust-dns = { - # wantedBy = [ "multi-user.target" ]; - # wantedBy = [ "trust-dns.service" ]; - timerConfig = { - OnStartupSec = "10min"; - OnUnitActiveSec = "10min"; - }; - }; - - systemd.services.trust-dns = { - description = "trust-dns DNS server"; - serviceConfig = { - ExecStart = '' - ${pkgs.trust-dns}/bin/named \ - --config ${./uninsane.org.toml} \ - --zonedir /var/lib/trust-dns - ''; - Type = "simple"; - Restart = "on-failure"; - RestartSec = "10s"; - # TODO: hardening (like, don't run as root!) - }; - wants = [ "ddns-trust-dns.service" "ddns-trust-dns.timer" ]; - # XXX: can't be after ddns-trust-dns.service, because the latter `restarts` this one -- *before* it's done activating. - after = [ "network.target" ]; - wantedBy = [ "multi-user.target" ]; - }; -} diff --git a/hosts/servo/services/trust-dns/uninsane.org.toml b/hosts/servo/services/trust-dns/uninsane.org.toml deleted file mode 100644 index 49112cff..00000000 --- a/hosts/servo/services/trust-dns/uninsane.org.toml +++ /dev/null @@ -1,14 +0,0 @@ -# specify each address explicitly, instead of using "*". -# this ensures responses are sent from the address at which the request was received. -listen_addrs_ipv4 = ["192.168.0.5", "10.0.1.5"] - -[[zones]] -# zone: this is the ORIGIN of the zone, aka the base name, '.' is implied on the end -zone = "uninsane.org" - -# zone_type: Primary, Secondary, Hint, Forward -zone_type = "Primary" - -# file: relative to the `zonedir` option -file = "uninsane.org.zone" - diff --git a/hosts/servo/services/trust-dns/uninsane.org.zone b/hosts/servo/services/trust-dns/uninsane.org.zone deleted file mode 100644 index a9633647..00000000 --- a/hosts/servo/services/trust-dns/uninsane.org.zone +++ /dev/null @@ -1,88 +0,0 @@ -$TTL 900 -; SOA record structure: -; SOA MNAME RNAME (... rest) -; MNAME = Master name server for this zone. this is where update requests should be sent. -; RNAME = admin contact (encoded email address) -; Serial = YYYYMMDDNN, where N is incremented every time this file changes, to trigger secondary NS to re-fetch it. -; Refresh = how frequently secondary NS should query master -; Retry = how long secondary NS should wait until re-querying master after a failure (must be < Refresh) -; Expire = how long secondary NS should continue to reply to queries after master fails (> Refresh + Retry) -@ IN SOA ns1.uninsane.org. admin-dns.uninsane.org. ( - 2022121207 ; Serial - 4h ; Refresh - 30m ; Retry - 7d ; Expire - 5m) ; Negative response TTL - -rev TXT "2022121207" - -@ A %NATIVE% -; XXX: RFC's specify that the MX record CANNOT BE A CNAME -mx A 185.157.162.178 -; XXX NS records must also not be CNAME -; it's best that we keep this identical, or a superset of, what org. lists as our NS. -; so, org. can specify ns2/ns3 as being to the VPN, with no mention of ns1. we provide ns1 here. -ns1 A %NATIVE% -ns2 A 185.157.162.178 -ns3 A 185.157.162.178 -native A %NATIVE% -ovpns A 185.157.162.178 - -@ NS ns1.uninsane.org. -@ NS ns2.uninsane.org. -@ NS ns3.uninsane.org. -;@ NS uninsane.port0.org. -;@ NS uninsane.psybnc.org. - -@ MX 10 mx.uninsane.org. - -bt CNAME native -fed CNAME native -git CNAME native -imap CNAME native -ipfs CNAME native -jackett CNAME native -jelly CNAME native -matrix CNAME native -web.matrix CNAME native -music CNAME native -nixcache CNAME native -pl-dev CNAME native -rss CNAME native -sink CNAME native -w CNAME native - -xmpp CNAME native -conference.xmpp CNAME native -pubsub.xmpp CNAME native -upload.xmpp CNAME native -vjid.xmpp CNAME native - -; _Service._Proto.Name TTL Class SRV Priority Weight Port Target -_xmpp-client._tcp SRV 0 0 5222 native -_xmpp-server._tcp SRV 0 0 5269 native - -; Sender Policy Framework: -; +mx => mail passes if it originated from the MX -; +a => mail passes if it originated from the A address of this domain -; +ip4:.. => mail passes if it originated from this IP -; -all => mail fails if none of these conditions were met -@ TXT "v=spf1 a mx -all" - -; DKIM public key: -mx._domainkey TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkSyMufc2KrRx3j17e/LyB+3eYSBRuEFT8PUka8EDX04QzCwDPdkwgnj3GNDvnB5Ktb05Cf2SJ/S1OLqNsINxJRWtkVfZd/C339KNh9wrukMKRKNELL9HLUw0bczOI4gKKFqyrRE9qm+4csCMAR79Te9FCjGV/jVnrkLdPT0GtFwIDAQAB" - -; DMARC fields : -; p=none|quarantine|reject: what to do with failures -; sp = p but for subdomains -; rua = where to send aggregrate reports -; ruf = where to send individual failure reports -; fo=0|1|d|s controls WHEN to send failure reports -; (1=on bad alignment; d=on DKIM failure; s=on SPF failure); -; Additionally: -; adkim=r|s (is DKIM relaxed [default] or strict) -; aspf=r|s (is SPF relaxed [default] or strict) -; pct = sampling ratio for punishing failures (default 100 for 100%) -; rf = report format -; ri = report interval -_dmarc TXT "v=DMARC1;p=quarantine;sp=reject;rua=mailto:admin+mail@uninsane.org;ruf=mailto:admin+mail@uninsane.org;fo=1:d:s" diff --git a/modules/services/default.nix b/modules/services/default.nix index a629a2b4..008b6287 100644 --- a/modules/services/default.nix +++ b/modules/services/default.nix @@ -3,5 +3,6 @@ imports = [ ./duplicity.nix ./nixserve.nix + ./trust-dns.nix ]; } diff --git a/modules/services/trust-dns.nix b/modules/services/trust-dns.nix new file mode 100644 index 00000000..6ce49b40 --- /dev/null +++ b/modules/services/trust-dns.nix @@ -0,0 +1,82 @@ +{ config, lib, pkgs, ... }: + +with lib; +let + cfg = config.sane.services.trust-dns; + toml = pkgs.formats.toml { }; + configFile = toml.generate "trust-dns.toml" { + listen_addrs_ipv4 = cfg.listenAddrsIPv4; + zones = attrValues ( + mapAttrs (zone: zcfg: { + inherit zone; + zone_type = "Primary"; + file = pkgs.writeText "${zone}.zone" ('' + $TTL ${toString zcfg.TTL} + ${zcfg.SOA} + '' + zcfg.records); + }) cfg.zones + ); + }; +in +{ + options = { + sane.services.trust-dns = { + enable = mkOption { + default = false; + type = types.bool; + }; + listenAddrsIPv4 = mkOption { + type = types.listOf types.str; + default = []; + description = "array of ipv4 addresses on which to listen for DNS queries"; + }; + # reference + zones = mkOption { + type = types.attrsOf (types.submodule { + options = { + TTL = mkOption { + type = types.int; + default = 3600; + description = "default TTL"; + }; + SOA = mkOption { + type = types.str; + description = "Start of Authority record"; + }; + records = mkOption { + type = types.lines; + default = ""; + description = "resource records, specified as strings"; + }; + }; + }); + default = {}; + description = "Declarative zone config"; + }; + }; + }; + + config = mkIf cfg.enable { + networking.firewall.allowedTCPPorts = [ 53 ]; + networking.firewall.allowedUDPPorts = [ 53 ]; + + systemd.services.trust-dns = { + description = "trust-dns DNS server"; + serviceConfig = { + ExecStart = '' + ${pkgs.trust-dns}/bin/named \ + --config ${configFile} \ + --zonedir / + ''; + Type = "simple"; + Restart = "on-failure"; + RestartSec = "10s"; + # TODO: hardening (like, don't run as root!) + }; + wants = [ "ddns-trust-dns.service" "ddns-trust-dns.timer" ]; + # XXX: can't be after ddns-trust-dns.service, because the latter `restarts` this one -- *before* it's done activating. + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + }; + }; +}