Skip to content

Commit 3c18268

Browse files
authored
HEDNS: Preserve Dynamic DNS state & add DDNS key management (#4087)
Modifying a Dynamic DNS record on Hurricane Electric DNS (e.g. changing TTL) silently dropped the dynamic flag and its associated DDNS key. There was also no way to manage DDNS keys from dnsconfig.js. This PR fixes #4085. cc @rblenkinsopp ## Changes - Record modifications now retain (or explicitly toggle) the Dynamic DNS flag. Unspecified records inherit their current state from the provider. - New record modifiers `HEDNS_DYNAMIC_ON`, `HEDNS_DYNAMIC_OFF`, and `HEDNS_DDNS_KEY(key)` allow controlling Dynamic DNS and setting DDNS keys from dnsconfig.js. Keys are write-only since HE DNS doesn't expose them for reading. - Zone export includes dynamic state in JS and TSV output formats for HEDNS. - Docs: updated provider docs and added record modifier reference pages. ## Tests Added new test groups to integration tests for HEDNS covering dynamic on/off, DDNS keys, state inheritance, and mixed records. <details> <summary> Full test run results ``` PASS ok github.com/StackExchange/dnscontrol/v4/integrationTest 1053.995s ``` </summary> ``` --- PASS: TestDNSProviders (1041.65s) --- PASS: TestDNSProviders/example.com (1036.81s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty (0.68s) --- PASS: TestDNSProviders/example.com/00:A:Create_A (1.14s) --- PASS: TestDNSProviders/example.com/00:A:Change_A_target (1.23s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#01 (0.83s) --- PASS: TestDNSProviders/example.com/01:Apex:Create_A (1.54s) --- PASS: TestDNSProviders/example.com/01:Apex:Change_A_target (1.23s) --- PASS: TestDNSProviders/example.com/02:Protocol-Wildcard_***SKIPPED(excluded_by_not("HEDNS"))***:Empty (0.87s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#02 (0.38s) --- PASS: TestDNSProviders/example.com/03:AAAA:Create_AAAA (1.21s) --- PASS: TestDNSProviders/example.com/03:AAAA:Change_AAAA_target (1.11s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#03 (0.79s) --- PASS: TestDNSProviders/example.com/04:CNAME:Create_a_CNAME (2.28s) --- PASS: TestDNSProviders/example.com/04:CNAME:Change_CNAME_target (1.23s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#04 (0.69s) --- PASS: TestDNSProviders/example.com/05:CNAME-short:Create_a_CNAME (10.13s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#05 (1.19s) --- PASS: TestDNSProviders/example.com/06:MX:Create_MX_apex (1.50s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_apex (1.50s) --- PASS: TestDNSProviders/example.com/06:MX:Create_MX (1.80s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_target (1.35s) --- PASS: TestDNSProviders/example.com/06:MX:Change_MX_p (19.12s) --- PASS: TestDNSProviders/example.com/07:RP_***SKIPPED(CanUseRP_not_supported)***:Empty (9.23s) --- PASS: TestDNSProviders/example.com/08:RP_***SKIPPED(CanUseRP_not_supported)***:Empty (0.48s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#06 (0.53s) --- PASS: TestDNSProviders/example.com/09:TXT:Create_TXT (1.82s) --- PASS: TestDNSProviders/example.com/09:TXT:Change_TXT_target (9.19s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#07 (0.86s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:CreateManyAtLabel (2.22s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Empty (9.90s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Create_an_A_record (1.23s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Add_at_label1 (1.36s) --- PASS: TestDNSProviders/example.com/10:ManyAtOnce:Add_at_label2 (1.37s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#08 (1.39s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:CreateManyTypesAtLabel (2.29s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Empty (1.32s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Create_an_A_record (1.15s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Add_Type_At_Label (1.36s) --- PASS: TestDNSProviders/example.com/11:manyTypesAtOnce:Add_Type_At_Label#01 (1.48s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#09 (1.44s) --- PASS: TestDNSProviders/example.com/12:Attl:Create_Arc (10.19s) --- PASS: TestDNSProviders/example.com/12:Attl:Change_TTL (1.23s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#10 (0.69s) --- PASS: TestDNSProviders/example.com/13:TTL:Start (2.08s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_a_ttl (1.77s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_single_target_from_set (1.22s) --- PASS: TestDNSProviders/example.com/13:TTL:Change_all_ttls (10.37s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#11 (1.39s) --- PASS: TestDNSProviders/example.com/14:add_to_label_and_change_orig_ttl:Setup (1.23s) --- PASS: TestDNSProviders/example.com/14:add_to_label_and_change_orig_ttl:Add_at_same_label,_new_ttl (1.57s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#12 (1.08s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Create_A (1.19s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Change_to_MX (1.60s) --- PASS: TestDNSProviders/example.com/15:TypeChange:Change_back_to_A (1.67s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#13 (0.69s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Create_a_CNAME (1.25s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Change_to_A_record (9.23s) --- PASS: TestDNSProviders/example.com/16:TypeChangeHard:Change_back_to_CNAME (10.16s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#14 (9.51s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Create_a_HTTPS_record (1.68s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Change_HTTPS_priority (11.36s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Change_HTTPS_target (1.22s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Change_HTTPS_params (1.20s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Change_HTTPS_params-empty (1.23s) --- PASS: TestDNSProviders/example.com/17:HTTPS:Change_HTTPS_all (1.60s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#15 (0.83s) --- PASS: TestDNSProviders/example.com/18:Ech:Create_a_HTTPS_record (1.23s) --- PASS: TestDNSProviders/example.com/18:Ech:Add_an_ECH_key (1.18s) --- PASS: TestDNSProviders/example.com/18:Ech:Ignore_the_ECH_key_while_changing_other_values (1.16s) --- PASS: TestDNSProviders/example.com/18:Ech:Change_the_ECH_key_and_other_values (8.88s) --- PASS: TestDNSProviders/example.com/18:Ech:Another_domain_with_a_different_ECH_value (1.58s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#16 (0.84s) --- PASS: TestDNSProviders/example.com/19:SVCB:Create_a_SVCB_record (1.23s) --- PASS: TestDNSProviders/example.com/19:SVCB:Change_SVCB_priority (1.24s) --- PASS: TestDNSProviders/example.com/19:SVCB:Change_SVCB_target (1.27s) --- PASS: TestDNSProviders/example.com/19:SVCB:Change_SVCB_params (1.20s) --- PASS: TestDNSProviders/example.com/19:SVCB:Change_SVCB_params-empty (1.17s) --- PASS: TestDNSProviders/example.com/19:SVCB:Change_SVCB_all (1.12s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#17 (0.78s) --- PASS: TestDNSProviders/example.com/20:CNAME:Record_pointing_to_@ (10.88s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#18 (2.19s) --- PASS: TestDNSProviders/example.com/21:ApexMX:Record_pointing_to_@ (1.80s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#19 (1.05s) --- SKIP: TestDNSProviders/example.com/22:NullMX:create (0.00s) --- PASS: TestDNSProviders/example.com/22:NullMX:unnull (12.58s) --- SKIP: TestDNSProviders/example.com/22:NullMX:renull (0.00s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#20 (1.81s) --- SKIP: TestDNSProviders/example.com/23:NullMXApex:create (0.00s) --- PASS: TestDNSProviders/example.com/23:NullMXApex:unnull (13.39s) --- SKIP: TestDNSProviders/example.com/23:NullMXApex:renull (0.00s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#21 (10.49s) --- PASS: TestDNSProviders/example.com/24:NS:NS_for_subdomain (1.30s) --- PASS: TestDNSProviders/example.com/24:NS:Dual_NS_for_subdomain (1.50s) --- PASS: TestDNSProviders/example.com/24:NS:NS_Record_pointing_to_@ (2.47s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#22 (1.17s) --- PASS: TestDNSProviders/example.com/25:NS_only_APEX:Single_NS_at_apex (1.42s) --- PASS: TestDNSProviders/example.com/25:NS_only_APEX:Dual_NS_at_apex (1.47s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#23 (1.01s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_0-byte_TXT (1.19s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_254-byte_TXT (1.57s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_255-byte_TXT (1.45s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_256-byte_TXT (1.44s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_509-byte_TXT (1.57s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_510-byte_TXT (1.65s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_511-byte_TXT (11.49s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_764-byte_TXT (9.13s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_765-byte_TXT (10.48s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:a_766-byte_TXT (8.60s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_single-quote (9.92s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_backtick (1.92s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-1interior (1.58s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_2_dq-2interior (1.57s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-left (1.24s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_1_dq-right (1.29s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_semicolon (1.66s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_with_semicolon_ws (1.58s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_interior_ws (1.56s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:TXT_trailing_ws (1.48s) --- PASS: TestDNSProviders/example.com/26:complex_TXT:Create_a_TXT/SPF (1.52s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#24 (9.46s) --- PASS: TestDNSProviders/example.com/27:TXT_backslashes:TXT_with_backslashs (2.63s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#25 (1.80s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Create_CAPS (1.46s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Downcase_label (1.25s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Downcase_target (1.92s) --- PASS: TestDNSProviders/example.com/28:Case_Sensitivity:Upcase_both (11.17s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#26 (9.31s) --- PASS: TestDNSProviders/example.com/29:testByLabel:initial (1.55s) --- PASS: TestDNSProviders/example.com/29:testByLabel:changeOne (1.20s) --- PASS: TestDNSProviders/example.com/29:testByLabel:deleteOne (1.05s) --- PASS: TestDNSProviders/example.com/29:testByLabel:addOne (9.10s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#27 (0.99s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:initial (11.77s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:changeOne (1.26s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:deleteOne (1.19s) --- PASS: TestDNSProviders/example.com/30:testByRecordSet:addOne (11.18s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#28 (3.65s) --- PASS: TestDNSProviders/example.com/31:IDNA:Internationalized_name (11.14s) --- PASS: TestDNSProviders/example.com/31:IDNA:Change_IDN (1.29s) --- PASS: TestDNSProviders/example.com/31:IDNA:Chinese_label (1.60s) --- PASS: TestDNSProviders/example.com/31:IDNA:Internationalized_CNAME_Target (9.07s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#29 (0.70s) --- PASS: TestDNSProviders/example.com/32:IDNAs_in_CNAME_targets:IDN_CNAME_AND_Target (1.38s) --- PASS: TestDNSProviders/example.com/33:pager101_***SKIPPED(excluded_by_not("HEDNS"))***:Empty (0.68s) --- PASS: TestDNSProviders/example.com/34:pager601_***SKIPPED(disabled_by_only)***:Empty (2.12s) --- PASS: TestDNSProviders/example.com/35:pager1201_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/36:batchRecordswithOthers_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#30 (0.49s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_record (17.47s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_change_tag (11.19s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_change_target (10.06s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_change_flag (1.32s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_many_records (1.55s) --- PASS: TestDNSProviders/example.com/37:CAA:CAA_whitespace (1.13s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#31 (0.77s) --- PASS: TestDNSProviders/example.com/38:LOC:Single_LOC_record (11.28s) --- PASS: TestDNSProviders/example.com/38:LOC:Update_single_LOC_record (4.88s) --- PASS: TestDNSProviders/example.com/38:LOC:Multiple_LOC_records-create_a-d_modify_apex (17.60s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#32 (1.99s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_record (1.16s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_second_record (1.27s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_delete_second_record (2.54s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_order (1.30s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_preference (10.28s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_flags (1.26s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_service (1.89s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_regexp (1.55s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_remove_regexp_and_add_target (1.52s) --- PASS: TestDNSProviders/example.com/39:NAPTR:NAPTR_change_target (8.69s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#33 (9.01s) --- PASS: TestDNSProviders/example.com/40:PTR:Create_PTR_record (8.69s) --- PASS: TestDNSProviders/example.com/40:PTR:Modify_PTR_record (1.27s) --- PASS: TestDNSProviders/example.com/41:SOA_***SKIPPED(CanUseSOA_not_supported)***:Empty (1.05s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#34 (0.42s) --- PASS: TestDNSProviders/example.com/42:SRV:SRV_record (1.55s) --- PASS: TestDNSProviders/example.com/42:SRV:Second_SRV_record,_same_prio (1.73s) --- PASS: TestDNSProviders/example.com/42:SRV:3_SRV (1.29s) --- PASS: TestDNSProviders/example.com/42:SRV:Delete_one (10.99s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Target (9.05s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Priority (1.18s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Weight (9.09s) --- PASS: TestDNSProviders/example.com/42:SRV:Change_Port (9.50s) --- PASS: TestDNSProviders/example.com/42:SRV:Empty (1.13s) --- PASS: TestDNSProviders/example.com/42:SRV:Null_Target (8.77s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#35 (0.68s) --- PASS: TestDNSProviders/example.com/43:SRV:Create_SRV333 (1.24s) --- PASS: TestDNSProviders/example.com/43:SRV:Change_TTL999 (1.57s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#36 (9.64s) --- PASS: TestDNSProviders/example.com/44:SSHFP:SSHFP_record (9.94s) --- PASS: TestDNSProviders/example.com/44:SSHFP:SSHFP_change_algorithm (8.59s) --- PASS: TestDNSProviders/example.com/44:SSHFP:SSHFP_change_fingerprint_and_type (1.20s) --- PASS: TestDNSProviders/example.com/45:TLSA_***SKIPPED(CanUseTLSA_not_supported)***:Empty (0.71s) --- PASS: TestDNSProviders/example.com/46:DS_***SKIPPED(CanUseDS_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/47:DS_(children_only)_***SKIPPED(CanUseDSForChildren_not_supported)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/48:DS_(children_only)_CLOUDNS_***SKIPPED(CanUseDSForChildren_not_supported)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/49:DHCID_***SKIPPED(CanUseDHCID_not_supported)***:Empty (0.43s) --- PASS: TestDNSProviders/example.com/50:DNAME_***SKIPPED(CanUseDNAME_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/51:DNSKEY_***SKIPPED(CanUseDNSKEY_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#37 (0.38s) --- PASS: TestDNSProviders/example.com/52:ALIAS_on_apex:ALIAS_at_root (6.42s) --- PASS: TestDNSProviders/example.com/52:ALIAS_on_apex:change_it (1.55s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#38 (0.70s) --- PASS: TestDNSProviders/example.com/53:ALIAS_to_nonfqdn:ALIAS_at_root (10.56s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#39 (1.11s) --- PASS: TestDNSProviders/example.com/54:ALIAS_on_subdomain:ALIAS_at_subdomain (10.94s) --- PASS: TestDNSProviders/example.com/54:ALIAS_on_subdomain:change_it (1.51s) --- PASS: TestDNSProviders/example.com/55:AZURE_ALIAS_A_***SKIPPED(CanUseAzureAlias_not_supported)***:Empty (10.00s) --- PASS: TestDNSProviders/example.com/56:AZURE_ALIAS_CNAME_***SKIPPED(CanUseAzureAlias_not_supported)***:Empty (0.67s) --- PASS: TestDNSProviders/example.com/57:R53_ALIAS2_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.53s) --- PASS: TestDNSProviders/example.com/58:R53_ALIAS_ORDER_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.40s) --- PASS: TestDNSProviders/example.com/59:R53_ALIAS_CNAME_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.45s) --- PASS: TestDNSProviders/example.com/60:R53_ALIAS_Loop_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/61:R53_alias_pre-existing_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/62:R53_alias_evaluate_target_health_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.47s) --- PASS: TestDNSProviders/example.com/63:R53_B3493_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/64:R53_B3493_REV_***SKIPPED(CanUseRoute53Alias_not_supported)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/65:CF_REDIRECT_CONVERT_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/66:CLOUDFLAREAPI_SINGLE_REDIRECT_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.45s) --- PASS: TestDNSProviders/example.com/67:CF_PROXY_A_create_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/68:CF_PROXY_A_off_to_on_***SKIPPED(disabled_by_only)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/69:CF_PROXY_A_on_to_off_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/70:CF_PROXY_CNAME_create_***SKIPPED(disabled_by_only)***:Empty (0.46s) --- PASS: TestDNSProviders/example.com/71:CF_PROXY_CNAME_off_to_on_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/72:CF_PROXY_CNAME_on_to_off_***SKIPPED(disabled_by_only)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/73:CF_CNAME_FLATTEN_create_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.82s) --- PASS: TestDNSProviders/example.com/74:CF_CNAME_FLATTEN_off_to_on_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/75:CF_CNAME_FLATTEN_on_to_off_***SKIPPED(excluded_by_alltrue([false]))***:Empty (1.75s) --- PASS: TestDNSProviders/example.com/76:CF_COMMENT_create_***SKIPPED(disabled_by_only)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/77:CF_TAGS_create_***SKIPPED(excluded_by_alltrue([false]))***:Empty (0.46s) --- PASS: TestDNSProviders/example.com/78:CF_WORKER_ROUTE_***SKIPPED(disabled_by_only)***:Empty (0.43s) --- PASS: TestDNSProviders/example.com/79:ADGUARDHOME_A_PASSTHROUGH_***SKIPPED(disabled_by_only)***:Empty (0.51s) --- PASS: TestDNSProviders/example.com/80:ADGUARDHOME_AAAA_PASSTHROUGH_***SKIPPED(disabled_by_only)***:Empty (0.46s) --- PASS: TestDNSProviders/example.com/81:VERCEL_CAA_whitespace_-_cansignhttpexchanges_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#40 (0.50s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:Create_some_records (18.35s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_label (0.88s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS (0.88s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_label,type (0.75s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#01 (0.83s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_label,type,target (0.74s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#02 (0.83s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_type (0.74s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#03 (0.83s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_type,target (0.75s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#04 (0.83s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_target (0.75s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#05 (0.84s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_manytypes (0.85s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#06 (3.33s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:ignore_label,type,target=* (0.87s) --- PASS: TestDNSProviders/example.com/82:IGNORE_main:VERIFY_PREVIOUS#07 (0.88s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#41 (7.91s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:Create_some_records (3.30s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_label (0.82s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS (0.96s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_label,type (1.15s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#01 (0.90s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_label,type,target (0.86s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#02 (0.87s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_type (0.93s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#03 (0.84s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_type,target (0.83s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#04 (0.76s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_target (0.85s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#05 (0.87s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:apex_manytypes (0.85s) --- PASS: TestDNSProviders/example.com/83:IGNORE_apex:VERIFY_PREVIOUS#06 (0.86s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#42 (3.29s) --- PASS: TestDNSProviders/example.com/84:IGNORE_unsafe:Create_some_records (3.54s) --- PASS: TestDNSProviders/example.com/84:IGNORE_unsafe:ignore_unsafe_apex (0.88s) --- PASS: TestDNSProviders/example.com/84:IGNORE_unsafe:VERIFY_PREVIOUS (0.87s) --- PASS: TestDNSProviders/example.com/84:IGNORE_unsafe:ignore_unsafe_label (0.86s) --- PASS: TestDNSProviders/example.com/84:IGNORE_unsafe:VERIFY_PREVIOUS#01 (0.82s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#43 (1.67s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:Create_some_records (5.76s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:ignore_label=foo.* (0.83s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:VERIFY_PREVIOUS (0.74s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:ignore_label=foo.bat,type (0.83s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:VERIFY_PREVIOUS#01 (0.75s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:ignore_target=*.domain (0.83s) --- PASS: TestDNSProviders/example.com/85:IGNORE_wilds:VERIFY_PREVIOUS#02 (0.88s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#44 (15.89s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:Create_some_records (30.83s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:IGNORE_change_ByZone (1.21s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:VERIFY_PREVIOUS (0.82s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:IGNORE_change_ByLabel (1.41s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:VERIFY_PREVIOUS#01 (0.88s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:IGNORE_change_ByRecordSet (8.39s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:VERIFY_PREVIOUS#02 (0.77s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:IGNORE_change_ByRecord (1.34s) --- PASS: TestDNSProviders/example.com/86:IGNORE_with_modify:VERIFY_PREVIOUS#03 (2.11s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#45 (21.46s) --- PASS: TestDNSProviders/example.com/87:IGNORE_TARGET_b2285:Create_some_records (2.36s) --- PASS: TestDNSProviders/example.com/87:IGNORE_TARGET_b2285:Add_a_new_record_-_ignoring_test.foo.com. (0.50s) --- PASS: TestDNSProviders/example.com/87:IGNORE_TARGET_b2285:VERIFY_PREVIOUS (0.84s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#46 (12.21s) --- PASS: TestDNSProviders/example.com/88:IGNORE_everything_b2822:Create_some_records (11.28s) --- PASS: TestDNSProviders/example.com/88:IGNORE_everything_b2822:ignore_them_all (0.95s) --- PASS: TestDNSProviders/example.com/88:IGNORE_everything_b2822:VERIFY_PREVIOUS (1.23s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#47 (1.92s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:Create_some_records (3.34s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:ignore (1.04s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:VERIFY_PREVIOUS (0.75s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:Verify_nothing_changed (0.82s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:VERIFY_PREVIOUS#01 (0.75s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:ignore_with_change (1.17s) --- PASS: TestDNSProviders/example.com/89:IGNORE_w/change_b3227:VERIFY_PREVIOUS#02 (0.75s) --- PASS: TestDNSProviders/example.com/90:structured_TXT_***SKIPPED(disabled_by_only)***:Empty (0.99s) --- PASS: TestDNSProviders/example.com/91:structured_TXT_as_native_records_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/92:CLOUDNS_geodns_tests_***SKIPPED(disabled_by_only)***:Empty (0.38s) --- PASS: TestDNSProviders/example.com/93:PORKBUN_URLFWD_tests_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/94:GCORE_metadata_tests_***SKIPPED(disabled_by_only)***:Empty (0.45s) --- PASS: TestDNSProviders/example.com/95:NAMECHEAP_url_redirect_records_***SKIPPED(disabled_by_only)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/96:OPENPGPKEY_***SKIPPED(CanUseOPENPGPKEY_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/97:SMIMEA_***SKIPPED(CanUseSMIMEA_not_supported)***:Empty (0.37s) --- PASS: TestDNSProviders/example.com/98:Bunny_DNS_Pull_Zone_***SKIPPED(disabled_by_only)***:Empty (1.68s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#48 (0.38s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Create_dynamic_A (7.35s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Change_target_preserves_dynamic (1.20s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Turn_off_dynamic (11.54s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Turn_on_dynamic (1.19s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Inherit_dynamic_on_modify (1.23s) --- PASS: TestDNSProviders/example.com/99:HEDNS_DYNAMIC_A_lifecycle:Add_static_record (1.13s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#49 (1.11s) --- PASS: TestDNSProviders/example.com/100:HEDNS_DYNAMIC_AAAA+TXT:Create_dynamic_AAAA (1.14s) --- PASS: TestDNSProviders/example.com/100:HEDNS_DYNAMIC_AAAA+TXT:Change_dynamic_AAAA_target (1.17s) --- PASS: TestDNSProviders/example.com/100:HEDNS_DYNAMIC_AAAA+TXT:Create_dynamic_TXT (1.43s) --- PASS: TestDNSProviders/example.com/100:HEDNS_DYNAMIC_AAAA+TXT:Turn_off_dynamic_TXT (1.19s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#50 (9.13s) --- PASS: TestDNSProviders/example.com/101:HEDNS_DDNS_KEY:Create_A_with_DDNS_key_(implicit_dynamic) (1.53s) --- PASS: TestDNSProviders/example.com/101:HEDNS_DDNS_KEY:Change_target_+_key (1.40s) --- PASS: TestDNSProviders/example.com/101:HEDNS_DDNS_KEY:Create_AAAA_with_DDNS_key (10.18s) --- PASS: TestDNSProviders/example.com/101:HEDNS_DDNS_KEY:Change_AAAA_target_+_key (1.44s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#51 (0.74s) --- PASS: TestDNSProviders/example.com/102:HEDNS_DYNAMIC_mixed_records:Create_mix_of_dynamic_and_static (1.52s) --- PASS: TestDNSProviders/example.com/102:HEDNS_DYNAMIC_mixed_records:Modify_only_the_static_record (18.29s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#52 (1.05s) --- PASS: TestDNSProviders/example.com/103:final:final (1.24s) --- PASS: TestDNSProviders/example.com/Clean_Slate:Empty#53 (1.45s) --- PASS: TestDNSProviders/example.com/104:final:final (10.52s) === RUN TestDualProviders Testing Profile="HEDNS" (TYPE="HEDNS") provider_test.go:50: Clearing everything provider_test.go:44: #1: - DELETE final.example.com TXT "TestDNSProviders was successful!" ttl=300 provider_test.go:57: Adding test nameservers provider_test.go:44: #1: + CREATE example.com NS ns1.example.com. ttl=300 provider_test.go:44: #2: + CREATE example.com NS ns2.example.com. ttl=300 provider_test.go:60: Running again to ensure stability provider_test.go:76: Removing test nameservers provider_test.go:44: #1: - DELETE example.com NS ns1.example.com. ttl=300 provider_test.go:44: #2: - DELETE example.com NS ns2.example.com. ttl=300 --- PASS: TestDualProviders (11.24s) === RUN TestNameserverDots Testing Profile="HEDNS" (TYPE="HEDNS") === RUN TestNameserverDots/No_trailing_dot_in_nameserver --- PASS: TestNameserverDots (0.20s) --- PASS: TestNameserverDots/No_trailing_dot_in_nameserver (0.00s) === RUN TestDuplicateNameservers Testing Profile="HEDNS" (TYPE="HEDNS") provider_test.go:145: Skipping. Deduplication logic is not implemented for this provider. --- SKIP: TestDuplicateNameservers (0.19s) PASS ok github.com/StackExchange/dnscontrol/v4/integrationTest 1053.995s ``` </details>
1 parent 01d7795 commit 3c18268

File tree

13 files changed

+601
-18
lines changed

13 files changed

+601
-18
lines changed

commands/getZones.go

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -340,33 +340,40 @@ func GetZone(args GetZoneArgs) error {
340340

341341
case "tsv":
342342
for _, rec := range recs {
343-
cfmeta := ""
343+
providerMeta := ""
344344
if cp, ok := rec.Metadata["cloudflare_proxy"]; ok {
345345
if cp == "true" {
346-
cfmeta += ",cloudflare_proxy=true"
346+
providerMeta += ",cloudflare_proxy=true"
347347
}
348348
}
349349
if cf, ok := rec.Metadata["cloudflare_cname_flatten"]; ok {
350350
if cf == "on" {
351-
cfmeta += ",cloudflare_cname_flatten=on"
351+
providerMeta += ",cloudflare_cname_flatten=on"
352352
}
353353
}
354354
if comment := rec.Metadata["cloudflare_comment"]; comment != "" {
355-
cfmeta += ",cloudflare_comment=" + comment
355+
providerMeta += ",cloudflare_comment=" + comment
356356
}
357357
if tags := rec.Metadata["cloudflare_tags"]; tags != "" {
358-
cfmeta += ",cloudflare_tags=" + tags
358+
providerMeta += ",cloudflare_tags=" + tags
359359
}
360-
if cfmeta != "" {
361-
cfmeta = "\t" + cfmeta[1:] // Remove leading comma, add tab
360+
// HEDNS metadata
361+
if dyn, ok := rec.Metadata["hedns_dynamic"]; ok && dyn == "on" {
362+
providerMeta += ",hedns_dynamic=on"
363+
if key := rec.Metadata["hedns_ddns_key"]; key != "" {
364+
providerMeta += ",hedns_ddns_key=" + key
365+
}
366+
}
367+
if providerMeta != "" {
368+
providerMeta = "\t" + providerMeta[1:] // Remove leading comma, add tab
362369
}
363370

364371
ty := rec.Type
365372
if rec.Type == "UNKNOWN" {
366373
ty = rec.UnknownTypeName
367374
}
368375
fmt.Fprintf(w, "%s\t%s\t%d\tIN\t%s\t%s%s\n",
369-
rec.NameFQDN, rec.Name, rec.TTL, ty, rec.GetTargetCombinedFunc(nil), cfmeta)
376+
rec.NameFQDN, rec.Name, rec.TTL, ty, rec.GetTargetCombinedFunc(nil), providerMeta)
370377
}
371378

372379
default:
@@ -447,6 +454,16 @@ func formatDsl(rec *models.RecordConfig, defaultTTL uint32) string {
447454
}
448455
}
449456

457+
// HEDNS metadata
458+
hednsDynamic := ""
459+
if dyn, ok := rec.Metadata["hedns_dynamic"]; ok && dyn == "on" {
460+
hednsDynamic = ", HEDNS_DYNAMIC_ON"
461+
if key := rec.Metadata["hedns_ddns_key"]; key != "" {
462+
// HEDNS_DDNS_KEY implies dynamic, so use it instead.
463+
hednsDynamic = fmt.Sprintf(", HEDNS_DDNS_KEY(%s)", jsonQuoted(key))
464+
}
465+
}
466+
450467
switch rec.Type { // #rtype_variations
451468
case "CAA":
452469
return makeCaa(rec, ttlop)
@@ -519,7 +536,7 @@ func formatDsl(rec *models.RecordConfig, defaultTTL uint32) string {
519536
target = `"` + target + `"`
520537
}
521538

522-
return fmt.Sprintf(`%s("%s", %s%s%s%s%s%s%s)`, rec.Type, rec.Name, target, cfproxy, cfflatten, cfcomment, cftags, mtmeta, ttlop)
539+
return fmt.Sprintf(`%s("%s", %s%s%s%s%s%s%s%s)`, rec.Type, rec.Name, target, cfproxy, cfflatten, cfcomment, cftags, mtmeta, hednsDynamic, ttlop)
523540
}
524541

525542
func makeCaa(rec *models.RecordConfig, ttlop string) string {

commands/types/dnscontrol.d.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1396,6 +1396,57 @@ declare function FRAME(name: string, target: string, ...modifiers: RecordModifie
13961396
*/
13971397
declare function HASH(algorithm: "SHA1" | "SHA256" | "SHA512", value: string): string;
13981398

1399+
/**
1400+
* `HEDNS_DDNS_KEY` enables Dynamic DNS on a record managed by the Hurricane Electric DNS provider and sets a specific DDNS key (token). This implies [`HEDNS_DYNAMIC_ON`](HEDNS_DYNAMIC_ON.md).
1401+
*
1402+
* The DDNS key can then be used with the HE DDNS update API (`https://dyn.dns.he.net/nic/update`) to update the record's value.
1403+
*
1404+
* **Note:** DDNS keys are **write-only**. dnscontrol sets the key on the provider but cannot read back the current key. This means a key-only change (same record data, new key) will not be detected as a difference. To force an update, also change another field such as the TTL.
1405+
*
1406+
* ```javascript
1407+
* D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
1408+
* A("dyn", "0.0.0.0", HEDNS_DDNS_KEY("my-secret-token")),
1409+
* AAAA("dyn6", "::1", HEDNS_DDNS_KEY("another-token")),
1410+
* );
1411+
* ```
1412+
*
1413+
* @see https://docs.dnscontrol.org/language-reference/record-modifiers/service-provider-specific//hedns_ddns_key
1414+
*/
1415+
declare function HEDNS_DDNS_KEY(key: string): RecordModifier;
1416+
1417+
/**
1418+
* `HEDNS_DYNAMIC_OFF` explicitly disables Dynamic DNS on a record managed by the Hurricane Electric DNS provider. This will clear any DDNS key previously associated with the record.
1419+
*
1420+
* Use this modifier when you want to ensure a record that was previously dynamic is returned to a static state.
1421+
*
1422+
* ```javascript
1423+
* D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
1424+
* A("static", "5.6.7.8", HEDNS_DYNAMIC_OFF),
1425+
* );
1426+
* ```
1427+
*
1428+
* @see https://docs.dnscontrol.org/language-reference/record-modifiers/service-provider-specific//hedns_dynamic_off
1429+
*/
1430+
declare const HEDNS_DYNAMIC_OFF: RecordModifier;
1431+
1432+
/**
1433+
* `HEDNS_DYNAMIC_ON` enables [Dynamic DNS](https://dns.he.net/) on a record managed by the Hurricane Electric DNS provider. When enabled, HE DNS assigns a DDNS key to the record that can be used with the HE DDNS update API (`https://dyn.dns.he.net/nic/update`).
1434+
*
1435+
* If a record is already dynamic, its dynamic state is preserved across modifications even without explicitly specifying this modifier.
1436+
*
1437+
* To set a specific DDNS key, use [`HEDNS_DDNS_KEY()`](HEDNS_DDNS_KEY.md) instead.
1438+
*
1439+
* ```javascript
1440+
* D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
1441+
* A("dyn", "0.0.0.0", HEDNS_DYNAMIC_ON),
1442+
* AAAA("dyn6", "::1", HEDNS_DYNAMIC_ON),
1443+
* );
1444+
* ```
1445+
*
1446+
* @see https://docs.dnscontrol.org/language-reference/record-modifiers/service-provider-specific//hedns_dynamic_on
1447+
*/
1448+
declare const HEDNS_DYNAMIC_ON: RecordModifier;
1449+
13991450
/**
14001451
* HTTPS adds an HTTPS record to a domain. The name should be the relative label for the record. Use `@` for the domain apex. The HTTPS record is a special form of the SVCB resource record.
14011452
*

documentation/SUMMARY.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@
112112
* Amazon Route 53
113113
* [R53_ZONE](language-reference/record-modifiers/R53_ZONE.md)
114114
* [R53_EVALUATE_TARGET_HEALTH](language-reference/record-modifiers/R53_EVALUATE_TARGET_HEALTH.md)
115+
* Hurricane Electric DNS
116+
* [HEDNS_DYNAMIC_ON](language-reference/record-modifiers/HEDNS_DYNAMIC_ON.md)
117+
* [HEDNS_DYNAMIC_OFF](language-reference/record-modifiers/HEDNS_DYNAMIC_OFF.md)
118+
* [HEDNS_DDNS_KEY](language-reference/record-modifiers/HEDNS_DDNS_KEY.md)
115119
* [Why CNAME/MX/NS targets require a "dot"](language-reference/why-the-dot.md)
116120

117121
## Provider
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
name: HEDNS_DDNS_KEY
3+
parameters:
4+
- key
5+
parameter_types:
6+
key: string
7+
ts_return: RecordModifier
8+
provider: HEDNS
9+
---
10+
11+
`HEDNS_DDNS_KEY` enables Dynamic DNS on a record managed by the Hurricane Electric DNS provider and sets a specific DDNS key (token). This implies [`HEDNS_DYNAMIC_ON`](HEDNS_DYNAMIC_ON.md).
12+
13+
The DDNS key can then be used with the HE DDNS update API (`https://dyn.dns.he.net/nic/update`) to update the record's value.
14+
15+
**Note:** DDNS keys are **write-only**. dnscontrol sets the key on the provider but cannot read back the current key. This means a key-only change (same record data, new key) will not be detected as a difference. To force an update, also change another field such as the TTL.
16+
17+
{% code title="dnsconfig.js" %}
18+
```javascript
19+
D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
20+
A("dyn", "0.0.0.0", HEDNS_DDNS_KEY("my-secret-token")),
21+
AAAA("dyn6", "::1", HEDNS_DDNS_KEY("another-token")),
22+
);
23+
```
24+
{% endcode %}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
name: HEDNS_DYNAMIC_OFF
3+
parameters: []
4+
ts_return: RecordModifier
5+
provider: HEDNS
6+
---
7+
8+
`HEDNS_DYNAMIC_OFF` explicitly disables Dynamic DNS on a record managed by the Hurricane Electric DNS provider. This will clear any DDNS key previously associated with the record.
9+
10+
Use this modifier when you want to ensure a record that was previously dynamic is returned to a static state.
11+
12+
{% code title="dnsconfig.js" %}
13+
```javascript
14+
D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
15+
A("static", "5.6.7.8", HEDNS_DYNAMIC_OFF),
16+
);
17+
```
18+
{% endcode %}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
name: HEDNS_DYNAMIC_ON
3+
parameters: []
4+
ts_return: RecordModifier
5+
provider: HEDNS
6+
---
7+
8+
`HEDNS_DYNAMIC_ON` enables [Dynamic DNS](https://dns.he.net/) on a record managed by the Hurricane Electric DNS provider. When enabled, HE DNS assigns a DDNS key to the record that can be used with the HE DDNS update API (`https://dyn.dns.he.net/nic/update`).
9+
10+
If a record is already dynamic, its dynamic state is preserved across modifications even without explicitly specifying this modifier.
11+
12+
To set a specific DDNS key, use [`HEDNS_DDNS_KEY()`](HEDNS_DDNS_KEY.md) instead.
13+
14+
{% code title="dnsconfig.js" %}
15+
```javascript
16+
D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
17+
A("dyn", "0.0.0.0", HEDNS_DYNAMIC_ON),
18+
AAAA("dyn6", "::1", HEDNS_DYNAMIC_ON),
19+
);
20+
```
21+
{% endcode %}

documentation/provider/hedns.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,9 +100,25 @@ This option is disabled by default when this key is not present,
100100
{% endcode %}
101101

102102
## Metadata
103-
This provider does not recognize any special metadata fields unique to Hurricane Electric DNS.
103+
104+
This provider supports the following record-level metadata:
105+
106+
| Modifier | Description |
107+
|---|---|
108+
| `HEDNS_DYNAMIC_ON` | Enable [Dynamic DNS](https://dns.he.net/) on the record. The record will be assigned a DDNS key that can be used to update its value via the HE DDNS API (`https://dyn.dns.he.net/nic/update`). |
109+
| `HEDNS_DYNAMIC_OFF` | Explicitly disable Dynamic DNS on the record. **Warning:** this will clear any associated DDNS key. |
110+
| `HEDNS_DDNS_KEY("key")` | Enable Dynamic DNS and set a specific DDNS key (token) on the record. Implies `HEDNS_DYNAMIC_ON`. |
111+
112+
### Dynamic DNS behavior
113+
114+
* When a record has Dynamic DNS enabled and is subsequently modified by dnscontrol (e.g. TTL change), the dynamic flag is **preserved** automatically. You do not need to re-specify `HEDNS_DYNAMIC_ON` on every run unless you want to be explicit.
115+
* If you do not specify any `HEDNS_DYNAMIC_*` modifier on a record that is already dynamic on the provider, the dynamic state is **inherited** — the record stays dynamic.
116+
* DDNS keys are **write-only**: dnscontrol will set the key you specify but cannot read back the current key from HE DNS. This means:
117+
* A key-only change (same record data, new key) requires changing another field (e.g. TTL) to trigger an update.
118+
* The `get-zones` export will include `HEDNS_DYNAMIC_ON` for dynamic records but will not include the DDNS key.
104119

105120
## Usage
121+
106122
An example configuration:
107123

108124
{% code title="dnsconfig.js" %}
@@ -111,7 +127,20 @@ var REG_NONE = NewRegistrar("none");
111127
var DSP_HEDNS = NewDnsProvider("hedns");
112128

113129
D("example.com", REG_NONE, DnsProvider(DSP_HEDNS),
130+
// Standard static record
114131
A("test", "1.2.3.4"),
132+
133+
// Dynamic DNS record (HE DNS assigns/preserves the DDNS key)
134+
A("dyn", "0.0.0.0", HEDNS_DYNAMIC_ON),
135+
136+
// Dynamic DNS record with a specific DDNS key
137+
A("dyn2", "0.0.0.0", HEDNS_DDNS_KEY("my-secret-token")),
138+
139+
// Dynamic AAAA record
140+
AAAA("dyn6", "::1", HEDNS_DYNAMIC_ON),
141+
142+
// Explicitly non-dynamic record (clears any prior DDNS key)
143+
A("static", "5.6.7.8", HEDNS_DYNAMIC_OFF),
115144
);
116145
```
117146
{% endcode %}

integrationTest/helpers_integration_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,45 @@ var (
3232
// Global variable to hold the current DomainConfig for use in FromRaw calls.
3333
var globalDCN *domaintags.DomainNameVarieties
3434

35+
// Helper constants/funcs for the HEDNS Dynamic DNS testing:
36+
37+
func hednsDynamicA(name, target, status string) *models.RecordConfig {
38+
r := a(name, target)
39+
r.Metadata = make(map[string]string)
40+
r.Metadata["hedns_dynamic"] = status
41+
return r
42+
}
43+
44+
func hednsDdnsKeyA(name, target, key string) *models.RecordConfig {
45+
r := a(name, target)
46+
r.Metadata = make(map[string]string)
47+
r.Metadata["hedns_dynamic"] = "on"
48+
r.Metadata["hedns_ddns_key"] = key
49+
return r
50+
}
51+
52+
func hednsDynamicAAAA(name, target, status string) *models.RecordConfig {
53+
r := aaaa(name, target)
54+
r.Metadata = make(map[string]string)
55+
r.Metadata["hedns_dynamic"] = status
56+
return r
57+
}
58+
59+
func hednsDdnsKeyAAAA(name, target, key string) *models.RecordConfig {
60+
r := aaaa(name, target)
61+
r.Metadata = make(map[string]string)
62+
r.Metadata["hedns_dynamic"] = "on"
63+
r.Metadata["hedns_ddns_key"] = key
64+
return r
65+
}
66+
67+
func hednsDynamicTXT(name, target, status string) *models.RecordConfig {
68+
r := txt(name, target)
69+
r.Metadata = make(map[string]string)
70+
r.Metadata["hedns_dynamic"] = status
71+
return r
72+
}
73+
3574
// Helper constants/funcs for the CLOUDFLARE proxy testing:
3675

3776
// A-record proxy off/on.

integrationTest/integration_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2082,6 +2082,53 @@ func makeTests() []*TestGroup {
20822082
tc("Change PZ", bunnyPullZone("@", "5269992")),
20832083
),
20842084

2085+
// HEDNS: Dynamic DNS
2086+
2087+
testgroup("HEDNS_DYNAMIC A lifecycle",
2088+
only("HEDNS"),
2089+
// Create a dynamic A record and verify target changes preserve the flag.
2090+
tc("Create dynamic A", hednsDynamicA("hdyn", "1.2.3.4", "on")),
2091+
tc("Change target preserves dynamic", hednsDynamicA("hdyn", "5.6.7.8", "on")),
2092+
// Toggle dynamic off, then back on.
2093+
tc("Turn off dynamic", hednsDynamicA("hdyn", "5.6.7.8", "off")),
2094+
tc("Turn on dynamic", hednsDynamicA("hdyn", "5.6.7.8", "on")),
2095+
// Change target without specifying hedns_dynamic — it should stay dynamic.
2096+
tc("Inherit dynamic on modify", a("hdyn", "10.0.0.1")),
2097+
// Create a non-dynamic record alongside.
2098+
tc("Add static record", a("hdyn", "10.0.0.1"), a("hstatic", "2.2.2.2")),
2099+
),
2100+
2101+
testgroup("HEDNS_DYNAMIC AAAA+TXT",
2102+
only("HEDNS"),
2103+
tc("Create dynamic AAAA", hednsDynamicAAAA("hdynv6", "2607:f8b0:4006:820::2006", "on")),
2104+
tc("Change dynamic AAAA target", hednsDynamicAAAA("hdynv6", "2607:f8b0:4006:820::2013", "on")),
2105+
tc("Create dynamic TXT", hednsDynamicTXT("hdyntxt", "dynamic-value", "on")),
2106+
tc("Turn off dynamic TXT", hednsDynamicTXT("hdyntxt", "dynamic-value", "off")),
2107+
),
2108+
2109+
testgroup("HEDNS_DDNS_KEY",
2110+
only("HEDNS"),
2111+
// Setting a DDNS key implicitly enables dynamic.
2112+
tc("Create A with DDNS key (implicit dynamic)", hednsDdnsKeyA("hkey", "1.2.3.4", "key1")),
2113+
// Change target and key together.
2114+
tc("Change target + key", hednsDdnsKeyA("hkey", "5.6.7.8", "key2")),
2115+
// AAAA with DDNS key.
2116+
tc("Create AAAA with DDNS key", hednsDdnsKeyAAAA("hkeyv6", "2607:f8b0:4006:820::2006", "v6key")),
2117+
tc("Change AAAA target + key", hednsDdnsKeyAAAA("hkeyv6", "2607:f8b0:4006:820::2013", "newv6key")),
2118+
),
2119+
2120+
testgroup("HEDNS_DYNAMIC mixed records",
2121+
only("HEDNS"),
2122+
tc("Create mix of dynamic and static",
2123+
hednsDynamicA("hdmix-dyn", "1.1.1.1", "on"),
2124+
a("hdmix-static", "2.2.2.2"),
2125+
),
2126+
tc("Modify only the static record",
2127+
hednsDynamicA("hdmix-dyn", "1.1.1.1", "on"),
2128+
a("hdmix-static", "3.3.3.3"),
2129+
),
2130+
),
2131+
20852132
// This MUST be the last test.
20862133
testgroup("final",
20872134
tc("final", txt("final", `TestDNSProviders was successful!`)),

pkg/js/helpers.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1479,6 +1479,17 @@ var CF_MANAGE_COMMENTS = { cloudflare_manage_comments: 'true' };
14791479
// Enable tag management for domain (opt-in to sync tags, requires paid plan):
14801480
var CF_MANAGE_TAGS = { cloudflare_manage_tags: 'true' };
14811481

1482+
// Hurricane Electric DNS (HEDNS) aliases:
1483+
1484+
// Enable Dynamic DNS on a record (preserves existing DDNS key):
1485+
var HEDNS_DYNAMIC_ON = { hedns_dynamic: 'on' };
1486+
// Disable Dynamic DNS on a record (WARNING: clears the associated DDNS key):
1487+
var HEDNS_DYNAMIC_OFF = { hedns_dynamic: 'off' };
1488+
// Set a specific DDNS key on a dynamic record (implies HEDNS_DYNAMIC_ON):
1489+
function HEDNS_DDNS_KEY(key) {
1490+
return { hedns_dynamic: 'on', hedns_ddns_key: key };
1491+
}
1492+
14821493
// CUSTOM, PROVIDER SPECIFIC RECORD TYPES
14831494

14841495
function _validateCloudflareRedirect(value) {

0 commit comments

Comments
 (0)