trust-dns: split the service into a generic config interface
This commit is contained in:
parent
700fef7df3
commit
8fe304d6c1
|
@ -18,7 +18,7 @@
|
|||
./postgres.nix
|
||||
./prosody.nix
|
||||
./transmission.nix
|
||||
./trust-dns
|
||||
./trust-dns.nix
|
||||
./wikipedia.nix
|
||||
];
|
||||
}
|
||||
|
|
167
hosts/servo/services/trust-dns.nix
Normal file
167
hosts/servo/services/trust-dns.nix
Normal 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";
|
||||
};
|
||||
};
|
||||
}
|
|
@ -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" ];
|
||||
};
|
||||
}
|
|
@ -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"
|
||||
|
|
@ -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"
|
|
@ -3,5 +3,6 @@
|
|||
imports = [
|
||||
./duplicity.nix
|
||||
./nixserve.nix
|
||||
./trust-dns.nix
|
||||
];
|
||||
}
|
||||
|
|
82
modules/services/trust-dns.nix
Normal file
82
modules/services/trust-dns.nix
Normal 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" ];
|
||||
};
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user