2016-08-22 18:31:50 -06:00
package cloudflare
import (
"encoding/json"
"fmt"
"log"
"net"
"strings"
"time"
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 )
- accountname ( 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 ( ) ,
2020-05-30 10:40:21 -04:00
providers . CanUseDS : 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 ( ) {
2018-01-04 19:19:35 -05:00
providers . RegisterDomainServiceProviderType ( "CLOUDFLAREAPI" , newCloudflare , features )
2017-05-19 14:15:57 -04:00
providers . RegisterCustomRecordType ( "CF_REDIRECT" , "CLOUDFLAREAPI" , "" )
providers . RegisterCustomRecordType ( "CF_TEMP_REDIRECT" , "CLOUDFLAREAPI" , "" )
}
2020-06-18 09:37:57 -04:00
// CloudflareAPI is the handle for API calls.
type CloudflareAPI struct {
2017-05-19 14:15:57 -04:00
ApiKey string ` json:"apikey" `
2020-01-28 12:10:58 -05:00
ApiToken string ` json:"apitoken" `
2017-05-19 14:15:57 -04:00
ApiUser string ` json:"apiuser" `
2018-12-19 15:48:27 +01:00
AccountID string ` json:"accountid" `
AccountName string ` json:"accountname" `
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
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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) 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.
func ( c * CloudflareAPI ) 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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) 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
}
}
return records , nil
}
2020-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) 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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) GetDomainCorrections ( dc * models . DomainConfig ) ( [ ] * models . Correction , error ) {
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
2017-01-11 12:38:07 -07:00
if labelMatches ( dnsutil . TrimDomainName ( rec . Original . ( * cfRecord ) . Name , dc . Name ) , c . ignoredLabels ) {
2018-10-08 13:10:44 -07:00
printer . Debugf ( "ignored_label: %s\n" , rec . Original . ( * cfRecord ) . 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 )
if err != nil {
return nil , err
}
records = append ( records , prs ... )
}
2019-06-13 13:32:54 +02:00
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 ( ) ,
F : func ( ) error { return c . deletePageRule ( ex . Original . ( * pageRule ) . ID , id ) } ,
} )
} else {
corrections = append ( corrections , c . deleteRec ( ex . Original . ( * cfRecord ) , id ) )
}
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
} )
} else {
corrections = append ( corrections , c . createRec ( des , id ) ... )
}
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 ( ) ,
2018-02-15 12:02:50 -05:00
F : func ( ) error { return c . updatePageRule ( ex . Original . ( * pageRule ) . ID , id , rec . GetTargetField ( ) ) } ,
2017-05-19 14:15:57 -04:00
} )
} else {
e := ex . Original . ( * cfRecord )
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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) checkUniversalSSL ( dc * models . DomainConfig , id string ) ( changed bool , newState bool , err error ) {
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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) 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"
}
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-06-18 09:37:57 -04:00
api := & CloudflareAPI { }
2019-10-23 17:48:00 +02:00
api . ApiUser , api . ApiKey , api . ApiToken = m [ "apiuser" ] , m [ "apikey" ] , m [ "apitoken" ]
2016-08-22 18:31:50 -06:00
// check api keys from creds json file
2019-10-23 17:48:00 +02:00
if api . ApiToken == "" && ( api . ApiKey == "" || api . 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
}
if api . ApiToken != "" && ( api . ApiKey != "" || api . 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
}
2018-12-19 15:48:27 +01:00
// Check account data if set
api . AccountID , api . AccountName = m [ "accountid" ] , m [ "accountname" ]
if ( api . AccountID != "" && api . AccountName == "" ) || ( api . AccountID == "" && api . AccountName != "" ) {
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 ( "either both cloudflare accountid and accountname must be provided or neither" )
2018-12-19 15:48:27 +01:00
}
2017-05-05 22:20:43 +01:00
err := api . fetchDomainList ( )
if err != nil {
return nil , err
}
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" `
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
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
}
2016-08-22 18:31:50 -06:00
type cfRecord struct {
2018-06-22 14:11:05 -04:00
ID string ` json:"id" `
Type string ` json:"type" `
Name string ` json:"name" `
Content string ` json:"content" `
Proxiable bool ` json:"proxiable" `
Proxied bool ` json:"proxied" `
TTL uint32 ` json:"ttl" `
Locked bool ` json:"locked" `
ZoneID string ` json:"zone_id" `
ZoneName string ` json:"zone_name" `
CreatedOn time . Time ` json:"created_on" `
ModifiedOn time . Time ` json:"modified_on" `
Data * cfRecData ` json:"data" `
Priority json . Number ` json:"priority" `
2016-08-22 18:31:50 -06:00
}
2018-02-15 12:02:50 -05:00
func ( c * cfRecord ) nativeToRecord ( domain string ) * models . RecordConfig {
2018-01-09 12:53:16 -05:00
// normalize cname,mx,ns records with dots to be consistent with our config format.
2017-08-10 16:02:06 -04:00
if c . Type == "CNAME" || c . Type == "MX" || c . Type == "NS" || c . Type == "SRV" {
2020-03-25 09:53:28 -04:00
if c . Content != "." {
c . Content = c . Content + "."
}
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 {
2017-12-20 10:25:23 -05:00
TTL : c . TTL ,
Original : c ,
2016-08-22 18:31:50 -06:00
}
2018-02-15 12:02:50 -05:00
rc . SetLabelFromFQDN ( c . 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
2019-05-18 17:08:18 +02:00
if c . Type == "SPF" {
c . Type = "TXT"
}
2018-02-15 12:02:50 -05:00
switch rType := c . Type ; rType { // #rtype_variations
2017-12-20 10:25:23 -05:00
case "MX" :
2018-06-22 14:11:05 -04:00
var priority uint16
2018-07-24 22:51:10 +10:00
if c . Priority == "" {
priority = 0
2018-06-22 14:11:05 -04:00
} else {
2018-07-24 22:51:10 +10:00
if p , err := c . Priority . Int64 ( ) ; err != 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
panic ( fmt . Errorf ( "error decoding priority from cloudflare record: %w" , err ) )
2018-07-24 22:51:10 +10:00
} else {
priority = uint16 ( p )
}
2018-06-22 14:11:05 -04:00
}
if err := rc . SetTargetMX ( priority , c . Content ) ; err != 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
panic ( 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" :
2017-08-10 16:02:06 -04:00
data := * c . Data
2018-02-15 12:02:50 -05:00
if err := rc . SetTargetSRV ( data . Priority , data . Weight , data . Port ,
2019-11-14 11:25:20 -05:00
dnsutil . AddOrigin ( data . Target . FQDN ( ) , domain ) ) ; err != 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
panic ( 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"
if err := rc . PopulateFromString ( rType , c . Content , domain ) ; err != 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
panic ( 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
2017-08-10 16:02:06 -04:00
return rc
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 {
proxied = r . Original . ( * cfRecord ) . Proxied
} 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-06-18 09:37:57 -04:00
func ( c * CloudflareAPI ) 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
}