Skip to content

Commit bbe5d42

Browse files
committed
improve cloudflare regional hostname implementation
- add flag to enable regional hostname feature - support deletion of regional hostname on annotation edit - correctly support differences detection with cloudflare state - increased tests coverage
1 parent e24f88c commit bbe5d42

File tree

9 files changed

+1229
-463
lines changed

9 files changed

+1229
-463
lines changed

controller/execute.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,10 @@ func buildProvider(
215215
zoneIDFilter,
216216
cfg.CloudflareProxied,
217217
cfg.DryRun,
218-
cfg.CloudflareRegionKey,
218+
cloudflare.RegionalServicesConfig{
219+
Enabled: cfg.CloudflareRegionalServices,
220+
RegionKey: cfg.CloudflareRegionKey,
221+
},
219222
cloudflare.CustomHostnamesConfig{
220223
Enabled: cfg.CloudflareCustomHostnames,
221224
MinTLSVersion: cfg.CloudflareCustomHostnamesMinTLSVersion,

docs/flags.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@
9393
| `--cloudflare-custom-hostnames-min-tls-version=1.0` | When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3) |
9494
| `--cloudflare-custom-hostnames-certificate-authority=none` | When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none) |
9595
| `--cloudflare-dns-records-per-page=100` | When using the Cloudflare provider, specify how many DNS records listed per page, max possible 5,000 (default: 100) |
96-
| `--cloudflare-region-key=CLOUDFLARE-REGION-KEY` | When using the Cloudflare provider, specify the region (default: earth) |
96+
| `--[no-]cloudflare-regional-services` | When using the Cloudflare provider, specify if Regional Services feature will be used (default: disabled) |
97+
| `--cloudflare-region-key=CLOUDFLARE-REGION-KEY` | When using the Cloudflare provider, specify the default region for Regional Services. Any value other than an empty string will enable the Regional Services feature (optional) |
9798
| `--cloudflare-record-comment=""` | When using the Cloudflare provider, specify the comment for the DNS records (default: '') |
9899
| `--coredns-prefix="/skydns/"` | When using the CoreDNS provider, specify the prefix name |
99100
| `--akamai-serviceconsumerdomain=""` | When using the Akamai provider, specify the base URL (required when --provider=akamai and edgerc-path not specified) |

docs/tutorials/cloudflare.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ spec:
128128
- --provider=cloudflare
129129
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
130130
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
131+
- --cloudflare-regional-services # (optional) enable the regional hostname feature that configure which region can decrypt HTTPS requests
131132
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
132133
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
133134
env:
@@ -205,6 +206,7 @@ spec:
205206
- --provider=cloudflare
206207
- --cloudflare-proxied # (optional) enable the proxy feature of Cloudflare (DDOS protection, CDN...)
207208
- --cloudflare-dns-records-per-page=5000 # (optional) configure how many DNS records to fetch per request
209+
- --cloudflare-regional-services # (optional) enable the regional hostname feature that configure which region can decrypt HTTPS requests
208210
- --cloudflare-region-key="eu" # (optional) configure which region can decrypt HTTPS requests
209211
- --cloudflare-record-comment="provisioned by external-dns" # (optional) configure comments for provisioned records; <=100 chars for free zones; <=500 chars for paid zones
210212
env:
@@ -303,13 +305,19 @@ kubectl delete -f externaldns.yaml
303305

304306
Using the `external-dns.alpha.kubernetes.io/cloudflare-proxied: "true"` annotation on your ingress, you can specify if the proxy feature of Cloudflare should be enabled for that record. This setting will override the global `--cloudflare-proxied` setting.
305307

306-
## Setting cloudflare-region-key to configure regional services
308+
## Setting cloudlfare regional services
307309

308-
Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on your ingress, you can restrict which data centers can decrypt and serve HTTPS traffic.
310+
With Cloudflare regional services you can restrict which data centers can decrypt and serve HTTPS traffic.
311+
312+
Configuration of Cloudflare Regional Services is enabled by the `--cloudflare-regional-services` flag.
313+
A default region can be defined using the `--cloudflare-region-key` flag.
314+
315+
Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on your ingress, you can specify the region for that record.
316+
317+
An empty string will result in no regional hostname configured.
309318

