trust-dns: split the service into a generic config interface

This commit is contained in:
colin 2022-12-15 11:16:07 +00:00
parent 700fef7df3
commit 8fe304d6c1
7 changed files with 251 additions and 185 deletions

View File

@ -18,7 +18,7 @@
./postgres.nix
./prosody.nix
./transmission.nix
./trust-dns
./trust-dns.nix
./wikipedia.nix
];
}

View File

@ -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: <https://en.wikipedia.org/wiki/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 <https://datatracker.ietf.org/doc/html/rfc7489>:
; 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";
};
};
}

View File

@ -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" ];
};
}

View File

@ -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"

View File

@ -1,88 +0,0 @@
$TTL 900
; SOA record structure: <https://en.wikipedia.org/wiki/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 <https://datatracker.ietf.org/doc/html/rfc7489>:
; 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"

View File

@ -3,5 +3,6 @@
imports = [
./duplicity.nix
./nixserve.nix
./trust-dns.nix
];
}

View File

@ -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 <nixpkgs:nixos/modules/services/web-servers/nginx/vhost-options.nix>
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" ];
};
};
}