MCPcopy Index your code
hub / github.com/crutonjohn/external-dns-opnsense-webhook

github.com/crutonjohn/external-dns-opnsense-webhook @v1.0.0

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.0.0 ↗ · + Follow
50 symbols 137 edges 11 files 31 documented · 62%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

ExternalDNS Webhook Provider for OPNsense

GitHub Release   Discord

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.

🗒️ Important Notes

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.

Structuring Your Unbound Records

[!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:

  • You run the webhook with the domain filter set for example.com
  • You have manually created a Host Override for host1.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.

🎯 Requirements

  • ExternalDNS >= v0.14.0
  • OPNsense >= 23.7.12_5
  • Unbound >= 1.19.0

⛵ Deployment

  1. Create a local user with a password in your OPNsense firewall. System > Access > Users

  2. Create an API keypair for the user you created in step 1.

  3. Create (or use an existing) group to limit your user's permissions. The known required privileges are:

  4. Services: Unbound DNS: Edit Host and Domain Override
  5. Services: Unbound (MVC)
  6. Status: DNS Overview

  7. Add the ExternalDNS Helm repository to your cluster.

    sh helm repo add external-dns https://kubernetes-sigs.github.io/external-dns/

  8. 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

  9. 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

  10. 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


👷 Building & Testing

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

🤝 Gratitude and Thanks

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

Extension points exported contracts — how you extend this code

OpnsenseProviderFactory (FuncType)
(no doc)
cmd/webhook/init/dnsprovider/dnsprovider.go

Core symbols most depended-on inside this repo

requestLog
called by 15
pkg/webhook/webhook.go
doRequest
called by 5
internal/opnsense-unbound/client.go
EmbellishUnboundType
called by 4
internal/opnsense-unbound/utils.go
mediaTypeVersion
called by 3
pkg/webhook/mediatype.go
Is
called by 3
pkg/webhook/mediatype.go
acceptHeaderCheck
called by 3
pkg/webhook/webhook.go
contentTypeHeaderCheck
called by 2
pkg/webhook/webhook.go
headerCheck
called by 2
pkg/webhook/webhook.go

Shape

Function 21
Method 19
Struct 8
FuncType 1
TypeAlias 1

Languages

Go100%

Modules by API surface

pkg/webhook/webhook.go10 symbols
internal/opnsense-unbound/client.go10 symbols
internal/opnsense-unbound/provider.go5 symbols
cmd/webhook/init/server/server.go5 symbols
pkg/webhook/mediatype.go4 symbols
internal/opnsense-unbound/utils.go4 symbols
internal/opnsense-unbound/types.go4 symbols
cmd/webhook/init/logging/log.go3 symbols
cmd/webhook/init/dnsprovider/dnsprovider.go2 symbols
cmd/webhook/init/configuration/configuration.go2 symbols
cmd/webhook/main.go1 symbols

For agents

$ claude mcp add external-dns-opnsense-webhook \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact

Ask about this repo answers extend the page