Skip to content

tJouve/ddnsbridge4extdns

Repository files navigation

ddnsbridge4extdns

RFC2136 DNS UPDATE Bridge for Kubernetes ExternalDNS

Overview

ddnsbridge4extdns is a lightweight RFC2136 DNS UPDATE server designed specifically for OPNsense DDNS integration with Kubernetes. It accepts DNS UPDATE messages (RFC2136) over UDP/TCP port 53, validates them using TSIG authentication, parses A/AAAA record create/update/delete operations, and translates them into Kubernetes DNSEndpoint resources that are consumed by ExternalDNS.

Key Features:

  • ✅ RFC2136 DNS UPDATE protocol support (UDP & TCP)
  • ✅ TSIG authentication (hmac-sha256, hmac-sha512, hmac-sha1, hmac-md5)
  • ✅ A and AAAA record support
  • ✅ Zone-scoped security (allow-list)
  • ✅ Stateless and idempotent
  • ✅ Native Kubernetes integration via DNSEndpoint CRD
  • ✅ Lightweight and secure by default

What it is NOT:

  • ❌ Not a DHCP server
  • ❌ Not a DNS resolver
  • ❌ Not an authoritative DNS server
  • ❌ Not a full DNS server implementation

Architecture

OPNsense DDNS Client
        ↓
   DNS UPDATE (RFC2136) over UDP/TCP:53
        ↓
   ddnsbridge4extdns (TSIG validation)
        ↓
   Kubernetes DNSEndpoint CRD
        ↓
   ExternalDNS
        ↓
   DNS Provider (Route53, Cloudflare, etc.)

Prerequisites

  • Kubernetes cluster (1.19+)
  • ExternalDNS installed and configured
  • DNSEndpoint CRD installed (comes with ExternalDNS)
  • OPNsense or any RFC2136-compatible DDNS client

Installation

1. Build the Docker Image

docker build -t ddnsbridge4extdns:latest .

2. Configure TSIG Credentials

Edit deploy/kubernetes/deployment.yaml and update the TSIG secret:

stringData:
  tsig-key: "your-key-name"
  tsig-secret: "your-base64-secret"

Generate a TSIG secret:

# Generate a random secret
openssl rand -base64 32

3. Configure Allowed Zones

Edit deploy/kubernetes/deployment.yaml and update the allowed zones:

data:
  ALLOWED_ZONES: "example.com,example.org,yourdomain.com"

4. Deploy to Kubernetes

kubectl apply -f deploy/kubernetes/deployment.yaml

5. Get the Service External IP

kubectl get svc -n ddnsbridge4extdns ddnsbridge4extdns

Note the EXTERNAL-IP - this is the IP address your OPNsense DDNS client should send updates to.

Configuration

Configuration is done via environment variables:

Variable Description Default Required
LISTEN_ADDR Listen address 0.0.0.0 No
PORT Listen port 53 No
TSIG_KEY TSIG key name - Yes
TSIG_SECRET TSIG shared secret - Yes
TSIG_ALGORITHM TSIG algorithm hmac-sha256 No
NAMESPACE Target Kubernetes namespace for DNSEndpoints default No
ALLOWED_ZONES Comma-separated list of allowed zones - Yes
CUSTOM_LABELS Custom labels for DNSEndpoint resources (format: key1=value1,key2=value2) - No
LOG_LEVEL Log level (TRACE, DEBUG, INFO, WARN, ERROR) INFO No

Supported Log Levels

  • TRACE - Most verbose; logs all internal operations (values, computations, flow)
  • DEBUG - Debug information; useful for troubleshooting (TSIG details, zone checks)
  • INFO - Standard logging level; logs important events (DNS updates received, processed)
  • WARN - Warning level; logs potentially problematic situations (rejected requests, zone mismatches)
  • ERROR - Error level; logs errors only (failures, exceptions)

Supported TSIG Algorithms

  • hmac-sha256 (recommended)
  • hmac-sha512
  • hmac-sha1

OPNsense Configuration

  1. Navigate to Services → Dynamic DNS
  2. Add a new entry with these settings:
    • Service: RFC2136
    • Server: <ddnsbridge4extdns-service-external-ip>
    • Zone: Your zone (e.g., example.com)
    • Key name: Your TSIG key name (matches TSIG_KEY)
    • Key: Your TSIG secret (matches TSIG_SECRET)
    • Key algorithm: HMAC-SHA256 (or your chosen algorithm)
    • Hostname: The hostname to update (e.g., router.example.com)