310319
**Accepted values for region key include:**
311320

312-
- `earth` (default): All data centers (global)
313321
- `eu`: European Union data centers only
314322
- `us`: United States data centers only
315323
- `ap`: Asia-Pacific data centers only
@@ -321,14 +329,11 @@ Using the `external-dns.alpha.kubernetes.io/cloudflare-region-key` annotation on
321329
- `br`: Brazil data centers only
322330
- `za`: South Africa data centers only
323331
- `ae`: United Arab Emirates data centers only
324-
- `global`: Alias for `earth`
325332

326333
For the most up-to-date list and details, see the [Cloudflare Regional Services documentation](https://developers.cloudflare.com/data-localization/regional-services/get-started/).
327334

328335
Currently, requires SuperAdmin or Admin role.
329336

330-
If not set the value will default to `global`.
331-
332337
## Setting cloudflare-custom-hostname
333338

334339
Automatic configuration of Cloudflare custom hostnames (using A/CNAME DNS records as custom origin servers) is enabled by the `--cloudflare-custom-hostnames` flag and the `external-dns.alpha.kubernetes.io/cloudflare-custom-hostname: <custom hostname>` annotation.

pkg/apis/externaldns/types.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type Config struct {
113113
CloudflareDNSRecordsComment string
114114
CloudflareCustomHostnamesMinTLSVersion string
115115
CloudflareCustomHostnamesCertificateAuthority string
116+
CloudflareRegionalServices bool
116117
CloudflareRegionKey string
117118
CloudflareRecordComment string
118119
CoreDNSPrefix string
@@ -256,6 +257,7 @@ var defaultConfig = &Config{
256257
CloudflareCustomHostnamesMinTLSVersion: "1.0",
257258
CloudflareDNSRecordsPerPage: 100,
258259
CloudflareProxied: false,
260+
CloudflareRegionalServices: false,
259261
CloudflareRegionKey: "earth",
260262

261263
CombineFQDNAndAnnotation: false,
@@ -533,7 +535,8 @@ func App(cfg *Config) *kingpin.Application {
533535
app.Flag("cloudflare-custom-hostnames-min-tls-version", "When using the Cloudflare provider with the Custom Hostnames, specify which Minimum TLS Version will be used by default. (default: 1.0, options: 1.0, 1.1, 1.2, 1.3)").Default("1.0").EnumVar(&cfg.CloudflareCustomHostnamesMinTLSVersion, "1.0", "1.1", "1.2", "1.3")
534536
app.Flag("cloudflare-custom-hostnames-certificate-authority", "When using the Cloudflare provider with the Custom Hostnames, specify which Certificate Authority will be used. A value of none indicates no Certificate Authority will be sent to the Cloudflare API (default: none, options: google, ssl_com, lets_encrypt, none)").Default("none").EnumVar(&cfg.CloudflareCustomHostnamesCertificateAuthority, "google", "ssl_com", "lets_encrypt", "none")
535537
app.Flag("cloudflare-dns-records-per-page", "When using the Cloudflare provider, specify how many DNS records listed per page, max possible 5,000 (default: 100)").Default(strconv.Itoa(defaultConfig.CloudflareDNSRecordsPerPage)).IntVar(&cfg.CloudflareDNSRecordsPerPage)
536-
app.Flag("cloudflare-region-key", "When using the Cloudflare provider, specify the region (default: earth)").StringVar(&cfg.CloudflareRegionKey)
538+
app.Flag("cloudflare-regional-services", "When using the Cloudflare provider, specify if Regional Services feature will be used (default: disabled)").Default(strconv.FormatBool(defaultConfig.CloudflareRegionalServices)).BoolVar(&cfg.CloudflareRegionalServices)
539+
app.Flag("cloudflare-region-key", "When using the Cloudflare provider, specify the default region for Regional Services. Any value other than an empty string will enable the Regional Services feature (optional)").StringVar(&cfg.CloudflareRegionKey)
537540
app.Flag("cloudflare-record-comment", "When using the Cloudflare provider, specify the comment for the DNS records (default: '')").Default("").StringVar(&cfg.CloudflareRecordComment)
538541

539542
app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)

pkg/apis/externaldns/types_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ var (
186186
CloudflareCustomHostnamesMinTLSVersion: "1.3",
187187
CloudflareCustomHostnamesCertificateAuthority: "google",
188188
CloudflareDNSRecordsPerPage: 5000,
189+
CloudflareRegionalServices: true,
189190
CloudflareRegionKey: "us",
190191
CoreDNSPrefix: "/coredns/",
191192
AkamaiServiceConsumerDomain: "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
@@ -296,6 +297,7 @@ func TestParseFlags(t *testing.T) {
296297
"--cloudflare-custom-hostnames-min-tls-version=1.3",
297298
"--cloudflare-custom-hostnames-certificate-authority=google",
298299
"--cloudflare-dns-records-per-page=5000",
300+
"--cloudflare-regional-services",
299301
"--cloudflare-region-key=us",
300302
"--coredns-prefix=/coredns/",
301303
"--akamai-serviceconsumerdomain=oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",
@@ -424,6 +426,7 @@ func TestParseFlags(t *testing.T) {
424426
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_MIN_TLS_VERSION": "1.3",
425427
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES_CERTIFICATE_AUTHORITY": "google",
426428
"EXTERNAL_DNS_CLOUDFLARE_DNS_RECORDS_PER_PAGE": "5000",
429+
"EXTERNAL_DNS_CLOUDFLARE_REGIONAL_SERVICES": "1",
427430
"EXTERNAL_DNS_CLOUDFLARE_REGION_KEY": "us",
428431
"EXTERNAL_DNS_COREDNS_PREFIX": "/coredns/",
429432
"EXTERNAL_DNS_AKAMAI_SERVICECONSUMERDOMAIN": "oooo-xxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxx.luna.akamaiapis.net",

provider/cloudflare/cloudflare.go

Lines changed: 56 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ type cloudFlareDNS interface {
121121
CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error)
122122
DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error
123123
UpdateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDNSRecordParams) error
124+
ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error)
124125
CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error
125126
UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error
126127
DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error
@@ -226,13 +227,13 @@ type CloudFlareProvider struct {
226227
provider.BaseProvider
227228
Client cloudFlareDNS
228229
// only consider hosted zones managing domains ending in this suffix
229-
domainFilter endpoint.DomainFilter
230-
zoneIDFilter provider.ZoneIDFilter
231-
proxiedByDefault bool
232-
DryRun bool
233-
CustomHostnamesConfig CustomHostnamesConfig
234-
DNSRecordsConfig DNSRecordsConfig
235-
RegionKey string
230+
domainFilter endpoint.DomainFilter
231+
zoneIDFilter provider.ZoneIDFilter
232+
proxiedByDefault bool
233+
DryRun bool
234+
CustomHostnamesConfig CustomHostnamesConfig
235+
DNSRecordsConfig DNSRecordsConfig
236+
RegionalServicesConfig RegionalServicesConfig
236237
}
237238

238239
// cloudFlareChange differentiates between ChangActions
@@ -289,7 +290,15 @@ func convertCloudflareError(err error) error {
289290
}
290291

291292
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
292-
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, regionKey string, customHostnamesConfig CustomHostnamesConfig, dnsRecordsConfig DNSRecordsConfig) (*CloudFlareProvider, error) {
293+
func NewCloudFlareProvider(
294+
domainFilter endpoint.DomainFilter,
295+
zoneIDFilter provider.ZoneIDFilter,
296+
proxiedByDefault bool,
297+
dryRun bool,
298+
regionalServicesConfig RegionalServicesConfig,
299+
customHostnamesConfig CustomHostnamesConfig,
300+
dnsRecordsConfig DNSRecordsConfig,
301+
) (*CloudFlareProvider, error) {
293302
// initialize via chosen auth method and returns new API object
294303
var (
295304
config *cloudflare.API
@@ -312,15 +321,19 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
312321
return nil, fmt.Errorf("failed to initialize cloudflare provider: %w", err)
313322
}
314323

324+
if regionalServicesConfig.RegionKey != "" {
325+
regionalServicesConfig.Enabled = true
326+
}
327+
315328
return &CloudFlareProvider{
316-
Client: zoneService{config},
317-
domainFilter: domainFilter,
318-
zoneIDFilter: zoneIDFilter,
319-
proxiedByDefault: proxiedByDefault,
320-
CustomHostnamesConfig: customHostnamesConfig,
321-
DryRun: dryRun,
322-
RegionKey: regionKey,
323-
DNSRecordsConfig: dnsRecordsConfig,
329+
Client: zoneService{config},
330+
domainFilter: domainFilter,
331+
zoneIDFilter: zoneIDFilter,
332+
proxiedByDefault: proxiedByDefault,
333+
CustomHostnamesConfig: customHostnamesConfig,
334+
DryRun: dryRun,
335+
RegionalServicesConfig: regionalServicesConfig,
336+
DNSRecordsConfig: dnsRecordsConfig,
324337
}, nil
325338
}
326339

@@ -389,7 +402,13 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
389402
// As CloudFlare does not support "sets" of targets, but instead returns
390403
// a single entry for each name/type/target, we have to group by name
391404
// and record to allow the planner to calculate the correct plan. See #992.
392-
endpoints = append(endpoints, groupByNameAndTypeWithCustomHostnames(records, chs)...)
405+
zoneEndpoints := groupByNameAndTypeWithCustomHostnames(records, chs)
406+
407+
if err := p.addEnpointsProviderSpecificRegionKeyProperty(ctx, zone.ID, zoneEndpoints); err != nil {
408+
return nil, err
409+
}
410+
411+
endpoints = append(endpoints, zoneEndpoints...)
393412
}
394413

395414
return endpoints, nil
@@ -605,16 +624,21 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
605624
}
606625
}
607626

608-
if regionalHostnamesChanges, err := dataLocalizationRegionalHostnamesChanges(zoneChanges); err == nil {
609-
if !p.submitDataLocalizationRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
610-
failedChange = true
627+
if p.RegionalServicesConfig.Enabled {
628+
desiredRegionalHostnames, err := desiredRegionalHostnames(zoneChanges)
629+
if err != nil {
630+
return fmt.Errorf("failed to build desired regional hostnames: %w", err)
611631
}
612-
} else {
613-
logFields := log.Fields{
614-
"zone": zoneID,
632+
if len(desiredRegionalHostnames) > 0 {
633+
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, resourceContainer)
634+
if err != nil {
635+
return fmt.Errorf("could not fetch regional hostnames from zone, %w", err)
636+
}
637+
regionalHostnamesChanges := regionalHostnamesChanges(desiredRegionalHostnames, regionalHostnames)
638+
if !p.submitRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
639+
failedChange = true
640+
}
615641
}
616-
log.WithFields(logFields).Errorf("failed to build data localization regional hostname changes: %v", err)
617-
failedChange = true
618642
}
619643

620644
if failedChange {
@@ -650,6 +674,13 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]
650674
e.DeleteProviderSpecificProperty(annotations.CloudflareCustomHostnameKey)
651675
}
652676

677+
if p.RegionalServicesConfig.Enabled {
678+
// Add default region key if not set
679+
if _, ok := e.GetProviderSpecificProperty(annotations.CloudflareRegionKey); !ok {
680+
e.SetProviderSpecificProperty(annotations.CloudflareRegionKey, p.RegionalServicesConfig.RegionKey)
681+
}
682+
}
683+
653684
adjustedEndpoints = append(adjustedEndpoints, e)
654685
}
655686
return adjustedEndpoints, nil

0 commit comments

Comments
 (0)