INWX: Fix INWX provider after their unexpected data-type breaking-change (#3855)

Fixes #3854 

Unfortunately I couldn't run the integrationTests properly as INWX
doesn't seem to have properly updated their sandbox environment (it
still presents `int` instead of `string` like production). Hence, the
tests do fail. I don't want to run this against my own production
account, to be frank.

See:
```shell
$ curl -X POST https://api.ote.domrobot.com/xmlrpc/ -H "Content-Type: application/xml" -d '<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
   <methodName>nameserver.info</methodName>
   <params>
      <param>
         <value>
            <struct>
               <member>
                  <name>user</name>
                  <value>
                     <string>[USER]</string>
                  </value>
               </member>
               <member>
                  <name>lang</name>
                  <value>
                     <string>en</string>
                  </value>
               </member>
               <member>
                  <name>pass</name>
                  <value>
                     <string>[PASS]</string>
                  </value>
               </member>
               <member>
                  <name>domain</name>
                  <value>
                     <string>[DOMAIN]</string>
                  </value>
               </member>
            </struct>
         </value>
      </param>
   </params>
</methodCall>' | xmllint --format - | grep -iE "id|roId" -C3
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100  3968    0  2971  100   997  13375   4488 --:--:-- --:--:-- --:--:-- 17954
            <value>
              <struct>
                <member>
                  <name>roId</name>
                  <value>
                    <int>9677</int>
                  </value>
--
                        <value>
                          <struct>
                            <member>
                              <name>id</name>
                              <value>
                                <int>118057</int>
                              </value>
--
                        <value>
                          <struct>
                            <member>
                              <name>id</name>
                              <value>
                                <int>118060</int>
                              </value>
--
                        <value>
                          <struct>
                            <member>
                              <name>id</name>
                              <value>
                                <int>79610</int>
                              </value>
--
                        <value>
                          <struct>
                            <member>
                              <name>id</name>
                              <value>
                                <int>77243</int>
                              </value>
--
            </value>
          </member>
          <member>
            <name>svTRID</name>
            <value>
              <string>20251127--ote</string>
            </value>
```

Hence, only done manualy tests via `dnscontrol push --domains
<example.com>`:
(tested create, delete and modify)

```text
CONCURRENTLY checking for 0 zone(s)
SERIALLY checking for 1 zone(s)
Serially checking for zone: "example.tld"
CONCURRENTLY gathering records of 0 zone(s)
SERIALLY gathering records of 1 zone(s)
Serially Gathering: "example.tld"
******************** Domain: example.tld
3 corrections (PK-INWX)
#1: - DELETE _test1.example.tld TXT "123" ttl=43200
SUCCESS!
#2: ± MODIFY _test2.example.tld TXT ("1234" ttl=43200) -> ("12345" ttl=43200)
SUCCESS!
#3: + CREATE _test4.example.tld TXT "123" ttl=43200
SUCCESS!
Done. 3 corrections.
```
This commit is contained in:
Patrik Kernstock 2025-11-29 18:17:13 +01:00 committed by GitHub
parent f306472d5a
commit 9aad2926fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 28 additions and 34 deletions

2
go.mod
View file