Testing

Test with nsupdate

You can test the server using the nsupdate command:

# Create a key file
cat > /tmp/ddns.key <<EOF
key "your-key-name" {
    algorithm hmac-sha256;
    secret "your-base64-secret";
};
EOF

# Create an update file
cat > /tmp/update.txt <<EOF
server <ddnsbridge4extdns-ip> 53
zone example.com
update delete test.example.com A
update add test.example.com 300 A 192.168.1.100
send
EOF

# Send the update
nsupdate -k /tmp/ddns.key /tmp/update.txt

Verify DNSEndpoint Creation

kubectl get dnsendpoint -n default

You should see a DNSEndpoint resource created for your update.

Check Logs

kubectl logs -n ddnsbridge4extdns -l app=ddnsbridge4extdns -f

Security Considerations

  1. TSIG Authentication: All DNS UPDATE messages must be authenticated with TSIG. Unauthenticated requests are rejected.

  2. Zone-Scoped: Only zones listed in ALLOWED_ZONES can be updated. This prevents unauthorized zone updates.

  3. Network Policies: Consider using Kubernetes Network Policies to restrict access to the ddnsbridge4extdns service.

  4. Secret Management: Store TSIG secrets securely using Kubernetes Secrets. Consider using external secret management solutions like Vault or Sealed Secrets.

  5. Minimal Permissions: The service account has minimal RBAC permissions - only DNSEndpoint resources.

ExternalDNS Integration

This server creates DNSEndpoint resources with the following structure:

apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: <sanitized-hostname>
  namespace: default
  labels:
    app.kubernetes.io/managed-by: ddnsbridge4extdns
    ddns-zone: <zone-name>
spec:
  endpoints:
  - dnsName: <fqdn>
    recordType: A  # or AAAA
    recordTTL: 300
    targets:
    - <ip-address>

ExternalDNS will automatically pick up these resources and create/update/delete the corresponding DNS records in your configured DNS provider.

Building from Source

# Clone the repository
git clone https://github.com/tJouve/ddnsbridge4extdns.git
cd ddnsbridge4extdns

# Build
go build -o ddnsbridge4extdns ./cmd/server

# Run locally (requires kubeconfig)
export TSIG_KEY="your-key"
export TSIG_SECRET="your-secret"
export ALLOWED_ZONES="example.com"
./ddnsbridge4extdns

Development

Project Structure

.
├── cmd/
│   └── server/          # Main application entry point
├── pkg/
│   ├── config/          # Configuration management
│   ├── tsig/            # TSIG validation
│   ├── update/          # DNS UPDATE parser
│   └── k8s/             # Kubernetes client
├── internal/
│   └── handler/         # DNS request handler
├── deploy/
│   └── kubernetes/      # Kubernetes manifests
├── Dockerfile
└── README.md

Running Tests

go test ./...

Troubleshooting

DNS UPDATE rejected with NOTAUTH

  • Verify TSIG key name matches between OPNsense and ddnsbridge4extdns
  • Verify TSIG secret matches (base64-encoded)
  • Verify TSIG algorithm matches
  • Check logs: kubectl logs -n ddnsbridge4extdns -l app=ddnsbridge4extdns

DNS UPDATE rejected with REFUSED

  • Verify the zone is in the ALLOWED_ZONES list
  • Ensure the zone name in OPNsense matches exactly (with or without trailing dot)

DNSEndpoint not created

  • Verify the service account has proper RBAC permissions
  • Check if DNSEndpoint CRD is installed: kubectl get crd dnsendpoints.externaldns.k8s.io
  • Check namespace configuration matches where ExternalDNS is watching

ExternalDNS not picking up DNSEndpoint

  • Verify ExternalDNS is configured to watch DNSEndpoint resources
  • Check ExternalDNS source configuration includes crd
  • Verify the namespace matches ExternalDNS watch configuration

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Author

Created for OPNsense DDNS integration with Kubernetes ExternalDNS.

About

RFC2136 DDNS Bridge for Kubernetes ExternalDNS

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors