2016-08-22 18:31:50 -06:00
package cloudflare
import (
"encoding/json"
"fmt"
"log"
"net"
"strings"
2021-09-30 05:09:42 -06:00
"github.com/cloudflare/cloudflare-go"
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
"github.com/miekg/dns/dnsutil"
2020-04-14 16:47:30 -04:00
"github.com/StackExchange/dnscontrol/v3/models"
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
"github.com/StackExchange/dnscontrol/v3/pkg/printer"
"github.com/StackExchange/dnscontrol/v3/pkg/transform"
"github.com/StackExchange/dnscontrol/v3/providers"
2016-08-22 18:31:50 -06:00
)
/ *
2017-04-10 20:26:11 -05:00
Cloudflare API DNS provider :
2016-08-22 18:31:50 -06:00
Info required in ` creds.json ` :
- apikey
- apiuser
2018-12-19 15:48:27 +01:00
- accountid ( optional )
2016-08-22 18:31:50 -06:00
2017-04-10 20:26:11 -05:00
Record level metadata available :
- cloudflare_proxy ( "on" , "off" , or "full" )
2016-08-22 18:31:50 -06:00
2017-04-10 20:26:11 -05:00
Domain level metadata available :
- cloudflare_proxy_default ( "on" , "off" , or "full" )
2016-08-22 18:31:50 -06:00
2017-04-10 20:26:11 -05:00
Provider level metadata available :
2016-08-22 18:31:50 -06:00
- ip_conversions
* /
2018-01-04 19:19:35 -05:00
var features = providers . DocumentationNotes {
providers . CanUseAlias : providers . Can ( "CF automatically flattens CNAME records into A records dynamically" ) ,
2019-05-21 04:32:39 +02:00
providers . CanUsePTR : providers . Cannot ( ) ,
2018-01-04 19:19:35 -05:00
providers . CanUseCAA : providers . Can ( ) ,
providers . CanUseSRV : providers . Can ( ) ,
2019-05-21 04:32:39 +02:00
providers . CanUseTLSA : providers . Can ( ) ,
providers . CanUseSSHFP : providers . Can ( ) ,
2021-01-24 16:36:23 -05:00
providers . CanUseDSForChildren : providers . Can ( ) ,
2017-09-14 16:13:17 -04:00
providers . DocCreateDomains : providers . Can ( ) ,
2018-01-04 19:19:35 -05:00
providers . DocDualHost : providers . Cannot ( "Cloudflare will not work well in situations where it is not the only DNS server" ) ,
2017-09-14 16:13:17 -04:00
providers . DocOfficiallySupported : providers . Can ( ) ,
2020-02-18 08:59:18 -05:00
providers . CanGetZones : providers . Can ( ) ,
2017-09-14 16:13:17 -04:00
}
2017-05-19 14:15:57 -04:00
func init ( ) {
2021-03-07 13:19:22 -05:00
fns := providers . DspFuncs {
2021-05-04 14:15:31 -04:00
Initializer : newCloudflare ,
2021-03-08 20:14:30 -05:00
RecordAuditor : AuditRecords ,
2021-03-07 13:19:22 -05:00
}
providers . RegisterDomainServiceProviderType ( "CLOUDFLAREAPI" , fns , features )
2017-05-19 14:15:57 -04:00
providers . RegisterCustomRecordType ( "CF_REDIRECT" , "CLOUDFLAREAPI" , "" )
providers . RegisterCustomRecordType ( "CF_TEMP_REDIRECT" , "CLOUDFLAREAPI" , "" )
2021-10-11 17:04:49 -03:00
providers . RegisterCustomRecordType ( "CF_WORKER_ROUTE" , "CLOUDFLAREAPI" , "" )
2017-05-19 14:15:57 -04:00
}
2020-10-26 09:25:30 -04:00
// cloudflareProvider is the handle for API calls.
type cloudflareProvider struct {
2017-05-19 14:15:57 -04:00
domainIndex map [ string ] string
nameservers map [ string ] [ ] string
2020-06-18 09:37:57 -04:00
ipConversions [ ] transform . IPConversion
2017-05-19 14:15:57 -04:00
ignoredLabels [ ] string
manageRedirects bool
2021-10-11 17:04:49 -03:00
manageWorkers bool
2021-09-30 05:09:42 -06:00
cfClient * cloudflare . API
2016-08-22 18:31:50 -06:00
}
func labelMatches ( label string , matches [ ] string ) bool {
2018-10-08 13:10:44 -07:00
printer . Debugf ( "DEBUG: labelMatches(%#v, %#v)\n" , label , matches )
2016-08-22 18:31:50 -06:00
for _ , tst := range matches {
if label == tst {
return true
}
}
return false
}
2017-05-19 14:15:57 -04:00
2018-01-09 12:53:16 -05:00
// GetNameservers returns the nameservers for a domain.
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) GetNameservers ( domain string ) ( [ ] * models . Nameserver , error ) {
2016-12-16 13:10:27 -07:00
if c . domainIndex == nil {
if err := c . fetchDomainList ( ) ; err != nil {
return nil , err
}
}
ns , ok := c . nameservers [ domain ]
if ! ok {
2020-08-30 19:52:37 -04:00
return nil , fmt . Errorf ( "nameservers for %s not found in cloudflare account" , domain )
2016-12-16 13:10:27 -07:00
}
2020-03-01 10:33:24 -05:00
return models . ToNameservers ( ns )
2016-12-16 13:10:27 -07:00
}
2016-08-22 18:31:50 -06:00
2020-06-18 09:37:57 -04:00
// ListZones returns a list of the DNS zones.
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) ListZones ( ) ( [ ] string , error ) {
2020-02-18 08:59:18 -05:00
if err := c . fetchDomainList ( ) ; err != nil {
return nil , err
}
zones := make ( [ ] string , 0 , len ( c . domainIndex ) )
for d := range c . domainIndex {
zones = append ( zones , d )
}
return zones , nil
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) GetZoneRecords ( domain string ) ( models . Records , error ) {
2020-02-18 08:59:18 -05:00
id , err := c . getDomainID ( domain )
if err != nil {
return nil , err
}
records , err := c . getRecordsForDomain ( id , domain )
if err != nil {
return nil , err
}
for _ , rec := range records {
if rec . TTL == 1 {
rec . TTL = 0
}
2020-11-24 10:30:21 -05:00
// Store the proxy status ("orange cloud") for use by get-zones:
m := getProxyMetadata ( rec )
if p , ok := m [ "proxy" ] ; ok {
if rec . Metadata == nil {
rec . Metadata = map [ string ] string { }
}
rec . Metadata [ "cloudflare_proxy" ] = p
}
2020-02-18 08:59:18 -05:00
}
return records , nil
}
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) getDomainID ( name string ) ( string , error ) {
2016-08-22 18:31:50 -06:00
if c . domainIndex == nil {
if err := c . fetchDomainList ( ) ; err != nil {
2020-02-18 08:59:18 -05:00
return "" , err
2016-08-22 18:31:50 -06:00
}
}
2020-02-18 08:59:18 -05:00
id , ok := c . domainIndex [ name ]
2016-08-22 18:31:50 -06:00
if ! ok {
2020-02-18 08:59:18 -05:00
return "" , fmt . Errorf ( "'%s' not a zone in cloudflare account" , name )
2016-08-22 18:31:50 -06:00
}
2020-02-18 08:59:18 -05:00
return id , nil
}
2019-06-13 13:32:54 +02:00
2020-02-18 08:59:18 -05:00
// GetDomainCorrections returns a list of corrections to update a domain.
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) GetDomainCorrections ( dc * models . DomainConfig ) ( [ ] * models . Correction , error ) {
2021-01-14 09:53:06 -05:00
err := dc . Punycode ( )
if err != nil {
return nil , err
}
2020-02-18 08:59:18 -05:00
id , err := c . getDomainID ( dc . Name )
if err != nil {
2016-08-22 18:31:50 -06:00
return nil , err
}
2017-01-11 12:38:07 -07:00
records , err := c . getRecordsForDomain ( id , dc . Name )
2016-08-22 18:31:50 -06:00
if err != nil {
return nil , err
}
2020-02-18 08:59:18 -05:00
if err := c . preprocessConfig ( dc ) ; err != nil {
return nil , err
}
2016-08-22 18:31:50 -06:00
for i := len ( records ) - 1 ; i >= 0 ; i -- {
rec := records [ i ]
// Delete ignore labels
2021-09-30 05:09:42 -06:00
if labelMatches ( dnsutil . TrimDomainName ( rec . Original . ( cloudflare . DNSRecord ) . Name , dc . Name ) , c . ignoredLabels ) {
printer . Debugf ( "ignored_label: %s\n" , rec . Original . ( cloudflare . DNSRecord ) . Name )
2016-08-22 18:31:50 -06:00
records = append ( records [ : i ] , records [ i + 1 : ] ... )
}
}
2019-06-13 13:32:54 +02:00
2017-05-19 14:15:57 -04:00
if c . manageRedirects {
prs , err := c . getPageRules ( id , dc . Name )
2021-01-24 16:36:23 -05:00
//fmt.Printf("GET PAGE RULES:\n")
//for i, p := range prs {
// fmt.Printf("%03d: %q\n", i, p.GetTargetField())
//}
2017-05-19 14:15:57 -04:00
if err != nil {
return nil , err
}
records = append ( records , prs ... )
}
2019-06-13 13:32:54 +02:00
2021-10-11 17:04:49 -03:00
if c . manageWorkers {
wrs , err := c . getWorkerRoutes ( id , dc . Name )
if err != nil {
return nil , err
}
records = append ( records , wrs ... )
}
2016-08-22 18:31:50 -06:00
for _ , rec := range dc . Records {
2017-04-19 13:13:28 -06:00
if rec . Type == "ALIAS" {
rec . Type = "CNAME"
}
2019-05-20 21:27:37 +02:00
// As per CF-API documentation proxied records are always forced to have a TTL of 1.
// When not forcing this property change here, dnscontrol tries each time to update
// the TTL of a record which simply cannot be changed anyway.
if rec . Metadata [ metaProxy ] != "off" {
rec . TTL = 1
}
2018-02-15 12:02:50 -05:00
if labelMatches ( rec . GetLabel ( ) , c . ignoredLabels ) {
log . Fatalf ( "FATAL: dnsconfig contains label that matches ignored_labels: %#v is in %v)\n" , rec . GetLabel ( ) , c . ignoredLabels )
2016-08-22 18:31:50 -06:00
}
}
2019-06-13 13:32:54 +02:00
2017-01-11 12:38:07 -07:00
checkNSModifications ( dc )
2017-11-07 14:12:17 -08:00
// Normalize
2018-01-04 19:19:35 -05:00
models . PostProcessRecords ( records )
2017-11-07 14:12:17 -08:00
2017-01-11 12:38:07 -07:00
differ := diff . New ( dc , getProxyMetadata )
2020-08-21 07:49:00 +12:00
_ , create , del , mod , err := differ . IncrementalDiff ( records )
if err != nil {
return nil , err
}
2016-08-22 18:31:50 -06:00
corrections := [ ] * models . Correction { }
for _ , d := range del {
2017-05-19 14:15:57 -04:00
ex := d . Existing
if ex . Type == "PAGE_RULE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
2021-09-30 05:09:42 -06:00
F : func ( ) error { return c . deletePageRule ( ex . Original . ( cloudflare . PageRule ) . ID , id ) } ,
2017-05-19 14:15:57 -04:00
} )
2021-10-11 17:04:49 -03:00
} else if ex . Type == "WORKER_ROUTE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
F : func ( ) error { return c . deleteWorkerRoute ( ex . Original . ( cloudflare . WorkerRoute ) . ID , id ) } ,
} )
2017-05-19 14:15:57 -04:00
} else {
2021-09-30 05:09:42 -06:00
corr := c . deleteRec ( ex . Original . ( cloudflare . DNSRecord ) , id )
2021-01-24 16:36:23 -05:00
// DS records must always have a corresponding NS record.
// Therefore, we remove DS records before any NS records.
if d . Existing . Type == "DS" {
corrections = append ( [ ] * models . Correction { corr } , corrections ... )
} else {
corrections = append ( corrections , corr )
}
2017-05-19 14:15:57 -04:00
}
2016-08-22 18:31:50 -06:00
}
for _ , d := range create {
2017-05-19 14:15:57 -04:00
des := d . Desired
if des . Type == "PAGE_RULE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
2018-02-15 12:02:50 -05:00
F : func ( ) error { return c . createPageRule ( id , des . GetTargetField ( ) ) } ,
2017-05-19 14:15:57 -04:00
} )
2021-10-11 17:04:49 -03:00
} else if des . Type == "WORKER_ROUTE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
F : func ( ) error { return c . createWorkerRoute ( id , des . GetTargetField ( ) ) } ,
} )
2017-05-19 14:15:57 -04:00
} else {
2021-01-24 16:36:23 -05:00
corr := c . createRec ( des , id )
// DS records must always have a corresponding NS record.
// Therefore, we create NS records before any DS records.
if d . Desired . Type == "NS" {
corrections = append ( corr , corrections ... )
} else {
corrections = append ( corrections , corr ... )
}
2017-05-19 14:15:57 -04:00
}
2016-08-22 18:31:50 -06:00
}
for _ , d := range mod {
2017-05-19 14:15:57 -04:00
rec := d . Desired
ex := d . Existing
if rec . Type == "PAGE_RULE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
2021-09-30 05:09:42 -06:00
F : func ( ) error { return c . updatePageRule ( ex . Original . ( cloudflare . PageRule ) . ID , id , rec . GetTargetField ( ) ) } ,
2017-05-19 14:15:57 -04:00
} )
2021-10-11 17:04:49 -03:00
} else if rec . Type == "WORKER_ROUTE" {
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
F : func ( ) error {
return c . updateWorkerRoute ( ex . Original . ( cloudflare . WorkerRoute ) . ID , id , rec . GetTargetField ( ) )
} ,
} )
2017-05-19 14:15:57 -04:00
} else {
2021-09-30 05:09:42 -06:00
e := ex . Original . ( cloudflare . DNSRecord )
2017-05-19 14:15:57 -04:00
proxy := e . Proxiable && rec . Metadata [ metaProxy ] != "off"
corrections = append ( corrections , & models . Correction {
Msg : d . String ( ) ,
F : func ( ) error { return c . modifyRecord ( id , e . ID , proxy , rec ) } ,
} )
}
2016-08-22 18:31:50 -06:00
}
2019-06-13 13:32:54 +02:00
// Add universalSSL change to corrections when needed
if changed , newState , err := c . checkUniversalSSL ( dc , id ) ; err == nil && changed {
var newStateString string
if newState {
newStateString = "enabled"
} else {
newStateString = "disabled"
}
corrections = append ( corrections , & models . Correction {
Msg : fmt . Sprintf ( "Universal SSL will be %s for this domain." , newStateString ) ,
F : func ( ) error { return c . changeUniversalSSL ( id , newState ) } ,
} )
}
2016-08-22 18:31:50 -06:00
return corrections , nil
}
2017-01-11 12:38:07 -07:00
func checkNSModifications ( dc * models . DomainConfig ) {
newList := make ( [ ] * models . RecordConfig , 0 , len ( dc . Records ) )
for _ , rec := range dc . Records {
2018-02-15 12:02:50 -05:00
if rec . Type == "NS" && rec . GetLabelFQDN ( ) == dc . Name {
if ! strings . HasSuffix ( rec . GetTargetField ( ) , ".ns.cloudflare.com." ) {
2018-10-08 13:10:44 -07:00
printer . Warnf ( "cloudflare does not support modifying NS records on base domain. %s will not be added.\n" , rec . GetTargetField ( ) )
2017-01-11 12:38:07 -07:00
}
continue
}
newList = append ( newList , rec )
}
dc . Records = newList
}
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) checkUniversalSSL ( dc * models . DomainConfig , id string ) ( changed bool , newState bool , err error ) {
2020-06-18 09:37:57 -04:00
expectedStr := dc . Metadata [ metaUniversalSSL ]
if expectedStr == "" {
return false , false , fmt . Errorf ( "metadata not set" )
2019-06-13 13:32:54 +02:00
}
if actual , err := c . getUniversalSSL ( id ) ; err == nil {
// convert str to bool
var expected bool
2020-06-18 09:37:57 -04:00
if expectedStr == "off" {
2019-06-13 13:32:54 +02:00
expected = false
} else {
expected = true
}
// did something change?
if actual != expected {
return true , expected , nil
}
return false , expected , nil
}
2020-06-18 09:37:57 -04:00
return false , false , fmt . Errorf ( "error receiving universal ssl state" )
2019-06-13 13:32:54 +02:00
}
2016-08-22 18:31:50 -06:00
const (
metaProxy = "cloudflare_proxy"
metaProxyDefault = metaProxy + "_default"
2019-06-13 13:32:54 +02:00
metaOriginalIP = "original_ip" // TODO(tlim): Unclear what this means.
metaUniversalSSL = "cloudflare_universalssl"
2016-08-22 18:31:50 -06:00
metaIPConversions = "ip_conversions" // TODO(tlim): Rename to obscure_rules.
)
func checkProxyVal ( v string ) ( string , error ) {
v = strings . ToLower ( v )
if v != "on" && v != "off" && v != "full" {
2020-08-30 19:52:37 -04:00
return "" , fmt . Errorf ( "bad metadata value for cloudflare_proxy: '%s'. Use on/off/full" , v )
2016-08-22 18:31:50 -06:00
}
return v , nil
}
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) preprocessConfig ( dc * models . DomainConfig ) error {
2016-08-22 18:31:50 -06:00
// Determine the default proxy setting.
var defProxy string
var err error
if defProxy = dc . Metadata [ metaProxyDefault ] ; defProxy == "" {
defProxy = "off"
} else {
defProxy , err = checkProxyVal ( defProxy )
if err != nil {
return err
}
}
2019-06-13 13:32:54 +02:00
// Check UniversalSSL setting
if u := dc . Metadata [ metaUniversalSSL ] ; u != "" {
u = strings . ToLower ( u )
2019-06-27 01:21:23 -04:00
if u != "on" && u != "off" {
2020-06-18 09:37:57 -04:00
return fmt . Errorf ( "bad metadata value for %s: '%s'. Use on/off" , metaUniversalSSL , u )
2019-06-13 13:32:54 +02:00
}
}
2017-05-19 14:15:57 -04:00
2016-08-22 18:31:50 -06:00
// Normalize the proxy setting for each record.
// A and CNAMEs: Validate. If null, set to default.
// else: Make sure it wasn't set. Set to default.
2017-05-19 14:15:57 -04:00
// iterate backwards so first defined page rules have highest priority
2019-06-13 13:32:54 +02:00
currentPrPrio := 1
2017-05-19 14:15:57 -04:00
for i := len ( dc . Records ) - 1 ; i >= 0 ; i -- {
rec := dc . Records [ i ]
2017-04-19 13:13:28 -06:00
if rec . Metadata == nil {
rec . Metadata = map [ string ] string { }
}
2019-06-17 15:12:23 -04:00
// cloudflare uses "1" to mean "auto-ttl"
2019-06-27 01:21:23 -04:00
// if we get here and ttl is not specified (or is the dnscontrol default of 300),
2019-06-17 15:12:23 -04:00
// use automatic mode instead.
2019-06-27 01:21:23 -04:00
if rec . TTL == 0 || rec . TTL == 300 {
2017-01-11 12:38:07 -07:00
rec . TTL = 1
}
2017-04-19 13:13:28 -06:00
if rec . TTL != 1 && rec . TTL < 120 {
rec . TTL = 120
}
2019-06-13 13:32:54 +02:00
2017-04-20 07:13:21 -06:00
if rec . Type != "A" && rec . Type != "CNAME" && rec . Type != "AAAA" && rec . Type != "ALIAS" {
2016-08-22 18:31:50 -06:00
if rec . Metadata [ metaProxy ] != "" {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
return fmt . Errorf ( "cloudflare_proxy set on %v record: %#v cloudflare_proxy=%#v" , rec . Type , rec . GetLabel ( ) , rec . Metadata [ metaProxy ] )
2016-08-22 18:31:50 -06:00
}
// Force it to off.
rec . Metadata [ metaProxy ] = "off"
} else {
if val := rec . Metadata [ metaProxy ] ; val == "" {
rec . Metadata [ metaProxy ] = defProxy
} else {
val , err := checkProxyVal ( val )
if err != nil {
return err
}
rec . Metadata [ metaProxy ] = val
}
}
2019-06-13 13:32:54 +02:00
2017-05-19 14:15:57 -04:00
// CF_REDIRECT record types. Encode target as $FROM,$TO,$PRIO,$CODE
if rec . Type == "CF_REDIRECT" || rec . Type == "CF_TEMP_REDIRECT" {
if ! c . manageRedirects {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
return fmt . Errorf ( "you must add 'manage_redirects: true' metadata to cloudflare provider to use CF_REDIRECT records" )
2017-05-19 14:15:57 -04:00
}
2018-02-15 12:02:50 -05:00
parts := strings . Split ( rec . GetTargetField ( ) , "," )
2017-05-19 14:15:57 -04:00
if len ( parts ) != 2 {
2020-08-30 19:52:37 -04:00
return fmt . Errorf ( "invalid data specified for cloudflare redirect record" )
2017-05-19 14:15:57 -04:00
}
code := 301
if rec . Type == "CF_TEMP_REDIRECT" {
code = 302
}
2018-02-15 12:02:50 -05:00
rec . SetTarget ( fmt . Sprintf ( "%s,%d,%d" , rec . GetTargetField ( ) , currentPrPrio , code ) )
2017-05-19 14:15:57 -04:00
currentPrPrio ++
2020-08-27 22:45:58 +02:00
rec . TTL = 1
2017-05-19 14:15:57 -04:00
rec . Type = "PAGE_RULE"
}
2021-10-11 17:04:49 -03:00
// CF_WORKER_ROUTE record types. Encode target as $PATTERN,$SCRIPT
if rec . Type == "CF_WORKER_ROUTE" {
parts := strings . Split ( rec . GetTargetField ( ) , "," )
if len ( parts ) != 2 {
return fmt . Errorf ( "invalid data specified for cloudflare worker record" )
}
rec . TTL = 1
rec . Type = "WORKER_ROUTE"
}
2016-08-22 18:31:50 -06:00
}
// look for ip conversions and transform records
for _ , rec := range dc . Records {
if rec . Type != "A" {
continue
}
2018-01-09 12:53:16 -05:00
// only transform "full"
2016-08-22 18:31:50 -06:00
if rec . Metadata [ metaProxy ] != "full" {
continue
}
2018-02-15 12:02:50 -05:00
ip := net . ParseIP ( rec . GetTargetField ( ) )
2016-08-22 18:31:50 -06:00
if ip == nil {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
return fmt . Errorf ( "%s is not a valid ip address" , rec . GetTargetField ( ) )
2016-08-22 18:31:50 -06:00
}
2020-06-18 09:37:57 -04:00
newIP , err := transform . IP ( ip , c . ipConversions )
2016-08-22 18:31:50 -06:00
if err != nil {
return err
}
2018-02-15 12:02:50 -05:00
rec . Metadata [ metaOriginalIP ] = rec . GetTargetField ( )
rec . SetTarget ( newIP . String ( ) )
2016-08-22 18:31:50 -06:00
}
return nil
}
func newCloudflare ( m map [ string ] string , metadata json . RawMessage ) ( providers . DNSServiceProvider , error ) {
2020-10-26 09:25:30 -04:00
api := & cloudflareProvider { }
2016-08-22 18:31:50 -06:00
// check api keys from creds json file
2021-10-03 14:40:50 -06:00
if m [ "apitoken" ] == "" && ( m [ "apikey" ] == "" || m [ "apiuser" ] == "" ) {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
return nil , fmt . Errorf ( "if cloudflare apitoken is not set, apikey and apiuser must be provided" )
2019-10-23 17:48:00 +02:00
}
2021-10-03 14:40:50 -06:00
if m [ "apitoken" ] != "" && ( m [ "apikey" ] != "" || m [ "apiuser" ] != "" ) {
Switch to Go 1.13 error wrapping (#604)
* Replaced errors.Wrap with fmt.Errorf (#589)
* Find: errors\.Wrap\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Replaced errors.Wrapf with fmt.Errorf (#589)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])\)
Replace: fmt.Errorf($2: %w$3, $1)
* Find: errors\.Wrapf\(([^,]+),\s+(["`][^"`]*)(["`])(,[^)]+)\)
* Replace: fmt.Errorf($2: %w$3$4, $1)
* Replaced errors.Errorf with fmt.Errorf (#589)
* Find: errors\.Errorf
Replace: fmt.Errorf
* Cleaned up remaining imports
* Cleanup
* Regenerate provider support matrix
This was broken by #533 ... and it's now the third time this has been missed.
2020-01-28 11:06:56 -05:00
return nil , fmt . Errorf ( "if cloudflare apitoken is set, apikey and apiuser should not be provided" )
2016-08-22 18:31:50 -06:00
}
2021-09-30 05:09:42 -06:00
var err error
2021-10-03 14:40:50 -06:00
if m [ "apitoken" ] != "" {
api . cfClient , err = cloudflare . NewWithAPIToken ( m [ "apitoken" ] )
2021-09-30 05:09:42 -06:00
} else {
2021-10-03 14:40:50 -06:00
api . cfClient , err = cloudflare . New ( m [ "apikey" ] , m [ "apiuser" ] )
2021-09-30 05:09:42 -06:00
}
if err != nil {
return nil , fmt . Errorf ( "cloudflare credentials: %w" , err )
}
2018-12-19 15:48:27 +01:00
// Check account data if set
2021-10-03 14:40:50 -06:00
if m [ "accountid" ] != "" {
api . cfClient . AccountID = m [ "accountid" ]
2018-12-19 15:48:27 +01:00
}
2016-08-22 18:31:50 -06:00
if len ( metadata ) > 0 {
parsedMeta := & struct {
2017-05-19 14:15:57 -04:00
IPConversions string ` json:"ip_conversions" `
IgnoredLabels [ ] string ` json:"ignored_labels" `
ManageRedirects bool ` json:"manage_redirects" `
2021-10-11 17:04:49 -03:00
ManageWorkers bool ` json:"manage_workers" `
2016-08-22 18:31:50 -06:00
} { }
err := json . Unmarshal ( [ ] byte ( metadata ) , parsedMeta )
if err != nil {
return nil , err
}
2017-05-19 14:15:57 -04:00
api . manageRedirects = parsedMeta . ManageRedirects
2021-10-11 17:04:49 -03:00
api . manageWorkers = parsedMeta . ManageWorkers
2016-08-22 18:31:50 -06:00
// ignored_labels:
2020-08-30 20:38:08 -04:00
api . ignoredLabels = append ( api . ignoredLabels , parsedMeta . IgnoredLabels ... )
2018-01-15 21:39:29 +01:00
if len ( api . ignoredLabels ) > 0 {
2018-10-08 13:10:44 -07:00
printer . Warnf ( "Cloudflare 'ignored_labels' configuration is deprecated and might be removed. Please use the IGNORE domain directive to achieve the same effect.\n" )
2018-01-15 21:39:29 +01:00
}
2016-08-22 18:31:50 -06:00
// parse provider level metadata
2017-05-19 14:15:57 -04:00
if len ( parsedMeta . IPConversions ) > 0 {
api . ipConversions , err = transform . DecodeTransformTable ( parsedMeta . IPConversions )
if err != nil {
return nil , err
}
2016-08-22 18:31:50 -06:00
}
}
return api , nil
}
// Used on the "existing" records.
2017-08-10 16:02:06 -04:00
type cfRecData struct {
2020-06-18 09:37:57 -04:00
Name string ` json:"name" `
Target cfTarget ` json:"target" `
Service string ` json:"service" ` // SRV
Proto string ` json:"proto" ` // SRV
Priority uint16 ` json:"priority" ` // SRV
Weight uint16 ` json:"weight" ` // SRV
Port uint16 ` json:"port" ` // SRV
Tag string ` json:"tag" ` // CAA
Flags uint8 ` json:"flags" ` // CAA
Value string ` json:"value" ` // CAA
Usage uint8 ` json:"usage" ` // TLSA
Selector uint8 ` json:"selector" ` // TLSA
MatchingType uint8 ` json:"matching_type" ` // TLSA
Certificate string ` json:"certificate" ` // TLSA
Algorithm uint8 ` json:"algorithm" ` // SSHFP/DS
HashType uint8 ` json:"type" ` // SSHFP
Fingerprint string ` json:"fingerprint" ` // SSHFP
KeyTag uint16 ` json:"key_tag" ` // DS
DigestType uint8 ` json:"digest_type" ` // DS
Digest string ` json:"digest" ` // DS
2019-11-14 11:25:20 -05:00
}
// cfTarget is a SRV target. A null target is represented by an empty string, but
// a dot is so acceptable.
type cfTarget string
// UnmarshalJSON decodes a SRV target from the Cloudflare API. A null target is
// represented by a false boolean or a dot. Domain names are FQDNs without a
// trailing period (as of 2019-11-05).
func ( c * cfTarget ) UnmarshalJSON ( data [ ] byte ) error {
var obj interface { }
if err := json . Unmarshal ( data , & obj ) ; err != nil {
return err
}
switch v := obj . ( type ) {
case string :
* c = cfTarget ( v )
case bool :
if v {
panic ( "unknown value for cfTarget bool: true" )
}
* c = "" // the "." is already added by nativeToRecord
}
return nil
}
// MarshalJSON encodes cfTarget for the Cloudflare API. Null targets are
// represented by a single period.
func ( c cfTarget ) MarshalJSON ( ) ( [ ] byte , error ) {
var obj string
switch c {
case "" , "." :
obj = "."
default :
obj = string ( c )
}
return json . Marshal ( obj )
}
// DNSControlString returns cfTarget normalized to be a FQDN. Null targets are
// represented by a single period.
func ( c cfTarget ) FQDN ( ) string {
return strings . TrimRight ( string ( c ) , "." ) + "."
2017-08-10 16:02:06 -04:00
}
2022-01-27 15:58:56 -05:00
func ( c * cloudflareProvider ) nativeToRecord ( domain string , cr cloudflare . DNSRecord ) ( * models . RecordConfig , error ) {
2018-01-09 12:53:16 -05:00
// normalize cname,mx,ns records with dots to be consistent with our config format.
2022-01-27 15:58:56 -05:00
if cr . Type == "CNAME" || cr . Type == "MX" || cr . Type == "NS" {
if cr . Content != "." {
cr . Content = cr . Content + "."
2020-03-25 09:53:28 -04:00
}
2016-08-22 18:31:50 -06:00
}
2018-02-15 12:02:50 -05:00
2017-08-10 16:02:06 -04:00
rc := & models . RecordConfig {
2022-01-27 15:58:56 -05:00
TTL : uint32 ( cr . TTL ) ,
Original : cr ,
2016-08-22 18:31:50 -06:00
}
2022-01-27 15:58:56 -05:00
rc . SetLabelFromFQDN ( cr . Name , domain )
2019-05-18 17:08:18 +02:00
2020-02-23 13:58:49 -05:00
// workaround for https://github.com/StackExchange/dnscontrol/issues/446
2022-01-27 15:58:56 -05:00
if cr . Type == "SPF" {
cr . Type = "TXT"
2019-05-18 17:08:18 +02:00
}
2022-01-27 15:58:56 -05:00
switch rType := cr . Type ; rType { // #rtype_variations
2017-12-20 10:25:23 -05:00
case "MX" :
2022-01-27 15:58:56 -05:00
if err := rc . SetTargetMX ( * cr . Priority , cr . Content ) ; err != nil {
2020-11-13 16:32:40 -05:00
return nil , fmt . Errorf ( "unparsable MX record received from cloudflare: %w" , err )
2018-02-15 12:02:50 -05:00
}
2017-12-20 10:25:23 -05:00
case "SRV" :
2022-01-27 15:58:56 -05:00
data := cr . Data . ( map [ string ] interface { } )
2021-09-30 05:09:42 -06:00
target := data [ "target" ] . ( string )
if target != "." {
target += "."
}
if err := rc . SetTargetSRV ( uint16 ( data [ "priority" ] . ( float64 ) ) , uint16 ( data [ "weight" ] . ( float64 ) ) , uint16 ( data [ "port" ] . ( float64 ) ) ,
target ) ; err != nil {
2020-11-13 16:32:40 -05:00
return nil , fmt . Errorf ( "unparsable SRV record received from cloudflare: %w" , err )
2018-02-15 12:02:50 -05:00
}
default : // "A", "AAAA", "ANAME", "CAA", "CNAME", "NS", "PTR", "TXT"
2022-01-27 15:58:56 -05:00
if err := rc . PopulateFromString ( rType , cr . Content , domain ) ; err != nil {
2020-11-13 16:32:40 -05:00
return nil , fmt . Errorf ( "unparsable record received from cloudflare: %w" , err )
2018-02-15 12:02:50 -05:00
}
2017-08-10 16:02:06 -04:00
}
2017-12-20 10:25:23 -05:00
2020-11-13 16:32:40 -05:00
return rc , nil
2016-08-22 18:31:50 -06:00
}
2017-01-11 12:38:07 -07:00
func getProxyMetadata ( r * models . RecordConfig ) map [ string ] string {
if r . Type != "A" && r . Type != "AAAA" && r . Type != "CNAME" {
return nil
2016-08-22 18:31:50 -06:00
}
2020-07-06 20:18:24 -04:00
var proxied bool
2017-01-11 12:38:07 -07:00
if r . Original != nil {
2021-09-30 05:09:42 -06:00
proxied = * r . Original . ( cloudflare . DNSRecord ) . Proxied
2017-01-11 12:38:07 -07:00
} else {
proxied = r . Metadata [ metaProxy ] != "off"
2016-08-22 18:31:50 -06:00
}
2017-01-11 12:38:07 -07:00
return map [ string ] string {
"proxy" : fmt . Sprint ( proxied ) ,
2016-08-22 18:31:50 -06:00
}
}
2017-05-05 22:20:43 +01:00
2018-01-09 12:53:16 -05:00
// EnsureDomainExists returns an error of domain does not exist.
2020-10-26 09:25:30 -04:00
func ( c * cloudflareProvider ) EnsureDomainExists ( domain string ) error {
2017-05-05 22:20:43 +01:00
if _ , ok := c . domainIndex [ domain ] ; ok {
return nil
}
var id string
id , err := c . createZone ( domain )
fmt . Printf ( "Added zone for %s to Cloudflare account: %s\n" , domain , id )
return err
}
2021-10-11 17:04:49 -03:00
2022-01-27 15:58:56 -05:00
// PrepareCloudflareTestWorkers creates Cloudflare Workers required for CF_WORKER_ROUTE tests.
2021-10-11 17:04:49 -03:00
func PrepareCloudflareTestWorkers ( prv providers . DNSServiceProvider ) error {
cf , ok := prv . ( * cloudflareProvider )
if ok {
err := cf . createTestWorker ( "dnscontrol_integrationtest_cnn" )
if err != nil {
return err
}
err = cf . createTestWorker ( "dnscontrol_integrationtest_msnbc" )
if err != nil {
return err
}
}
return nil
}