From 086905241969ebd7cf28c1a43157b81291da4eb2 Mon Sep 17 00:00:00 2001 From: Tom Limoncelli Date: Thu, 18 Jul 2024 12:10:46 -0400 Subject: [PATCH] CLOUDFLAREAPI: CF_SINGLE_REDIRECT improvements: fix bugs, log translated redirects (#3051) --- commands/types/dnscontrol.d.ts | 14 +- .../domain-modifiers/CF_REDIRECT.md | 7 + .../domain-modifiers/CF_TEMP_REDIRECT.md | 7 + documentation/provider/cloudflareapi.md | 56 +++++- integrationTest/integration_test.go | 3 +- models/record.go | 5 +- pkg/rtypes/postprocess.go | 1 + providers/cloudflare/cloudflareProvider.go | 160 +++++++----------- providers/cloudflare/rest.go | 62 +++---- .../cfsingleredirect/cfsingleredirect.go | 15 +- .../rtypes/cfsingleredirect/convert.go | 46 ++--- .../rtypes/cfsingleredirect/convert_test.go | 9 - .../rtypes/cfsingleredirect/from.go | 122 +++++++++++++ .../rtypes/cfsingleredirect/native.go | 19 --- 14 files changed, 309 insertions(+), 217 deletions(-) create mode 100644 providers/cloudflare/rtypes/cfsingleredirect/from.go delete mode 100644 providers/cloudflare/rtypes/cfsingleredirect/native.go diff --git a/commands/types/dnscontrol.d.ts b/commands/types/dnscontrol.d.ts index c3b6e70843..dbaa705ba3 100644 --- a/commands/types/dnscontrol.d.ts +++ b/commands/types/dnscontrol.d.ts @@ -472,6 +472,11 @@ declare function CAA(name: string, tag: "issue" | "issuewild" | "iodef", value: declare function CAA_BUILDER(opts: { label?: string; iodef: string; iodef_critical?: boolean; issue: string[]; issue_critical?: boolean; issuewild: string[]; issuewild_critical?: boolean; ttl?: Duration }): DomainModifier; /** + * WARNING: Cloudflare is removing this feature and replacing it with a new + * feature called "Dynamic Single Redirect". DNSControl will automatically + * generate "Dynamic Single Redirects" for a limited number of use cases. See + * [`CLOUDFLAREAPI`](../provider/cloudflareapi.md) for details. + * * `CF_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page Rules) to * generate a HTTP 301 permanent redirect. * @@ -533,6 +538,11 @@ declare function CF_REDIRECT(source: string, destination: string, ...modifiers: declare function CF_SINGLE_REDIRECT(name: string, code: number, when: string, then: string, ...modifiers: RecordModifier[]): DomainModifier; /** + * WARNING: Cloudflare is removing this feature and replacing it with a new + * feature called "Dynamic Single Redirect". DNSControl will automatically + * generate "Dynamic Single Redirects" for a limited number of use cases. See + * [`CLOUDFLAREAPI`](../provider/cloudflareapi.md) for details. + * * `CF_TEMP_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page * Rules) to generate a HTTP 302 temporary redirect. * @@ -1810,7 +1820,7 @@ declare function LOC_BUILDER_STR(opts: { label?: string; str: string; alt?: numb * * ```javascript * D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), - * M365_BUILDER({ + * M365_BUILDER("example.com", { * initialDomain: "example.onmicrosoft.com", * }), * END); @@ -1822,7 +1832,7 @@ declare function LOC_BUILDER_STR(opts: { label?: string; str: string; alt?: numb * * ```javascript * D("example.com", REG_MY_PROVIDER, DnsProvider(DSP_MY_PROVIDER), - * M365_BUILDER({ + * M365_BUILDER("example.com", { * label: "test", * mx: false, * autodiscover: false, diff --git a/documentation/language-reference/domain-modifiers/CF_REDIRECT.md b/documentation/language-reference/domain-modifiers/CF_REDIRECT.md index f530aafbb7..95aacf5e35 100644 --- a/documentation/language-reference/domain-modifiers/CF_REDIRECT.md +++ b/documentation/language-reference/domain-modifiers/CF_REDIRECT.md @@ -11,6 +11,13 @@ parameter_types: "modifiers...": RecordModifier[] --- +{% hint style="warning" %} +WARNING: Cloudflare is removing this feature and replacing it with a new +feature called "Dynamic Single Redirect". DNSControl will automatically +generate "Dynamic Single Redirects" for a limited number of use cases. See +[`CLOUDFLAREAPI`](../provider/cloudflareapi.md) for details. +{% endhint %} + `CF_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page Rules) to generate a HTTP 301 permanent redirect. diff --git a/documentation/language-reference/domain-modifiers/CF_TEMP_REDIRECT.md b/documentation/language-reference/domain-modifiers/CF_TEMP_REDIRECT.md index 6578ba2364..6b97fc454f 100644 --- a/documentation/language-reference/domain-modifiers/CF_TEMP_REDIRECT.md +++ b/documentation/language-reference/domain-modifiers/CF_TEMP_REDIRECT.md @@ -11,6 +11,13 @@ parameter_types: "modifiers...": RecordModifier[] --- +{% hint style="warning" %} +**WARNING**: Cloudflare is removing this feature and replacing it with a new +feature called "Dynamic Single Redirect". DNSControl will automatically +generate "Dynamic Single Redirects" for a limited number of use cases. See +[`CLOUDFLAREAPI`](../provider/cloudflareapi.md) for details. +{% endhint %} + `CF_TEMP_REDIRECT` uses Cloudflare-specific features ("Forwarding URL" Page Rules) to generate a HTTP 302 temporary redirect. diff --git a/documentation/provider/cloudflareapi.md b/documentation/provider/cloudflareapi.md index 3f84b1210d..f9bd347a31 100644 --- a/documentation/provider/cloudflareapi.md +++ b/documentation/provider/cloudflareapi.md @@ -211,7 +211,8 @@ Enable it using: ```javascript var DSP_CLOUDFLARE = NewDnsProvider("cloudflare", { - "manage_redirects": true + "manage_redirects": true, + "transcode_log": "transcode.log", }); ``` @@ -231,8 +232,7 @@ New-style redirects ("Single Redirect Rules") are a new feature of DNSControl as of v4.12.0 and may have bugs. Please test carefully. {% endhint %} - -Conversion mode: +### Conversion mode: DNSControl can convert from old-style redirects (Page Rules) to new-style redirect (Single Redirects). To enable this mode, set both `manage_redirects` @@ -268,7 +268,7 @@ via the CloudFlare control panel or wait for Cloudflare to remove support for th {% hint style="warning" %} Cloudflare's announcement says that they will convert old-style redirects (Page Rules) to new-style -redirect (Single Redirects) but they do not give a date for when this will happen. DNSControl +redirect (Single Redirects) but they do not give an exact date for when this will happen. DNSControl will probably see these new redirects as foreign and delete them. Therefore it is probably safer to do the conversion ahead of them. @@ -279,6 +279,54 @@ than DNSControl's. However there's no way for DNSControl to manage them since t If you have suggestions on how to handle this better please file a bug. {% endhint %} +### Converting to CF_SINGLE_REDIRECT permanently + +DNSControl will help convert `CF_REDIRECT`/`CF_TEMP_REDIRECT` statements into +`CF_SINGLE_REDIRECT` statements. You might choose to do this if you do not want +to rely on the automatic translation, or if you want to edit the results of the +translation. + +DNSControl will generate a file of the translated statements if you specify +a filename using the `transcode_log` meta option. + +```javascript +var DSP_CLOUDFLARE = NewDnsProvider("cloudflare", { + "manage_single_redirects": true, + "transcode_log": "transcode.log", +}); +``` + +After running `dnscontrol preview` the contents will look something like this: + +{% code title="transcode.log" %} +```text +D("example.com", ... + CF_SINGLE_REDIRECT("1,302,https://example.com/*,https://replacement.example.com/$1", + 302, + 'http.host eq "example.com"', + 'concat("https://replacement.example.com", http.request.uri.path)' + ), + CF_SINGLE_REDIRECT("2,302,https://img.example.com/*,https://replacement.example.com/$1", + 302, + 'http.host eq "img.example.com"', + 'concat("https://replacement.example.com", http.request.uri.path)' + ), + CF_SINGLE_REDIRECT("3,302,https://i.example.com/*,https://replacement.example.com/$1", + 302, + 'http.host eq "i.example.com"', + 'concat("https://replacement.example.com", http.request.uri.path)' + ), +D("otherdomain.com", ... + CF_SINGLE_REDIRECT("1,301,https://one.otherdomain.com/,https://www.google.com/", + 301, + 'http.host eq "one.otherdomain.com" and http.request.uri.path eq "/"', + 'concat("https://www.google.com/", "")' + ), +``` +{% endcode %} + +Copying the statements to the proper place in `dnsconfig.js` is manual. + ## Redirects The Cloudflare provider can manage "Forwarding URL" Page Rules (redirects) for your domains. Simply use the `CF_REDIRECT` and `CF_TEMP_REDIRECT` functions to make redirects: diff --git a/integrationTest/integration_test.go b/integrationTest/integration_test.go index 02ea3129be..36d3cacadb 100644 --- a/integrationTest/integration_test.go +++ b/integrationTest/integration_test.go @@ -503,7 +503,7 @@ func cfSingleRedirectEnabled() bool { } func cfSingleRedirect(name string, code any, when, then string) *models.RecordConfig { - r := makeRec("@", name, "CLOUDFLAREAPI_SINGLE_REDIRECT") + r := makeRec("@", name, cfsingleredirect.SINGLEREDIRECT) err := cfsingleredirect.FromRaw(r, []any{name, code, when, then}) if err != nil { panic("Should not happen... cfSingleRedirect") @@ -1947,6 +1947,7 @@ func makeTests() []*TestGroup { tc("changecode", cfSingleRedirect(`name1`, `302`, `http.host eq "cnn.slackoverflow.com"`, `concat("https://www.cnn.com", http.request.uri.path)`)), tc("changewhen", cfSingleRedirect(`name1`, `302`, `http.host eq "msnbc.slackoverflow.com"`, `concat("https://www.cnn.com", http.request.uri.path)`)), tc("changethen", cfSingleRedirect(`name1`, `302`, `http.host eq "msnbc.slackoverflow.com"`, `concat("https://www.msnbc.com", http.request.uri.path)`)), + tc("changename", cfSingleRedirect(`name1bis`, `302`, `http.host eq "msnbc.slackoverflow.com"`, `concat("https://www.msnbc.com", http.request.uri.path)`)), ), // CLOUDFLAREAPI: PROXY diff --git a/models/record.go b/models/record.go index efe98dcbd5..a00c92654a 100644 --- a/models/record.go +++ b/models/record.go @@ -154,17 +154,18 @@ type CloudflareSingleRedirectConfig struct { // Code uint16 `json:"code,omitempty"` // 301 or 302 // PR == PageRule - PRDisplay string `json:"pr_display,omitempty"` // How is this displayed to the user PRWhen string `json:"pr_when,omitempty"` PRThen string `json:"pr_then,omitempty"` PRPriority int `json:"pr_priority,omitempty"` // Really an identifier for the rule. + PRDisplay string `json:"pr_display,omitempty"` // How is this displayed to the user (SetTarget) for CF_REDIRECT/CF_TEMP_REDIRECT // // SR == SingleRedirect - SRDisplay string `json:"sr_display,omitempty"` // How is this displayed to the user + SRName string `json:"sr_name,omitempty"` // How is this displayed to the user SRWhen string `json:"sr_when,omitempty"` SRThen string `json:"sr_then,omitempty"` SRRRulesetID string `json:"sr_rulesetid,omitempty"` SRRRulesetRuleID string `json:"sr_rulesetruleid,omitempty"` + SRDisplay string `json:"sr_display,omitempty"` // How is this displayed to the user (SetTarget) for CF_SINGLE_REDIRECT } // MarshalJSON marshals RecordConfig. diff --git a/pkg/rtypes/postprocess.go b/pkg/rtypes/postprocess.go index 5f468b9a80..91a14ddad7 100644 --- a/pkg/rtypes/postprocess.go +++ b/pkg/rtypes/postprocess.go @@ -38,6 +38,7 @@ func PostProcess(domains []*models.DomainConfig) error { case "CLOUDFLAREAPI_SINGLE_REDIRECT": err = cfsingleredirect.FromRaw(rec, rawRec.Args) + rec.SetLabel("@", dc.Name) default: err = fmt.Errorf("unknown rawrec type=%q", rawRec.Type) diff --git a/providers/cloudflare/cloudflareProvider.go b/providers/cloudflare/cloudflareProvider.go index bb79575cab..0188aa36c4 100644 --- a/providers/cloudflare/cloudflareProvider.go +++ b/providers/cloudflare/cloudflareProvider.go @@ -87,23 +87,17 @@ type cloudflareProvider struct { cfClient *cloudflare.API // manageSingleRedirects bool // New "Single Redirects"-style redirects. + // + // Used by + tcLogFilename string // Transcode Log file name + tcLogFh *os.File // Transcode Log file handle + tcZone string // Transcode Current zone sync.Mutex // Protects all access to the following fields: domainIndex map[string]string // Cache of zone name to zone ID. nameservers map[string][]string // Cache of zone name to list of nameservers. } -// TODO(dlemenkov): remove this function after deleting all commented code referecing it -//func labelMatches(label string, matches []string) bool { -// printer.Debugf("DEBUG: labelMatches(%#v, %#v)\n", label, matches) -// for _, tst := range matches { -// if label == tst { -// return true -// } -// } -// return false -//} - // GetNameservers returns the nameservers for a domain. func (c *cloudflareProvider) GetNameservers(domain string) ([]*models.Nameserver, error) { @@ -162,17 +156,6 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin } } - // // FIXME(tlim) Why is this needed??? - // // I don't know. Let's comment it out and see if anything breaks. - // for i := len(records) - 1; i >= 0; i-- { - // rec := records[i] - // // Delete ignore labels - // if labelMatches(dnsutil.TrimDomainName(rec.Original.(cloudflare.DNSRecord).Name, dc.Name), c.ignoredLabels) { - // printer.Debugf("ignored_label: %s\n", rec.Original.(cloudflare.DNSRecord).Name) - // records = append(records[:i], records[i+1:]...) - // } - // } - if c.manageRedirects { // if old prs, err := c.getPageRules(domainID, domain) if err != nil { @@ -183,14 +166,11 @@ func (c *cloudflareProvider) GetZoneRecords(domain string, meta map[string]strin if c.manageSingleRedirects { // if new xor old // Download the list of Single Redirects. - // For each one, generate a CLOUDFLAREAPI_SINGLE_REDIRECT record - // Append these records to `records` + // For each one, generate a SINGLEREDIRECT record prs, err := c.getSingleRedirects(domainID, domain) if err != nil { return nil, err } - //printer.Printf("DEBUG: Single Redirects") - //fmt.Fprintf(os.Stdout, "DEBUG: Single Redirects") records = append(records, prs...) } @@ -284,8 +264,6 @@ func (c *cloudflareProvider) GetZoneRecordsCorrections(dc *models.DomainConfig, case diff2.DELETE: deleteRec := inst.Old[0] deleteRecType := deleteRec.Type - //deleteRecOrig := deleteRec.Original - //corrs = c.mkDeleteCorrection(deleteRecType, deleteRecOrig, domainID, msg) corrs = c.mkDeleteCorrection(deleteRecType, deleteRec, domainID, msg) // DS records must always have a corresponding NS record. // Therefore, we remove DS records before any NS records. @@ -344,10 +322,7 @@ func (c *cloudflareProvider) mkCreateCorrection(newrec *models.RecordConfig, dom Msg: msg, F: func() error { return c.createWorkerRoute(domainID, newrec.GetTargetField()) }, }} - case "CLOUDFLAREAPI_SINGLE_REDIRECT": - //fmt.Printf("DEBUG: mkCreateSingleRedir: newrec=%+v\n", *newrec) - //fmt.Printf("DEBUG: mkCreateSingleRedir: crn=%+v\n", (*newrec).CloudflareRedirect) - //fmt.Printf("DEBUG: mkCreateSingleRedir: cr=%+v\n", (*newrec).CloudflareRedirect) + case cfsingleredirect.SINGLEREDIRECT: return []*models.Correction{{ Msg: msg, F: func() error { @@ -367,8 +342,7 @@ func (c *cloudflareProvider) mkChangeCorrection(oldrec, newrec *models.RecordCon idTxt = oldrec.Original.(cloudflare.PageRule).ID case "WORKER_ROUTE": idTxt = oldrec.Original.(cloudflare.WorkerRoute).ID - case "CLOUDFLAREAPI_SINGLE_REDIRECT": - //idTxt = oldrec.Original.(cloudflare.RulesetRule).ID + case cfsingleredirect.SINGLEREDIRECT: idTxt = oldrec.CloudflareRedirect.SRRRulesetID default: idTxt = oldrec.Original.(cloudflare.DNSRecord).ID @@ -383,7 +357,7 @@ func (c *cloudflareProvider) mkChangeCorrection(oldrec, newrec *models.RecordCon return c.updatePageRule(idTxt, domainID, *newrec.CloudflareRedirect) }, }} - case "CLOUDFLAREAPI_SINGLE_REDIRECT": + case cfsingleredirect.SINGLEREDIRECT: return []*models.Correction{{ Msg: msg, F: func() error { @@ -416,7 +390,7 @@ func (c *cloudflareProvider) mkDeleteCorrection(recType string, origRec *models. idTxt = origRec.Original.(cloudflare.PageRule).ID case "WORKER_ROUTE": idTxt = origRec.Original.(cloudflare.WorkerRoute).ID - case "CLOUDFLAREAPI_SINGLE_REDIRECT": + case cfsingleredirect.SINGLEREDIRECT: idTxt = origRec.Original.(cloudflare.RulesetRule).ID default: idTxt = origRec.Original.(cloudflare.DNSRecord).ID @@ -431,12 +405,7 @@ func (c *cloudflareProvider) mkDeleteCorrection(recType string, origRec *models. return c.deletePageRule(origRec.Original.(cloudflare.PageRule).ID, domainID) case "WORKER_ROUTE": return c.deleteWorkerRoute(origRec.Original.(cloudflare.WorkerRoute).ID, domainID) - case "CLOUDFLAREAPI_SINGLE_REDIRECT": - //o := origRec.Original.(cloudflare.Ruleset) - //printer.Printf("DEBUG: DELETE %+v\n", o) - // printer.Printf("DEBUG: DELETE ID = %+v\n", o.ID) - // printer.Printf("DEBUG: DELETE ACTION %+v\n", o.ActionParameters) - // printer.Printf("DEBUG: DELETE FROMVALUE %+v\n", o.ActionParameters.FromValue) + case cfsingleredirect.SINGLEREDIRECT: return c.deleteSingleRedirects(domainID, *origRec.CloudflareRedirect) default: return c.deleteDNSRecord(origRec.Original.(cloudflare.DNSRecord), domainID) @@ -530,7 +499,7 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { // A and CNAMEs: Validate. If null, set to default. // else: Make sure it wasn't set. Set to default. // iterate backwards so first defined page rules have highest priority - currentPrPrio := 1 + prPriority := 0 for i := len(dc.Records) - 1; i >= 0; i-- { rec := dc.Records[i] if rec.Metadata == nil { @@ -564,9 +533,7 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { } } - // CF_REDIRECT record types. Encode target as - // $FROM,$TO,$PRIO,$CODE or build Cfsr struct for new-style - // (Single Redirect) versions. + // CF_REDIRECT record types: if rec.Type == "CF_REDIRECT" || rec.Type == "CF_TEMP_REDIRECT" { if !c.manageRedirects && !c.manageSingleRedirects { return fmt.Errorf("you must add 'manage_single_redirects: true' metadata to cloudflare provider to use CF_REDIRECT/CF_TEMP_REDIRECT records") @@ -576,65 +543,49 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { code = 302 } + part := strings.SplitN(rec.GetTargetField(), ",", 2) + prWhen, prThen := part[0], part[1] + prPriority++ + + // Convert this record to a PAGE_RULE. + cfsingleredirect.MakePageRule(rec, prPriority, code, prWhen, prThen) + rec.SetLabel("@", dc.Name) + if c.manageRedirects && !c.manageSingleRedirects { - // Old-Style only. Convert this record to PAGE_RULE. - //printer.Printf("DEBUG: prepro() target=%q\n", rec.GetTargetField()) - sr, err := cfsingleredirect.FromUserInput(rec.GetTargetField(), code, currentPrPrio) - if err != nil { - return err - } - fixPageRule(rec, sr) - currentPrPrio++ + // Old-Style only. No additional work needed. + } else if !c.manageRedirects && c.manageSingleRedirects { - // New-Style only. Convert this record to a CLOUDFLAREAPI_SINGLE_REDIRECT. - sr, err := cfsingleredirect.FromUserInput(rec.GetTargetField(), code, currentPrPrio) - if err != nil { - return err - } - err = fixSingleRedirect(rec, sr) - if err != nil { + // New-Style only. Convert PAGE_RULE to SINGLEREDIRECT. + cfsingleredirect.TranscodePRtoSR(rec) + if err := c.LogTranscode(dc.Name, rec.CloudflareRedirect); err != nil { return err } - } else { - // Both! Convert this record to PAGE_RULE and append an additional CLOUDFLAREAPI_SINGLE_REDIRECT. - target := rec.GetTargetField() + } else { + // Both old-style and new-style enabled! + // Retain the PAGE_RULE and append an additional SINGLEREDIRECT. // make a copy: newRec, err := rec.Copy() if err != nil { return err } - // The copy becomes the CF SingleRedirect - sr, err := cfsingleredirect.FromUserInput(target, code, currentPrPrio) - if err != nil { + cfsingleredirect.TranscodePRtoSR(rec) + if err := c.LogTranscode(dc.Name, rec.CloudflareRedirect); err != nil { return err } - err = fixSingleRedirect(newRec, sr) - if err != nil { - return err - } - // Append the copy to the end of the list. dc.Records = append(dc.Records, newRec) - // The original becomes the PAGE_RULE: - sr, err = cfsingleredirect.FromUserInput(target, code, currentPrPrio) - if err != nil { - return err - } - fixPageRule(rec, sr) - currentPrPrio++ - + // The original PAGE_RULE remains untouched. } - } else if rec.Type == "CLOUDFLAREAPI_SINGLE_REDIRECT" { - // CLOUDFLAREAPI_SINGLE_REDIRECT record types. + } else if rec.Type == cfsingleredirect.SINGLEREDIRECT { + // SINGLEREDIRECT record types. Verify they are enabled. if !c.manageSingleRedirects { return fmt.Errorf("you must add 'manage_single_redirects: true' metadata to cloudflare provider to use CF_SINGLE__REDIRECT records") } - // Nothing needs to be done. } else if rec.Type == "CF_WORKER_ROUTE" { // CF_WORKER_ROUTE record types. Encode target as $PATTERN,$SCRIPT @@ -672,20 +623,35 @@ func (c *cloudflareProvider) preprocessConfig(dc *models.DomainConfig) error { return nil } -func fixPageRule(rc *models.RecordConfig, sr *models.CloudflareSingleRedirectConfig) { - rc.Type = "PAGE_RULE" - rc.TTL = 1 - rc.SetTarget(sr.PRDisplay) - rc.CloudflareRedirect = sr -} +func (c *cloudflareProvider) LogTranscode(zone string, redirect *models.CloudflareSingleRedirectConfig) error { + // No filename? Don't log anything. + filename := c.tcLogFilename + if filename == "" { + return nil + } + + // File not opened already? Open it. + if c.tcLogFh == nil { + f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + c.tcLogFh = f + } + fh := c.tcLogFh -func fixSingleRedirect(rc *models.RecordConfig, sr *models.CloudflareSingleRedirectConfig) error { - rc.Type = "CLOUDFLAREAPI_SINGLE_REDIRECT" - rc.TTL = 1 - rc.SetTarget(sr.SRDisplay) - rc.CloudflareRedirect = sr + // Output "D(zone)" if needed. + var text string + if c.tcZone != zone { + text = fmt.Sprintf("D(%q, ...\n", zone) + } + c.tcZone = zone - err := cfsingleredirect.AddNewStyleFields(sr) + // Generate the new command and output. + text = text + fmt.Sprintf(" CF_SINGLE_REDIRECT(%q,\n %03d,\n '%s',\n '%s'\n ),\n", + redirect.SRName, redirect.Code, + redirect.SRWhen, redirect.SRThen) + _, err := fh.WriteString(text) return err } @@ -732,7 +698,8 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS ManageRedirects bool `json:"manage_redirects"` // Old-style PAGE_RULE-based redirects ManageWorkers bool `json:"manage_workers"` // - ManageSingleRedirects bool `json:"manage_single_redirects"` // New-style Dynamic "Single Redirects" + ManageSingleRedirects bool `json:"manage_single_redirects"` // New-style Dynamic "Single Redirects" + TranscodeLogFilename string `json:"transcode_log"` // Log the PAGE_RULE conversions. }{} err := json.Unmarshal([]byte(metadata), parsedMeta) if err != nil { @@ -740,6 +707,7 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS } api.manageSingleRedirects = parsedMeta.ManageSingleRedirects api.manageRedirects = parsedMeta.ManageRedirects + api.tcLogFilename = parsedMeta.TranscodeLogFilename api.manageWorkers = parsedMeta.ManageWorkers // ignored_labels: api.ignoredLabels = append(api.ignoredLabels, parsedMeta.IgnoredLabels...) diff --git a/providers/cloudflare/rest.go b/providers/cloudflare/rest.go index 70d3bea187..6280c2393b 100644 --- a/providers/cloudflare/rest.go +++ b/providers/cloudflare/rest.go @@ -286,38 +286,28 @@ func (c *cloudflareProvider) getSingleRedirects(id string, domain string) ([]*mo } return nil, fmt.Errorf("failed fetching redirect rule list cloudflare: %s (%T)", err, err) } - //var rulelist []cloudflare.RulesetRule - //rulelist = rules.Rules - //rulelist := rules.Rules - //printer.Printf("DEBUG: rules %+v\n", rules) recs := []*models.RecordConfig{} for _, pr := range rules.Rules { - //printer.Printf("DEBUG: %+v\n", pr) var thisPr = pr r := &models.RecordConfig{ - Type: "CLOUDFLAREAPI_SINGLE_REDIRECT", Original: thisPr, - TTL: 1, } - r.SetLabel("@", domain) // Extract the valuables from the rule, use it to make the sr: + srName := pr.Description srWhen := pr.Expression srThen := pr.ActionParameters.FromValue.TargetURL.Expression code := uint16(pr.ActionParameters.FromValue.StatusCode) - sr := cfsingleredirect.FromAPIData(srWhen, srThen, code) - //sr.SRRRuleList = rulelist - //printer.Printf("DEBUG: DESCRIPTION = %v\n", pr.Description) - sr.SRDisplay = pr.Description - // printer.Printf("DEBUG: PR = %+v\n", pr) - // printer.Printf("DEBUG: rules = %+v\n", rules) - sr.SRRRulesetID = rules.ID - sr.SRRRulesetRuleID = pr.ID //correct - r.CloudflareRedirect = sr - r.SetTarget(pr.Description) + cfsingleredirect.MakeSingleRedirectFromAPI(r, code, srName, srWhen, srThen) + r.SetLabel("@", domain) + + // Store the IDs + sr := r.CloudflareRedirect + sr.SRRRulesetID = rules.ID + sr.SRRRulesetRuleID = pr.ID recs = append(recs, r) } @@ -327,9 +317,6 @@ func (c *cloudflareProvider) getSingleRedirects(id string, domain string) ([]*mo func (c *cloudflareProvider) createSingleRedirect(domainID string, cfr models.CloudflareSingleRedirectConfig) error { - //printer.Printf("DEBUG: createSingleRedir: d=%v crf=%+v\n", domainID, cfr) - // Asumption for target: - newSingleRedirectRulesActionParameters := cloudflare.RulesetRuleActionParameters{} newSingleRedirectRule := cloudflare.RulesetRule{} newSingleRedirectRules := []cloudflare.RulesetRule{} @@ -347,7 +334,8 @@ func (c *cloudflareProvider) createSingleRedirect(domainID string, cfr models.Cl // Redirect expression newSingleRedirectRulesActionParameters.FromValue.TargetURL.Expression = cfr.SRThen // Redirect name - newSingleRedirectRules[0].Description = cfr.SRDisplay + newSingleRedirectRules[0].Description = cfr.SRName + // Rule action, should always be redirect in this case newSingleRedirectRules[0].Action = "redirect" // Phase should always be http_request_dynamic_redirect @@ -401,7 +389,7 @@ func (c *cloudflareProvider) deleteSingleRedirects(domainID string, cfr models.C //printer.Printf("DEBUG: CALLING API DeleteRulesetRule: SRRRulesetID=%v, cfr.SRRRulesetRuleID=%v\n", cfr.SRRRulesetID, cfr.SRRRulesetRuleID) err := c.cfClient.DeleteRulesetRule(context.Background(), cloudflare.ZoneIdentifier(domainID), cfr.SRRRulesetID, cfr.SRRRulesetRuleID) - // TODO(tlim): This is terrible. It returns an error even when it is successful. + // NB(tlim): Yuck. This returns an error even when it is successful. Dig into the JSON for the real status. if strings.Contains(err.Error(), `"success": true,`) { return nil } @@ -410,13 +398,9 @@ func (c *cloudflareProvider) deleteSingleRedirects(domainID string, cfr models.C } func (c *cloudflareProvider) updateSingleRedirect(domainID string, oldrec, newrec *models.RecordConfig) error { - // rulesetID := cfr.SRRRulesetID - // rulesetRuleID := cfr.SRRRulesetRuleID - //printer.Printf("DEBUG: UPDATE-DEL domID=%v sr=%+v\n", domainID, cfr) if err := c.deleteSingleRedirects(domainID, *oldrec.CloudflareRedirect); err != nil { return err } - //printer.Printf("DEBUG: UPDATE-CREATE domID=%v sr=%+v\n", domainID, newrec.CloudflareRedirect) return c.createSingleRedirect(domainID, *newrec.CloudflareRedirect) } @@ -437,24 +421,17 @@ func (c *cloudflareProvider) getPageRules(id string, domain string) ([]*models.R value := pr.Actions[0].Value.(map[string]interface{}) var thisPr = pr r := &models.RecordConfig{ - Type: "PAGE_RULE", Original: thisPr, - TTL: 1, } - r.SetLabel("@", domain) + code := intZero(value["status_code"]) - raw := fmt.Sprintf("%s,%s,%d,%d", // $FROM,$TO,$PRIO,$CODE - pr.Targets[0].Constraint.Value, - value["url"], - pr.Priority, - code) - r.SetTarget(raw) - - cr, err := cfsingleredirect.FromUserInput(raw, code, pr.Priority) - if err != nil { - return nil, err - } - r.CloudflareRedirect = cr + + when := pr.Targets[0].Constraint.Value + then := value["url"].(string) + currentPrPrio := pr.Priority + + cfsingleredirect.MakePageRule(r, currentPrPrio, code, when, then) + r.SetLabel("@", domain) recs = append(recs, r) } @@ -492,7 +469,6 @@ func (c *cloudflareProvider) createPageRule(domainID string, cfr models.Cloudfla }}, }, } - //printer.Printf("DEBUG: createPageRule pr=%+v\n", pr) _, err := c.cfClient.CreatePageRule(context.Background(), domainID, pr) return err } diff --git a/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go b/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go index 792514a51a..a00ca4a5dc 100644 --- a/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go +++ b/providers/cloudflare/rtypes/cfsingleredirect/cfsingleredirect.go @@ -7,10 +7,14 @@ import ( "github.com/StackExchange/dnscontrol/v4/pkg/rtypecontrol" ) +// SINGLEREDIRECT is the string name for this rType. +const SINGLEREDIRECT = "CLOUDFLAREAPI_SINGLE_REDIRECT" + func init() { - rtypecontrol.Register("CLOUDFLAREAPI_SINGLE_REDIRECT") + rtypecontrol.Register(SINGLEREDIRECT) } +// FromRaw convert RecordConfig using data from a RawRecordConfig's parameters. func FromRaw(rc *models.RecordConfig, items []any) error { // Validate types. @@ -23,17 +27,14 @@ func FromRaw(rc *models.RecordConfig, items []any) error { var code uint16 name = items[0].(string) - code = items[1].(uint16) if code != 301 && code != 302 { return fmt.Errorf("code (%03d) is not 301 or 302", code) } + when = items[2].(string) + then = items[3].(string) - when, then = items[2].(string), items[3].(string) - - rc.Name = name - rc.CloudflareRedirect = FromAPIData(when, then, code) - rc.SetTarget(rc.CloudflareRedirect.SRDisplay) + makeSingleRedirectFromRawRec(rc, code, name, when, then) return nil } diff --git a/providers/cloudflare/rtypes/cfsingleredirect/convert.go b/providers/cloudflare/rtypes/cfsingleredirect/convert.go index 078add236f..40ada49867 100644 --- a/providers/cloudflare/rtypes/cfsingleredirect/convert.go +++ b/providers/cloudflare/rtypes/cfsingleredirect/convert.go @@ -9,51 +9,29 @@ import ( "github.com/StackExchange/dnscontrol/v4/models" ) -func FromUserInput(target string, code uint16, priority int) (*models.CloudflareSingleRedirectConfig, error) { - // target: matcher,replacement,priority,code - // target: cable.slackoverflow.com/*,https://change.cnn.com/$1,1,302 - - r := &models.CloudflareSingleRedirectConfig{} - - // Break apart the 4-part string and store into the individual fields: - parts := strings.Split(target, ",") - //printer.Printf("DEBUG: cfsrFromOldStyle: parts=%v\n", parts) - r.PRDisplay = fmt.Sprintf("%s,%d,%03d", target, priority, code) - r.PRWhen = parts[0] - r.PRThen = parts[1] - r.PRPriority = priority - r.Code = code - - // Convert old-style to new-style: - if err := AddNewStyleFields(r); err != nil { - return nil, err - } - return r, nil -} - -// AddNewStyleFields takes a PAGE_RULE-style target and populates the CFSRC. -func AddNewStyleFields(sr *models.CloudflareSingleRedirectConfig) error { +// TranscodePRtoSR takes a PAGE_RULE record, stores transcoded versions of the fields, and makes the record a CLOUDFLAREAPI_SINGLE_REDDIRECT. +func TranscodePRtoSR(rec *models.RecordConfig) error { + rec.Type = SINGLEREDIRECT // This record is now a CLOUDFLAREAPI_SINGLE_REDIRECT // Extract the fields we're reading from: + sr := rec.CloudflareRedirect + code := sr.Code prWhen := sr.PRWhen prThen := sr.PRThen - code := sr.Code + srName := sr.PRDisplay // Convert old-style patterns to new-style rules: srWhen, srThen, err := makeRuleFromPattern(prWhen, prThen) if err != nil { return err } - display := fmt.Sprintf(`%s,%s,%d,%03d matcher=%s replacement=%s`, + + // Fix the RecordConfig + makeSingleRedirectFromConvert(rec, + sr.PRPriority, prWhen, prThen, - sr.PRPriority, code, - srWhen, srThen, - ) - - // Store the results in the fields we're writing to: - sr.SRWhen = srWhen - sr.SRThen = srThen - sr.SRDisplay = display + code, + srName, srWhen, srThen) return nil } diff --git a/providers/cloudflare/rtypes/cfsingleredirect/convert_test.go b/providers/cloudflare/rtypes/cfsingleredirect/convert_test.go index 88f328edae..54e9bef7e2 100644 --- a/providers/cloudflare/rtypes/cfsingleredirect/convert_test.go +++ b/providers/cloudflare/rtypes/cfsingleredirect/convert_test.go @@ -94,15 +94,6 @@ func Test_makeSingleDirectRule(t *testing.T) { wantExpr: `concat("https://survey.stackoverflow.co/2021", "")`, wantErr: false, }, - // { - // name: "27", - // pattern: "*www.stackoverflow.help/*", - // replace: "https://stackoverflow.help/$1", - /// FIXME(tlim): Should "$1" should be a "$2"? See dnsconfig.js:4344 - // wantMatch: `FIXME`, - // wantExpr: `FIXME`, - // wantErr: false, - // }, { name: "28", pattern: "*stackoverflow.help/support/solutions/articles/36000241656-write-an-article", diff --git a/providers/cloudflare/rtypes/cfsingleredirect/from.go b/providers/cloudflare/rtypes/cfsingleredirect/from.go new file mode 100644 index 0000000000..f1bd3f71f0 --- /dev/null +++ b/providers/cloudflare/rtypes/cfsingleredirect/from.go @@ -0,0 +1,122 @@ +package cfsingleredirect + +import ( + "fmt" + + "github.com/StackExchange/dnscontrol/v4/models" +) + +// MakePageRule updates a RecordConfig to be a PAGE_RULE using PAGE_RULE data. +func MakePageRule(rc *models.RecordConfig, priority int, code uint16, when, then string) { + display := mkPageRuleBlob(priority, code, when, then) + + rc.Type = "PAGE_RULE" + rc.TTL = 1 + rc.CloudflareRedirect = &models.CloudflareSingleRedirectConfig{ + Code: code, + // + PRWhen: when, + PRThen: then, + PRPriority: priority, + PRDisplay: display, + } + rc.SetTarget(display) +} + +// mkPageRuleBlob creates the 1,301,when,then string used in displays. +func mkPageRuleBlob(priority int, code uint16, when, then string) string { + return fmt.Sprintf("%d,%03d,%s,%s", priority, code, when, then) +} + +// makeSingleRedirectFromRawRec updates a RecordConfig to be a +// SINGLEREDIRECT using the data from a RawRecord. +func makeSingleRedirectFromRawRec(rc *models.RecordConfig, code uint16, name, when, then string) { + target := targetFromRaw(name, code, when, then) + + rc.Type = SINGLEREDIRECT + rc.TTL = 1 + rc.CloudflareRedirect = &models.CloudflareSingleRedirectConfig{ + Code: code, + // + PRWhen: "UNKNOWABLE", + PRThen: "UNKNOWABLE", + PRPriority: 0, + PRDisplay: "UNKNOWABLE", + // + SRName: name, + SRWhen: when, + SRThen: then, + SRDisplay: target, + } + rc.SetTarget(rc.CloudflareRedirect.SRDisplay) +} + +// targetFromRaw create the display text used for a normal Redirect. +func targetFromRaw(name string, code uint16, when, then string) string { + return fmt.Sprintf("%s code=(%03d) when=(%s) then=(%s)", + name, + code, + when, + then, + ) +} + +// MakeSingleRedirectFromAPI updatese a RecordConfig to be a SINGLEREDIRECT using data downloaded via the API. +func MakeSingleRedirectFromAPI(rc *models.RecordConfig, code uint16, name, when, then string) { + // The target is the same as the name. It is the responsibility of the record creator to name it something diffable. + target := targetFromAPIData(name, code, when, then) + + rc.Type = SINGLEREDIRECT + rc.TTL = 1 + rc.CloudflareRedirect = &models.CloudflareSingleRedirectConfig{ + Code: code, + // + PRWhen: "UNKNOWABLE", + PRThen: "UNKNOWABLE", + PRPriority: 0, + PRDisplay: "UNKNOWABLE", + // + SRName: name, + SRWhen: when, + SRThen: then, + SRDisplay: target, + } + rc.SetTarget(rc.CloudflareRedirect.SRDisplay) +} + +// targetFromAPIData creates the display text used for a Redirect as received from Cloudflare's API. +func targetFromAPIData(name string, code uint16, when, then string) string { + return fmt.Sprintf("%s code=(%03d) when=(%s) then=(%s)", + name, + code, + when, + then, + ) +} + +// makeSingleRedirectFromConvert updates a RecordConfig to be a SINGLEREDIRECT using data from a PAGE_RULE conversion. +func makeSingleRedirectFromConvert(rc *models.RecordConfig, + priority int, + prWhen, prThen string, + code uint16, + srName, srWhen, srThen string) { + + srDisplay := targetFromConverted(priority, code, prWhen, prThen, srWhen, srThen) + + rc.Type = SINGLEREDIRECT + rc.TTL = 1 + sr := rc.CloudflareRedirect + sr.Code = code + + sr.SRName = srName + sr.SRWhen = srWhen + sr.SRThen = srThen + sr.SRDisplay = srDisplay + + rc.SetTarget(rc.CloudflareRedirect.SRDisplay) +} + +// targetFromConverted makes the display text used when a redirect was the result of converting a PAGE_RULE. +func targetFromConverted(prPriority int, code uint16, prWhen, prThen, srWhen, srThen string) string { + return fmt.Sprintf("%d,%03d,%s,%s code=(%03d) when=(%s) then=(%s)", prPriority, code, prWhen, prThen, code, srWhen, srThen) +} diff --git a/providers/cloudflare/rtypes/cfsingleredirect/native.go b/providers/cloudflare/rtypes/cfsingleredirect/native.go deleted file mode 100644 index fcfd22d568..0000000000 --- a/providers/cloudflare/rtypes/cfsingleredirect/native.go +++ /dev/null @@ -1,19 +0,0 @@ -package cfsingleredirect - -import ( - "fmt" - - "github.com/StackExchange/dnscontrol/v4/models" -) - -func FromAPIData(sm, sr string, code uint16) *models.CloudflareSingleRedirectConfig { - r := &models.CloudflareSingleRedirectConfig{ - PRWhen: "UNKNOWABLE", - PRThen: "UNKNOWABLE", - Code: code, - SRDisplay: fmt.Sprintf("code=%03d when=(%v) then=(%v)", code, sm, sr), - SRWhen: sm, - SRThen: sr, - } - return r -}