NEW PROVIDER: UniFi Network DNS provider#4013
NEW PROVIDER: UniFi Network DNS provider#4013tlimoncelli merged 8 commits intoStackExchange:mainfrom
Conversation
|
Hi @tlimoncelli, Unifi is launching a new API to manage DNS records in v10.1. |
Either is fine. If you want to skip v1 and only support v2, that's fine too. (edited for clarity) |
Right now v2 is Early Access. And v1 would support all Unifi versions back to 8.x. That could be immediately useful for more users. I'll go with a flag that defaults to "auto" for version detection, best of both. |
|
Thank you for contributing this new provider, @zupolgec ! Some notes:
My home internet router doesn't have an API. Maybe I should check out UniFi! Thanks again! |
tlimoncelli
left a comment
There was a problem hiding this comment.
See previous comment
054902c to
a6c2c60
Compare
Docs added. This is the result of the integration tests:
I love Ubiquiti and their products, huge fan. I like to think of them as the Apple of networking equipment (the old Apple at least). |
## Summary This PR introduces a new DNS provider for MikroTik RouterOS devices, managing DNS static entries and forwarders via the RouterOS REST API. **Requires RouterOS 7.x.** I absolutely loved dnscontrol's features and the ease of management for domains it provides. However, part of my personal DNS-related infrastructure is powered by Mikrotik devices, and in order to unify configuration management I drafted this implementation. Hope others will find it an useful addition (#4013 (comment)) ## Changes For the new `MIKROTIK` provider: * Supported record types: `A, AAAA, CNAME, MX, NS, SRV, TXT` * Custom record types: - `MIKROTIK_FWD`: conditional DNS forwarding with address list population - `MIKROTIK_NXDOMAIN`: respond with NXDOMAIN for matching queries - `MIKROTIK_FORWARDER`: manage `/ip/dns/forwarders` entries via a synthetic `_forwarders.mikrotik` zone * ROS-specific record metadata (`match_subdomain`, `regexp`, `address_list`, `comment`) exposed as dnscontrol metadata and round-tripped through the API * **Zone detection**: RouterOS has no native zone concept; zones are inferred from record names using configurable zonehints (longest match), publicsuffix, or last-two-labels fallback * `get-zones` enhancements: - Auto-emits `{no_ns: "true"}` when a provider returns no nameservers (useful for any provider without NS records, not just MikroTik) - Serializes MikroTik-specific metadata and custom record types into the generated DSL ## Configuration ```json { "home_gateway": { "TYPE": "MIKROTIK", "host": "http://192.168.88.1:80", "username": "admin", "password": "passw0rd", "zonehints": "home.example.com" } } ``` ## Usage Example ```js var DSP_MIKROTIK = NewDnsProvider("home_gateway"); var REG_NONE = NewRegistrar("none"); D("_forwarders.mikrotik", REG_NONE , {no_ns: "true"} , DnsProvider(DSP_MIKROTIK) , MIKROTIK_FORWARDER("site2site-fwd", "192.168.13.1") , MIKROTIK_FORWARDER("doh-upstream", "1.1.1.1", {doh_servers: "https://cloudflare-dns.com/dns-query", verify_doh_cert: "true"}), ) D("home.example.com", REG_NONE , {no_ns: "true"} , DnsProvider(DSP_MIKROTIK) , DefaultTTL(900) , A("mikrotik", "192.168.10.1", TTL(86400)) , A("srv", "192.168.10.2", TTL(86400)) , A("pve", "192.168.10.5", TTL(86400)) , A("galaxy-a20", "192.168.10.193", {comment: "dhcp0_lan-3C:20:F6:11:11:11"}) , CNAME("proxmox", "srv.home.example.com.", TTL(86400)) , CNAME("admin", "srv.home.example.com.", TTL(86400)) , MIKROTIK_FWD("remote", "site2site-fwd", {match_subdomain: "true"}, TTL(86400)) ) D("nextdns.io", REG_NONE , {no_ns: "true"} , DnsProvider(DSP_MIKROTIK) , DefaultTTL(86400) , A("dns", "45.90.28.0") , A("dns", "45.90.30.0") , AAAA("dns", "2a07:a8c0::") , AAAA("dns", "2a07:a8c1::") ) D("twimg.com", REG_NONE , {no_ns: "true"} , DnsProvider(DSP_MIKROTIK) , DefaultTTL(86400) // Needed for dynamic routing with address lists based on queries to the resolver , MIKROTIK_FWD("@", "8.8.8.8", {match_subdomain: "true", address_list: "dst-to-vpn-list"}) D("example.com", REG_NONE , {no_ns: "true"} , DnsProvider(DSP_MIKROTIK) , MIKROTIK_FWD("@", "doh-upstream", {match_subdomain: "true"}) ) ``` ## Potential improvements - Implement access using `api` service with proprietary protocol on port 8728/8729 (with TLS), instead of REST API relying on the `www` service ## Tests <details> <summary>Integration tests verified against a physical RouterOS 7.21.3 device, hap ax3 ``` PASS ok github.com/StackExchange/dnscontrol/v4/integrationTest 39.183s ``` </summary> ``` === RUN TestDNSProviders/example.com/103:final:final helpers_integration_test.go:196: + CREATE final.example.com TXT "TestDNSProviders was successful!" ttl=300 --- PASS: TestDNSProviders (38.64s) --- PASS: TestDNSProviders/example.com (38.64s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty (4.13s) --- PASS: TestDNSProviders/example.com/00:A:Create_A (0.06s) --- PASS: TestDNSProviders/example.com/00:A:Change_A_target (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#01 (0.03s) --- PASS: TestDNSProviders/example.com/01:Apex:Create_A (0.05s) --- PASS: TestDNSProviders/example.com/01:Apex:Change_A_target (0.16s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#02 (0.03s) --- PASS: TestDNSProviders/example.com/02:Protocol-Wildcard:Create_wildcard (0.09s) --- PASS: TestDNSProviders/example.com/02:Protocol-Wildcard:Delete_wildcard (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#03 (0.03s) --- PASS: TestDNSProviders/example.com/03:AAAA:Create_AAAA (0.06s) --- PASS: TestDNSProviders/example.com/03:AAAA:Change_AAAA_target (0.08s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#04 (0.03s) --- PASS: TestDNSProviders/example.com/04:CNAME:Create_a_CNAME (0.07s) --- PASS: TestDNSProviders/example.com/04:CNAME:Change_CNAME_target (0.06s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#05 (0.03s) --- PASS: TestDNSProviders/example.com/05:CNAME-short:Create_a_CNAME (0.08s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#06 (0.06s) --- PASS: TestDNSProviders/example.com/06:MX:Create_MX_apex (0.07s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_apex (0.07s) --- PASS: TestDNSProviders/example.com/06:MX:Create_MX (0.09s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_target (0.15s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_p (0.06s) --- PASS: TestDNSProviders/example.com/07:RP_***SKIPPED(CanUseRP_not_supported)***:Empty (0.04s) --- PASS: TestDNSProviders/example.com/08:RP_***SKIPPED(CanUseRP_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#07 (0.01s) --- PASS: TestDNSProviders/example.com/09:TXT:Create_TXT (0.05s) --- PASS: TestDNSProviders/example.com/09:TXT:Change_TXT_target (0.06s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#08 (0.03s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:CreateManyAtLabel (0.17s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Empty (0.07s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Create_an_A_record (0.05s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Add_at_label1 (0.07s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Add_at_label2 (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#09 (0.06s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:CreateManyTypesAtLabel (0.13s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Empty (0.15s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Create_an_A_record (0.05s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Add_Type_At_Label (0.06s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Add_Type_At_Label#01 (0.10s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#10 (0.07s) --- PASS: TestDNSProviders/example.com/12:Attl:Create_Arc (0.05s) --- PASS: TestDNSProviders/example.com/12:Attl:Change_TTL (0.09s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#11 (0.07s) --- PASS: TestDNSProviders/example.com/13:TTL:Start (0.15s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_a_ttl (0.17s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_single_target_from_set (0.07s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_all_ttls (0.21s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#12 (0.09s) --- PASS: TestDNSProviders/example.com/14:add_to_label_and_change_orig_ttl:Setup (0.06s) --- PASS: TestDNSProviders/example.com/14:add_to_label_and_change_orig_ttl:Add_at_same_label,_new_ttl (0.09s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#13 (0.11s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Create_A (0.08s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Change_to_MX (0.08s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Change_back_to_A (0.10s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#14 (0.03s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Create_a_CNAME (0.06s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Change_to_A_record (0.07s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Change_back_to_CNAME (0.20s) --- PASS: TestDNSProviders/example.com/17:HTTPS_***SKIPPED(CanUseHTTPS_not_supported)***:Empty (0.05s) --- PASS: TestDNSProviders/example.com/18:Ech_***SKIPPED(CanUseHTTPS_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/19:SVCB_***SKIPPED(CanUseSVCB_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#15 (0.01s) --- PASS: TestDNSProviders/example.com/20:CNAME:Record_pointing_to_@ (0.56s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#16 (0.05s) --- PASS: TestDNSProviders/example.com/21:ApexMX:Record_pointing_to_@ (0.12s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#17 (0.05s) --- SKIP: TestDNSProviders/example.com/22:NullMX:create (0.00s) --- PASS: TestDNSProviders/example.com/22:NullMX:unnull (0.26s) --- SKIP: TestDNSProviders/example.com/22:NullMX:renull (0.00s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#18 (0.11s) --- SKIP: TestDNSProviders/example.com/23:NullMXApex:create (0.00s) --- PASS: TestDNSProviders/example.com/23:NullMXApex:unnull (0.21s) --- SKIP: TestDNSProviders/example.com/23:NullMXApex:renull (0.00s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#19 (0.10s) --- PASS: TestDNSProviders/example.com/24:NS:NS_for_subdomain (0.07s) --- PASS: TestDNSProviders/example.com/24:NS:Dual_NS_for_subdomain (0.11s) --- PASS: TestDNSProviders/example.com/24:NS:NS_Record_pointing_to_@ (0.13s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#20 (0.07s) --- PASS: TestDNSProviders/example.com/25:NS_only_APEX:Single_NS_at_apex (0.06s) --- PASS: TestDNSProviders/example.com/25:NS_only_APEX:Dual_NS_at_apex (0.21s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#21 (0.06s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_0-byte_TXT (0.12s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_254-byte_TXT (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_255-byte_TXT (0.11s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_256-byte_TXT (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_509-byte_TXT (0.20s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_510-byte_TXT (0.11s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_511-byte_TXT (0.08s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_764-byte_TXT (0.19s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_765-byte_TXT (0.10s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_766-byte_TXT (0.10s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_single-quote (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_backtick (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-1interior (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_2_dq-2interior (0.07s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-left (0.06s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-right (0.06s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_semicolon (0.07s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_semicolon_ws (0.09s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_interior_ws (0.17s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_trailing_ws (0.08s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:Create_a_TXT/SPF (0.11s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#22 (0.03s) --- PASS: TestDNSProviders/example.com/27:TXT_backslashes:TXT_with_backslashs (0.19s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#23 (0.11s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Create_CAPS (0.08s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Downcase_label (0.29s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Downcase_target (0.16s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Upcase_both (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#24 (0.05s) --- PASS: TestDNSProviders/example.com/29:testByLabel:initial (0.11s) --- PASS: TestDNSProviders/example.com/29:testByLabel:changeOne (0.07s) --- PASS: TestDNSProviders/example.com/29:testByLabel:deleteOne (0.16s) --- PASS: TestDNSProviders/example.com/29:testByLabel:addOne (0.11s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#25 (0.05s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:initial (0.22s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:changeOne (0.20s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:deleteOne (0.09s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:addOne (0.08s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#26 (0.14s) --- PASS: TestDNSProviders/example.com/31:IDNA:Internationalized_name (0.06s) --- PASS: TestDNSProviders/example.com/31:IDNA:Change_IDN (0.06s) --- PASS: TestDNSProviders/example.com/31:IDNA:Chinese_label (0.12s) --- PASS: TestDNSProviders/example.com/31:IDNA:Internationalized_CNAME_Target (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#27 (0.03s) --- PASS: TestDNSProviders/example.com/32:IDNAs_in_CNAME_targets:IDN_CNAME_AND_Target (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#28 (0.03s) --- PASS: TestDNSProviders/example.com/33:pager101:99_records (7.55s) --- PASS: TestDNSProviders/example.com/33:pager101:100_records (0.75s) --- PASS: TestDNSProviders/example.com/33:pager101:101_records (0.61s) --- PASS: TestDNSProviders/example.com/34:pager601_***SKIPPED(disabled_by_only)***:Empty (3.46s) --- PASS: TestDNSProviders/example.com/35:pager1201_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/36:batchRecordswithOthers_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/37:CAA_***SKIPPED(CanUseCAA_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/38:LOC_***SKIPPED(CanUseLOC_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/39:NAPTR_***SKIPPED(CanUseNAPTR_not_supported)***:Empty (0.03s) --- PASS: TestDNSProviders/example.com/40:PTR_***SKIPPED(CanUsePTR_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/41:SOA_***SKIPPED(CanUseSOA_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#29 (0.02s) --- PASS: TestDNSProviders/example.com/42:SRV:SRV_record (0.06s) --- PASS: TestDNSProviders/example.com/42:SRV:Second_SRV_record,_same_prio (0.07s) --- PASS: TestDNSProviders/example.com/42:SRV:3_SRV (0.10s) --- PASS: TestDNSProviders/example.com/42:SRV:Delete_one (0.05s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Target (0.14s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Priority (0.10s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Weight (0.07s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Port (0.26s) --- PASS: TestDNSProviders/example.com/42:SRV:Empty (0.42s) --- SKIP: TestDNSProviders/example.com/42:SRV:Null_Target (0.00s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#30 (0.02s) --- PASS: TestDNSProviders/example.com/43:SRV:Create_SRV333 (0.15s) --- PASS: TestDNSProviders/example.com/43:SRV:Change_TTL999 (0.06s) --- PASS: TestDNSProviders/example.com/44:SSHFP_***SKIPPED(CanUseSSHFP_not_supported)***:Empty (0.03s) --- PASS: TestDNSProviders/example.com/45:TLSA_***SKIPPED(CanUseTLSA_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/46:DS_***SKIPPED(CanUseDS_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/47:DS_(children_only)_***SKIPPED(CanUseDSForChildren_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/48:DS_(children_only)_CLOUDNS_***SKIPPED(CanUseDSForChildren_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/49:DHCID_***SKIPPED(CanUseDHCID_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/50:DNAME_***SKIPPED(CanUseDNAME_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/51:DNSKEY_***SKIPPED(CanUseDNSKEY_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/52:ALIAS_on_apex_***SKIPPED(CanUseAlias_not_supported)***:Empty (0.03s) --- PASS: TestDNSProviders/example.com/53:ALIAS_to_nonfqdn_***SKIPPED(CanUseAlias_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/54:ALIAS_on_subdomain_***SKIPPED(CanUseAlias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/55:AZURE_ALIAS_A_***SKIPPED(CanUseAzureAlias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/56:AZURE_ALIAS_CNAME_***SKIPPED(CanUseAzureAlias_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/57:R53_ALIAS2_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/58:R53_ALIAS_ORDER_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/59:R53_ALIAS_CNAME_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/60:R53_ALIAS_Loop_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/61:R53_alias_pre-existing_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/62:R53_alias_evaluate_target_health_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/63:R53_B3493_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/64:R53_B3493_REV_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.03s) --- PASS: TestDNSProviders/example.com/65:CF_REDIRECT_CONVERT_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/66:CLOUDFLAREAPI_SINGLE_REDIRECT_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/67:CF_PROXY_A_create_***SKIPPED(disabled_by_only)***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/68:CF_PROXY_A_off_to_on_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/69:CF_PROXY_A_on_to_off_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/70:CF_PROXY_CNAME_create_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/71:CF_PROXY_CNAME_off_to_on_***SKIPPED(disabled_by_only)***:Empty (0.03s) --- PASS: TestDNSProviders/example.com/72:CF_PROXY_CNAME_on_to_off_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/73:CF_CNAME_FLATTEN_create_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/74:CF_CNAME_FLATTEN_off_to_on_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.02s) --- PASS: TestDNSProviders/example.com/75:CF_CNAME_FLATTEN_on_to_off_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/76:CF_COMMENT_create_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/77:CF_TAGS_create_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/78:CF_WORKER_ROUTE_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/79:ADGUARDHOME_A_PASSTHROUGH_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/80:ADGUARDHOME_AAAA_PASSTHROUGH_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#31 (0.01s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:create_FWD_record (0.10s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:change_FWD_target (0.06s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:FWD_with_match_subdomain (0.07s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:FWD_with_address_list (0.06s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:FWD_with_comment (0.12s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:multiple_FWD_records (0.11s) --- PASS: TestDNSProviders/example.com/81:MIKROTIK_FWD:delete_one_FWD (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#32 (0.03s) --- PASS: TestDNSProviders/example.com/82:MIKROTIK_NXDOMAIN:create_NXDOMAIN (0.06s) --- PASS: TestDNSProviders/example.com/82:MIKROTIK_NXDOMAIN:multiple_NXDOMAIN (0.09s) --- PASS: TestDNSProviders/example.com/82:MIKROTIK_NXDOMAIN:delete_one_NXDOMAIN (0.18s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#33 (0.03s) --- PASS: TestDNSProviders/example.com/83:MIKROTIK_METADATA:A_record_with_comment (0.07s) --- PASS: TestDNSProviders/example.com/83:MIKROTIK_METADATA:change_comment (0.25s) --- PASS: TestDNSProviders/example.com/83:MIKROTIK_METADATA:A_with_match_subdomain (0.08s) --- PASS: TestDNSProviders/example.com/84:VERCEL_CAA_whitespace_-_cansignhttpexchanges_***SKIPPED(disabled_by_only)***:Empty (0.13s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#34 (0.02s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:Create_some_records (0.19s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_label (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_label,type (0.06s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#01 (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_label,type,target (0.06s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#02 (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_type (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#03 (0.07s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_type,target (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#04 (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_target (0.04s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#05 (0.04s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_manytypes (0.05s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#06 (0.15s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:ignore_label,type,target=* (0.06s) --- PASS: TestDNSProviders/example.com/85:IGNORE_main:VERIFY_PREVIOUS#07 (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#35 (0.13s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:Create_some_records (0.27s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_label (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS (0.04s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_label,type (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#01 (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_label,type,target (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#02 (0.08s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_type (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#03 (0.04s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_type,target (0.12s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#04 (0.06s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_target (0.06s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#05 (0.06s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:apex_manytypes (0.05s) --- PASS: TestDNSProviders/example.com/86:IGNORE_apex:VERIFY_PREVIOUS#06 (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#36 (0.14s) --- PASS: TestDNSProviders/example.com/87:IGNORE_unsafe:Create_some_records (0.19s) --- PASS: TestDNSProviders/example.com/87:IGNORE_unsafe:ignore_unsafe_apex (0.04s) --- PASS: TestDNSProviders/example.com/87:IGNORE_unsafe:VERIFY_PREVIOUS (0.06s) --- PASS: TestDNSProviders/example.com/87:IGNORE_unsafe:ignore_unsafe_label (0.04s) --- PASS: TestDNSProviders/example.com/87:IGNORE_unsafe:VERIFY_PREVIOUS#01 (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#37 (0.11s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:Create_some_records (0.30s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:ignore_label=foo.* (0.05s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:VERIFY_PREVIOUS (0.06s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:ignore_label=foo.bat,type (0.05s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:VERIFY_PREVIOUS#01 (0.05s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:ignore_target=*.domain (0.05s) --- PASS: TestDNSProviders/example.com/88:IGNORE_wilds:VERIFY_PREVIOUS#02 (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#38 (0.16s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:Create_some_records (0.42s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:IGNORE_change_ByZone (0.12s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:VERIFY_PREVIOUS (0.08s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:IGNORE_change_ByLabel (0.10s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:VERIFY_PREVIOUS#01 (0.11s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:IGNORE_change_ByRecordSet (0.09s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:VERIFY_PREVIOUS#02 (0.09s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:IGNORE_change_ByRecord (0.11s) --- PASS: TestDNSProviders/example.com/89:IGNORE_with_modify:VERIFY_PREVIOUS#03 (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#39 (0.27s) --- PASS: TestDNSProviders/example.com/90:IGNORE_TARGET_b2285:Create_some_records (0.09s) --- PASS: TestDNSProviders/example.com/90:IGNORE_TARGET_b2285:Add_a_new_record_-_ignoring_test.foo.com. (0.02s) --- PASS: TestDNSProviders/example.com/90:IGNORE_TARGET_b2285:VERIFY_PREVIOUS (0.04s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#40 (0.09s) --- PASS: TestDNSProviders/example.com/91:IGNORE_everything_b2822:Create_some_records (0.18s) --- PASS: TestDNSProviders/example.com/91:IGNORE_everything_b2822:ignore_them_all (0.05s) --- PASS: TestDNSProviders/example.com/91:IGNORE_everything_b2822:VERIFY_PREVIOUS (0.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#41 (0.23s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:Create_some_records (0.10s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:ignore (0.05s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:VERIFY_PREVIOUS (0.04s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:Verify_nothing_changed (0.15s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:VERIFY_PREVIOUS#01 (0.03s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:ignore_with_change (0.07s) --- PASS: TestDNSProviders/example.com/92:IGNORE_w/change_b3227:VERIFY_PREVIOUS#02 (0.03s) --- PASS: TestDNSProviders/example.com/93:structured_TXT_***SKIPPED(disabled_by_only)***:Empty (0.08s) --- PASS: TestDNSProviders/example.com/94:structured_TXT_as_native_records_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/95:CLOUDNS_geodns_tests_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/96:PORKBUN_URLFWD_tests_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/97:GCORE_metadata_tests_***SKIPPED(disabled_by_only)***:Empty (0.05s) --- PASS: TestDNSProviders/example.com/98:NAMECHEAP_url_redirect_records_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/99:OPENPGPKEY_***SKIPPED(CanUseOPENPGPKEY_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/100:SMIMEA_***SKIPPED(CanUseSMIMEA_not_supported)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/101:Bunny_DNS_Pull_Zone_***SKIPPED(disabled_by_only)***:Empty (0.01s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#42 (0.01s) --- PASS: TestDNSProviders/example.com/102:final:final (0.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#43 (0.06s) --- PASS: TestDNSProviders/example.com/103:final:final (0.07s) === RUN TestDualProviders Testing Profile="MIKROTIK" (TYPE="MIKROTIK") provider_test.go:29: Skipping. DocDualHost == Cannot --- SKIP: TestDualProviders (0.00s) === RUN TestNameserverDots Testing Profile="MIKROTIK" (TYPE="MIKROTIK") provider_test.go:108: Skipping. DocDualHost == Cannot --- SKIP: TestNameserverDots (0.00s) === RUN TestDuplicateNameservers Testing Profile="MIKROTIK" (TYPE="MIKROTIK") provider_test.go:140: Skipping. DocDualHost == Cannot --- SKIP: TestDuplicateNameservers (0.00s) PASS ok github.com/StackExchange/dnscontrol/v4/integrationTest 39.183s ``` </details> --------- Co-authored-by: Tom Limoncelli <6293917+tlimoncelli@users.noreply.github.com>
|
Hello friend! Please rebase (there are merge conflicts, sorry!). If you can do this in the today or tomorrow, I'll include this in the next release. |
|
Hi @zupolgec, Apologies for the delay, I thought I had already posted here. As Tom mentioned I am the Maintainer Liaison. Would you be able to send your contact email to us? You can reach me at dnscontrol at faisal dot fm and Tom at tlimoncelli at stack over flow dot com Your email will not be public and will only be used the the project team to send out maintainer communications. As a maintainer, we’d like to remind you of your role and expectations we have so that everyone has a positive experience using dnscontrol:
Thank you!! |
|
Hey @tlimoncelli, saw your rebase, thanks! I fixed a couple things on my end — removed some unused functions and fixed godot lint warnings in the unifi provider. Just pushed that. There are two issues left from the merge that I think are on your side:
Let me know! |
|
Hello @zupolgec ! Thanks for pointing those out. The first one is now fixed in |
Add support for managing static DNS records on UniFi Network controllers via the v2 API (Network 8.2+). Supports both local access and remote cloud access via api.ui.com. Features: - Supported record types: A, AAAA, CNAME, MX, TXT, SRV, NS - Local access with optional TLS verification skip for self-signed certs - Cloud access via UniFi Cloud Connector (requires UniFi OS 5.0.3+) - Automatic record filtering by domain suffix Configuration options: - host: Local controller URL (e.g., https://192.168.1.1) - console_id: Cloud console ID for remote access - api_key: UniFi API key - site: Site name (defaults to 'default') - skip_tls_verify: Skip TLS verification for self-signed certs
Support both UniFi Network APIs:
- Legacy API (v2/api/site/{site}/static-dns) for Network 8.2+
- New API (integration/v1/sites/{siteId}/dns/policies) for Network 10.1+
The new 'api_version' config parameter accepts:
- 'auto' (default): probes both APIs, prefers new, falls back to legacy
- 'new': forces new API only
- 'legacy': forces legacy API only
The new API supports native PUT for updates (no delete+create needed).
- Add UNIFI profile to profiles.json with known failures (26, 43) - Improve AuditRecords to properly return nil when no errors - Add audit rules for: TXT > 255 chars, null MX, null SRV target - Remove NS from supported types (requires IP, not hostname)
8892775 to
bda1c5b
Compare
|
All green now 😀 |
tlimoncelli
left a comment
There was a problem hiding this comment.
Please update the regex on line 41 of .goreleaser.yml
Please review Step 15 of https://docs.dnscontrol.org/developer-info/writing-providers for a checklist of files that should be modified.
providers/unifi/types.go
Outdated
|
|
||
| // recordToLegacyMap converts a dnscontrol RecordConfig to a map for API requests. | ||
| // UniFi is strict about which fields can be set for each record type. | ||
| func recordToLegacyMap(domain string, rc *models.RecordConfig) (map[string]any, error) { |
There was a problem hiding this comment.
parameter "domain" is not used. Please remove.
providers/unifi/types.go
Outdated
| } | ||
|
|
||
| // recordToNew converts a dnscontrol RecordConfig to a UniFi new API record. | ||
| func recordToNew(domain string, rc *models.RecordConfig) (*dnsPolicyRecord, error) { |
There was a problem hiding this comment.
parameter "domain" is never used. Please remove.
|
🥳 thanks @tlimoncelli |
Summary
Add support for managing static DNS records on UniFi Network controllers via the v2 API (Network 8.2+).
Features
api.ui.com) - requires UniFi OS 5.0.3+Configuration
Local Access
{ "unifi": { "TYPE": "UNIFI", "host": "https://192.168.1.1", "api_key": "your-api-key", "site": "default", "skip_tls_verify": "true" } }Cloud Access
{ "unifi_cloud": { "TYPE": "UNIFI", "console_id": "your-console-id", "api_key": "cloud-api-key", "site": "default" } }Usage Example
Testing
Tested using the OLD API (
/v2/api/site/{site}/static-dns) via:api.ui.com(Network 10.1.78)All CRUD operations verified working on both access methods.
References