From 1c04affe7c69f50c38c798754fadbd4b1a54c4c6 Mon Sep 17 00:00:00 2001 From: Eli Heady Date: Sat, 3 May 2025 08:23:38 -0400 Subject: [PATCH] INWX: Add AutoDNSSEC support (#3534) Co-authored-by: Tom Limoncelli --- documentation/provider/index.md | 2 +- providers/inwx/dnssec.go | 55 +++++++++++++++++++++++++++++++++ providers/inwx/inwxProvider.go | 46 ++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 5 deletions(-) create mode 100644 providers/inwx/dnssec.go diff --git a/documentation/provider/index.md b/documentation/provider/index.md index b3294c170..56cbea969 100644 --- a/documentation/provider/index.md +++ b/documentation/provider/index.md @@ -43,7 +43,7 @@ If a feature is definitively not supported for whatever reason, we would also li | [`HOSTINGDE`](hostingde.md) | ❌ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ❔ | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | | [`HUAWEICLOUD`](huaweicloud.md) | ❌ | ✅ | ❌ | ❔ | ❌ | ✅ | ❔ | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | | [`INTERNETBS`](internetbs.md) | ❌ | ❌ | ✅ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ❔ | -| [`INWX`](inwx.md) | ❌ | ✅ | ✅ | ❔ | ❌ | ✅ | ❔ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | +| [`INWX`](inwx.md) | ❌ | ✅ | ✅ | ❔ | ❌ | ✅ | ✅ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ✅ | ✅ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | | [`LINODE`](linode.md) | ❌ | ✅ | ❌ | ❔ | ❔ | ✅ | ❔ | ❔ | ❌ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❔ | ❌ | ❌ | ✅ | | [`LOOPIA`](loopia.md) | ❌ | ✅ | ✅ | ❔ | ❌ | ✅ | ❌ | ❔ | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ | ❔ | ✅ | ❌ | ❔ | ❔ | ❔ | ✅ | ❌ | ✅ | | [`LUADNS`](luadns.md) | ❌ | ✅ | ❌ | ❔ | ✅ | ✅ | ❔ | ❔ | ❌ | ❔ | ✅ | ❔ | ✅ | ✅ | ❔ | ✅ | ❔ | ❔ | ❔ | ❔ | ✅ | ✅ | ✅ | diff --git a/providers/inwx/dnssec.go b/providers/inwx/dnssec.go new file mode 100644 index 000000000..7053644f0 --- /dev/null +++ b/providers/inwx/dnssec.go @@ -0,0 +1,55 @@ +package inwx + +import ( + "golang.org/x/net/idna" +) + +const ( + // https://www.inwx.com/en/help/apidoc/f/ch03.html#type.dnssecdomainstatus + // claims status values can be 'DELETE_ALL', 'MANUAL', 'UPDATE', but + // testing shows 'AUTO' is what to expect if the domain has automatic + // DNSSEC enabled. + + // AutoDNSSEC is the status for DNSSEC enabled with automatic management + AutoDNSSECStatus = "AUTO" + // ManualDNSSEC is the status for DNSSEC enabled with manual management + ManualDNSSECStatus = "MANUAL" +) + +// DNSSecStatus returns domain dnssec status +func (api *inwxAPI) DNSSecStatus(domain string) (string, error) { + + resp, err := api.client.Dnssec.Info([]string{domain}) + if err != nil { + return "", err + } + + // domain has no DNSSEC configuration + if len(resp.Data) == 0 { + return "", nil + } + + return resp.Data[0].DNSSecStatus, nil +} + +// enableAutoDNSSEC enables automatic management of DNSSEC +func (api *inwxAPI) enableAutoDNSSEC(domain string) error { + // if the domain is IDN, it must be in Unicode - ACE encoding is not supported + // in the INWX dnssec.enablednssec endpoint + domain, err := idna.ToUnicode(domain) + if err != nil { + return err + } + + err = api.client.Dnssec.Enable(domain) + + return err +} + +// disableAutoDNSSEC disables automatic management of DNSSEC +func (api *inwxAPI) disableAutoDNSSEC(domain string) error { + + err := api.client.Dnssec.Disable(domain) + + return err +} diff --git a/providers/inwx/inwxProvider.go b/providers/inwx/inwxProvider.go index d435c57a7..c8ca82cab 100644 --- a/providers/inwx/inwxProvider.go +++ b/providers/inwx/inwxProvider.go @@ -47,7 +47,7 @@ var InwxSandboxDefaultNs = []string{"ns.ote.inwx.de", "ns2.ote.inwx.de"} var features = providers.DocumentationNotes{ // The default for unlisted capabilities is 'Cannot'. // See providers/capabilities.go for the entire list of capabilities. - providers.CanAutoDNSSEC: providers.Unimplemented("Supported by INWX but not implemented yet."), + providers.CanAutoDNSSEC: providers.Can(), providers.CanGetZones: providers.Can(), providers.CanConcur: providers.Unimplemented(), providers.CanUseAlias: providers.Cannot("INWX does not support the ALIAS or ANAME record type."), @@ -252,7 +252,7 @@ func isNullMX(rec *models.RecordConfig) bool { } // MXCorrections generates required delete corrections when a MX change can not be applied in an updateRecord call. -func (api *inwxAPI) MXCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, models.Records, error) { +func (api *inwxAPI) MXCorrections(dc *models.DomainConfig, foundRecords models.Records, corrections []*models.Correction) ([]*models.Correction, models.Records, error) { // If a null MX is present in the zone, we have to take special care of any // planned MX changes: No non-null MX records can be added until the null @@ -261,7 +261,6 @@ func (api *inwxAPI) MXCorrections(dc *models.DomainConfig, foundRecords models.R // MX record because an update would be rejected with "2308 Data management policy violation" removals := make(map[string]struct{}) - corrections := []*models.Correction{} tempRecords := []*models.RecordConfig{} // Detect Null MX in foundRecords @@ -334,10 +333,49 @@ func (api *inwxAPI) MXCorrections(dc *models.DomainConfig, foundRecords models.R return corrections, cleanedRecords, nil } +// AutoDnssecToggle enables and disables AutoDNSSEC for INWX domains. +func (api *inwxAPI) AutoDnssecToggle(dc *models.DomainConfig, corrections []*models.Correction) ([]*models.Correction, error) { + + dnssecStatus, err := api.DNSSecStatus(dc.Name) + if err != nil { + return corrections, err + } + + if dnssecStatus == ManualDNSSECStatus && dc.AutoDNSSEC != "" { + return corrections, fmt.Errorf("INWX: Domain %s has manual DNSSEC enabled. Disable it before using AUTODNSSEC_ON/AUTODNSSEC_OFF", dc.Name) + } + + if dnssecStatus != AutoDNSSECStatus && dc.AutoDNSSEC == "on" { + corrections = append(corrections, &models.Correction{ + Msg: color.YellowString("Enable AutoDNSSEC"), + F: func() error { + return api.enableAutoDNSSEC(dc.Name) + }, + }) + } + + if dnssecStatus == AutoDNSSECStatus && dc.AutoDNSSEC == "off" { + corrections = append(corrections, &models.Correction{ + Msg: color.RedString("Disable AutoDNSSEC"), + F: func() error { + return api.disableAutoDNSSEC(dc.Name) + }, + }) + } + return corrections, nil +} + // GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records. func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundRecords models.Records) ([]*models.Correction, int, error) { - corrections, records, err := api.MXCorrections(dc, foundRecords) + corrections := []*models.Correction{} + + corrections, records, err := api.MXCorrections(dc, foundRecords, corrections) + if err != nil { + return nil, 0, err + } + + corrections, err = api.AutoDnssecToggle(dc, corrections) if err != nil { return nil, 0, err }