DNS and DNSSEC

TSIG for zone transfers and key rotation, the secure way

The TSIG secret in your DNS config was generated years ago by someone who has since left, it is hmac-md5, and three servers, a backup and a wiki page know it. Rotating it feels risky, so nobody does.

The short answer

Use one hmac-sha256 TSIG key per primary and secondary pair, named with a version. On the primary, allow transfer only when both the source address and the key match. Rotate by overlap: the primary accepts old and new keys, the secondary switches, NOTIFY switches, then the old key is removed and tested as refused.

Updated Houssam Hammoudi, CTOTested with Knot DNS 3.5.4

On this page
  1. What goes wrong
  2. What the docs say
  3. The secure configuration
  4. Prove it
  5. Mistakes people make
  6. Checklist

What goes wrong

A zone transfer (AXFR or IXFR) copies the whole zone from a primary to a secondary. NOTIFY tells the secondary to fetch a new version. Without authentication, anyone who can reach the primary can dump the zone, and anyone can send NOTIFY to make secondaries poll.

TSIG adds a signature to each DNS message with a shared secret (an HMAC). It proves the message came from a host that knows the key, and it is only valid within a short time window.

The usual problems are old keys and no rotation. Many deployments still use hmac-md5, one key shared by every server and every tool, and the same secret for years. When a laptop or a backup with that key leaks, the only fix is a rotation that nobody has practiced.

What the docs say

It should be possible for more than one key to be in simultaneous use among a set of interacting hosts. This allows for periodic key rotation as per best operational practices

Source: RFC 8945, Section 4.2

The secret SHOULD be at least as long as the keyed hash output

Source: RFC 8945, Section 8

This value MUST be exactly the same as the name of the TSIG key on the opposite primary/secondary server(s).

Source: Knot DNS 3.5 reference, key section

RFC 8945 marks HMAC-MD5 as MUST NOT use and hmac-sha1 as NOT RECOMMENDED. It allows several keys at once, but neither the RFC nor the Knot docs give a rotation procedure. The Knot ACL reference does say the address and key of a rule must both match, which is what makes a leaked key alone useless against Knot. PowerDNS behaves differently: a TSIG key there skips the IP check (see the hidden primary page).

The secure configuration

Generate one key per primary and secondary pair. Put a version in the name, so two keys can exist side by side during a rotation:

bash
keymgr -t xfr-2026a hmac-sha256   # 256-bit random secret, as long as the hash output

Primary, /etc/knot/knot.conf:

yaml
key:
  - id: xfr-2026a                 # must match the name on the secondary exactly
    algorithm: hmac-sha256        # RFC 8945: RECOMMENDED; never hmac-md5 or hmac-sha1
    secret: <base64 secret>

remote:
  - id: secondary
    address: 198.51.100.20@53
    key: xfr-2026a                # signs outgoing NOTIFY

acl:
  - id: secondary_xfr
    address: 198.51.100.20        # the secondary's address AND ...
    key: xfr-2026a                # ... the key: both must match
    action: transfer

zone:
  - domain: example.com
    notify: secondary
    acl: secondary_xfr

Secondary:

yaml
key:
  - id: xfr-2026a
    algorithm: hmac-sha256
    secret: <base64 secret>

remote:
  - id: primary
    address: 203.0.113.10@53
    via: 198.51.100.20            # source address the primary's ACL expects
    key: xfr-2026a                # signs SOA checks, AXFR and IXFR

acl:
  - id: primary_notify
    address: 203.0.113.10
    key: xfr-2026a
    action: notify

zone:
  - domain: example.com
    master: primary
    acl: primary_notify

Keep secrets out of the main file so the config can live in git:

yaml
include: /etc/knot/keys/xfr.conf   # mode 0640, owner root, group knot; not in git

Rotation from xfr-2026a to xfr-2026b. Each step is a config change and knotc reload; nothing restarts, and no transfer fails:

text
1. Both sides:  add key xfr-2026b to the key section.
2. Primary:     acl secondary_xfr key: [xfr-2026a, xfr-2026b]
3. Secondary:   remote primary key: xfr-2026b
                acl primary_notify key: [xfr-2026a, xfr-2026b]
4. Primary:     remote secondary key: xfr-2026b   (NOTIFY now signed with B)
                acl secondary_xfr key: xfr-2026b  (A no longer accepted)
5. Secondary:   acl primary_notify key: xfr-2026b
6. Both sides:  delete key xfr-2026a; test that it is refused.

Prove it

The test (secure-tests/tsig-zone-transfers-key-rotation/run.sh) runs two Knot instances in one container. The primary listens on 127.0.0.1, the secondary on 127.0.0.2.

Key generation:

bash
keymgr -t xfr-2026a hmac-sha256
text
# hmac-sha256:xfr-2026a:9aGx64MjijfVY6QcNT5R62MGWBP0zQY7oVNyDJIMDYw=
key:
  - id: xfr-2026a
    algorithm: hmac-sha256
    secret: 9aGx64MjijfVY6QcNT5R62MGWBP0zQY7oVNyDJIMDYw=

