From 17c65de6e77a04391c8b3024e8c1780b58443f4d Mon Sep 17 00:00:00 2001 From: Marcus Grando Date: Mon, 7 Jul 2025 14:48:57 -0300 Subject: [PATCH] azion: improve zone lookup (#2564) Co-authored-by: Fernandez Ludovic --- providers/dns/azion/azion.go | 25 ++-- providers/dns/azion/azion_test.go | 134 ++++++++++++++++++ providers/dns/azion/fixtures/zones.json | 19 +++ providers/dns/azion/fixtures/zones_empty.json | 10 ++ 4 files changed, 175 insertions(+), 13 deletions(-) create mode 100644 providers/dns/azion/fixtures/zones.json create mode 100644 providers/dns/azion/fixtures/zones_empty.json diff --git a/providers/dns/azion/azion.go b/providers/dns/azion/azion.go index 660c0807..bc25586d 100644 --- a/providers/dns/azion/azion.go +++ b/providers/dns/azion/azion.go @@ -12,6 +12,7 @@ import ( "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/challenge/dns01" "github.com/go-acme/lego/v4/platform/config/env" + "github.com/miekg/dns" ) // Environment variables names. @@ -182,13 +183,12 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } defer func() { - // Remove the record ID from our map + // Cleans the record ID. d.recordIDsMu.Lock() delete(d.recordIDs, token) d.recordIDsMu.Unlock() }() - // Find the existing TXT record existingRecord, err := d.findExistingTXTRecord(ctxAuth, zone.GetId(), subDomain) if err != nil { return fmt.Errorf("azion: find existing record: %w", err) @@ -198,7 +198,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { return nil } - // Get current answers and remove the specific value currentAnswers := existingRecord.GetAnswersList() var updatedAnswers []string @@ -239,11 +238,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error { } func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, error) { - authZone, err := dns01.FindZoneByFqdn(fqdn) - if err != nil { - return nil, fmt.Errorf("could not find a zone for domain %q: %w", fqdn, err) - } - resp, _, err := d.client.ZonesAPI.GetZones(ctx).Execute() if err != nil { return nil, fmt.Errorf("get zones: %w", err) @@ -253,14 +247,19 @@ func (d *DNSProvider) findZone(ctx context.Context, fqdn string) (*idns.Zone, er return nil, errors.New("get zones: no results") } - targetZone := dns01.UnFqdn(authZone) - for _, zone := range resp.GetResults() { - if zone.GetName() == targetZone { - return &zone, nil + labelIndexes := dns.Split(fqdn) + + for _, index := range labelIndexes { + domain := dns01.UnFqdn(fqdn[index:]) + + for _, zone := range resp.GetResults() { + if zone.GetDomain() == domain { + return &zone, nil + } } } - return nil, fmt.Errorf("zone %q not found (fqdn: %q)", authZone, fqdn) + return nil, fmt.Errorf("zone not found (fqdn: %q)", fqdn) } // findExistingTXTRecord searches for an existing TXT record with the given name in the specified zone. diff --git a/providers/dns/azion/azion_test.go b/providers/dns/azion/azion_test.go index e96efb56..de25e7c6 100644 --- a/providers/dns/azion/azion_test.go +++ b/providers/dns/azion/azion_test.go @@ -1,9 +1,17 @@ package azion import ( + "context" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" "testing" + "github.com/aziontech/azionapi-go-sdk/idns" "github.com/go-acme/lego/v4/platform/tester" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -113,3 +121,129 @@ func TestLiveCleanUp(t *testing.T) { err = provider.CleanUp(envTest.GetDomain(), "", "123d==") require.NoError(t, err) } + +func TestDNSProvider_findZone(t *testing.T) { + provider, mux := setupTest(t) + mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler("zones.json")) + + testCases := []struct { + desc string + fqdn string + expected *idns.Zone + }{ + { + desc: "apex", + fqdn: "example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(1), + Domain: idns.PtrString("example.com"), + }, + }, + { + desc: "sub domain", + fqdn: "sub.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(2), + Domain: idns.PtrString("sub.example.com"), + }, + }, + { + desc: "long sub domain", + fqdn: "_acme-challenge.api.sub.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(2), + Domain: idns.PtrString("sub.example.com"), + }, + }, + { + desc: "long sub domain, apex", + fqdn: "_acme-challenge.test.example.com.", + expected: &idns.Zone{ + Id: idns.PtrInt32(1), + Domain: idns.PtrString("example.com"), + }, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + zone, err := provider.findZone(context.Background(), test.fqdn) + require.NoError(t, err) + + assert.Equal(t, test.expected, zone) + }) + } +} + +func TestDNSProvider_findZone_error(t *testing.T) { + testCases := []struct { + desc string + fqdn string + response string + expected string + }{ + { + desc: "no parent zone found", + fqdn: "_acme-challenge.example.org.", + response: "zones.json", + expected: `zone not found (fqdn: "_acme-challenge.example.org.")`, + }, + { + desc: "empty zones list", + fqdn: "example.com.", + response: "zones_empty.json", + expected: `zone not found (fqdn: "example.com.")`, + }, + } + + for _, test := range testCases { + t.Run(test.desc, func(t *testing.T) { + provider, mux := setupTest(t) + mux.HandleFunc("GET /intelligent_dns", writeFixtureHandler(test.response)) + + zone, err := provider.findZone(context.Background(), test.fqdn) + require.EqualError(t, err, test.expected) + + assert.Nil(t, zone) + }) + } +} + +func setupTest(t *testing.T) (*DNSProvider, *http.ServeMux) { + t.Helper() + + mux := http.NewServeMux() + server := httptest.NewServer(mux) + + config := NewDefaultConfig() + config.PersonalToken = "secret" + + provider, err := NewDNSProviderConfig(config) + require.NoError(t, err) + + clientConfig := provider.client.GetConfig() + clientConfig.HTTPClient = server.Client() + clientConfig.Servers = idns.ServerConfigurations{ + { + URL: server.URL, + Description: "Production", + }, + } + + return provider, mux +} + +func writeFixtureHandler(filename string) http.HandlerFunc { + return func(rw http.ResponseWriter, req *http.Request) { + rw.Header().Set("Content-Type", "application/json") + + file, err := os.Open(filepath.Join("fixtures", filename)) + if err != nil { + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + defer func() { _ = file.Close() }() + + _, _ = io.Copy(rw, file) + } +} diff --git a/providers/dns/azion/fixtures/zones.json b/providers/dns/azion/fixtures/zones.json new file mode 100644 index 00000000..7dccedf1 --- /dev/null +++ b/providers/dns/azion/fixtures/zones.json @@ -0,0 +1,19 @@ +{ + "count": 2, + "links": { + "previous": null, + "next": null + }, + "total_pages": 1, + "results": [ + { + "id": 1, + "domain": "example.com" + }, + { + "id": 2, + "domain": "sub.example.com" + } + ], + "schema_version": 3 +} diff --git a/providers/dns/azion/fixtures/zones_empty.json b/providers/dns/azion/fixtures/zones_empty.json new file mode 100644 index 00000000..54006383 --- /dev/null +++ b/providers/dns/azion/fixtures/zones_empty.json @@ -0,0 +1,10 @@ +{ + "count": 0, + "links": { + "previous": null, + "next": null + }, + "total_pages": 0, + "results": null, + "schema_version": 3 +}