This webhook graciously ~~stolen~~ inspired by Kashall's Unifi Webhook.
[!WARNING] This software is experimental and NOT FIT FOR PRODUCTION USE!
ExternalDNS is a Kubernetes add-on for automatically managing DNS records for Kubernetes ingresses and services by using different DNS providers. This webhook provider allows you to automate DNS records from your Kubernetes clusters into your OPNsense Firewall's Unbound service.
As of this writing this webhook supports A, AAAA, and TXT records using Unbound's Host Overrides. A/AAAA/TXT records work because they effectively map 1:1 with Host Overrides.
With significantly more effort, CNAMEs could be supported and mapped to Host Override Aliases, which may be implemented in the future.
[!NOTE] TXT record support requires OPNsense >= 25.7 or later versions with TXT record support in Unbound.
[!WARNING] If you don't follow this manually entered A/AAAA records can be permanently destroyed
If you have records that are managed manually or by some process other than this webhook and you intend for those records to share a domain, then you must structure them in a way that avoids conflict. The webhook examines all records defined in Host Overrides but does not evaluate any Aliases. To avoid ownership conflicts you should create a "stub" Host Override pointing to your intended IP address, then create aliases that use your desired domain.
For example:
example.comhost1.example.com in OPNSense's Unbound Web UI pointed to 192.168.10.2.You will need to:
- Create a new Host Override with a different domain such as host1.fake.com pointing to 192.168.10.2
- Create an Alias under host1.fake.com that uses the original domain like host1.example.com
This effecively protects your record from ownership conflicts while still allowing you to define custom records for a domain used by this webhook. Another option would be to create dnsendpoint CRDs for all the records you need in Unbound and let the webhook manage everything.
Create a local user with a password in your OPNsense firewall. System > Access > Users
Create an API keypair for the user you created in step 1.
Create (or use an existing) group to limit your user's permissions. The known required privileges are:
Services: Unbound DNS: Edit Host and Domain OverrideServices: Unbound (MVC)Status: DNS Overview
Add the ExternalDNS Helm repository to your cluster.
sh
helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/
Create a Kubernetes secret called external-dns-opnsense-secret that holds api_key and api_secret with their respective values from step 1:
yaml
apiVersion: v1
stringData:
api_secret: <INSERT API SECRET>
api_key: <INSERT API KEY>
kind: Secret
metadata:
name: external-dns-opnsense-secret
type: Opaque
Create the helm values file, for example external-dns-webhook-values.yaml:
yaml
fullnameOverride: external-dns-opnsense
logLevel: debug
provider:
name: webhook
webhook:
image:
repository: ghcr.io/crutonjohn/external-dns-opnsense-webhook
tag: main # replace with a versioned release tag
env:
- name: OPNSENSE_API_SECRET
valueFrom:
secretKeyRef:
name: external-dns-opnsense-secret
key: api_secret
- name: OPNSENSE_API_KEY
valueFrom:
secretKeyRef:
name: external-dns-opnsense-secret
key: api_key
- name: OPNSENSE_HOST
value: https://192.168.1.1 # replace with the address to your OPNsense router
- name: OPNSENSE_SKIP_TLS_VERIFY
value: "true" # optional depending on your environment
- name: LOG_LEVEL
value: debug
livenessProbe:
httpGet:
path: /healthz
port: http-wh-metrics
initialDelaySeconds: 10
timeoutSeconds: 5
readinessProbe:
httpGet:
path: /readyz
port: http-wh-metrics
initialDelaySeconds: 10
timeoutSeconds: 5
extraArgs:
- --ignore-ingress-tls-spec
policy: sync
sources: ["ingress", "service", "crd"]
registry: noop
domainFilters: ["example.com"] # replace with your domain
Install the Helm chart
sh
helm install external-dns-opnsense external-dns/external-dns -f external-dns-opnsense-values yaml --version 1.14.3 -n external-dns
Build:
go build -ldflags "-s -w -X main.Version=test -X main.Gitsha=test" ./cmd/webhook
Run:
OPNSENSE_HOST=https://192.168.0.1 OPNSENSE_API_SECRET=<secret value> OPNSENSE_API_KEY=<key value> ./webhook
Thanks to all the people who donate their time to the Home Operations Discord community.
I'd like to thank the following people for answering my hare-brained questions: - @kashalls - @onedr0p - @uhthomas - @tyzbit - @buroa
$ claude mcp add external-dns-opnsense-webhook \
-- python -m otcore.mcp_server <graph>