2023-10-02 21:30:51 +00:00
# TODO: split this file apart into smaller files to make it easier to understand
2023-06-07 23:34:00 +00:00
{ config , lib , pkgs , . . . }:
2022-12-15 11:16:07 +00:00
2023-09-22 14:36:56 +00:00
let
2023-10-02 22:33:54 +00:00
nativeAddrs = lib . mapAttrs ( _name : builtins . head ) config . sane . dns . zones . " u n i n s a n e . o r g " . inet . A ;
2023-09-22 18:54:12 +00:00
bindOvpn = " 1 0 . 0 . 1 . 5 " ;
2023-10-02 21:30:51 +00:00
in lib . mkMerge [
2022-12-15 11:16:07 +00:00
{
2023-07-13 09:57:11 +00:00
services . trust-dns . enable = true ;
2022-12-15 11:16:07 +00:00
2023-07-15 09:07:57 +00:00
# don't bind to IPv6 until i explicitly test that stack
services . trust-dns . settings . listen_addrs_ipv6 = [ ] ;
2023-07-13 09:57:11 +00:00
services . trust-dns . quiet = true ;
# services.trust-dns.debug = true;
2023-07-02 08:21:33 +00:00
sane . ports . ports . " 5 3 " = {
protocol = [ " u d p " " t c p " ] ;
visibleTo . lan = true ;
visibleTo . wan = true ;
2023-10-17 09:42:13 +00:00
visibleTo . ovpn = true ;
2023-07-02 08:21:33 +00:00
description = " c o l i n - d n s - h o s t i n g " ;
} ;
2022-12-15 11:16:07 +00:00
2023-06-07 23:34:00 +00:00
sane . dns . zones . " u n i n s a n e . o r g " . TTL = 900 ;
2022-12-19 04:00:27 +00:00
# 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)
2023-06-07 23:34:00 +00:00
sane . dns . zones . " u n i n s a n e . o r g " . inet = {
2023-01-02 13:23:52 +00:00
SOA . " @ " = ''
2022-12-19 04:38:41 +00:00
ns1 . uninsane . org . admin-dns . uninsane . org . (
2023-09-22 12:36:48 +00:00
2023092101 ; Serial
2022-12-19 04:38:41 +00:00
4 h ; Refresh
3 0 m ; Retry
7 d ; Expire
5 m ) ; Negative response TTL
2023-01-02 13:23:52 +00:00
'' ;
2023-09-22 12:36:48 +00:00
TXT . " r e v " = " 2 0 2 3 0 9 2 1 0 1 " ;
2023-05-30 12:00:30 +00:00
CNAME . " n a t i v e " = " % C N A M E N A T I V E % " ;
A . " @ " = " % A N A T I V E % " ;
2023-10-02 22:33:54 +00:00
A . " s e r v o . w a n " = " % A W A N % " ;
2023-05-30 12:00:30 +00:00
A . " s e r v o . l a n " = config . sane . hosts . by-name . " s e r v o " . lan-ip ;
2023-09-22 12:36:48 +00:00
A . " s e r v o . h n " = config . sane . hosts . by-name . " s e r v o " . wg-home . ip ;
2022-12-17 01:29:12 +00:00
2022-12-19 04:38:41 +00:00
# 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.
2023-05-30 12:00:30 +00:00
A . " n s 1 " = " % A N A T I V E % " ;
2023-01-02 13:23:52 +00:00
A . " n s 2 " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
A . " n s 3 " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
A . " o v p n s " = " 1 8 5 . 1 5 7 . 1 6 2 . 1 7 8 " ;
2022-12-19 04:38:41 +00:00
NS . " @ " = [
" n s 1 . u n i n s a n e . o r g . "
" n s 2 . u n i n s a n e . o r g . "
" n s 3 . u n i n s a n e . o r g . "
] ;
} ;
2022-12-15 11:16:07 +00:00
2023-07-13 10:04:20 +00:00
services . trust-dns . settings . zones = [ " u n i n s a n e . o r g " ] ;
2023-05-30 12:00:30 +00:00
2023-10-02 21:30:51 +00:00
# TODO: can i transform this into some sort of service group?
# have `systemctl restart trust-dns.service` restart all the individual services?
2023-07-10 09:00:37 +00:00
systemd . services . trust-dns . serviceConfig = {
DynamicUser = lib . mkForce false ;
2023-07-15 09:07:57 +00:00
User = " t r u s t - d n s " ;
Group = " t r u s t - d n s " ;
2023-10-02 21:30:51 +00:00
wantedBy = lib . mkForce [ ] ;
2023-07-10 09:00:37 +00:00
} ;
2023-10-02 21:30:51 +00:00
systemd . services . trust-dns . enable = false ;
2023-07-10 09:00:37 +00:00
users . groups . trust-dns = { } ;
users . users . trust-dns = {
group = " t r u s t - d n s " ;
isSystemUser = true ;
} ;
2023-10-02 21:30:51 +00:00
# sane.services.dyn-dns.restartOnChange = [ "trust-dns.service" ];
2023-05-30 12:00:30 +00:00
2023-05-31 00:56:52 +00:00
networking . nat . enable = true ;
networking . nat . extraCommands = ''
# redirect incoming DNS requests from LAN addresses
# to the LAN-specialized DNS service
# N.B.: use the `nixos-*` chains instead of e.g. PREROUTING
# because they get cleanly reset across activations or `systemctl restart firewall`
# instead of accumulating cruft
iptables - t nat - A nixos-nat-pre - p udp - - dport 53 \
- m iprange - - src-range 10 .78 .76 . 0 -10 .78 .79 .255 \
- j DNAT - - to-destination : 1053
iptables - t nat - A nixos-nat-pre - p tcp - - dport 53 \
- m iprange - - src-range 10 .78 .76 . 0 -10 .78 .79 .255 \
- j DNAT - - to-destination : 1053
'' ;
2023-05-31 04:25:39 +00:00
sane . ports . ports . " 1 0 5 3 " = {
# because the NAT above redirects in nixos-nat-pre, LAN requests behave as though they arrived on the external interface at the redirected port.
# TODO: try nixos-nat-post instead?
2023-09-22 14:36:56 +00:00
# TODO: or, don't NAT from port 53 -> port 1053, but rather nat from LAN addr to a loopback addr.
# - this is complicated in that loopback is a different interface than eth0, so rewriting the destination address would cause the packets to just be dropped by the interface
2023-05-31 04:25:39 +00:00
protocol = [ " u d p " " t c p " ] ;
visibleTo . lan = true ;
description = " c o l i n - r e d i r e c t e d - d n s - f o r - l a n - n a m e s p a c e " ;
} ;
2023-10-02 21:30:51 +00:00
}
{
systemd . services =
let
sed = " ${ pkgs . gnused } / b i n / s e d " ;
2023-10-02 22:12:09 +00:00
stateDir = " / v a r / l i b / t r u s t - d n s " ;
2023-10-02 21:30:51 +00:00
zoneTemplate = pkgs . writeText " u n i n s a n e . o r g . z o n e . i n " config . sane . dns . zones . " u n i n s a n e . o r g " . rendered ;
2023-10-02 22:12:09 +00:00
zoneDirFor = flavor : " ${ stateDir } / ${ flavor } " ;
zoneFor = flavor : " ${ zoneDirFor flavor } / u n i n s a n e . o r g . z o n e " ;
2023-10-02 21:30:51 +00:00
mkTrustDnsService = opts : flavor : let
flags = let baseCfg = config . services . trust-dns ; in
( lib . optional baseCfg . debug " - - d e b u g " ) ++ ( lib . optional baseCfg . quiet " - - q u i e t " ) ;
flagsStr = builtins . concatStringsSep " " flags ;
2023-10-02 22:33:54 +00:00
anative = nativeAddrs . " s e r v o . ${ flavor } " ;
2023-10-02 21:30:51 +00:00
toml = pkgs . formats . toml { } ;
2023-10-02 22:02:46 +00:00
configTemplate = opts . config or ( toml . generate " t r u s t - d n s - ${ flavor } . t o m l " (
(
lib . filterAttrsRecursive ( _ : v : v != null ) config . services . trust-dns . settings
) // {
listen_addrs_ipv4 = opts . listen or [ anative ] ;
}
) ) ;
2023-10-02 22:12:09 +00:00
configFile = " ${ stateDir } / ${ flavor } - c o n f i g . t o m l " ;
2023-10-02 22:02:46 +00:00
2023-10-02 21:30:51 +00:00
port = opts . port or 53 ;
2023-10-02 22:02:46 +00:00
in {
description = " t r u s t - d n s D o m a i n N a m e S e r v e r ( s e r v i n g ${ flavor } ) " ;
unitConfig . Documentation = " h t t p s : / / t r u s t - d n s . o r g / " ;
preStart = ''
2023-10-02 21:30:51 +00:00
wan = $ ( cat ' $ { config . sane . services . dyn-dns . ipPath } ' )
2023-10-02 22:02:46 +00:00
$ { sed } s / % AWAN % / $ wan / $ { configTemplate } > $ { configFile }
'' + l i b . o p t i o n a l S t r i n g ( ! o p t s ? c o n f i g ) ''
2023-10-02 22:33:54 +00:00
mkdir - p $ { zoneDirFor flavor }
2023-10-02 21:30:51 +00:00
$ { sed } \
- e s / % CNAMENATIVE % /servo. $ { flavor } / \
- e s / % ANATIVE % / $ { anative } / \
2023-10-02 22:02:46 +00:00
- e s / % AWAN % / $ wan / \
2023-10-17 09:43:55 +00:00
- e s / % AOVPNS % /185.157.162.178 / \
2023-10-02 22:02:46 +00:00
$ { zoneTemplate } > $ { zoneFor flavor }
2023-10-02 21:30:51 +00:00
'' ;
serviceConfig = config . systemd . services . trust-dns . serviceConfig // {
ExecStart = ''
$ { pkgs . trust-dns } /bin/trust-dns \
- - port $ { builtins . toString port } \
2023-10-02 22:12:09 +00:00
- - zonedir $ { zoneDirFor flavor } / \
2023-10-02 21:30:51 +00:00
- - config $ { configFile } $ { flagsStr }
'' ;
} ;
after = [ " n e t w o r k . t a r g e t " ] ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
} ;
in {
2023-10-02 22:33:54 +00:00
trust-dns-wan = mkTrustDnsService { listen = [ nativeAddrs . " s e r v o . l a n " bindOvpn ] ; } " w a n " ;
2023-10-02 21:30:51 +00:00
trust-dns-lan = mkTrustDnsService { port = 1053 ; } " l a n " ;
trust-dns-hn = mkTrustDnsService { port = 1053 ; } " h n " ;
2023-10-02 22:02:46 +00:00
trust-dns-hn-resolver = mkTrustDnsService {
config = pkgs . writeText " h n - r e s o l v e r - c o n f i g . t o m l " ''
# i host a resolver in the wireguard VPN so that clients can resolve DNS through the VPN.
# (that's what this file achieves).
#
# one would expect this resolver could host the authoritative zone for `uninsane.org`, and then forward everything else to the system resolver...
# and while that works for `dig`, it breaks for `nslookup` (and so `ssh`, etc).
#
# DNS responses include a flag for if the responding server is the authority of the zone queried.
# it seems that default Linux stub resolvers either:
# - expect DNSSEC when the response includes that bit, or
# - expect A records to be in the `answer` section instead of `additional` section.
# or perhaps something more nuanced. but for `nslookup` to be reliable, it has to talk to an
# instance of trust-dns which is strictly a resolver, with no authority.
# hence, this config: a resolver which forwards to the actual authority.
2023-10-02 22:33:54 +00:00
listen_addrs_ipv4 = [ " ${ nativeAddrs . " s e r v o . h n " } " ]
2023-10-02 22:02:46 +00:00
listen_addrs_ipv6 = [ ]
[ [ zones ] ]
zone = " u n i n s a n e . o r g "
zone_type = " F o r w a r d "
2023-10-02 22:33:54 +00:00
stores = { type = " f o r w a r d " , name_servers = [ { socket_addr = " ${ nativeAddrs . " s e r v o . h n " } : 1 0 5 3 " , protocol = " u d p " , trust_nx_responses = true } ] }
2023-10-02 22:02:46 +00:00
[ [ zones ] ]
# forward the root zone to the local DNS resolver
zone = " . "
zone_type = " F o r w a r d "
stores = { type = " f o r w a r d " , name_servers = [ { socket_addr = " 1 2 7 . 0 . 0 . 5 3 : 5 3 " , protocol = " u d p " , trust_nx_responses = true } ] }
'' ;
} " h n - r e s o l v e r " ;
2023-10-02 21:30:51 +00:00
} ;
sane . services . dyn-dns . restartOnChange = [
" t r u s t - d n s - w a n . s e r v i c e "
" t r u s t - d n s - l a n . s e r v i c e "
" t r u s t - d n s - h n . s e r v i c e "
# "trust-dns-hn-resolver.service" # doesn't need restart because it doesn't know about WAN IP
] ;
2022-12-15 11:16:07 +00:00
}
2023-10-02 21:30:51 +00:00
]