Personal DNS server

Linux

After personal Email server the next thing to setup is a Domain Name Server (DNS) server for costan․ro domain with DNSSEC, TLS and everything in between.

1. Authoritative DNS with NSD

I know Bind but this is also a bit old-fashion to me; more than that, I believe in SRP and since Bind is both caching and authoritative name server, I prefer NSD which is authoritative only server, lighter and newer.

1.1 Installation

Just install nsd and ldnsutils package.

  pacman -S nsd ldnsutils

1.2 Configuration

Daemon
  grep -v -e '^[[:space:]]*#' -e '^[[:space:]]*$' /etc/nsd/nsd.conf
server:
    verbosity: 2
    username: "nsd"
    zonesdir: "//etc/nsd/zones"
    hide-version: yes
    statistics: 86400
    refuse-any: yes
verify:
    enable: yes
remote-control:
    control-enable: yes
zone:
    name: "costan.ro"
    zonefile: "costan.ro"

Non-default configuration is pretty basic:

  • server: username, zonesdir for daemon to chdir into; verbosity, statistics to show in logs once a day; and last hide-version, refuse-any minimal security settings.
  • verify: to verify each zone
  • remote-control: to locally control the daemon via nsd-control tool.
  • zone: zone for my personal domain
Zone
  cat /etc/nsd/zones/costan.ro
;## NSD authoritative only DNS
;## FORWARD Zone - costan.ro

$ORIGIN costan.ro.       ; default zone domain
$TTL 3600                ; default time to live - 1h

@ IN SOA ns.costan.ro. admin.costan.ro. (
           2022072902  ; Serial number
           3600        ; Refresh - 1h
           600         ; Retry - 10m
           1209600     ; Expire - 2w
           3600        ; Min TTL - 1h
           )

@       IN   NS    ns.costan.ro.
@       IN   MX    5 smtp.costan.ro.

@       IN   A     86.124.145.184
ns      IN   A     86.124.145.184
smtp    IN   A     86.124.145.184
rig     IN   A     86.124.145.184

blog          IN  CNAME   icostan.gitlab.io.
deribase      IN  CNAME   rig.costan.ro.
gasherbrum2   IN  CNAME   dimensional-ant-eqt6rarkgtemacbmeid10exz.herokudns.com.
huveragy      IN  CNAME   icostan.github.io.
imap          IN  CNAME   rig.costan.ro.
mail          IN  CNAME   rig.costan.ro.
www           IN  CNAME   rig.costan.ro.

@                                     IN  TXT     "v=spf1 a mx ip4:86.124.145.184 ~all"
smtp                                  IN  TXT     "v=spf1 a mx ip4:86.124.145.184 ~all"
_dmarc                                IN  TXT     "v=DMARC1; p=quarantine; rua=mailto:postmaster@costan.ro; ruf=mailto:forensic@costan.ro; adkim=s; aspf=s; fo=1; pct=25"
rig._domainkey                        IN  TXT     "v=DKIM1; k=rsa; s=email; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQFlti46dceD5rk3+RGnoYStK6np+cIucrOrkMHbjoRLcOxNikOfi0ABgG2CxK/0X+VNmiL5PsaWWnXhYGOJWz82LM0zhDzoD1bQ0OIb/PWyPMz22udwnPa6FRypEEnjAdC6c8g7tX8fMovqX/09PHKKjLq4zX0X3CMT+t3QhXlQIDAQAB"
_gitlab-pages-verification-code.blog  IN  TXT     "gitlab-pages-verification-code=3b0fe70a3b7910b2760d02227529e240"
_acme-challenge                       IN  TXT     "PDFYVU1v3Jlo6Yeo50-LHiokC8PwNeZl_-FCz13CKPs"

;## NSD authoritative only DNS
;## FORWARD Zone - costan.ro

1.3 Tuning

Enable TCP Fast Open as suggested by NSD daemon on start.

  echo "net.ipv4.tcp_fastopen=2" > /etc/sysctl.d/30-nsd.conf

1.4 Service