With key A, the secondary transfers the zone and both logs name the key:

text
secondary: info: [example.com.] AXFR, incoming, remote 127.0.0.1@53 TCP, key xfr-2026a., finished, remote serial 1, 0.00 seconds, 1 messages, 286 bytes
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026a., zone updated, 0.00 seconds, serial none -> 1, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@36931 TCP, key xfr-2026a., started, serial 1
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@36931 TCP, key xfr-2026a., finished, 0.00 seconds, 1 messages, 286 bytes

No key, a wrong secret, and the right key from the wrong address are all refused:

bash
kdig @127.0.0.1 example.com AXFR +tcp
kdig @127.0.0.1 example.com AXFR -y hmac-sha256:xfr-2026a:<wrong secret>
kdig @127.0.0.1 -b 127.0.0.9 example.com AXFR -y hmac-sha256:xfr-2026a:<secret A>
text
;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)

;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)

;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)

Overlap: the primary accepts A and B, the secondary switches to B. The next change transfers with key B:

text
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., remote serial 2, zone is outdated
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., zone updated, 0.00 seconds, serial 1 -> 2, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@41301 TCP, key xfr-2026b., finished, 0.00 seconds, 1 messages, 286 bytes

The primary signs NOTIFY with B and drops A. The next change still flows:

text
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., remote serial 3, zone is outdated
secondary: info: [example.com.] refresh, remote 127.0.0.1@53, key xfr-2026b., zone updated, 0.00 seconds, serial 2 -> 3, expires in 600 seconds
primary: info: [example.com.] AXFR, outgoing, remote 127.0.0.2@44455 TCP, key xfr-2026b., finished, 0.00 seconds, 1 messages, 286 bytes

The old key is refused even from the secondary's own address; the new key works:

bash
kdig @127.0.0.1 -b 127.0.0.2 example.com AXFR -y hmac-sha256:xfr-2026a:<secret A>
kdig @127.0.0.1 -b 127.0.0.2 example.com AXFR -y hmac-sha256:xfr-2026b:<secret B> | tail -4 | head -1
kdig @127.0.0.2 example.com SOA +short
text
;; ERROR: server replied with error 'NOTAUTH'
;; ERROR: failed to query server 127.0.0.1@53(TCP)

xfr-2026b.          	0	ANY	TSIG	hmac-sha256. 1790283061 300 32 l0pk9z+6ftvQu/0M78swyzpQzOIqNKxYE/lXTrcCJXk= 48883 NOERROR 0

ns1.example.com. hostmaster.example.com. 3 60 60 600 300

The 300 in the TSIG record is the fudge: the signature is accepted only within 300 seconds of the signer's clock.

Mistakes people make

One key for every server and every tool

A single key shared by all secondaries, the ACME client and a CI job means one leak exposes everything, and one rotation touches everything. Use one key per pair of hosts, and separate keys for dynamic updates.

Key-only ACLs

An ACL with key: but no address: accepts the key from anywhere. In Knot, put both in the same rule so a leaked key alone is refused, as the test shows. In PowerDNS, a key bypasses IP settings, so use network isolation.

Reusing the key name for the new secret

If the new key has the same name as the old one, both sides must change at exactly the same moment. Any gap fails transfers. A version in the name (xfr-2026a, xfr-2026b) allows the overlap.

Forgetting NOTIFY

Teams rotate the transfer key and forget that the primary signs NOTIFY with the key in its remote entry. The secondary then drops NOTIFY and only refreshes on the SOA timer, so changes arrive minutes or hours late.

Clock drift

TSIG signatures carry a time and a 300-second fudge. A secondary with a stopped NTP client fails every transfer with a time error. Monitor time sync on every DNS server.

Secrets in git

The key file ends up in the same repository as the zone files. Keep the secret in a separate included file, readable only by root and the knot group, and delivered by your secret manager.

Checklist

  • Generate TSIG keys with keymgr -t <name> hmac-sha256.
  • Remove every hmac-md5 and hmac-sha1 key.
  • Use one key per primary and secondary pair, with a version in the name.
  • Use separate keys for zone transfers and for dynamic updates.
  • Put both address and key in every Knot transfer and notify ACL.
  • Set key on each remote so SOA checks, transfers and NOTIFY are signed.
  • Keep secrets in an included file with mode 0640, outside git.
  • Rotate on a schedule with the overlap steps; never reuse a key name.
  • After rotation, test that the old key gets NOTAUTH.
  • Monitor NTP on every DNS server.

A TSIG rotation you have rehearsed is a routine config change. One you have not rehearsed is the reason that hmac-md5 key is still in production.

H2-CPQE

Learn it on a live range

DNSSEC with Knot, in Edge and Post-Quantum Networking: a real host in your browser, and every objective checked on the machine.

Start free

The Dome

Want it run for you?

The Dome puts post-quantum TLS, a WAF that blocks, signed DNS and a zero-trust mesh in front of your application. Tell us what you run.

See the Dome