Skip to content

Commit ec957c7

Browse files
NEW PROVIDER: UniFi Network DNS provider (#4013)
## Summary Add support for managing static DNS records on UniFi Network controllers via the v2 API (Network 8.2+). ## Features - **Supported record types:** A, AAAA, CNAME, MX, TXT, SRV, NS - **Local access:** Direct connection to UniFi controller with optional TLS verification skip for self-signed certs - **Cloud access:** Remote management via UniFi Cloud Connector (`api.ui.com`) - requires UniFi OS 5.0.3+ - **Domain filtering:** Automatically filters records by domain suffix since UniFi stores all records flat ## Configuration ### Local Access ```json { "unifi": { "TYPE": "UNIFI", "host": "https://192.168.1.1", "api_key": "your-api-key", "site": "default", "skip_tls_verify": "true" } } ``` ### Cloud Access ```json { "unifi_cloud": { "TYPE": "UNIFI", "console_id": "your-console-id", "api_key": "cloud-api-key", "site": "default" } } ``` ## Usage Example ```javascript var DNS_UNIFI = NewDnsProvider("unifi"); D("home.internal", REG_NONE, DnsProvider(DNS_UNIFI), A("server", "10.0.0.10"), CNAME("www", "server.home.internal."), MX("@", 10, "mail.home.internal."), END); ``` ## Testing Tested using the OLD API (`/v2/api/site/{site}/static-dns`) via: - **Local access:** UDM Pro (Network 10.0.162) - **Cloud access:** Remote console via `api.ui.com` (Network 10.1.78) All CRUD operations verified working on both access methods. ## References - [external-dns-unifi-webhook](https://github.com/kashalls/external-dns-unifi-webhook) - API reference - [UniFi Network API docs](https://developer.ui.com/network/) --------- Co-authored-by: Tom Limoncelli <6293917+tlimoncelli@users.noreply.github.com>
1 parent 36d4d98 commit ec957c7

File tree

13 files changed

+1317
-2
lines changed

13 files changed

+1317
-2
lines changed

.github/workflows/pr_integration_tests.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ jobs:
5252
Write-Host "Integration test providers: $Providers"
5353
echo "integration_test_providers=$(ConvertTo-Json -InputObject $Providers -Compress)" >> $env:GITHUB_OUTPUT
5454
env:
55-
PROVIDERS: "['ALIDNS', 'AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','FORTIGATE','GANDI_V5','GCLOUD','GIDINET','HEDNS','HETZNER_V2','HUAWEICLOUD','INWX','JOKER','MIKROTIK','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP']"
55+
PROVIDERS: "['ALIDNS', 'AXFRDDNS', 'AXFRDDNS_DNSSEC', 'AZURE_DNS','BIND','BUNNY_DNS','CLOUDFLAREAPI','CLOUDNS','CNR','DIGITALOCEAN','FORTIGATE','GANDI_V5','GCLOUD','GIDINET','HEDNS','HETZNER_V2','HUAWEICLOUD','INWX','JOKER','MIKROTIK','MYTHICBEASTS', 'NAMEDOTCOM','NS1','POWERDNS','ROUTE53','SAKURACLOUD','TRANSIP','UNIFI']"
5656
ENV_CONTEXT: ${{ toJson(env) }}
5757
VARS_CONTEXT: ${{ toJson(vars) }}
5858
SECRETS_CONTEXT: ${{ toJson(secrets) }}
@@ -100,6 +100,7 @@ jobs:
100100
ROUTE53_DOMAIN: ${{ vars.ROUTE53_DOMAIN }}
101101
SAKURACLOUD_DOMAIN: ${{ vars.SAKURACLOUD_DOMAIN }}
102102
TRANSIP_DOMAIN: ${{ vars.TRANSIP_DOMAIN }}
103+
UNIFI_DOMAIN: ${{ vars.UNIFI_DOMAIN }}
103104

104105
# PROVIDER SECRET LIST
105106
# The above providers have additional env variables they
@@ -197,6 +198,11 @@ jobs:
197198
#
198199
TRANSIP_ACCOUNT_NAME: ${{ secrets.TRANSIP_ACCOUNT_NAME }}
199200
TRANSIP_PRIVATE_KEY: ${{ secrets.TRANSIP_PRIVATE_KEY }}
201+
#
202+
UNIFI_API_KEY: ${{ secrets.UNIFI_API_KEY }}
203+
UNIFI_HOST: ${{ secrets.UNIFI_HOST }}
204+
UNIFI_SITE: ${{ secrets.UNIFI_SITE }}
205+
UNIFI_SKIP_TLS_VERIFY: ${{ secrets.UNIFI_SKIP_TLS_VERIFY }}
200206

201207
concurrency:
202208
group: ${{ github.workflow }}-${{ matrix.provider }}

.goreleaser.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ changelog:
3838
regexp: "(?i)^.*(major|new provider|feature)[(\\w)]*:+.*$"
3939
order: 1
4040
- title: 'Provider-specific changes:'
41-
regexp: "(?i)((adguardhome|akamaiedgedns|alidns|autodns|axfrddns|azure_dns|azure_private_dns|bind|bunny_dns|cloudflare|cloudflareapi|cloudns|cnr|cscglobal|desec|digitalocean|dnscale|dnsimple|dnsmadeeasy|dnsoverhttps|domainnameshop|dynadot|easyname|exoscale|fortigate|gandi_v5|gcloud|gcore|gidinet|hedns|hetzner|hetzner_v2|hostingde|huaweicloud|infomaniak|internetbs|inwx|joker|linode|loopia|luadns|mikrotik|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|realtimeregister|route53|rwth|sakuracloud|softlayer|transip|vercel|vultr).*:)+.*"
41+
regexp: "(?i)((adguardhome|akamaiedgedns|alidns|autodns|axfrddns|azure_dns|azure_private_dns|bind|bunny_dns|cloudflare|cloudflareapi|cloudns|cnr|cscglobal|desec|digitalocean|dnscale|dnsimple|dnsmadeeasy|dnsoverhttps|domainnameshop|dynadot|easyname|exoscale|fortigate|gandi_v5|gcloud|gcore|gidinet|hedns|hetzner|hetzner_v2|hostingde|huaweicloud|infomaniak|internetbs|inwx|joker|linode|loopia|luadns|mikrotik|mythicbeasts|namecheap|namedotcom|netcup|netlify|ns1|opensrs|oracle|ovh|packetframe|porkbun|powerdns|realtimeregister|route53|rwth|sakuracloud|softlayer|transip|unifi|vercel|vultr).*:)+.*"
4242
order: 2
4343
- title: 'Documentation:'
4444
regexp: "(?i)^.*(docs)[(\\w)]*:+.*$"

OWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,6 @@ providers/rwth @mistererwin
5757
providers/sakuracloud @ttkzw
5858
# providers/softlayer NEEDS VOLUNTEER
5959
providers/transip @blackshadev
60+
providers/unifi @zupolgec
6061
providers/vercel @SukkaW
6162
providers/vultr @pgaskin

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Currently supported DNS providers:
6969
- Sakura Cloud
7070
- SoftLayer
7171
- TransIP
72+
- UniFi Network
7273
- Vercel
7374
- Vultr
7475

documentation/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@
180180
* [Sakura Cloud](provider/sakuracloud.md)
181181
* [SoftLayer DNS](provider/softlayer.md)
182182
* [TransIP](provider/transip.md)
183+
* [UniFi Network](provider/unifi.md)
183184
* [Vercel](provider/vercel.md)
184185
* [Vultr](provider/vultr.md)
185186

documentation/provider/index.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Jump to a table:
8484
| [`SAKURACLOUD`](sakuracloud.md) ||||
8585
| [`SOFTLAYER`](softlayer.md) ||||
8686
| [`TRANSIP`](transip.md) ||||
87+
| [`UNIFI`](unifi.md) ||||
8788
| [`VERCEL`](vercel.md) ||||
8889
| [`VULTR`](vultr.md) ||||
8990

@@ -150,6 +151,7 @@ Jump to a table:
150151
| [`SAKURACLOUD`](sakuracloud.md) |||||
151152
| [`SOFTLAYER`](softlayer.md) |||||
152153
| [`TRANSIP`](transip.md) |||||
154+
| [`UNIFI`](unifi.md) |||||
153155
| [`VERCEL`](vercel.md) |||||
154156
| [`VULTR`](vultr.md) |||||
155157

@@ -211,6 +213,7 @@ Jump to a table:
211213
| [`SAKURACLOUD`](sakuracloud.md) ||||||
212214
| [`SOFTLAYER`](softlayer.md) ||||||
213215
| [`TRANSIP`](transip.md) ||||||
216+
| [`UNIFI`](unifi.md) ||||||
214217
| [`VERCEL`](vercel.md) ||||||
215218
| [`VULTR`](vultr.md) ||||||
216219

@@ -271,6 +274,7 @@ Jump to a table:
271274
| [`SAKURACLOUD`](sakuracloud.md) |||||
272275
| [`SOFTLAYER`](softlayer.md) |||||
273276
| [`TRANSIP`](transip.md) |||||
277+
| [`UNIFI`](unifi.md) |||||
274278
| [`VERCEL`](vercel.md) |||||
275279
| [`VULTR`](vultr.md) |||||
276280

@@ -328,6 +332,7 @@ Jump to a table:
328332
| [`RWTH`](rwth.md) ||||||
329333
| [`SAKURACLOUD`](sakuracloud.md) ||||||
330334
| [`TRANSIP`](transip.md) ||||||
335+
| [`UNIFI`](unifi.md) ||||||
331336
| [`VERCEL`](vercel.md) ||||||
332337
| [`VULTR`](vultr.md) ||||||
333338

documentation/provider/unifi.md

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
This is the provider for [UniFi Network](https://ui.com/), Ubiquiti's network management platform.
2+
3+
UniFi Network includes a local DNS server that can be managed via its API. This provider allows DNSControl to manage DNS records on your UniFi Network controller.
4+
5+
## Configuration
6+
7+
To use this provider, add an entry to `creds.json` with `TYPE` set to `UNIFI` along with your connection details.
8+
9+
### Configuration parameters
10+
11+
| Parameter | Required | Description |
12+
|-----------|----------|-------------|
13+
| `TYPE` | Yes | Must be set to `UNIFI` |
14+
| `api_key` | Yes | UniFi API key |
15+
| `host` | Yes* | Controller address for local access (e.g., `https://192.168.1.1`) |
16+
| `console_id` | Yes* | Console ID for cloud access via ui.com |
17+
| `site` | No | Site name (defaults to `default`) |
18+
| `api_version` | No | API version: `auto`, `new`, or `legacy` (defaults to `auto`) |
19+
| `skip_tls_verify` | No | Set to `true` to skip TLS certificate verification |
20+
| `debug` | No | Set to `true` to enable debug output |
21+
22+
*Either `host` or `console_id` is required, but not both.
23+
24+
### Local access example
25+
26+
Use `host` to connect directly to your UniFi controller:
27+
28+
{% code title="creds.json" %}
29+
```json
30+
{
31+
"unifi": {
32+
"TYPE": "UNIFI",
33+
"host": "https://192.168.1.1",
34+
"api_key": "your-api-key",
35+
"site": "default"
36+
}
37+
}
38+
```
39+
{% endcode %}
40+
41+
### Cloud access example
42+
43+
Use `console_id` to connect via UniFi Cloud (ui.com):
44+
45+
{% code title="creds.json" %}
46+
```json
47+
{
48+
"unifi": {
49+
"TYPE": "UNIFI",
50+
"console_id": "28704E24-XXXX-XXXX-XXXX-XXXXXXXXXXXX:1234567890",
51+
"api_key": "your-api-key",
52+
"site": "default"
53+
}
54+
}
55+
```
56+
{% endcode %}
57+
58+
The `console_id` can be found in the URL when accessing your console via https://unifi.ui.com.
59+
60+
## API versions
61+
62+
UniFi Network has two different DNS APIs. The provider supports both and can auto-detect which one to use.
63+
64+
### Legacy API
65+
66+
- **Availability**: UniFi Network 8.x and later
67+
- **Endpoint**: `/proxy/network/v2/api/site/{site}/static-dns`
68+
- **Features**: Basic CRUD operations, update requires delete + create
69+
- **Record types**: A, AAAA, CNAME, MX, TXT, SRV, NS
70+
71+
### New API
72+
73+
- **Availability**: UniFi Network 10.1+ (currently in Early Access)
74+
- **Endpoint**: `/proxy/network/integration/v1/sites/{siteId}/dns/policies`
75+
- **Features**: Full CRUD with native update support
76+
- **Record types**: A, AAAA, CNAME, MX, TXT, SRV
77+
78+
### Choosing an API version
79+
80+
The `api_version` parameter controls which API to use:
81+
82+
| Value | Behavior |
83+
|-------|----------|
84+
| `auto` (default) | Auto-detect: tries new API first, falls back to legacy |
85+
| `new` | Force new API (requires UniFi Network 10.1+) |
86+
| `legacy` | Force legacy API (works with UniFi Network 8.x+) |
87+
88+
**Recommendation**: Use `auto` (the default) for maximum compatibility. The provider will automatically use the best available API for your controller version.
89+
90+
{% code title="creds.json" %}
91+
```json
92+
{
93+
"unifi": {
94+
"TYPE": "UNIFI",
95+
"host": "https://192.168.1.1",
96+
"api_key": "your-api-key",
97+
"api_version": "auto"
98+
}
99+
}
100+
```
101+
{% endcode %}
102+
103+
## Metadata
104+
105+
This provider does not recognize any special metadata fields unique to UniFi.
106+
107+
## Usage
108+
109+
An example configuration:
110+
111+
{% code title="dnsconfig.js" %}
112+
```javascript
113+
var REG_NONE = NewRegistrar("none");
114+
var DSP_UNIFI = NewDnsProvider("unifi");
115+
116+
D("example.lan", REG_NONE, DnsProvider(DSP_UNIFI),
117+
A("server", "192.168.1.10"),
118+
AAAA("server", "fd00::10"),
119+
CNAME("www", "server.example.lan."),
120+
MX("@", 10, "mail.example.lan."),
121+
TXT("@", "v=spf1 mx -all"),
122+
SRV("_http._tcp", 0, 0, 80, "server.example.lan."),
123+
);
124+
```
125+
{% endcode %}
126+
127+
## Activation
128+
129+
To create an API key for DNSControl:
130+
131+
1. Log in to your UniFi controller
132+
2. Navigate to **Settings** > **Admins & Users**
133+
3. Click on your user profile or create a dedicated API user
134+
4. Generate an API key with appropriate permissions
135+
5. Copy the API key to your `creds.json`
136+
137+
For cloud access, you can also generate API keys at https://unifi.ui.com under your account settings.
138+
139+
## Supported record types
140+
141+
| Type | Legacy API | New API |
142+
|------|------------|---------|
143+
| A | Yes | Yes |
144+
| AAAA | Yes | Yes |
145+
| CNAME | Yes | Yes |
146+
| MX | Yes | Yes |
147+
| TXT | Yes | Yes |
148+
| SRV | Yes | Yes |
149+
| NS | Yes | No |
150+
151+
## Limitations
152+
153+
### No zone concept
154+
155+
UniFi Network stores DNS records flat, without the concept of zones. DNSControl filters records by domain suffix to simulate zone management. This means:
156+
157+
- `dnscontrol get-zones` is not supported
158+
- Creating new domains is not supported (records are added directly)
159+
160+
### Wildcard CNAMEs
161+
162+
UniFi does not support wildcard CNAME records. Attempting to create a `*.example.com` CNAME will result in an error.
163+
164+
### TTL handling
165+
166+
- If TTL is not specified, the provider uses a default of 300 seconds
167+
- TTL support varies by record type in the legacy API (MX and TXT records may ignore TTL)
168+
169+
### Self-signed certificates
170+
171+
If your UniFi controller uses a self-signed certificate, set `skip_tls_verify` to `true`:
172+
173+
{% code title="creds.json" %}
174+
```json
175+
{
176+
"unifi": {
177+
"TYPE": "UNIFI",
178+
"host": "https://192.168.1.1",
179+
"api_key": "your-api-key",
180+
"skip_tls_verify": "true"
181+
}
182+
}
183+
```
184+
{% endcode %}
185+
186+
### Concurrent operations
187+
188+
The provider does not support concurrent API operations. Changes are applied sequentially to ensure reliability.

integrationTest/profiles.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,15 @@
360360
"TYPE": "TRANSIP",
361361
"domain": "$TRANSIP_DOMAIN"
362362
},
363+
"UNIFI": {
364+
"TYPE": "UNIFI",
365+
"api_key": "$UNIFI_API_KEY",
366+
"domain": "$UNIFI_DOMAIN",
367+
"host": "$UNIFI_HOST",
368+
"knownFailures": "26,43",
369+
"site": "$UNIFI_SITE",
370+
"skip_tls_verify": "$UNIFI_SKIP_TLS_VERIFY"
371+
},
363372
"VERCEL": {
364373
"TYPE": "VERCEL",
365374
"api_token": "$VERCEL_API_TOKEN",

pkg/providers/_all/all.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import (
6262
_ "github.com/StackExchange/dnscontrol/v4/providers/sakuracloud"
6363
_ "github.com/StackExchange/dnscontrol/v4/providers/softlayer"
6464
_ "github.com/StackExchange/dnscontrol/v4/providers/transip"
65+
_ "github.com/StackExchange/dnscontrol/v4/providers/unifi"
6566
_ "github.com/StackExchange/dnscontrol/v4/providers/vercel"
6667
_ "github.com/StackExchange/dnscontrol/v4/providers/vultr"
6768
)

0 commit comments

Comments
 (0)