Start/enable the daemon, allow port 53 in firewall and this is it.

  systemctl start nsd
  systemctl enable nsd
  ufw limit "DNS"

1.5 Testing

The easiest test is to query the DNS server directly:

  drill rig.costan.ro @ns.costan.ro
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 11080
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; rig.costan.ro.	IN	A

;; ANSWER SECTION:
rig.costan.ro.	3555	IN	A	86.124.145.184

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 1 msec
;; SERVER: 86.124.145.184
;; WHEN: Sun Aug 28 11:25:50 2022
;; MSG SIZE  rcvd: 47

Or use these 3rd party tools to look deeper into DNS config and security.

2. Optional (highly recommended) DNS extensions

By default DNS protocol is old and pretty basic, just a giant hashmap that maps a domain name (e.g. costan.ro) to an IP address (e.g. 86.124.145.184) but there are other extensions for data integrity, privacy, etc.

2.1 DNSSEC for authentication and integrity

Generate ZSK keypair

This will be used to sign the zone file and for now I am OK with RSASHA256 standard algorithm, see RSA vs ECDSA in DNSSEC for details.

  ldns-keygen -a RSASHA256 -b 1024 costan.ro
  ls -l /etc/nsd/zones/Kcostan.ro*
-rw-r--r-- 1 nsd  nsd   239 Jul 29 11:48 /etc/nsd/zones/Kcostan.ro.+008+03304.key
-rw------- 1 root root  939 Jul 29 11:48 /etc/nsd/zones/Kcostan.ro.+008+03304.private
Generate KSK keypair

This will be used to sign the key file generated above, notice larger keysize for stronger security.

  ldns-keygen -k -a RSASHA256 -b 2048 costan.ro
  ls -l /etc/nsd/zones/Kcostan.ro*
-rw-r--r-- 1 nsd  nsd   239 Jul 29 11:48 /etc/nsd/zones/Kcostan.ro.+008+03304.key
-rw------- 1 root root  939 Jul 29 11:48 /etc/nsd/zones/Kcostan.ro.+008+03304.private
-rw-r--r-- 1 nsd  nsd    97 Jul 29 12:40 /etc/nsd/zones/Kcostan.ro.+008+19957.ds
-rw-r--r-- 1 nsd  nsd   412 Jul 29 11:49 /etc/nsd/zones/Kcostan.ro.+008+19957.key
-rw------- 1 root root 1703 Jul 29 11:49 /etc/nsd/zones/Kcostan.ro.+008+19957.private
Sign zone

This will digitally sign each record in plaintext zone and output the signed zone file plus the public keys above.

  ldns-signzone -n -p -i 20220729174119 -e 20230729174119 costan.ro Kcostan.ro.+008+03304 Kcostan.ro.+008+19957
  ls -l /etc/nsd/zones/costan.ro*
-rw-r--r-- 1 nsd nsd  1773 Jul 29 20:39 /etc/nsd/zones/costan.ro
-rw-r--r-- 1 nsd nsd 13869 Aug 28 11:08 /etc/nsd/zones/costan.ro.signed
Configuration

Update nsd.conf file to use the signed zone file instead of the plaintext one.

  grep -B 2 costan.ro.signed /etc/nsd/nsd.conf
zone:
	name: "costan.ro"
	zonefile: "costan.ro.signed"
Service

Trigger daemon reconfig and reload zone file.

  nsd-control reconfig
  nsd-control reload costan.ro
Test DNSSEC keys

After reload we need to check if DNSKEY records are published and propagated. These are the public ZSK, KSK keys used by DNS resolvers to verify and authenticate the records.

  drill -D costan.ro DNSKEY
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 3888
;; flags: qr rd ra ad ; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; costan.ro.	IN	DNSKEY

