Skip to content

Commit 5b06dac

Browse files
committed
Merge branch 'main' into tlim_testexplicit
2 parents b1d9b87 + d0a0a02 commit 5b06dac

File tree

11 files changed

+175
-7
lines changed

11 files changed

+175
-7
lines changed

commands/getZones.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -311,19 +311,27 @@ func GetZone(args GetZoneArgs) error {
311311

312312
case "tsv":
313313
for _, rec := range recs {
314-
cfproxy := ""
314+
cfmeta := ""
315315
if cp, ok := rec.Metadata["cloudflare_proxy"]; ok {
316316
if cp == "true" {
317-
cfproxy = "\tcloudflare_proxy=true"
317+
cfmeta += ",cloudflare_proxy=true"
318318
}
319319
}
320+
if cf, ok := rec.Metadata["cloudflare_cname_flatten"]; ok {
321+
if cf == "on" {
322+
cfmeta += ",cloudflare_cname_flatten=on"
323+
}
324+
}
325+
if cfmeta != "" {
326+
cfmeta = "\t" + cfmeta[1:] // Remove leading comma, add tab
327+
}
320328

321329
ty := rec.Type
322330
if rec.Type == "UNKNOWN" {
323331
ty = rec.UnknownTypeName
324332
}
325333
fmt.Fprintf(w, "%s\t%s\t%d\tIN\t%s\t%s%s\n",
326-
rec.NameFQDN, rec.Name, rec.TTL, ty, rec.GetTargetCombinedFunc(nil), cfproxy)
334+
rec.NameFQDN, rec.Name, rec.TTL, ty, rec.GetTargetCombinedFunc(nil), cfmeta)
327335
}
328336

329337
default:
@@ -360,6 +368,13 @@ func formatDsl(rec *models.RecordConfig, defaultTTL uint32) string {
360368
}
361369
}
362370

371+
cfflatten := ""
372+
if cf, ok := rec.Metadata["cloudflare_cname_flatten"]; ok {
373+
if cf == "on" {
374+
cfflatten = ", CF_CNAME_FLATTEN_ON"
375+
}
376+
}
377+
363378
switch rec.Type { // #rtype_variations
364379
case "CAA":
365380
return makeCaa(rec, ttlop)
@@ -412,7 +427,7 @@ func formatDsl(rec *models.RecordConfig, defaultTTL uint32) string {
412427
target = `"` + target + `"`
413428
}
414429

415-
return fmt.Sprintf(`%s("%s", %s%s%s)`, rec.Type, rec.Name, target, cfproxy, ttlop)
430+
return fmt.Sprintf(`%s("%s", %s%s%s%s)`, rec.Type, rec.Name, target, cfproxy, cfflatten, ttlop)
416431
}
417432

