From 222666414a9e4e33b254abf02253b5d7a4611166 Mon Sep 17 00:00:00 2001 From: Yannik Sembritzki Date: Mon, 6 Feb 2023 21:04:31 +0700 Subject: [PATCH] HOSTINGDE: Implement SOA record (fixes #1972) (#2023) Co-authored-by: Yannik Sembritzki --- providers/hostingde/api.go | 1 + providers/hostingde/hostingdeProvider.go | 75 +++++++++++++++++++++++- providers/hostingde/types.go | 28 +++++---- 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/providers/hostingde/api.go b/providers/hostingde/api.go index 895ca3420..861e3070f 100644 --- a/providers/hostingde/api.go +++ b/providers/hostingde/api.go @@ -19,6 +19,7 @@ type hostingdeProvider struct { filterAccountId string baseURL string nameservers []string + defaultSoa soaValues } func (hp *hostingdeProvider) getDomainConfig(domain string) (*domainConfig, error) { diff --git a/providers/hostingde/hostingdeProvider.go b/providers/hostingde/hostingdeProvider.go index aecee1ae2..98e49ff63 100644 --- a/providers/hostingde/hostingdeProvider.go +++ b/providers/hostingde/hostingdeProvider.go @@ -21,6 +21,7 @@ var features = providers.DocumentationNotes{ providers.CanGetZones: providers.Can(), providers.CanUseAlias: providers.Can(), providers.CanUseCAA: providers.Can(), + providers.CanUseSOA: providers.Can(), providers.CanUseDS: providers.Can(), providers.CanUseNAPTR: providers.Cannot(), providers.CanUsePTR: providers.Can(), @@ -111,12 +112,18 @@ func (hp *hostingdeProvider) ApiRecordsToStandardRecordsModel(domain string, src return records } +func soaToString(s soaValues) string { + return fmt.Sprintf("refresh=%d retry=%d expire=%d negativettl=%d ttl=%d", s.Refresh, s.Retry, s.Expire, s.NegativeTTL, s.TTL) +} + func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { err := dc.Punycode() if err != nil { return nil, err } + zoneChanged := false + // TTL must be between (inclusive) 1m and 1y (in fact, a little bit more) for _, r := range dc.Records { if r.TTL < 60 { @@ -148,11 +155,65 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m del = []diff.Correlation{} } + // remove SOA record from corrections as it is handled separately + for i, r := range create { + if r.Desired.Type == "SOA" { + create = append(create[:i], create[i+1:]...) + break + } + } + + if len(create) != 0 || len(del) != 0 || len(mod) != 0 { + zoneChanged = true + } + msg := []string{} for _, c := range append(del, append(create, mod...)...) { msg = append(msg, c.String()) } + var desiredSoa *models.RecordConfig + for _, r := range dc.Records { + if r.Type == "SOA" && r.Name == "@" { + desiredSoa = r + break + } + } + if desiredSoa == nil { + desiredSoa = &models.RecordConfig{} + } + + defaultSoa := &hp.defaultSoa + if defaultSoa == nil { + defaultSoa = &soaValues{} + } + + newSOA := soaValues{ + Refresh: firstNonZero(desiredSoa.SoaRefresh, defaultSoa.Refresh, 86400), + Retry: firstNonZero(desiredSoa.SoaRetry, defaultSoa.Retry, 7200), + Expire: firstNonZero(desiredSoa.SoaExpire, defaultSoa.Expire, 3600000), + NegativeTTL: firstNonZero(desiredSoa.SoaMinttl, defaultSoa.NegativeTTL, 900), + TTL: firstNonZero(desiredSoa.TTL, defaultSoa.TTL, 86400), + } + + if zone.ZoneConfig.SOAValues != newSOA { + msg = append(msg, fmt.Sprintf("Updating SOARecord from (%s) to (%s)", soaToString(zone.ZoneConfig.SOAValues), soaToString(newSOA))) + zone.ZoneConfig.SOAValues = newSOA + zoneChanged = true + } + + if desiredSoa.SoaMbox != "" { + var desiredMail string = "" + if desiredSoa.SoaMbox[len(desiredSoa.SoaMbox)-1] != '.' { + desiredMail = desiredSoa.SoaMbox + "@" + dc.Name + } + if desiredMail != "" && zone.ZoneConfig.EmailAddress != desiredMail { + msg = append(msg, fmt.Sprintf("Changing SOA Mail from %s to %s", zone.ZoneConfig.EmailAddress, desiredMail)) + zone.ZoneConfig.EmailAddress = desiredMail + zoneChanged = true + } + } + existingAutoDNSSecEnabled := zone.ZoneConfig.DNSSECMode == "automatic" desiredAutoDNSSecEnabled := dc.AutoDNSSEC == "on" @@ -168,6 +229,7 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m msg = append(msg, "Enabling publishKsk for AutoDNSSec") DnsSecOptions = CurrentDnsSecOptions DnsSecOptions.PublishKSK = true + zoneChanged = true } } @@ -178,12 +240,14 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m PublishKSK: true, } zone.ZoneConfig.DNSSECMode = "automatic" + zoneChanged = true } else if existingAutoDNSSecEnabled && !desiredAutoDNSSecEnabled { msg = append(msg, "Disable AutoDNSSEC") zone.ZoneConfig.DNSSECMode = "off" + zoneChanged = true } - if len(create) == 0 && len(del) == 0 && len(mod) == 0 && existingAutoDNSSecEnabled == desiredAutoDNSSecEnabled && DnsSecOptions == nil { + if !zoneChanged { return nil, nil } @@ -213,6 +277,15 @@ func (hp *hostingdeProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*m return corrections, nil } +func firstNonZero(items ...uint32) uint32 { + for _, item := range items { + if item != 0 { + return item + } + } + return 999 +} + func (hp *hostingdeProvider) GetRegistrarCorrections(dc *models.DomainConfig) ([]*models.Correction, error) { err := dc.Punycode() if err != nil { diff --git a/providers/hostingde/types.go b/providers/hostingde/types.go index 7f24e0b80..70b0ec50c 100644 --- a/providers/hostingde/types.go +++ b/providers/hostingde/types.go @@ -56,24 +56,26 @@ type domainConfig struct { } type zoneConfig struct { - ID string `json:"id"` - DNSSECMode string `json:"dnsSecMode"` - EmailAddress string `json:"emailAddress,omitempty"` - MasterIP string `json:"masterIp"` - Name string `json:"name"` // Not required per docs, but required IRL - NameUnicode string `json:"nameUnicode"` - // SOAValues struct { - // Refresh uint32 `json:"refresh"` - // Retry uint32 `json:"retry"` - // Expire uint32 `json:"expire"` - // TTL uint32 `json:"ttl"` - // NegativeTTL uint32 `json:"negativeTtl"` - // } `json:"soaValues,omitempty"` + ID string `json:"id"` + DNSSECMode string `json:"dnsSecMode"` + EmailAddress string `json:"emailAddress,omitempty"` + MasterIP string `json:"masterIp"` + Name string `json:"name"` // Not required per docs, but required IRL + NameUnicode string `json:"nameUnicode"` + SOAValues soaValues `json:"soaValues,omitempty"` Type string `json:"type"` TemplateValues json.RawMessage `json:"templateValues,omitempty"` ZoneTransferWhitelist []string `json:"zoneTransferWhitelist"` } +type soaValues struct { + Refresh uint32 `json:"refresh"` + Retry uint32 `json:"retry"` + Expire uint32 `json:"expire"` + NegativeTTL uint32 `json:"negativeTtl"` + TTL uint32 `json:"ttl"` +} + type zone struct { ZoneConfig zoneConfig `json:"zoneConfig"` Records []record `json:"records"`