Skip to content

Commit 7108979

Browse files
vflauxmloiseleur
andauthored
improve cloudflare regional hostname implementation (#5309)
- 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 Co-authored-by: Michel Loiseleur <[email protected]>
1 parent 104157f commit 7108979

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
@@ -119,6 +119,7 @@ type cloudFlareDNS interface {
119119
CreateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDNSRecordParams) (cloudflare.DNSRecord, error)
120120
DeleteDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, recordID string) error
121121
UpdateDNSRecord(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDNSRecordParams) error
122+
ListDataLocalizationRegionalHostnames(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.ListDataLocalizationRegionalHostnamesParams) ([]cloudflare.RegionalHostname, error)
122123
CreateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.CreateDataLocalizationRegionalHostnameParams) error
123124
UpdateDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, rp cloudflare.UpdateDataLocalizationRegionalHostnameParams) error
124125
DeleteDataLocalizationRegionalHostname(ctx context.Context, rc *cloudflare.ResourceContainer, hostname string) error
@@ -216,13 +217,13 @@ type CloudFlareProvider struct {
216217
provider.BaseProvider
217218
Client cloudFlareDNS
218219
// only consider hosted zones managing domains ending in this suffix
219-
domainFilter *endpoint.DomainFilter
220-
zoneIDFilter provider.ZoneIDFilter
221-
proxiedByDefault bool
222-
DryRun bool
223-
CustomHostnamesConfig CustomHostnamesConfig
224-
DNSRecordsConfig DNSRecordsConfig
225-
RegionKey string
220+
domainFilter *endpoint.DomainFilter
221+
zoneIDFilter provider.ZoneIDFilter
222+
proxiedByDefault bool
223+
DryRun bool
224+
CustomHostnamesConfig CustomHostnamesConfig
225+
DNSRecordsConfig DNSRecordsConfig
226+
RegionalServicesConfig RegionalServicesConfig
226227
}
227228

228229
// cloudFlareChange differentiates between ChangActions
@@ -279,7 +280,15 @@ func convertCloudflareError(err error) error {
279280
}
280281

281282
// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
282-
func NewCloudFlareProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, regionKey string, customHostnamesConfig CustomHostnamesConfig, dnsRecordsConfig DNSRecordsConfig) (*CloudFlareProvider, error) {
283+
func NewCloudFlareProvider(
284+
domainFilter *endpoint.DomainFilter,
285+
zoneIDFilter provider.ZoneIDFilter,
286+
proxiedByDefault bool,
287+
dryRun bool,
288+
regionalServicesConfig RegionalServicesConfig,
289+
customHostnamesConfig CustomHostnamesConfig,
290+
dnsRecordsConfig DNSRecordsConfig,
291+
) (*CloudFlareProvider, error) {
283292
// initialize via chosen auth method and returns new API object
284293
var (
285294
config *cloudflare.API
@@ -302,15 +311,19 @@ func NewCloudFlareProvider(domainFilter *endpoint.DomainFilter, zoneIDFilter pro
302311
return nil, fmt.Errorf("failed to initialize cloudflare provider: %w", err)
303312
}
304313

314+
if regionalServicesConfig.RegionKey != "" {
315+
regionalServicesConfig.Enabled = true
316+
}
317+
305318
return &CloudFlareProvider{
306-
Client: zoneService{config},
307-
domainFilter: domainFilter,
308-
zoneIDFilter: zoneIDFilter,
309-
proxiedByDefault: proxiedByDefault,
310-
CustomHostnamesConfig: customHostnamesConfig,
311-
DryRun: dryRun,
312-
RegionKey: regionKey,
313-
DNSRecordsConfig: dnsRecordsConfig,
319+
Client: zoneService{config},
320+
domainFilter: domainFilter,
321+
zoneIDFilter: zoneIDFilter,
322+
proxiedByDefault: proxiedByDefault,
323+
CustomHostnamesConfig: customHostnamesConfig,
324+
DryRun: dryRun,
325+
RegionalServicesConfig: regionalServicesConfig,
326+
DNSRecordsConfig: dnsRecordsConfig,
314327
}, nil
315328
}
316329

@@ -379,7 +392,13 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
379392
// As CloudFlare does not support "sets" of targets, but instead returns
380393
// a single entry for each name/type/target, we have to group by name
381394
// and record to allow the planner to calculate the correct plan. See #992.
382-
endpoints = append(endpoints, groupByNameAndTypeWithCustomHostnames(records, chs)...)
395+
zoneEndpoints := groupByNameAndTypeWithCustomHostnames(records, chs)
396+
397+
if err := p.addEnpointsProviderSpecificRegionKeyProperty(ctx, zone.ID, zoneEndpoints); err != nil {
398+
return nil, err
399+
}
400+
401+
endpoints = append(endpoints, zoneEndpoints...)
383402
}
384403

385404
return endpoints, nil
@@ -595,16 +614,21 @@ func (p *CloudFlareProvider) submitChanges(ctx context.Context, changes []*cloud
595614
}
596615
}
597616

598-
if regionalHostnamesChanges, err := dataLocalizationRegionalHostnamesChanges(zoneChanges); err == nil {
599-
if !p.submitDataLocalizationRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
600-
failedChange = true
617+
if p.RegionalServicesConfig.Enabled {
618+
desiredRegionalHostnames, err := desiredRegionalHostnames(zoneChanges)
619+
if err != nil {
620+
return fmt.Errorf("failed to build desired regional hostnames: %w", err)
601621
}
602-
} else {
603-
logFields := log.Fields{
604-
"zone": zoneID,
622+
if len(desiredRegionalHostnames) > 0 {
623+
regionalHostnames, err := p.listDataLocalisationRegionalHostnames(ctx, resourceContainer)
624+
if err != nil {
625+
return fmt.Errorf("could not fetch regional hostnames from zone, %w", err)
626+
}
627+
regionalHostnamesChanges := regionalHostnamesChanges(desiredRegionalHostnames, regionalHostnames)
628+
if !p.submitRegionalHostnameChanges(ctx, regionalHostnamesChanges, resourceContainer) {
629+
failedChange = true
630+
}
605631
}
606-
log.WithFields(logFields).Errorf("failed to build data localization regional hostname changes: %v", err)
607-
failedChange = true
608632
}
609633

610634
if failedChange {
@@ -640,6 +664,13 @@ func (p *CloudFlareProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]
640664
e.DeleteProviderSpecificProperty(annotations.CloudflareCustomHostnameKey)
641665
}
642666

667+
if p.RegionalServicesConfig.Enabled {
668+
// Add default region key if not set
669+
if _, ok := e.GetProviderSpecificProperty(annotations.CloudflareRegionKey); !ok {
670+
e.SetProviderSpecificProperty(annotations.CloudflareRegionKey, p.RegionalServicesConfig.RegionKey)
671+
}
672+
}
673+
643674
adjustedEndpoints = append(adjustedEndpoints, e)
644675
}
645676
return adjustedEndpoints, nil

0 commit comments

Comments
 (0)