Skip to content

Commit

Permalink
Use new --cloudflare-custom-hostnames flag to enable cloudflare custo…
Browse files Browse the repository at this point in the history
…m hostnames support
  • Loading branch information
mrozentsvayg committed Mar 9, 2025
1 parent 89cc46f commit 3dbf015
Show file tree
Hide file tree
Showing 6 changed files with 55 additions and 36 deletions.
1 change: 1 addition & 0 deletions docs/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
| `--tencent-cloud-config-file="/etc/kubernetes/tencent-cloud.json"` | When using the Tencent Cloud provider, specify the Tencent Cloud configuration file (required when --provider=tencentcloud) |
| `--tencent-cloud-zone-type=` | When using the Tencent Cloud provider, filter for zones with visibility (optional, options: public, private) |
| `--[no-]cloudflare-proxied` | When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled) |
| `--[no-]cloudflare-custom-hostnames` | When using the Cloudflare provider, specify if the Custom Hostnames feature will be used. Requires "Cloudflare for SaaS" enabled. (default: disabled) |
| `--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) |
| `--cloudflare-region-key=CLOUDFLARE-REGION-KEY` | When using the Cloudflare provider, specify the region (default: earth) |
| `--coredns-prefix="/skydns/"` | When using the CoreDNS provider, specify the prefix name |
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func main() {
case "civo":
p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun)
case "cloudflare":
p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareProxied, cfg.DryRun, cfg.CloudflareDNSRecordsPerPage, cfg.CloudflareRegionKey)
p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareProxied, cfg.CloudflareCustomHostnames, cfg.DryRun, cfg.CloudflareDNSRecordsPerPage, cfg.CloudflareRegionKey)
case "google":
p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
case "digitalocean":
Expand Down
2 changes: 2 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ type Config struct {
AzureActiveDirectoryAuthorityHost string
AzureZonesCacheDuration time.Duration
CloudflareProxied bool
CloudflareCustomHostnames bool
CloudflareDNSRecordsPerPage int
CloudflareRegionKey string
CoreDNSPrefix string
Expand Down Expand Up @@ -515,6 +516,7 @@ func App(cfg *Config) *kingpin.Application {
app.Flag("tencent-cloud-zone-type", "When using the Tencent Cloud provider, filter for zones with visibility (optional, options: public, private)").Default(defaultConfig.TencentCloudZoneType).EnumVar(&cfg.TencentCloudZoneType, "", "public", "private")

app.Flag("cloudflare-proxied", "When using the Cloudflare provider, specify if the proxy mode must be enabled (default: disabled)").BoolVar(&cfg.CloudflareProxied)
app.Flag("cloudflare-custom-hostnames", "When using the Cloudflare provider, specify if the Custom Hostnames feature will be used. Requires \"Cloudflare for SaaS\" enabled. (default: disabled)").BoolVar(&cfg.CloudflareCustomHostnames)
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)
app.Flag("cloudflare-region-key", "When using the Cloudflare provider, specify the region (default: earth)").StringVar(&cfg.CloudflareRegionKey)
app.Flag("coredns-prefix", "When using the CoreDNS provider, specify the prefix name").Default(defaultConfig.CoreDNSPrefix).StringVar(&cfg.CoreDNSPrefix)
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ var (
AzureResourceGroup: "",
AzureSubscriptionID: "",
CloudflareProxied: false,
CloudflareCustomHostnames: false,
CloudflareDNSRecordsPerPage: 100,
CloudflareRegionKey: "",
CoreDNSPrefix: "/skydns/",
Expand Down Expand Up @@ -179,6 +180,7 @@ var (
AzureResourceGroup: "arg",
AzureSubscriptionID: "arg",
CloudflareProxied: true,
CloudflareCustomHostnames: true,
CloudflareDNSRecordsPerPage: 5000,
CloudflareRegionKey: "us",
CoreDNSPrefix: "/coredns/",
Expand Down Expand Up @@ -287,6 +289,7 @@ func TestParseFlags(t *testing.T) {
"--azure-resource-group=arg",
"--azure-subscription-id=arg",
"--cloudflare-proxied",
"--cloudflare-custom-hostnames",
"--cloudflare-dns-records-per-page=5000",
"--cloudflare-region-key=us",
"--coredns-prefix=/coredns/",
Expand Down Expand Up @@ -413,6 +416,7 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_AZURE_RESOURCE_GROUP": "arg",
"EXTERNAL_DNS_AZURE_SUBSCRIPTION_ID": "arg",
"EXTERNAL_DNS_CLOUDFLARE_PROXIED": "1",
"EXTERNAL_DNS_CLOUDFLARE_CUSTOM_HOSTNAMES": "1",
"EXTERNAL_DNS_CLOUDFLARE_DNS_RECORDS_PER_PAGE": "5000",
"EXTERNAL_DNS_CLOUDFLARE_REGION_KEY": "us",
"EXTERNAL_DNS_COREDNS_PREFIX": "/coredns/",
Expand Down
62 changes: 33 additions & 29 deletions provider/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ var proxyEnabled *bool = boolPtr(true)
// proxyDisabled is a pointer to a bool false showing the record should not be proxied through cloudflare
var proxyDisabled *bool = boolPtr(false)

// customHostnamesEnabled controls if custom hostnames feature is used, will self-disable if SaaS API fails to authenticate
var customHostnamesEnabled bool = true

var recordTypeProxyNotSupported = map[string]bool{
"LOC": true,
"MX": true,
Expand Down Expand Up @@ -155,6 +152,7 @@ type CloudFlareProvider struct {
domainFilter endpoint.DomainFilter
zoneIDFilter provider.ZoneIDFilter
proxiedByDefault bool
customHostnames bool
DryRun bool
DNSRecordsPerPage int
RegionKey string
Expand Down Expand Up @@ -205,7 +203,7 @@ func getCreateDNSRecordParam(cfc cloudFlareChange) cloudflare.CreateDNSRecordPar
}

// NewCloudFlareProvider initializes a new CloudFlare DNS based Provider.
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, dryRun bool, dnsRecordsPerPage int, regionKey string) (*CloudFlareProvider, error) {
func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, proxiedByDefault bool, customHostnames bool, dryRun bool, dnsRecordsPerPage int, regionKey string) (*CloudFlareProvider, error) {
// initialize via chosen auth method and returns new API object
var (
config *cloudflare.API
Expand Down Expand Up @@ -233,6 +231,7 @@ func NewCloudFlareProvider(domainFilter endpoint.DomainFilter, zoneIDFilter prov
domainFilter: domainFilter,
zoneIDFilter: zoneIDFilter,
proxiedByDefault: proxiedByDefault,
customHostnames: customHostnames,
DryRun: dryRun,
DNSRecordsPerPage: dnsRecordsPerPage,
RegionKey: regionKey,
Expand Down Expand Up @@ -303,9 +302,13 @@ func (p *CloudFlareProvider) Records(ctx context.Context) ([]*endpoint.Endpoint,
return nil, err
}

chs, chErr := p.listCustomHostnamesWithPagination(ctx, zone.ID)
if chErr != nil {
return nil, chErr
chs := []cloudflare.CustomHostname{}
if p.customHostnames {
var chErr error
chs, chErr = p.listCustomHostnamesWithPagination(ctx, zone.ID)
if chErr != nil {
return nil, chErr
}
}

// As CloudFlare does not support "sets" of targets, but instead returns
Expand Down Expand Up @@ -565,9 +568,26 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoi
ttl = int(endpoint.RecordTTL)
}
dt := time.Now()

customHostnamePrev := ""
if current != nil {
customHostnamePrev = getEndpointCustomHostname(current)
newCustomHostname := cloudflare.CustomHostname{}
if p.customHostnames {
if current != nil {
customHostnamePrev = getEndpointCustomHostname(current)
}
newCustomHostname = cloudflare.CustomHostname{
Hostname: getEndpointCustomHostname(endpoint),
CustomOriginServer: endpoint.DNSName,
SSL: &cloudflare.CustomHostnameSSL{
Type: "dv",
Method: "http",
CertificateAuthority: "google",
BundleMethod: "ubiquitous",
Settings: cloudflare.CustomHostnameSSLSettings{
MinTLSVersion: "1.0",
},
},
}
}
return &cloudFlareChange{
Action: action,
Expand All @@ -589,19 +609,7 @@ func (p *CloudFlareProvider) newCloudFlareChange(action string, endpoint *endpoi
CreatedOn: &dt,
},
CustomHostnamePrev: customHostnamePrev,
CustomHostname: cloudflare.CustomHostname{
Hostname: getEndpointCustomHostname(endpoint),
CustomOriginServer: endpoint.DNSName,
SSL: &cloudflare.CustomHostnameSSL{
Type: "dv",
Method: "http",
CertificateAuthority: "google",
BundleMethod: "ubiquitous",
Settings: cloudflare.CustomHostnameSSLSettings{
MinTLSVersion: "1.0",
},
},
},
CustomHostname: newCustomHostname,
}
}

Expand Down Expand Up @@ -634,7 +642,7 @@ func (p *CloudFlareProvider) listDNSRecordsWithAutoPagination(ctx context.Contex

// listCustomHostnamesWithPagination performs automatic pagination of results on requests to cloudflare.CustomHostnames
func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx context.Context, zoneID string) ([]cloudflare.CustomHostname, error) {
if !customHostnamesEnabled {
if !p.customHostnames {
return nil, nil
}
var chs []cloudflare.CustomHostname
Expand All @@ -647,11 +655,6 @@ func (p *CloudFlareProvider) listCustomHostnamesWithPagination(ctx context.Conte
if apiErr.ClientRateLimited() || apiErr.StatusCode >= http.StatusInternalServerError {
// Handle rate limit error as a soft error
return nil, provider.NewSoftError(err)
} else if apiErr.ErrorMessageContains("Authentication error") {
// "Cloudflare for SaaS" fails toauthenticate, log once and don't use cusotm hostnames
log.Debugf("\"Cloudflare for SaaS\" is not enabled, custom hostnames will not be collected.")
customHostnamesEnabled = false
return nil, nil
}
}
log.Errorf("zone %s failed to fetch custom hostnames. Please check if \"Cloudflare for SaaS\" is enabled and API key permissions, %v", zoneID, err)
Expand Down Expand Up @@ -719,7 +722,7 @@ func groupByNameAndTypeWithCustomHostnames(records []cloudflare.DNSRecord, chs [
// map custom origin to custom hostname, custom origin should match to a dns record
customOriginServers := map[string]string{}

// only one latest custom hostname for a dns record would work
// only one latest custom hostname for a dns record would work; noop (chs is empty) if custom hostnames feature is not in use
for _, c := range chs {
customOriginServers[c.CustomOriginServer] = c.Hostname
}
Expand All @@ -746,6 +749,7 @@ func groupByNameAndTypeWithCustomHostnames(records []cloudflare.DNSRecord, chs [
continue
}
ep = ep.WithProviderSpecific(source.CloudflareProxiedKey, strconv.FormatBool(proxied))
// noop (customOriginServers is empty) if custom hostnames feature is not in use
if customHostname, ok := customOriginServers[records[0].Name]; ok {
ep = ep.WithProviderSpecific(source.CloudflareCustomHostnameKey, customHostname)
}
Expand Down
20 changes: 14 additions & 6 deletions provider/cloudflare/cloudflare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ func TestCloudflareProvider(t *testing.T) {
endpoint.NewDomainFilter([]string{"bar.com"}),
provider.NewZoneIDFilter([]string{""}),
false,
false,
true,
5000,
"")
Expand All @@ -878,6 +879,7 @@ func TestCloudflareProvider(t *testing.T) {
endpoint.NewDomainFilter([]string{"bar.com"}),
provider.NewZoneIDFilter([]string{""}),
false,
false,
true,
5000,
"")
Expand All @@ -892,6 +894,7 @@ func TestCloudflareProvider(t *testing.T) {
endpoint.NewDomainFilter([]string{"bar.com"}),
provider.NewZoneIDFilter([]string{""}),
false,
false,
true,
5000,
"")
Expand All @@ -905,6 +908,7 @@ func TestCloudflareProvider(t *testing.T) {
endpoint.NewDomainFilter([]string{"bar.com"}),
provider.NewZoneIDFilter([]string{""}),
false,
false,
true,
5000,
"")
Expand Down Expand Up @@ -1526,7 +1530,7 @@ func TestCustomTTLWithEnabledProxyNotChanged(t *testing.T) {
func TestCloudFlareProvider_Region(t *testing.T) {
_ = os.Setenv("CF_API_TOKEN", "abc123def")
_ = os.Setenv("CF_API_EMAIL", "[email protected]")
provider, err := NewCloudFlareProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.ZoneIDFilter{}, true, false, 50, "us")
provider, err := NewCloudFlareProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.ZoneIDFilter{}, true, false, false, 50, "us")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1557,7 +1561,7 @@ func TestCloudFlareProvider_updateDataLocalizationRegionalHostnameParams(t *test
func TestCloudFlareProvider_newCloudFlareChange(t *testing.T) {
_ = os.Setenv("CF_API_KEY", "xxxxxxxxxxxxxxxxx")
_ = os.Setenv("CF_API_EMAIL", "[email protected]")
provider, err := NewCloudFlareProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.ZoneIDFilter{}, true, false, 50, "us")
provider, err := NewCloudFlareProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.ZoneIDFilter{}, true, false, false, 50, "us")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -1705,7 +1709,8 @@ func TestCloudflareZoneRecordsFail(t *testing.T) {
customHostnames: map[string]map[string]cloudflare.CustomHostname{},
}
failingProvider := &CloudFlareProvider{
Client: client,
Client: client,
customHostnames: true,
}
ctx := context.Background()

Expand All @@ -1718,7 +1723,8 @@ func TestCloudflareZoneRecordsFail(t *testing.T) {
func TestCloudflareDNSRecordsOperationsFail(t *testing.T) {
client := NewMockCloudFlareClient()
provider := &CloudFlareProvider{
Client: client,
Client: client,
customHostnames: true,
}
ctx := context.Background()
domainFilter := endpoint.NewDomainFilter([]string{"bar.com"})
Expand Down Expand Up @@ -1831,7 +1837,8 @@ func TestCloudflareDNSRecordsOperationsFail(t *testing.T) {
func TestCloudflareCustomHostnameOperations(t *testing.T) {
client := NewMockCloudFlareClient()
provider := &CloudFlareProvider{
Client: client,
Client: client,
customHostnames: true,
}
ctx := context.Background()
domainFilter := endpoint.NewDomainFilter([]string{"bar.com"})
Expand Down Expand Up @@ -2172,7 +2179,8 @@ func TestCloudflareCustomHostnameOperations(t *testing.T) {
func TestCloudflareCustomHostnameNotFoundOnRecordDeletion(t *testing.T) {
client := NewMockCloudFlareClient()
provider := &CloudFlareProvider{
Client: client,
Client: client,
customHostnames: true,
}
ctx := context.Background()
zoneID := "001"
Expand Down

0 comments on commit 3dbf015

Please sign in to comment.