Skip to content

Commit 83a3b71

Browse files
authored
add Verify method for Cloudflare to prevent full resource fetch (#741)
* add Verify method for Cloudflare to prevent full resource fetch
1 parent d88768a commit 83a3b71

2 files changed

Lines changed: 139 additions & 0 deletions

File tree

pkg/providers/cloudflare/cloudflare.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cloudflare
22

33
import (
44
"context"
5+
"fmt"
56
"strings"
67

78
"github.com/cloudflare/cloudflare-go"
@@ -97,6 +98,34 @@ func (p *Provider) Services() []string {
9798
return p.services.Keys()
9899
}
99100

101+
// Verify checks if the provider credentials are valid using minimal API calls.
102+
func (p *Provider) Verify(ctx context.Context) error {
103+
if !p.services.Has("dns") {
104+
return nil
105+
}
106+
107+
zones, err := p.client.ListZones(ctx)
108+
if err != nil {
109+
return fmt.Errorf("failed to verify Cloudflare zone access: %w", err)
110+
}
111+
112+
if len(zones) == 0 {
113+
return fmt.Errorf("no accessible Cloudflare zones found with provided credentials")
114+
}
115+
116+
_, _, err = p.client.ListDNSRecords(ctx, cloudflare.ZoneIdentifier(zones[0].ID), cloudflare.ListDNSRecordsParams{
117+
ResultInfo: cloudflare.ResultInfo{
118+
Page: 1,
119+
PerPage: 1,
120+
},
121+
})
122+
if err != nil {
123+
return fmt.Errorf("failed to verify Cloudflare DNS access for zone %s: %w", zones[0].Name, err)
124+
}
125+
126+
return nil
127+
}
128+
100129
// Resources returns the provider for an resource deployment source.
101130
func (p *Provider) Resources(ctx context.Context) (*schema.Resources, error) {
102131
finalResources := schema.NewResources()

pkg/providers/cloudflare/cloudflare_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,28 @@ func (f *failingRecordsClient) ListDNSRecords(context.Context, *cloudflare.Resou
3535
return nil, nil, f.err
3636
}
3737

38+
type trackingClient struct {
39+
zones []cloudflare.Zone
40+
listZonesCalls int
41+
listDNSCalls int
42+
lastZoneID string
43+
lastListDNSParam cloudflare.ListDNSRecordsParams
44+
}
45+
46+
func (t *trackingClient) ListZones(context.Context, ...string) ([]cloudflare.Zone, error) {
47+
t.listZonesCalls++
48+
return t.zones, nil
49+
}
50+
51+
func (t *trackingClient) ListDNSRecords(_ context.Context, zoneID *cloudflare.ResourceContainer, params cloudflare.ListDNSRecordsParams) ([]cloudflare.DNSRecord, *cloudflare.ResultInfo, error) {
52+
t.listDNSCalls++
53+
if zoneID != nil {
54+
t.lastZoneID = zoneID.Identifier
55+
}
56+
t.lastListDNSParam = params
57+
return nil, &cloudflare.ResultInfo{}, nil
58+
}
59+
3860
func TestProviderResourcesPropagatesZoneError(t *testing.T) {
3961
t.Parallel()
4062

@@ -67,3 +89,91 @@ func TestProviderResourcesPropagatesRecordError(t *testing.T) {
6789
require.Error(t, err)
6890
require.Contains(t, err.Error(), "record fetch failed")
6991
}
92+
93+
func TestProviderVerifyPropagatesZoneError(t *testing.T) {
94+
t.Parallel()
95+
96+
p := &Provider{
97+
id: "test",
98+
client: &failingZonesClient{
99+
err: errors.New("zones down"),
100+
},
101+
services: schema.ServiceMap{"dns": struct{}{}},
102+
}
103+
104+
err := p.Verify(context.Background())
105+
require.Error(t, err)
106+
require.Contains(t, err.Error(), "zones down")
107+
}
108+
109+
func TestProviderVerifyReturnsErrorWhenNoZonesExist(t *testing.T) {
110+
t.Parallel()
111+
112+
client := &trackingClient{}
113+
p := &Provider{
114+
id: "test",
115+
client: client,
116+
services: schema.ServiceMap{"dns": struct{}{}},
117+
}
118+
119+
err := p.Verify(context.Background())
120+
require.Error(t, err)
121+
require.Contains(t, err.Error(), "no accessible Cloudflare zones found")
122+
require.Equal(t, 1, client.listZonesCalls)
123+
require.Zero(t, client.listDNSCalls)
124+
}
125+
126+
func TestProviderVerifySkipsWhenDNSServiceDisabled(t *testing.T) {
127+
t.Parallel()
128+
129+
client := &trackingClient{}
130+
p := &Provider{
131+
id: "test",
132+
client: client,
133+
services: schema.ServiceMap{"workers": struct{}{}},
134+
}
135+
136+
err := p.Verify(context.Background())
137+
require.NoError(t, err)
138+
require.Zero(t, client.listZonesCalls)
139+
require.Zero(t, client.listDNSCalls)
140+
}
141+
142+
func TestProviderVerifyUsesSingleRecordProbe(t *testing.T) {
143+
t.Parallel()
144+
145+
client := &trackingClient{
146+
zones: []cloudflare.Zone{{ID: "zone-id", Name: "example.com"}},
147+
}
148+
p := &Provider{
149+
id: "test",
150+
client: client,
151+
services: schema.ServiceMap{"dns": struct{}{}},
152+
}
153+
154+
err := p.Verify(context.Background())
155+
require.NoError(t, err)
156+
require.Equal(t, 1, client.listZonesCalls)
157+
require.Equal(t, 1, client.listDNSCalls)
158+
require.Equal(t, "zone-id", client.lastZoneID)
159+
require.Equal(t, 1, client.lastListDNSParam.Page)
160+
require.Equal(t, 1, client.lastListDNSParam.PerPage)
161+
}
162+
163+
func TestProviderVerifyPropagatesRecordError(t *testing.T) {
164+
t.Parallel()
165+
166+
p := &Provider{
167+
id: "test",
168+
client: &failingRecordsClient{
169+
zones: []cloudflare.Zone{{ID: "zone-id", Name: "example.com"}},
170+
err: errors.New("record fetch failed"),
171+
},
172+
services: schema.ServiceMap{"dns": struct{}{}},
173+
}
174+
175+
err := p.Verify(context.Background())
176+
require.Error(t, err)
177+
require.Contains(t, err.Error(), "failed to verify Cloudflare DNS access")
178+
require.Contains(t, err.Error(), "record fetch failed")
179+
}

0 commit comments

Comments
 (0)