PowerDNS seems pretty flexible. I’m just looking for a way to serve my own DNS since my registrar doesn’t support SSHFP records among other things. Oh, also I wanted to be able to get a wildcard cert from LetsEncrypt, so I’d need to do a DNS-01 challenge.
I’m just playing around with PowerDNS for now, since I’m actually pretty bad in terms of understanding how DNS works.
Links
- Homepage
- https://www.powerdns.com/powerdns-authoritative-server
- Source
- https://github.com/PowerDNS/pdns/
- Documentation
- https://doc.powerdns.com/authoritative/indexTOC.html
The Nix package is powerdns
, so you can try:
$ nix-shell -p powerdns $ pdns_server --help syntax: --8bit-dns | --8bit-dns=yes | --8bit-dns=no Allow 8bit dns queries --allow-axfr-ips=... Allow zonetransfers only to these subnets --allow-dnsupdate-from=... A global setting to allow DNS updates from these IP ranges. --allow-notify-from=... Allow AXFR NOTIFY from these IP ranges. If empty, drop all incoming notifies. --allow-unsigned-autoprimary=... Allow autoprimaries to create zones without TSIG signed NOTIFY --allow-unsigned-notify=... Allow unsigned notifications for TSIG secured zones
Configuration
I wanted to set up a pretty simple example to play around with PowerDNS without being root.
By default PowerDNS is going to try to load stuff from /etc/pdns/pdns.conf
, and I don’t really want to edit a file there.
Instead, I want to try modifying some stuff in some directory I control.
So, to do this, I load configuration from the current directrory like so:
pdns_server "--socket-dir=$(pwd)" "--config-dir=$(pwd)/"
Then you’ll need to have a pdns.conf
in this directory.
Here’s a basic one that tries to load a BIND
configuration:
# A super basic and bad PowerDNS config
launch=bind
bind-config=named.conf
local-address=127.0.0.1
local-port=5300
webserver=yes
webserver-port=8989
webserver-address=127.0.0.1
webserver-allow-from=127.0.0.1,::1
log-dns-details
log-dns-queries
loglevel=5
# This is just so I filter out timestamps when posting stuff online
log-timestamp=no
Logging here was pretty helpful for figuring out what’s going wrong.
I struggled for 20 minutes because it turned out that the bind backend wasn’t loading my config and just silently failing.
This caused pdns
to think it wasn’t authoritative for some jmm.example
domain I was trying.
Here’s the named.conf
:
zone "jmm.example" IN {
type master;
file "jmm.example.zone";
allow-update { none; };
};
And here’s jmm.example.zone
:
$TTL 86400
@ IN SOA ns1.jmm.example. admin.jmm.example. (
2024071110 ; serial number
3600 ; refresh
1800 ; retry
604800 ; expire
86400 ) ; minimum
; Name servers
jmm.example. IN NS ns1.jmm.example.
jmm.example. IN NS ns2.jmm.example.
ns1 IN A 192.0.2.1
ns2 IN A 192.0.2.2
@ IN NS jmm.example.
@ IN A 127.0.0.1
@ IN AAAA ::1
www IN A 127.0.0.5
www IN TXT jmm=Josh Moller-Mara
www IN TXT hello=World
Now, to actually start running pdns_server
here, I do:
[nix-shell]$ pdns_server "--socket-dir=$(pwd)" "--config-dir=$(pwd)/" Loading '/nix/store/lf8642l65iigdjr57lm8kv1cjb59ir2h-pdns-4.8.4/lib/pdns/libbindbackend.so' This is a standalone pdns Listening on controlsocket in '/░░░░░░░░░░░░░░░░░░░░░/powerdns/pdns.controlsocket' UDP server bound to 127.0.0.1:5300 TCP server bound to 127.0.0.1:5300 PowerDNS Authoritative Server 4.8.4 (C) 2001-2022 PowerDNS.COM BV Using 64-bits mode. Built using gcc 12.3.0. PowerDNS comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it according to the terms of the GPL version 2. [webserver] Listening for HTTP requests on 127.0.0.1:8989 Polled security status of version 4.8.4 at startup, no known issues reported: OK [bindbackend] Parsing 1 domain(s), will report when done [bindbackend] Done parsing domains, 0 rejected, 1 new, 0 removed Creating backend connection for TCP About to create 3 backend threads for UDP Done launching threads, ready to distribute questions
Then, to query pdns
, I do:
$ dig +norecurse ANY www.jmm.example @127.0.0.1 -p 5300 ; <<>> DiG 9.18.24 <<>> +norecurse ANY www.jmm.example @127.0.0.1 -p 5300 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 20717 ;; flags: qr aa; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1232 ;; QUESTION SECTION: ;www.jmm.example. IN ANY ;; ANSWER SECTION: www.jmm.example. 86400 IN TXT "hello=World" www.jmm.example. 86400 IN TXT "jmm=Josh Moller-Mara" www.jmm.example. 86400 IN A 127.0.0.5 ;; Query time: 1 msec ;; SERVER: 127.0.0.1#5300(127.0.0.1) (TCP) ;; WHEN: Thu Jul 11 ░░░░░░░░ PDT 2024 ;; MSG SIZE rcvd: 117
PostgreSQL backend
See the documentation for the generic PostgreSQL backend. A schema for the PostgreSQL backend is at https://github.com/PowerDNS/pdns/blob/master/modules/gpgsqlbackend/schema.pgsql.sql.
NixOS
NixOS already has a services.powerdns
option, but here’s a modified version of basically that:
{
config,
lib,
pkgs,
...
}:
with lib;
let
cfg = config.services.jmm-powerdns;
configOut = pkgs.runCommand "pdnsconf" {} ''
mkdir -p $out
cp ${./pdns.conf} $out/pdns.conf
cp ${./named.conf} $out/named.conf
cp ${./jmm.example.zone} $out/jmm.example.zone
'';
in
{
options = {
services.jmm-powerdns = {
enable = mkEnableOption "JMM’s PowerDNS domain name server";
};
};
config = mkIf cfg.enable {
# This sets the default `/etc/systemd/system/pdns.service`, which you probably want otherwise.
# If you use this, other settings are created via an override.
# systemd.packages = [ pkgs.pdns ];
systemd.services.pdns-jmm = {
description = "JMM PowerDNS authoritative setup";
wantedBy = [ "multi-user.target" ];
after = [
"network.target"
"network-online.target"
"postgresql.service"
];
wants = [ "network-online.target" ];
serviceConfig = {
Type = "notify";
Restart = "on-failure";
RestartSec = "1";
# StartLimitInterval = "0";
RuntimeDirectory = "pdns";
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
CapabilityBoundingSet = "CAP_NET_BIND_SERVICE";
User = "pdns";
Group = "pdns";
LockPersonality = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateTmp = true;
ProtectHome = true;
ProtectClock = true;
ProtectControlGroups = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
ProtectSystem = "full";
RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6";
RestrictSUIDSGID = true;
SystemCallArchitecture = "native";
SystemCallFilter = "~ @clock @debug @module @mount @raw-io @reboot @swap @cpu-emulation @obsolete";
ProtectProc = "invisible";
TasksMax = "50";
MemoryMax = "512M";
WorkingDirectory = "${configOut}";
ExecStart = [
# "${pkgs.pdns}/bin/pdns_server --config-dir=${configOut} --guardian=no --daemon=no --disable-syslog --log-timestamp=no --write-pid=no"
"${pkgs.pdns}/bin/pdns_server --config-dir=${configOut} --guardian=no --daemon=no --log-timestamp=no --write-pid=no"
];
};
};
users.users.pdns = {
isSystemUser = true;
group = "pdns";
description = "PowerDNS";
};
users.groups.pdns = { };
};
}
Misc
- DNS “white lies” is called “narrow mode”. See https://blog.apnic.net/2023/01/17/subdomain-enumeration-with-dnssec/.
- Julia Evans’s “Mess with DNS” tool uses PowerDNS: https://jvns.ca/blog/2024/08/19/migrating-mess-with-dns-to-use-powerdns/