Enable and start service:
rcctl enable nsd
rcctl start nsd
Create control keys:
# crypto partition (optional)
mkdir /mnt/nsd
chmod 750 /mnt/nsd
ln -s /mnt/nsd/nsd_control.key /var/nsd/etc/nsd_control.key
ln -s /mnt/nsd/nsd_server.key /var/nsd/etc/nsd_server.key
# create keys
nsd-control-setup
# crypto partition (optional)
chgrp _nsd /mnt/nsd/*.key
cat <<EOF> /var/nsd/etc/nsd.conf
server:
hide-version: yes
verbosity: 1
database: ""
username: _nsd
remote-control:
control-enable: yes
control-interface: /var/run/nsd.sock
zone:
name: "example.com"
zonefile: "/var/nsd/etc/example.com.zone"
notify: <secondary dns server> NOKEY
provide-xfr: <secondary dns server> NOKEY
zone:
name: "example.org"
zonefile: "/var/nsd/etc/example.org.zone"
notify: <secondary dns server> NOKEY
provide-xfr: <secondary dns server> NOKEY
EOF
chmod 640 /var/nsd/etc/nsd.conf
cat <<EOF> /var/nsd/etc/example.com.zone
$ORIGIN example.com.
$TTL 3600
@ IN SOA ns.example.com. hostmaster.example.com. (
1535663699
3600
1200
604800
600 )
IN NS ns.example.com.
IN NS ns2.example.com.
IN MX 10 mail.example.com.
IN CAA 128 issue "letsencrypt.org"
; add to deny wildcard certificates
;IN CAA 128 issuewild ";"
www IN A 198.51.100.1
dyn IN A 198.51.100.1
EOF
This is based on https://github.com/exitnode/nsd-dyndns
cat <<'EOF'> /etc/httpd.conf
domain="ns.example.com"
server $domain {
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location "/*" {
block return 301 "https://$SERVER_NAME$DOCUMENT_URI"
}
}
server $domain {
listen on * tls port 443
tls {
certificate "/etc/ssl/ns.example.com.fullchain.pem"
key "/etc/ssl/private/ns.example.com.key"
ciphers ECDHE-RSA-CHACHA20-POLY1305:CHACHA20:ECDHE+AESGCM:ECDHE+AES
}
hsts { max-age 31536000, subdomains }
root "/htdocs/dyndns"
location "/robots.txt" {
root "/htdocs"
}
location "/update.html" {
log access dyndns.log
}
}
EOF
cat <<EOF> /var/www/htdocs/robots.txt
User-agent: *
Disallow: /
EOF
mkdir /var/www/htdocs/dyndns
touch /var/www/htdocs/dyndns/update.html
rcctl enable httpd
rcctl start httpd
Create config directory: mkdir -m 700 /etc/dyndns.d
Example A Record:
cat <<EOF> /etc/dyndns.d/dyn.conf
key="$(openssl rand -hex 16)"
init="127.0.0.1"
logfile=/var/www/logs/dyndns.log
timestamp=\$(date +%s)
zonefile=/var/nsd/etc/example.com.zone
ttl=60
subdomain="dyn"
rtype=A
dnssec=0
EOF
chmod 600 /etc/dyndns.d/dyn.conf
Example TXT Record for ACME DNS01 challenge:
cat <<EOF> /etc/dyndns.d/acme.conf
key="$(openssl rand -hex 16)"
logfile=/var/www/logs/dyndns.log
timestamp=\$(date +%s)
zonefile=/var/nsd/etc/example.com.zone
ttl=600
subdomain="_acme-challenge"
rtype=TXT
dnssec=0
EOF
chmod 600 /etc/dyndns.d/acme.conf
Create update script:
cat <<'EOF'> /usr/local/sbin/dyndns-update
#!/bin/sh
i=0
for conf in $(find /etc/dyndns.d -name "*.conf" -type f); do
. ${conf}
changed=0
record="${subdomain} ${ttl} IN ${rtype}"
case ${rtype} in
A)
data=$(grep ${key} ${logfile} | tail -1 | cut -d" " -f2)
if [ -z "${data}" ]; then
grep -q "${subdomain} .*${rtype}" ${zonefile} && continue
echo "${record} ${init}" >>${zonefile}; changed=1
else
grep -q "${record} ${data}" ${zonefile} && continue
sed -i "s/^${subdomain} .*${rtype}.*/${record} ${data}/" ${zonefile}; changed=1
fi
;;
TXT)
data="\"$(grep -o "${key}=[^ ]*" ${logfile} | tail -1 | cut -d"=" -f2)\""
if [ "${data}" = '""' ]; then
grep -q "${subdomain} .*${rtype}" ${zonefile} || continue
sed -i "/^${subdomain} .*${rtype}/d" ${zonefile}; changed=1
else
grep -q "${record} ${data}" ${zonefile} && continue
echo "${record} ${data}" >>${zonefile}; changed=1
fi
;;
esac
if [[ ${changed} -eq 1 ]]; then
sed -i "4s/.*/${timestamp}/" ${zonefile}
[[ ${dnssec} -eq 1 ]] && /usr/local/sbin/sign-zone ${zonefile}
fi
logger -t dyndns-update ${conf##*/} ${rtype} set to ${data}
i=$((i+1))
done
[ $i -ne 0 ] && { nsd-control reload >/dev/null; nsd-control notify >/dev/null; }
EOF
chmod 755 /usr/local/sbin/dyndns-update
Extract the key from the config file: grep key /etc/dyndns.d/dyn.conf
Replace <KEY> with the key in your config file.
On OpenBSD
echo "*/5 * * * * /usr/bin/ftp -o /dev/null https://ns.example.com/update.html?<KEY> >/dev/null" >> /var/cron/tabs/nobody
chown nobody:crontab /var/cron/tabs/nobody
chmod 600 /var/cron/tabs/nobody
On Linux
echo "*/5 * * * * /usr/bin/curl -s https://ns.example.com/update.html?<KEY> >/dev/null" >> /var/cron/tabs/nobody
chown nobody:crontab /var/spool/cron/crontabs/nobody
chmod 600 /var/spool/cron/crontabs/nobody
Replace <KEY> with the key in your config file.
Replace <YOUR TXT CONTENT> with the value you want to have in the TXT record.
On OpenBSD
echo "*/5 * * * * /usr/bin/ftp -o /dev/null https://ns.example.com/update.html?<KEY>=<YOUR TXT CONTENT> >/dev/null" >> /var/cron/tabs/nobody
chown nobody:crontab /var/cron/tabs/nobody
chmod 600 /var/cron/tabs/nobody
On Linux
echo "*/5 * * * * /usr/bin/curl -s https://ns.example.com/update.html?<KEY>=<YOUR TXT CONTENT> >/dev/null" >> /var/spool/cron/crontabs/nobody
chown nobody:crontab /var/spool/cron/crontabs/nobody
chmod 600 /var/spool/cron/crontabs/nobody
pkg_add ldns-utils
mkdir -p /var/nsd/etc/keys
# crypto partition (optional)
mkdir -p /mnt/nsd/keys
chmod 750 /mnt/nsd/keys
ln -s /mnt/nsd/keys /var/nsd/etc/keys
cd /var/nsd/etc/keys
domain=example.com
zsk=$(ldns-keygen -a RSASHA256 -b 2048 ${domain})
mv ${zsk}.key ${zsk%%.+*}.zsk.key
mv ${zsk}.private ${zsk%%.+*}.zsk.private
ksk=$(ldns-keygen -a RSASHA256 -b 2048 -k ${domain})
mv ${ksk}.key ${ksk%%.+*}.ksk.key
mv ${ksk}.ds ${ksk%%.+*}.ksk.ds
mv ${ksk}.private ${ksk%%.+*}.ksk.private
You need to supply the contents of Kexample.com.ksk.ds and Kexample.com.ksk.key to your domain registry.
cat <<'EOF'> /usr/local/sbin/sign-zone
#!/bin/sh
zonefile="$1"
[[ -z "${zonefile}" ]] && { echo "missing zonefile argument"; exit 1; }
domain=${zonefile##*/}
domain=${domain%.zone}
valid=6
hashes=24
dir="/var/nsd/etc"
keys="${dir}/keys"
zone="${dir}/${domain}.zone"
signed="${zone}.signed"
temp="${dir}/tmp"
now=$(date +%s)
sed -i "4s/.*/${now}/" ${zone}
/usr/local/bin/ldns-read-zone ${zone} >${temp}
cat ${keys}/K${domain}.ksk.ds ${keys}/K${domain}*.key >>${temp}
exp=$(($now+$valid * 24 * 3600))
/usr/local/bin/ldns-signzone -i $now -e $exp \
-n -s $(openssl rand -hex 8) -t ${hashes} -o ${domain} \
-f ${signed} ${temp} ${keys}/K${domain}.ksk ${keys}/K${domain}.zsk
rm ${temp}
EOF
chmod 755 /usr/local/sbin/sign-zone
cat <<'EOF'> /usr/local/sbin/dnssec-update
#!/bin/sh
[[ $# -eq 0 ]] && { echo "missing domain arguments"; exit 1; }
for domain in $@; do
/usr/local/sbin/sign-zone ${domain}
done
nsd-control reload >/dev/null
nsd-control notify >/dev/null
EOF
chmod 755 /usr/local/sbin/dnssec-update
echo "0 2 * * * /usr/local/sbin/dnssec-update example.com example.net >/dev/null 2>&1" >> /var/cron/tabs/root
Test your DNSSEC domain: https://dnssec-analyzer.verisignlabs.com/
ssh server "ssh-keyscan -D 127.0.0.1 2>/dev/null" | grep -E "SSHFP 1 2|SSHFP 4 2"