Actions

PowerDNS: use dnsdist to send NOTIFY packets to hidden primary

From falz.net

Overview

This shows how to use dnsdist, which is a dns proxy from the good folks at PowerDNS

The use case here is a DNS layout which has 3 servers

  • ns0 - hidden primary (formerly known as master).
  • ns1 / ns2 - nameservers in use, listed on whois records.

This is using the MySQL backend with MariaDB replication, so ns0 is the only one that can write to the database. ns1/ns2 intentionally have read only MySQL passwords.


The Issue

This all would work fine if all zones you host are NATIVE powerdns format, in that you use MySQL replication to get data to sync between servers. The issue arises if you are SECONDARY (formerly known as slave) to zones, where the PRIMARY is out of your control.

Primary servers will follow RFC1996 and send NOTIFY packets to ns1/ns2 as they're the NS records on the domain. Those servers are ready only, so what can they do with said packets?


The Fix

dnsdist can be setup EDNS Client Subject (ECS) extensions to forward the packets to the hidden primary. Without ECS, the packets are not spoofed so stats on the target server will just show a lot of source packets coming from your nameservers, NOT the original source.


Thanks to everyone in the PowerDNS IRC channel to help figure this all out. (Yes IRC still exists)


Our Layout is

  • ns0 - powerdns running natively on port 53. The primary DNS server must allow AXFR from this IP as we're not doing anything special to reroute this request back via ns1/ns2.
  • ns1 / ns2 - powerdns running on port 5353, i've left this on the public ip but firewalled off so we can query it directly, remotely. dnsdist is running on 53.


PowerDNS Config

PowerDNS is running on a nonstandard port on the public nameservers ns1/ns2. primary=yes is still set here as it has to send DNS notifies on zones where we're PRIMARY (not NATIVE or SECONDARY), which isn't covered here. secondary=no is set as it should not process NOTIFY requests.


  • Relevant contents of /etc/pdns/pdns.conf: on ns1/ns2
local-port=5353
edns-subnet-processing=yes
trusted-notification-proxy=10.20.108.53, 2001:db80:108:1::53, 10.30.163.53, 2001:db80:2:163::53
primary=yes
secondary=no 


dnsdist Config

This dnsdist config is slightly longer than it may need to be, but I wanted to keep ipv4 and ipv6 traffic separate. By default, pools can send ipv6 clients to an ipv4 pool and vice versa. I wanted to have better tabs on what real level of ipv4/ipv6 traffic was coming in.


I find the web interface to be incredibly useful to troubleshoot real time as well, so left that part in as well.


  • Entire contents of /etc/dnsdist/dnsdist.conf. This may be harder to read as we're using Lua variables which makes it easier to copy between servers. (variable string concatenation in lua is the .. ':' .. stuff)
------------------------------------------
-- BEGIN CONFIG OPTIONS
my_v4 = "10.20.163.53"
my_v6 = "[2001:0db8:163::53]"
my_port_dnsdist = "53"
my_port_powerdns = "5353"
my_port_web = "8083"
my_pass_web_user = "<redacted>"
my_pass_web_api = "<redacted>"
my_web_sources = "10.10.47.0/24, !192.0.2.1"
-- where is the real ns0 server
ns0_v4 = "10.20.18.53"
ns0_v6 = "[2001:0db8:18::53]"
-- END CONFIG OPTIONS
------------------------------------------
 
 
------------------------------------------
-- STATIC SETTINGS, KEEP SAME ON NS1/NS2

-- which IPs may query us, all types
setACL({'0.0.0.0/0', '::/0'})
 
setLocal(my_v4 .. ':' .. my_port_dnsdist)
addLocal(my_v6 .. ':' .. my_port_dnsdist)
 
-- Webserver has useful stats, comment to not use it
setWebserverConfig({password=my_pass_web_user, apiKey=my_pass_web_api, acl=my_web_sources, statsRequireAuthentication=TRUE})
webserver(my_v4 .. ':' .. my_port_web)
 
-- load balance policy to use. Doesn't do much if only a single server in pool, if commented it uses "leastOutstanding"
-- setServerPolicy(firstAvailable)
 
-- local powerdns service
newServer({address=my_v4 .. ':' .. my_port_powerdns, source=my_v4, name='self_v4', pool='self_v4', order=1})
newServer({address=my_v6 .. ':' .. my_port_powerdns, source=my_v6, name='self_v6', pool='self_v6', order=1})
 
-- create "ns0" server pool, which supports ECS. last 3 flags below enable ECS from dnsdist
newServer({address=ns0_v4 .. ':53', source=my_v4, name="ns0_v4", pool="ns0_v4", useClientSubnet=true, setECSSourcePrefixV4(32), setECSSourcePrefixV6(128) })
newServer({address=ns0_v6 .. ':53', source=my_v6, name="ns0_v6", pool="ns0_v6", useClientSubnet=true, setECSSourcePrefixV4(32), setECSSourcePrefixV6(128) })
 
 
------------------------------------------
-- BEGIN NOTIFY REQUESTS
 
-- rule to send ALL NOTIFY packets. Here as a backup since we're using the next rule
addAction(AndRule({
    makeRule('0.0.0.0/0'),
    OpcodeRule(DNSOpcode.Notify),
    }),
    PoolAction('ns0_v4')
)
addAction(AndRule({
    makeRule('::/0'),
    OpcodeRule(DNSOpcode.Notify),
    }),
    PoolAction('ns0_v6')
)
 
-- END NOTIFY REQUESTS
------------------------------------------
 
------------------------------------------
-- NORMAL TRAFFIC leave at end of file
 
addAction(makeRule('0.0.0.0/0'), PoolAction('self_v4'))
addAction(makeRule('::/0'),      PoolAction('self_v6'))
 
-- these should never get hit, but just in case
addAction(AllRule(), PoolAction('self_v4'))
addAction(AllRule(), PoolAction('self_v6'))