Files
unkinben 02e3e0315d
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Initial implementation: dns-updater daemon
RFC2136 dynamic-DNS updater. Watches a records file (inotify) and new
interface addresses and pushes TSIG-signed updates to BIND per zone, sending
only the delta. Native miekg/dns (structured per-zone RCODEs), local status
API + facter fact, systemd unit, nfpm RPM, Woodpecker CI.

Replaces the puppet dns-update shell script; keeps the same records-file and
TSIG-key contract.
2026-07-17 23:24:49 +10:00

86 lines
2.1 KiB
Go

// Package tsig loads a BIND-style TSIG key file for signing RFC2136 updates.
//
// The file looks like:
//
// key "client-update" {
// algorithm hmac-sha256;
// secret "base64secret==";
// };
package tsig
import (
"fmt"
"os"
"regexp"
"strings"
"github.com/miekg/dns"
)
// Key is a parsed TSIG key ready for use with miekg/dns.
type Key struct {
Name string // fully qualified (trailing dot), as miekg/dns wants
Algorithm string // e.g. dns.HmacSHA256 ("hmac-sha256.")
Secret string // base64
}
var (
reName = regexp.MustCompile(`(?s)key\s+"([^"]+)"\s*\{(.*?)\}`)
reAlgo = regexp.MustCompile(`algorithm\s+([A-Za-z0-9\-]+)\s*;`)
reSec = regexp.MustCompile(`secret\s+"([^"]+)"\s*;`)
)
// algorithms maps BIND algorithm names to the fully-qualified constants
// miekg/dns expects in SetTsig and the TsigSecret map.
var algorithms = map[string]string{
"hmac-md5": dns.HmacMD5,
"hmac-sha1": dns.HmacSHA1,
"hmac-sha224": dns.HmacSHA224,
"hmac-sha256": dns.HmacSHA256,
"hmac-sha384": dns.HmacSHA384,
"hmac-sha512": dns.HmacSHA512,
}
// Load reads and parses the first key definition in the file at path.
func Load(path string) (*Key, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, err
}
return Parse(string(b))
}
// Parse extracts a Key from BIND key-file text.
func Parse(text string) (*Key, error) {
m := reName.FindStringSubmatch(text)
if m == nil {
return nil, fmt.Errorf("no key { ... } block found")
}
name, body := m[1], m[2]
algoM := reAlgo.FindStringSubmatch(body)
if algoM == nil {
return nil, fmt.Errorf("key %q: missing algorithm", name)
}
algo, ok := algorithms[strings.ToLower(algoM[1])]
if !ok {
return nil, fmt.Errorf("key %q: unsupported algorithm %q", name, algoM[1])
}
secM := reSec.FindStringSubmatch(body)
if secM == nil {
return nil, fmt.Errorf("key %q: missing secret", name)
}
return &Key{
Name: dns.Fqdn(name),
Algorithm: algo,
Secret: secM[1],
}, nil
}
// SecretMap returns the name→secret map for dns.Client.TsigSecret.
func (k *Key) SecretMap() map[string]string {
return map[string]string{k.Name: k.Secret}
}