418433
func makeCaa(rec *models.RecordConfig, ttlop string) string {

commands/types/dnscontrol.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ declare const CF_PROXY_OFF: RecordModifier;
138138
declare const CF_PROXY_ON: RecordModifier;
139139
/** Proxy+Railgun enabled. */
140140
declare const CF_PROXY_FULL: RecordModifier;
141+
/** Per-record CNAME flattening disabled (default) */
142+
declare const CF_CNAME_FLATTEN_OFF: RecordModifier;
143+
/** Per-record CNAME flattening enabled (requires Cloudflare paid plan) */
144+
declare const CF_CNAME_FLATTEN_ON: RecordModifier;
141145

142146
/** Proxy default off for entire domain (the default) */
143147
declare const CF_PROXY_DEFAULT_OFF: DomainModifier;

commands/types/others.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ declare const CF_PROXY_OFF: RecordModifier;
2727
declare const CF_PROXY_ON: RecordModifier;
2828
/** Proxy+Railgun enabled. */
2929
declare const CF_PROXY_FULL: RecordModifier;
30+
/** Per-record CNAME flattening disabled (default) */
31+
declare const CF_CNAME_FLATTEN_OFF: RecordModifier;
32+
/** Per-record CNAME flattening enabled (requires Cloudflare paid plan) */
33+
declare const CF_CNAME_FLATTEN_ON: RecordModifier;
3034

3135
/** Proxy default off for entire domain (the default) */
3236
declare const CF_PROXY_DEFAULT_OFF: DomainModifier;

documentation/provider/cloudflareapi.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ This provider accepts some optional metadata:
103103

104104
Record level metadata available:
105105
* `cloudflare_proxy` ("on", "off", or "full")
106+
* `cloudflare_cname_flatten` ("on" or "off") - Per-record CNAME flattening (paid plans only)
106107

107108
Domain level metadata available:
108109
* `cloudflare_proxy_default` ("on", "off", or "full")
@@ -141,6 +142,8 @@ the following aliases are *pre-defined*:
141142
var CF_PROXY_OFF = {"cloudflare_proxy": "off"}; // Proxy disabled.
142143
var CF_PROXY_ON = {"cloudflare_proxy": "on"}; // Proxy enabled.
143144
var CF_PROXY_FULL = {"cloudflare_proxy": "full"}; // Proxy+Railgun enabled.
145+
var CF_CNAME_FLATTEN_OFF = {"cloudflare_cname_flatten": "off"}; // CNAME flattening disabled (default).
146+
var CF_CNAME_FLATTEN_ON = {"cloudflare_cname_flatten": "on"}; // CNAME flattening enabled (paid plans only).
144147
// Per-domain meta settings:
145148
// Proxy default off for entire domain (the default):
146149
var CF_PROXY_DEFAULT_OFF = {"cloudflare_proxy_default": "off"};
@@ -201,6 +204,33 @@ D("example2.tld", REG_NONE, DnsProvider(DSP_CLOUDFLARE),
201204
If a domain does not exist in your Cloudflare account, DNSControl
202205
will automatically add it when `dnscontrol push` is executed.
203206

207+
## CNAME flattening
208+
209+
Cloudflare supports [CNAME flattening](https://developers.cloudflare.com/dns/cname-flattening/), which resolves CNAME targets to their IP addresses at the edge. This can be enabled zone-wide (for zones on paid Cloudflare plans) or per-record.
210+
211+
DNSControl supports per-record CNAME flattening using the `CF_CNAME_FLATTEN_ON` modifier:
212+
213+
{% code title="dnsconfig.js" %}
214+
```javascript
215+
var REG_NONE = NewRegistrar("none");
216+
var DSP_CLOUDFLARE = NewDnsProvider("cloudflare");
217+
218+
D("example.com", REG_NONE, DnsProvider(DSP_CLOUDFLARE),
219+
// Enable CNAME flattening for this record
220+
CNAME("cdn", "cdn.provider.com.", CF_CNAME_FLATTEN_ON),
221+
222+
// CNAME flattening disabled (default behavior)
223+
CNAME("www", "www.example.com."),
224+
CNAME("api", "api.example.com.", CF_CNAME_FLATTEN_OFF),
225+
);
226+
```
227+
{% endcode %}
228+
229+
{% hint style="warning" %}
230+
**Paid plans only:** Per-record CNAME flattening requires a Cloudflare paid subscription (Pro, Business, or Enterprise). Free plans do not support this feature. If you attempt to enable CNAME flattening on a free zone, the Cloudflare API will return an error.
231+
{% endhint %}
232+
233+
For more information, see [Cloudflare's CNAME flattening documentation](https://developers.cloudflare.com/dns/cname-flattening/).
204234

205235
## Old-style vs new-style redirects
206236

@@ -421,6 +451,18 @@ go test -v -verbose -profile CLOUDFLAREAPI -cfworkers=false
421451

422452
When `-cfworkers=false` is set, tests related to Workers are skipped. The Account ID is not required.
423453

454+
### CNAME flattening tests
455+
456+
Tests for per-record CNAME flattening (`CF_CNAME_FLATTEN_ON`/`CF_CNAME_FLATTEN_OFF`) are disabled by default
457+
because they require a paid Cloudflare plan. To enable these tests, use the `-cfflatten=true` flag:
458+
459+
```shell
460+
cd integrationTest
461+
go test -v -verbose -profile CLOUDFLAREAPI -cfflatten=true
462+
```
463+
464+
If you run with `-cfflatten=true` on a free zone, the tests will fail with an error from the Cloudflare API.
465+
424466
## Cloudflare special TTLs
425467

426468
Cloudflare plays tricks with TTLs. Cloudflare uses "1" to mean "auto-ttl";

integrationTest/helpers_integration_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ func CfProxyOn() *TestCase { return tc("proxyon", cfProxyA("prxy", "174.136.107
4141
func CfCProxyOff() *TestCase { return tc("cproxyoff", cfProxyCNAME("cproxy", "example.com.", "off")) }
4242
func CfCProxyOn() *TestCase { return tc("cproxyon", cfProxyCNAME("cproxy", "example.com.", "on")) }
4343

44+
// Helper constants/funcs for the CLOUDFLARE CNAME flattening testing:
45+
46+
// CNAME flattening off/on (requires paid plan)
47+
func CfFlattenOff() *TestCase {
48+
return tc("flattenoff", cfFlattenCNAME("cflatten", "example.com.", "off"))
49+
}
50+
func CfFlattenOn() *TestCase {
51+
return tc("flattenon", cfFlattenCNAME("cflatten", "example.com.", "on"))
52+
}
53+
4454
func getDomainConfigWithNameservers(t *testing.T, prv providers.DNSServiceProvider, domainName string) *models.DomainConfig {
4555
dc := &models.DomainConfig{
4656
Name: domainName,
@@ -340,6 +350,13 @@ func cfProxyCNAME(name, target, status string) *models.RecordConfig {
340350
return r
341351
}
342352

353+
func cfFlattenCNAME(name, target, status string) *models.RecordConfig {
354+
r := cname(name, target)
355+
r.Metadata = make(map[string]string)
356+
r.Metadata["cloudflare_cname_flatten"] = status
357+
return r
358+
}
359+
343360
func cfSingleRedirectEnabled() bool {
344361
return (*enableCFRedirectMode)
345362
}

integrationTest/helpers_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var (
1919
profileFlag = flag.String("profile", "", "Entry in profiles.json to use (if empty, copied from -provider)")
2020
enableCFWorkers = flag.Bool("cfworkers", true, "enable CF worker tests (default true)")
2121
enableCFRedirectMode = flag.Bool("cfredirect", false, "enable CF SingleRedirect tests (default false)")
22+
enableCFFlatten = flag.Bool("cfflatten", false, "enable CF CNAME flattening tests (requires paid plan, default false)")
2223
)
2324

2425
func init() {

integrationTest/integration_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1227,6 +1227,27 @@ func makeTests() []*TestGroup {
12271227
CfCProxyOn(), CfCProxyOff(),
12281228
),
12291229

1230+
// CLOUDFLAREAPI: CNAME FLATTENING (requires paid plan)
1231+
1232+
testgroup("CF_CNAME_FLATTEN create",
1233+
only("CLOUDFLAREAPI"),
1234+
alltrue(*enableCFFlatten),
1235+
CfFlattenOff(), tcEmptyZone(),
1236+
CfFlattenOn(), tcEmptyZone(),
1237+
),
1238+
1239+
testgroup("CF_CNAME_FLATTEN off to on",
1240+
only("CLOUDFLAREAPI"),
1241+
alltrue(*enableCFFlatten),
1242+
CfFlattenOff(), CfFlattenOn(),
1243+
),
1244+
1245+
testgroup("CF_CNAME_FLATTEN on to off",
1246+
only("CLOUDFLAREAPI"),
1247+
alltrue(*enableCFFlatten),
1248+
CfFlattenOn(), CfFlattenOff(),
1249+
),
1250+
12301251
testgroup("CF_WORKER_ROUTE",
12311252
only("CLOUDFLAREAPI"),
12321253
alltrue(*enableCFWorkers),

pkg/js/helpers.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,8 @@ function num2dot(num) {
14521452
var CF_PROXY_OFF = { cloudflare_proxy: 'off' }; // Proxy disabled.
14531453
var CF_PROXY_ON = { cloudflare_proxy: 'on' }; // Proxy enabled.
14541454
var CF_PROXY_FULL = { cloudflare_proxy: 'full' }; // Proxy+Railgun enabled.
1455+
var CF_CNAME_FLATTEN_OFF = { cloudflare_cname_flatten: 'off' }; // CNAME flattening disabled (default).
1456+
var CF_CNAME_FLATTEN_ON = { cloudflare_cname_flatten: 'on' }; // CNAME flattening enabled (paid plans only).
14551457
// Per-domain meta settings:
14561458
// Proxy default off for entire domain (the default):
14571459
var CF_PROXY_DEFAULT_OFF = { cloudflare_proxy_default: 'off' };

pkg/prettyzone/prettyzone.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,17 @@ func (z *ZoneGenData) generateZoneFileHelper(w io.Writer) error {
137137
comment := ""
138138
if cp, ok := rr.Metadata["cloudflare_proxy"]; ok {
139139
if cp == "true" {
140-
comment = " ; CF_PROXY_ON"
140+
comment += " CF_PROXY_ON"
141141
}
142142
}
143+
if cf, ok := rr.Metadata["cloudflare_cname_flatten"]; ok {
144+
if cf == "on" {
145+
comment += " CF_CNAME_FLATTEN_ON"
146+
}
147+
}
148+
if comment != "" {
149+
comment = " ;" + comment
150+
}
143151

144152
fmt.Fprintf(w, "%s%s%s\n",
145153
prefix, FormatLine([]int{10, 5, 2, 5, 0}, []string{name, ttl, "IN", typeStr, target}), comment)

providers/cloudflare/cloudflareProvider.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ func (c *cloudflareProvider) GetZoneRecordsCorrections(dc *models.DomainConfig,
257257
}
258258

259259
func genComparable(rec *models.RecordConfig) string {
260+
var parts []string
260261
if rec.Type == "A" || rec.Type == "AAAA" || rec.Type == "CNAME" {
261262
proxy := rec.Metadata[metaProxy]
262263
if proxy != "" {
@@ -266,10 +267,18 @@ func genComparable(rec *models.RecordConfig) string {
266267
if proxy == "off" {
267268
proxy = "false"
268269
}
269-
return "proxy=" + proxy
270+
parts = append(parts, "proxy="+proxy)
270271
}
271272
}
272-
return ""
273+
if rec.Type == "CNAME" {
274+
flatten := rec.Metadata[metaCNAMEFlatten]
275+
if flatten == "on" {
276+
parts = append(parts, "flatten=true")
277+
} else {
278+
parts = append(parts, "flatten=false")
279+
}
280+
}
281+
return strings.Join(parts, ",")
273282
}
274283

275284
func (c *cloudflareProvider) mkCreateCorrection(newrec *models.RecordConfig, domainID, msg string) []*models.Correction {
@@ -408,6 +417,7 @@ const (
408417
metaProxyDefault = metaProxy + "_default"
409418
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
410419
metaUniversalSSL = "cloudflare_universalssl"
420+
metaCNAMEFlatten = "cloudflare_cname_flatten"
411421
)
412422

413423
func checkProxyVal(v string) (string, error) {
@@ -418,6 +428,14 @@ func checkProxyVal(v string) (string, error) {
418428
return v, nil
419429
}
420430

431+
func checkCNAMEFlattenVal(v string) (string, error) {
432+
v = strings.ToLower(v)
433+
if v != "on" && v != "off" {
434+
return "", fmt.Errorf("bad metadata value for cloudflare_cname_flatten: '%s'. Use on/off", v)
435+
}
436+
return v, nil
437+
}
438+
421439
func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error {
422440

423441
for _, rec := range dc.Records {
@@ -483,6 +501,18 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error {
483501
}
484502
}
485503

504+
// Validate CNAME flattening metadata (only valid on CNAME records)
505+
if val := rec.Metadata[metaCNAMEFlatten]; val != "" {
506+
if rec.Type != "CNAME" {
507+
return fmt.Errorf("cloudflare_cname_flatten set on %v record: %#v (only valid on CNAME records)", rec.Type, rec.GetLabel())
508+
}
509+
val, err := checkCNAMEFlattenVal(val)
510+
if err != nil {
511+
return err
512+
}
513+
rec.Metadata[metaCNAMEFlatten] = val
514+
}
515+
486516
if rec.Type == "CLOUDFLAREAPI_SINGLE_REDIRECT" {
487517
// SINGLEREDIRECT record types. Verify they are enabled.
488518
if !c.manageSingleRedirects {
@@ -784,6 +814,15 @@ func (c *cloudflareProvider) nativeToRecord(domain string, cr cloudflare.DNSReco
784814
}
785815
}
786816

817+
// Check for CNAME flattening setting
818+
if cr.Type == "CNAME" {
819+
if cr.Settings.FlattenCNAME != nil && *cr.Settings.FlattenCNAME {
820+
rc.Metadata[metaCNAMEFlatten] = "on"
821+
} else {
822+
rc.Metadata[metaCNAMEFlatten] = "off"
823+
}
824+
}
825+
787826
switch rType := cr.Type; rType { // #rtype_variations
788827
case "MX":
789828
if err := rc.SetTargetMX(*cr.Priority, cr.Content); err != nil {

0 commit comments

Comments
 (0)