PowerDNS: support for HTTPS/SVCB records
I tried to add this myself, but got stuck. Setting providers.{CanUseHTTPS,CanUseSVCB} allows get-zones to correctly pull the records from pdns (generating HTTPS('@', 1, '.', 'alpn=h2'), but making any changes results in an error as the field values (i.e. h2 in this example) get encoded with double quotes, resulting in the following error
#1: ± MODIFY example.com HTTPS (1 . alpn="h2" ttl=86400) -> (1 . alpn="h3" ttl=86400)
FAILURE! unexpected status code 422: https://pdns.example.com/api/v1/servers/localhost/zones/example.com Record example.com./HTTPS '1 . alpn="h3"': Not in expected format (parsed as '1 . alpn=h3')
I've verified that the API works correctly when called manually:
curl -X PATCH https://pdns.example.com/api/v1/servers/localhost/zones/example.com. \
-H 'x-api-key: <snip>' -H 'content-type: application/json' \
-d '{"rrsets":[{"name":"example.com.","type":"HTTPS","ttl":86400,"changetype":"REPLACE","records":[{"content":"1 . alpn=h3"}]}]}'
Unfortunately, I couldn't figure out where to make the change for this. It looks as though the responsible code is all the way down in github.com/miekg/dns/svcb.go and I couldn't figure out how to implement a special case in the dnscontrol provider.
Ping @jpbede, the maintainer of the PowerDNS provider.
I'd also like to see those record types supported. I'm new to dnscontrol, started tests with my first zone & ran into this. For me it's a blocker, unfortunately; meaning I won't continue testing & implementing it just now.
Why this is important to me: other DNS providers I usually use (Hetzner, hosting.de) don't support those record types via their native web interfaces/APIs yet. However, their slave servers do support the record types. Meaning I can run my own primary DNS server with PowerDNS & use their slave servers as the name servers for my zones. Would have loved to be able to manage all this with dnscontrol.
I'll see if I can figure out anything in the next few days.
That's very kind of you. Thanks!
I've looked into this and found prior to PowerDNS Authoritative 4.9.3 (released 2025/12/17) there was a bug handling the port parameter. This was fixed in PowerDNS/pdns#14968.
As of now, activating SVCB and HTTPS in PowerDNS can be done, but there's still an issue with adding parameters (after showing they work without parameters).
$ git diff
diff --git a/providers/powerdns/powerdnsProvider.go b/providers/powerdns/powerdnsProvider.go
index fd611bf4..e0f5b397 100644
--- a/providers/powerdns/powerdnsProvider.go
+++ b/providers/powerdns/powerdnsProvider.go
@@ -20,12 +20,14 @@ var features = providers.DocumentationNotes{
providers.CanUseCAA: providers.Can(),
providers.CanUseDS: providers.Can(),
providers.CanUseDHCID: providers.Can(),
+ providers.CanUseHTTPS: providers.Can(),
providers.CanUseLOC: providers.Unimplemented("Normalization within the PowerDNS API seems to be buggy, so disabled", "https://github.com/PowerDNS/pdns/issues/10558"),
providers.CanUseNAPTR: providers.Can(),
providers.CanUsePTR: providers.Can(),
providers.CanUseSOA: providers.Can(),
providers.CanUseSRV: providers.Can(),
providers.CanUseSSHFP: providers.Can(),
+ providers.CanUseSVCB: providers.Can(),
providers.CanUseTLSA: providers.Can(),
providers.DocCreateDomains: providers.Can(),
providers.DocDualHost: providers.Can(),
Creating a zone that has some content and adding the SVCB and HTTPS records without any parameters:
var REG_NONE = NewRegistrar("none");
var PROV_PDNS = NewDnsProvider("pdns");
D("test.internal", REG_NONE, DnsProvider(PROV_PDNS),
SOA('@', 'ns.test.internal.', 'hostmaster.test.internal.', 60, 60, 604800, 60, TTL(60)),
NAMESERVER("ns.test.internal."),
A('ns', '172.18.0.2'),
A("@", "172.18.0.2"),
A("bump", "172.18.0.3"),
SVCB("_8443._https", 1, "bump.test.internal.", ""),
HTTPS("www", 1, '.', ''),
END);
I added the SVCB and HTTPS in separate pushes:
$ dnscontrol push
CONCURRENTLY gathering 0 zone(s)
SERIALLY gathering 1 zone(s)
Serially Gathering: "test.internal"
******************** Domain: test.internal
1 correction (pdns)
#1: ± BATCHED CHANGE/CREATEs for test.internal
+ CREATE _8443._https.test.internal SVCB 1 bump.test.internal. ttl=300
SUCCESS!
Done. 1 corrections.
$ dnscontrol push
CONCURRENTLY gathering 0 zone(s)
SERIALLY gathering 1 zone(s)
Serially Gathering: "test.internal"
******************** Domain: test.internal
1 correction (pdns)
#1: ± BATCHED CHANGE/CREATEs for test.internal
+ CREATE www.test.internal HTTPS 1 . ttl=300
SUCCESS!
Done. 1 corrections.
Once I add a parameter PowerDNS fails parsing it. I'm leaning towards this is a PowerDNS parsing issue, but I'm not sure.
$ dnscontrol push
CONCURRENTLY gathering 0 zone(s)
SERIALLY gathering 1 zone(s)
Serially Gathering: "test.internal"
******************** Domain: test.internal
1 correction (pdns)
#1: ± BATCHED CHANGE/CREATEs for test.internal
± MODIFY _8443._https.test.internal SVCB (1 bump.test.internal. ttl=300) -> (1 bump.test.internal. ipv4hint="172.18.0.3" ttl=300)
FAILURE! unexpected status code 422: http://127.0.0.1:8081/api/v1/servers/localhost/zones/test.internal. Record _8443._https.test.internal./SVCB '1 bump.test.internal. ipv4hint="172.18.0.3"': Not in expected format (parsed as '1 bump.test.internal. ipv4hint=172.18.0.3')
Done. 1 corrections.
completed with errors
I don't suspect I have the records wrong, but I'm not familiar with using them and not sure if PowerDNS expects them in a certain format. I've tried all sorts of variations of parameters and they all result with the Not in expected format error.
Did some more testing, I managed to get HTTPS to work with a parameter using curl.
Attempting to replicate this with dnscontrol though failed. Trying some more iterations led me to finding out that the quotes are causing the parsing error.
This caused an error:
"records": [
{
"content": "1 www.test.internal. ipv4hint=\"172.18.0.3\"",
"disabled": false
}
],
whereas this did not and successfully created the record:
"records": [
{
"content": "1 www.test.internal. ipv4hint=172.18.0.3",
"disabled": false
}
],
The quotes are added through the miekg/dns package used here: https://github.com/StackExchange/dnscontrol/blob/main/models/target.go#L88
Here's full after converting:
(dlv) p full
"www.test.internal.\t300\tIN\tHTTPS\t1 www.test.internal. ipv4hint=\"172.18.0.3\""
and in the mittwald/go-powerdns Record format:
(dlv) p records[0]
github.com/mittwald/go-powerdns/apis/zones.Record {
Content: "1 www.test.internal. ipv4hint=\"172.18.0.3\"",
Disabled: false,
SetPTR: false,}
I tried removing all quotes from SVCB and HTTPS records by adding this to the powerdns provider function which converts to the mittwald/go-powerdns API format:
diff --git a/providers/powerdns/diff.go b/providers/powerdns/diff.go
index fdd2ee20..9df4893d 100644
--- a/providers/powerdns/diff.go
+++ b/providers/powerdns/diff.go
@@ -81,9 +81,16 @@ func (dsp *powerdnsProvider) getDiff2DomainCorrections(dc *models.DomainConfig,
// buildRecordList returns a list of records for the PowerDNS resource record set from a change
func buildRecordList(change diff2.Change) (records []zones.Record) {
for _, recordContent := range change.New {
- records = append(records, zones.Record{
+ newRecord := zones.Record{
Content: recordContent.GetTargetCombined(),
- })
+ }
+ switch recordContent.Type {
+ case "HTTPS":
+ newRecord.Content = strings.ReplaceAll(newRecord.Content, "\"", "")
+ case "SVCB":
+ newRecord.Content = strings.ReplaceAll(newRecord.Content, "\"", "")
+ }
+ records = append(records, newRecord)
}
return
}
which let me get to this result:
$ dnscontrol push
CONCURRENTLY gathering 0 zone(s)
SERIALLY gathering 1 zone(s)
Serially Gathering: "test.internal"
******************** Domain: test.internal
1 correction (pdns)
#1: ± BATCHED CHANGE/CREATEs for test.internal
+ CREATE www.test.internal HTTPS 1 www.test.internal. ipv4hint="172.18.0.3" ttl=300
SUCCESS!
Done. 1 corrections.
This does lead me to believe that this needs to be fixed on the PowerDNS side, similar to how the port issue mentioned above was fixed.
I've opened PowerDNS/pdns#15135 to check if this is a bug on their side.
That turned out to be quite the odyssey. Thank you so much for all your work.
Thanks @Veratil for your extensive research on this, much appreciated! 👍
Looks like this was resolved in #3525, works for me now.