;; ANSWER SECTION:
costan.ro.	3564	IN	DNSKEY	257 3 8 AwEAAeM6ahMDg1TJ2enWZGaZxMarqrdZIqGm0xqnqR/4rr1LFYlY9M9cgHpLx++sqFPH6OWfbP/P5L8Y9k1GWHLp68HKRSuGljlVaKlStoauk+PCk83SNbp0btJQdFSqzuxNOPppMrhthd4yHsIGzTwy2h+qkyT/EYReV+IwAISvw9PJH3xj7XtG+3mvrs/WqrXqfXb4y1+jzbv3GJL2RCsDpUM3Cut3QTNrjqTJsc48wz/wu0HvXAnlCnyLTL2fJ69Bjf4hFJaiggvje2cTWxWixdUjiSPuBWRQcu/H5konkxtqV4eZR8DiLy7+mKZZUkKMPxTCUZ50qgtmNdLlRNs28zk= ;{id = 19957 (ksk), size = 2048b}
costan.ro.	3564	IN	DNSKEY	256 3 8 AwEAAckIPA6ENdhhPjlKEJo/57IC7MzfcuWRkS40wXKSKNh8nZyYVg9K92Kr5SgAD1kSAnaE4eFTOXZgYBE97eS6lBcljw0iWyPOkQZXaatSCduHCIrMbSg7xGjXeQzAiD8YOVbS4X0881h3Gi919zmiZ5tDTmNpHfxAKabEJXv6IfYL ;{id = 3304 (zsk), size = 1024b}
costan.ro.	3564	IN	RRSIG	DNSKEY 8 2 3600 20230729174119 20220729174119 19957 costan.ro. gbwkTvxq94RLUrn+JyQeEXNvs1g5ucF3b+UHsCTJX71oFw/nend/tbRpSyYG/V61YBXo5Z4qT+LazY1wwyaCw5lxz0gnm/ZWyXMAZPYwX0k4yRLhVGIs5sDxL+Qy4MPcvgze4APF+WSmCZBF3hqfiB0J/6in0BnoBPyJmPWd/vM5QL4hue1EmJuu2fgDLvCloUNPcIfVoFau32WjRYgMwTVZyLK22FE6edAPAuPXNvHFnsx0hIJlexdTWyGEGF2IzMEd46DwCzPXnjbYywTV/WSmSJssZT2kQF+uVS1No9PvSQve75KIyusGcT+UFiGbZvV3tH8fIeE0oFCcvYh8wA==

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 0 msec
;; EDNS: version 0; flags: do ; udp: 1232
;; SERVER: 127.0.0.1
;; WHEN: Sun Aug 28 11:20:15 2022
;; MSG SIZE  rcvd: 759
Chain of trust

Generate DS (Delegation Signer) record for our signed zone that allows:

  • DNS resolvers know that my domain is DNSSEC-enabled.
  • transfer of trust from a trusted parent zone (ro.) and my domain (costan.)
  ldns-key2ds costan.ro.signed
  cat /etc/nsd/zones/Kcostan.ro.+008+19957.ds
costan.ro.	3600	IN	DS	19957 8 2 083b1f4914402506d842029241041cc5869fd91c1887f41fb73a832fc78bbb8c

Update DS record in registrar (rotld.ro is the top-level registrar for ro. TLD zone).

  drill costan.ro DS
;; ->>HEADER<<- opcode: QUERY, rcode: NOERROR, id: 57649
;; flags: qr rd ra ; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;; costan.ro.	IN	DS

;; ANSWER SECTION:
costan.ro.	35005	IN	DS	19957 8 2 083b1f4914402506d842029241041cc5869fd91c1887f41fb73a832fc78bbb8c

;; AUTHORITY SECTION:

;; ADDITIONAL SECTION:

;; Query time: 0 msec
;; SERVER: 127.0.0.1
;; WHEN: Sun Aug 28 11:27:22 2022
;; MSG SIZE  rcvd: 75

2.2 DNS over TLS for privacy

TBD: once I solve one of the following issues:

  • automate wildcard certificate issuance with Let's Encrypt
  • figure out and configure DANE

Updates

  • 2022-07-29 - initial blog post
  • 2022-08-28 - increase sig expiry time, use drill instead of dig
  • 2022-12-19 - acme challenge as TXT record, resign zone