@ -38,7 +38,7 @@ require (
github.com/miekg/dns v1.1.68
github.com/mittwald/go-powerdns v0.6.7
github.com/namedotcom/go v0.0.0-20180403034216-08470befbe04
github.com/nrdcg/goinwx v0.11.0
github.com/nrdcg/goinwx v0.12.0
github.com/ovh/go-ovh v1.9.0
github.com/philhug/opensrs-go v0.0.0-20171126225031-9dfa7433020d
github.com/pkg/errors v0.9.1

4
go.sum
View file

@ -307,8 +307,8 @@ github.com/nicholas-fedor/shoutrrr v0.12.0 h1:8mwJdfU+uBEybSymwQJMGl/grG7lvVUKbV
github.com/nicholas-fedor/shoutrrr v0.12.0/go.mod h1:WYiRalR4C43Qmd2zhPWGIFIxu633NB1hDM6Ap/DQcsA=
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481 h1:Up6+btDp321ZG5/zdSLo48H9Iaq0UQGthrhWC6pCxzE=
github.com/nozzle/throttler v0.0.0-20180817012639-2ea982251481/go.mod h1:yKZQO8QE2bHlgozqWDiRVqTFlLQSj30K/6SAK8EeYFw=
github.com/nrdcg/goinwx v0.11.0 h1:GER0SE3POub7rxARt3Y3jRy1OON1hwF1LRxHz5xsFBw=
github.com/nrdcg/goinwx v0.11.0/go.mod h1:0BXSC0FxVtU4aTjX0Zw3x0DK32tjugLzeNIAGtwXvPQ=
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns=
github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo=
github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A=

View file

@ -19,7 +19,7 @@ func CorrectZoneRecords(driver models.DNSProvider, dc *models.DomainConfig) ([]*
models.CanonicalizeTargets(existingRecords, dc.Name)
models.CanonicalizeTargets(dc.Records, dc.Name)
// Copy dc so that any corrections code that wants to
// Copy dc so that any correction code that wants to
// modify the records may. For example, if the provider only
// supports certain TTL values, it will adjust the ones in
// dc.Records.

View file

@ -10,12 +10,8 @@ import (
// supported, an empty list is returned.
func AuditRecords(records []*models.RecordConfig) []error {
a := rejectif.Auditor{}
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasBackticks) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtHasTrailingSpace) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
a.Add("TXT", rejectif.TxtIsEmpty) // Last verified 2021-03-01
return a.Audit(records)
}

View file

@ -10,25 +10,22 @@ const (
// 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 is the status for DNSSEC enabled with automatic management
AutoDNSSECStatus = "AUTO"
// ManualDNSSEC is the status for DNSSEC enabled with manual management
// ManualDNSSECStatus 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
}
@ -40,16 +37,12 @@ func (api *inwxAPI) enableAutoDNSSEC(domain string) error {
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
}

View file

@ -22,6 +22,9 @@ import (
/*
INWX Registrar and DNS provider
Based on this great INWX API implementation:
https://github.com/nrdcg/goinwx
Info required in `creds.json`:
- username
- password
@ -34,7 +37,6 @@ Either of the following settings is required when two factor authentication is e
Additional settings available in `creds.json`:
- sandbox (set to 1 to use the sandbox API from INWX)
*/
// InwxProductionDefaultNs contains the default INWX nameservers.
@ -182,10 +184,10 @@ func makeNameserverRecordRequest(domain string, rec *models.RecordConfig) *goinw
switch rType := rec.Type; rType {
/*
INWX is a little bit special for CNAME,NS,MX and SRV records:
INWX is a little bit special for CNAME, NS, MX and SRV records:
The API will not accept any target with a final dot but will
instead always add this final dot internally.
Records with empty targets (i.e. records with target ".")
Records with empty targets (i.e., records with target ".")
are allowed.
*/
case "CNAME", "NS", "ALIAS":
@ -219,14 +221,14 @@ func (api *inwxAPI) createRecord(domain string, rec *models.RecordConfig) error
}
// updateRecord is used by GetDomainCorrections to update an existing record.
func (api *inwxAPI) updateRecord(RecordID int, rec *models.RecordConfig) error {
func (api *inwxAPI) updateRecord(RecordID string, rec *models.RecordConfig) error {
req := makeNameserverRecordRequest("", rec)
err := api.client.Nameservers.UpdateRecord(RecordID, req)
return err
}
// deleteRecord is used by GetDomainCorrections to delete a record.
func (api *inwxAPI) deleteRecord(RecordID int) error {
func (api *inwxAPI) deleteRecord(RecordID string) error {
return api.client.Nameservers.DeleteRecord(RecordID)
}
@ -244,7 +246,8 @@ func (api *inwxAPI) AutoDnssecToggle(dc *models.DomainConfig, corrections []*mod
}
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)
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" {
@ -289,23 +292,25 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
return nil, 0, err
}
for _, change := range changes {
changeMsgs := change.MsgsJoined
changeMessage := change.MsgsJoined
switch change.Type {
case diff2.REPORT:
corrections = append(corrections, &models.Correction{Msg: changeMsgs})
corrections = append(corrections, &models.Correction{Msg: changeMessage})
case diff2.CHANGE:
oldRec := change.Old[0]
newRec := change.New[0]
if isNullMX(newRec) || isNullMX(oldRec) {
// changing to or from a Null MX has to be delete then create
// changing to or from a Null MX has to be deleted then create
deletes = append(deletes, &models.Correction{
Msg: color.RedString("- DELETE %s %s %s ttl=%d", oldRec.GetLabelFQDN(), oldRec.Type, oldRec.ToComparableNoTTL(), oldRec.TTL),
Msg: color.RedString("- DELETE %s %s %s ttl=%d", oldRec.GetLabelFQDN(), oldRec.Type,
oldRec.ToComparableNoTTL(), oldRec.TTL),
F: func() error {
return api.deleteRecord(oldRec.Original.(goinwx.NameserverRecord).ID)
},
})
deferred = append(deferred, &models.Correction{
Msg: color.GreenString("+ CREATE %s %s %s ttl=%d", newRec.GetLabelFQDN(), newRec.Type, newRec.ToComparableNoTTL(), newRec.TTL),
Msg: color.GreenString("+ CREATE %s %s %s ttl=%d", newRec.GetLabelFQDN(), newRec.Type,
newRec.ToComparableNoTTL(), newRec.TTL),
F: func() error {
return api.createRecord(dc.Name, newRec)
},
@ -313,7 +318,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
} else {
recID := oldRec.Original.(goinwx.NameserverRecord).ID
corrections = append(corrections, &models.Correction{
Msg: changeMsgs,
Msg: changeMessage,
F: func() error {
return api.updateRecord(recID, newRec)
},
@ -322,7 +327,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
}
case diff2.CREATE:
creates = append(creates, &models.Correction{
Msg: changeMsgs,
Msg: changeMessage,
F: func() error {
return api.createRecord(dc.Name, change.New[0])
},
@ -330,7 +335,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
case diff2.DELETE:
recID := change.Old[0].Original.(goinwx.NameserverRecord).ID
deletes = append(deletes, &models.Correction{
Msg: changeMsgs,
Msg: changeMessage,
F: func() error { return api.deleteRecord(recID) },
})
default:
@ -343,7 +348,7 @@ func (api *inwxAPI) GetZoneRecordsCorrections(dc *models.DomainConfig, foundReco
return corrections, actualChangeCount, nil
}
// getDefaultNameservers returns string map with default nameservers based on e.g. sandbox mode.
// getDefaultNameservers returns a string map with default nameservers based on e.g. sandbox mode.
func (api *inwxAPI) getDefaultNameservers() []string {
if api.sandbox {
return InwxSandboxDefaultNs