mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
Cloudflare Redirects (#119)
* function sig * sig * some custom record infrastructure * CLOUDFLARE REDIRECTS! * comments out * guarding redirects behind provider metadata to manage * catch commas in js to ensure proper encoding. * gen * small fix * revendor otto * docs
This commit is contained in:
@ -22,13 +22,14 @@ username and access token:
|
|||||||
## Metadata
|
## Metadata
|
||||||
|
|
||||||
Record level metadata availible:
|
Record level metadata availible:
|
||||||
* cloudflare_proxy ("on", "off", or "full")
|
* `cloudflare_proxy` ("on", "off", or "full")
|
||||||
|
|
||||||
Domain level metadata availible:
|
Domain level metadata availible:
|
||||||
* cloudflare_proxy_default ("on", "off", or "full")
|
* `cloudflare_proxy_default` ("on", "off", or "full")
|
||||||
|
|
||||||
Provider level metadata availible:
|
Provider level metadata availible:
|
||||||
* ip_conversions
|
* `ip_conversions`
|
||||||
|
* `manage_redirects`: set to `true` to manage page-rule based redirects
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -55,3 +56,31 @@ If a domain does not exist in your CloudFlare account, DNSControl
|
|||||||
will *not* automatically add it. You'll need to do that via the
|
will *not* automatically add it. You'll need to do that via the
|
||||||
control panel manually or via the command `dnscontrol create-domains`
|
control panel manually or via the command `dnscontrol create-domains`
|
||||||
-command.
|
-command.
|
||||||
|
|
||||||
|
## Redirects
|
||||||
|
|
||||||
|
The cloudflare provider can manage Page-Rule based redirects for your domains. Simply use the `CF_REDIRECT` and `CF_TEMP_REDIRECT` functions to make redirects:
|
||||||
|
|
||||||
|
{% highlight js %}
|
||||||
|
|
||||||
|
// chiphacker.com is an alias for electronics.stackexchange.com
|
||||||
|
|
||||||
|
D("chiphacker.com", REG_NAMECOM, DnsProvider(CFLARE),
|
||||||
|
// must have A records with orange cloud on. Otherwise page rule will never run.
|
||||||
|
A("@","1.2.3.4", CF_PROXY_ON),
|
||||||
|
A("www", "1.2.3.4", CF_PROXY_ON)
|
||||||
|
A("meta", "1.2.3.4", CF_PROXY_ON),
|
||||||
|
|
||||||
|
// 302 for meta subdomain
|
||||||
|
CF_TEMP_REDIRECT("meta.chiphacker.com/*", "https://electronics.meta.stackexchange.com/$1),
|
||||||
|
|
||||||
|
// 301 all subdomains and preserve path
|
||||||
|
CF_REDIRECT("*chiphacker.com/*", "https://electronics.stackexchange.com/$2),
|
||||||
|
);
|
||||||
|
{%endhighlight%}
|
||||||
|
|
||||||
|
Notice a few details:
|
||||||
|
|
||||||
|
1. We need an A record with cloudflare proxy on, or the page rule will never run.
|
||||||
|
2. The IP address in those A records may be mostly irrelevant, as cloudflare should handle all requests (assuming some page rule matches).
|
||||||
|
3. Ordering matters for priority. CF_REDIRECT records will be added in the order they appear in your js. So put catch-alls at the bottom.
|
@ -1 +0,0 @@
|
|||||||
D("foo.com","reg","dsp")
|
|
@ -303,3 +303,21 @@ function num2dot(num)
|
|||||||
}
|
}
|
||||||
return d;
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CUSTOM, PROVIDER SPECIFIC RECORD TYPES
|
||||||
|
function CF_REDIRECT(src, dst) {
|
||||||
|
return function(d) {
|
||||||
|
if (src.indexOf(",") !== -1 || dst.indexOf(",") !== -1){
|
||||||
|
throw("redirect src and dst must not have commas")
|
||||||
|
}
|
||||||
|
addRecord(d,"CF_REDIRECT","@",src+","+dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function CF_TEMP_REDIRECT(src, dst) {
|
||||||
|
return function(d) {
|
||||||
|
if (src.indexOf(",") !== -1 || dst.indexOf(",") !== -1){
|
||||||
|
throw("redirect src and dst must not have commas")
|
||||||
|
}
|
||||||
|
addRecord(d,"CF_TEMP_REDIRECT","@",src+","+dst)
|
||||||
|
}
|
||||||
|
}
|
@ -72,6 +72,8 @@ func TestErrors(t *testing.T) {
|
|||||||
{"old dsp style", `D("foo.com","reg","dsp")`},
|
{"old dsp style", `D("foo.com","reg","dsp")`},
|
||||||
{"MX no priority", `D("foo.com","reg",MX("@","test."))`},
|
{"MX no priority", `D("foo.com","reg",MX("@","test."))`},
|
||||||
{"MX reversed", `D("foo.com","reg",MX("@","test.", 5))`},
|
{"MX reversed", `D("foo.com","reg",MX("@","test.", 5))`},
|
||||||
|
{"CF_REDIRECT With comma", `D("foo.com","reg",CF_REDIRECT("foo.com,","baaa"))`},
|
||||||
|
{"CF_TEMP_REDIRECT With comma", `D("foo.com","reg",CF_TEMP_REDIRECT("foo.com","baa,a"))`},
|
||||||
}
|
}
|
||||||
for _, tst := range tests {
|
for _, tst := range tests {
|
||||||
t.Run(tst.desc, func(t *testing.T) {
|
t.Run(tst.desc, func(t *testing.T) {
|
||||||
|
4
js/parse_tests/011-cfRedirect.js
Normal file
4
js/parse_tests/011-cfRedirect.js
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
D("foo.com","none",
|
||||||
|
CF_REDIRECT("test.foo.com","https://goo.com/$1"),
|
||||||
|
CF_TEMP_REDIRECT("test.foo.com","https://goo.com/$1")
|
||||||
|
);
|
24
js/parse_tests/011-cfRedirect.json
Normal file
24
js/parse_tests/011-cfRedirect.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"registrars": [],
|
||||||
|
"dns_providers": [],
|
||||||
|
"domains": [
|
||||||
|
{
|
||||||
|
"name": "foo.com",
|
||||||
|
"registrar": "none",
|
||||||
|
"dnsProviders": {},
|
||||||
|
"records": [
|
||||||
|
{
|
||||||
|
"type": "CF_REDIRECT",
|
||||||
|
"name": "@",
|
||||||
|
"target": "test.foo.com,https://goo.com/$1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "CF_TEMP_REDIRECT",
|
||||||
|
"name": "@",
|
||||||
|
"target": "test.foo.com,https://goo.com/$1"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"keepunknown": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
83
js/static.go
83
js/static.go
@ -190,48 +190,51 @@ var _escData = map[string]*_escFile{
|
|||||||
|
|
||||||
"/helpers.js": {
|
"/helpers.js": {
|
||||||
local: "js/helpers.js",
|
local: "js/helpers.js",
|
||||||
size: 7758,
|
size: 8320,
|
||||||
modtime: 0,
|
modtime: 0,
|
||||||
compressed: `
|
compressed: `
|
||||||
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
|
||||||
|
/mw0vvmfs0FNqf2G+HcAAAD//xD4Q9mAIAAA
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ func checkTarget(target string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validateRecordTypes list of valid rec.Type values. Returns true if this is a real DNS record type, false means it is a pseudo-type used internally.
|
// validateRecordTypes list of valid rec.Type values. Returns true if this is a real DNS record type, false means it is a pseudo-type used internally.
|
||||||
func validateRecordTypes(rec *models.RecordConfig, domain string) error {
|
func validateRecordTypes(rec *models.RecordConfig, domain string, pTypes []string) error {
|
||||||
var validTypes = map[string]bool{
|
var validTypes = map[string]bool{
|
||||||
"A": true,
|
"A": true,
|
||||||
"AAAA": true,
|
"AAAA": true,
|
||||||
@ -55,10 +55,23 @@ func validateRecordTypes(rec *models.RecordConfig, domain string) error {
|
|||||||
"NS": true,
|
"NS": true,
|
||||||
"ALIAS": false,
|
"ALIAS": false,
|
||||||
}
|
}
|
||||||
|
_, ok := validTypes[rec.Type]
|
||||||
if _, ok := validTypes[rec.Type]; !ok {
|
if !ok {
|
||||||
|
cType := providers.GetCustomRecordType(rec.Type)
|
||||||
|
if cType == nil {
|
||||||
return fmt.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.Name)
|
return fmt.Errorf("Unsupported record type (%v) domain=%v name=%v", rec.Type, domain, rec.Name)
|
||||||
}
|
}
|
||||||
|
for _, providerType := range pTypes {
|
||||||
|
if providerType != cType.Provider {
|
||||||
|
return fmt.Errorf("Custom record type %s is not compatible with provider type %s", rec.Type, providerType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//it is ok. Lets replace the type with real type and add metadata to say we checked it
|
||||||
|
rec.Metadata["orig_custom_type"] = rec.Type
|
||||||
|
if cType.RealType != "" {
|
||||||
|
rec.Type = cType.RealType
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,6 +141,10 @@ func checkTargets(rec *models.RecordConfig, domain string) (errs []error) {
|
|||||||
check(checkTarget(target))
|
check(checkTarget(target))
|
||||||
case "TXT", "IMPORT_TRANSFORM":
|
case "TXT", "IMPORT_TRANSFORM":
|
||||||
default:
|
default:
|
||||||
|
if rec.Metadata["orig_custom_type"] != "" {
|
||||||
|
//it is a valid custom type. We perform no validation on target
|
||||||
|
return
|
||||||
|
}
|
||||||
errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
|
errs = append(errs, fmt.Errorf("Unimplemented record type (%v) domain=%v name=%v",
|
||||||
rec.Type, domain, rec.Name))
|
rec.Type, domain, rec.Name))
|
||||||
}
|
}
|
||||||
@ -207,21 +224,34 @@ type Warning struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
func NormalizeAndValidateConfig(config *models.DNSConfig) (errs []error) {
|
||||||
|
ptypeMap := map[string]string{}
|
||||||
|
for _, p := range config.DNSProviders {
|
||||||
|
ptypeMap[p.Name] = p.Type
|
||||||
|
}
|
||||||
|
|
||||||
for _, domain := range config.Domains {
|
for _, domain := range config.Domains {
|
||||||
|
pTypes := []string{}
|
||||||
|
for p := range domain.DNSProviders {
|
||||||
|
pType, ok := ptypeMap[p]
|
||||||
|
if !ok {
|
||||||
|
errs = append(errs, fmt.Errorf("%s uses undefined DNS provider %s", domain.Name, p))
|
||||||
|
} else {
|
||||||
|
pTypes = append(pTypes, pType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Normalize Nameservers.
|
// Normalize Nameservers.
|
||||||
for _, ns := range domain.Nameservers {
|
for _, ns := range domain.Nameservers {
|
||||||
ns.Name = dnsutil.AddOrigin(ns.Name, domain.Name)
|
ns.Name = dnsutil.AddOrigin(ns.Name, domain.Name)
|
||||||
ns.Name = strings.TrimRight(ns.Name, ".")
|
ns.Name = strings.TrimRight(ns.Name, ".")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalize Records.
|
// Normalize Records.
|
||||||
for _, rec := range domain.Records {
|
for _, rec := range domain.Records {
|
||||||
if rec.TTL == 0 {
|
if rec.TTL == 0 {
|
||||||
rec.TTL = models.DefaultTTL
|
rec.TTL = models.DefaultTTL
|
||||||
}
|
}
|
||||||
// Validate the unmodified inputs:
|
// Validate the unmodified inputs:
|
||||||
if err := validateRecordTypes(rec, domain.Name); err != nil {
|
if err := validateRecordTypes(rec, domain.Name, pTypes); err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
}
|
}
|
||||||
if err := checkLabel(rec.Name, rec.Type, domain.Name); err != nil {
|
if err := checkLabel(rec.Name, rec.Type, domain.Name); err != nil {
|
||||||
|
@ -33,6 +33,12 @@ Domain level metadata available:
|
|||||||
- ip_conversions
|
- ip_conversions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseAlias)
|
||||||
|
providers.RegisterCustomRecordType("CF_REDIRECT", "CLOUDFLAREAPI", "")
|
||||||
|
providers.RegisterCustomRecordType("CF_TEMP_REDIRECT", "CLOUDFLAREAPI", "")
|
||||||
|
}
|
||||||
|
|
||||||
type CloudflareApi struct {
|
type CloudflareApi struct {
|
||||||
ApiKey string `json:"apikey"`
|
ApiKey string `json:"apikey"`
|
||||||
ApiUser string `json:"apiuser"`
|
ApiUser string `json:"apiuser"`
|
||||||
@ -40,6 +46,7 @@ type CloudflareApi struct {
|
|||||||
nameservers map[string][]string
|
nameservers map[string][]string
|
||||||
ipConversions []transform.IpConversion
|
ipConversions []transform.IpConversion
|
||||||
ignoredLabels []string
|
ignoredLabels []string
|
||||||
|
manageRedirects bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func labelMatches(label string, matches []string) bool {
|
func labelMatches(label string, matches []string) bool {
|
||||||
@ -51,6 +58,7 @@ func labelMatches(label string, matches []string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
func (c *CloudflareApi) GetNameservers(domain string) ([]*models.Nameserver, error) {
|
||||||
if c.domainIndex == nil {
|
if c.domainIndex == nil {
|
||||||
if err := c.fetchDomainList(); err != nil {
|
if err := c.fetchDomainList(); err != nil {
|
||||||
@ -89,6 +97,13 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||||||
records = append(records[:i], records[i+1:]...)
|
records = append(records[:i], records[i+1:]...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if c.manageRedirects {
|
||||||
|
prs, err := c.getPageRules(id, dc.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
records = append(records, prs...)
|
||||||
|
}
|
||||||
for _, rec := range dc.Records {
|
for _, rec := range dc.Records {
|
||||||
if rec.Type == "ALIAS" {
|
if rec.Type == "ALIAS" {
|
||||||
rec.Type = "CNAME"
|
rec.Type = "CNAME"
|
||||||
@ -103,20 +118,46 @@ func (c *CloudflareApi) GetDomainCorrections(dc *models.DomainConfig) ([]*models
|
|||||||
corrections := []*models.Correction{}
|
corrections := []*models.Correction{}
|
||||||
|
|
||||||
for _, d := range del {
|
for _, d := range del {
|
||||||
corrections = append(corrections, c.deleteRec(d.Existing.Original.(*cfRecord), id))
|
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for _, d := range create {
|
for _, d := range create {
|
||||||
corrections = append(corrections, c.createRec(d.Desired, id)...)
|
des := d.Desired
|
||||||
|
if des.Type == "PAGE_RULE" {
|
||||||
|
corrections = append(corrections, &models.Correction{
|
||||||
|
Msg: d.String(),
|
||||||
|
F: func() error { return c.createPageRule(id, des.Target) },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
corrections = append(corrections, c.createRec(des, id)...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range mod {
|
for _, d := range mod {
|
||||||
e, rec := d.Existing.Original.(*cfRecord), d.Desired
|
rec := d.Desired
|
||||||
|
ex := d.Existing
|
||||||
|
if rec.Type == "PAGE_RULE" {
|
||||||
|
corrections = append(corrections, &models.Correction{
|
||||||
|
Msg: d.String(),
|
||||||
|
F: func() error { return c.updatePageRule(ex.Original.(*pageRule).ID, id, rec.Target) },
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
e := ex.Original.(*cfRecord)
|
||||||
proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
|
proxy := e.Proxiable && rec.Metadata[metaProxy] != "off"
|
||||||
corrections = append(corrections, &models.Correction{
|
corrections = append(corrections, &models.Correction{
|
||||||
Msg: d.String(),
|
Msg: d.String(),
|
||||||
F: func() error { return c.modifyRecord(id, e.ID, proxy, rec) },
|
F: func() error { return c.modifyRecord(id, e.ID, proxy, rec) },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return corrections, nil
|
return corrections, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +204,14 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
currentPrPrio := 1
|
||||||
|
|
||||||
// Normalize the proxy setting for each record.
|
// Normalize the proxy setting for each record.
|
||||||
// A and CNAMEs: Validate. If null, set to default.
|
// A and CNAMEs: Validate. If null, set to default.
|
||||||
// else: Make sure it wasn't set. Set to default.
|
// else: Make sure it wasn't set. Set to default.
|
||||||
for _, rec := range dc.Records {
|
// iterate backwards so first defined page rules have highest priority
|
||||||
|
for i := len(dc.Records) - 1; i >= 0; i-- {
|
||||||
|
rec := dc.Records[i]
|
||||||
if rec.Metadata == nil {
|
if rec.Metadata == nil {
|
||||||
rec.Metadata = map[string]string{}
|
rec.Metadata = map[string]string{}
|
||||||
}
|
}
|
||||||
@ -193,6 +238,23 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
|
|||||||
rec.Metadata[metaProxy] = val
|
rec.Metadata[metaProxy] = val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// CF_REDIRECT record types. Encode target as $FROM,$TO,$PRIO,$CODE
|
||||||
|
if rec.Type == "CF_REDIRECT" || rec.Type == "CF_TEMP_REDIRECT" {
|
||||||
|
if !c.manageRedirects {
|
||||||
|
return fmt.Errorf("you must add 'manage_redirects: true' metadata to cloudflare provider to use CF_REDIRECT records")
|
||||||
|
}
|
||||||
|
parts := strings.Split(rec.Target, ",")
|
||||||
|
if len(parts) != 2 {
|
||||||
|
return fmt.Errorf("Invalid data specified for cloudflare redirect record")
|
||||||
|
}
|
||||||
|
code := 301
|
||||||
|
if rec.Type == "CF_TEMP_REDIRECT" {
|
||||||
|
code = 302
|
||||||
|
}
|
||||||
|
rec.Target = fmt.Sprintf("%s,%d,%d", rec.Target, currentPrPrio, code)
|
||||||
|
currentPrPrio++
|
||||||
|
rec.Type = "PAGE_RULE"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// look for ip conversions and transform records
|
// look for ip conversions and transform records
|
||||||
@ -224,7 +286,7 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||||||
api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
|
api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
|
||||||
// check api keys from creds json file
|
// check api keys from creds json file
|
||||||
if api.ApiKey == "" || api.ApiUser == "" {
|
if api.ApiKey == "" || api.ApiUser == "" {
|
||||||
return nil, fmt.Errorf("Cloudflare apikey and apiuser must be provided.")
|
return nil, fmt.Errorf("cloudflare apikey and apiuser must be provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := api.fetchDomainList()
|
err := api.fetchDomainList()
|
||||||
@ -236,28 +298,28 @@ func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNS
|
|||||||
parsedMeta := &struct {
|
parsedMeta := &struct {
|
||||||
IPConversions string `json:"ip_conversions"`
|
IPConversions string `json:"ip_conversions"`
|
||||||
IgnoredLabels []string `json:"ignored_labels"`
|
IgnoredLabels []string `json:"ignored_labels"`
|
||||||
|
ManageRedirects bool `json:"manage_redirects"`
|
||||||
}{}
|
}{}
|
||||||
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
err := json.Unmarshal([]byte(metadata), parsedMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
api.manageRedirects = parsedMeta.ManageRedirects
|
||||||
// ignored_labels:
|
// ignored_labels:
|
||||||
for _, l := range parsedMeta.IgnoredLabels {
|
for _, l := range parsedMeta.IgnoredLabels {
|
||||||
api.ignoredLabels = append(api.ignoredLabels, l)
|
api.ignoredLabels = append(api.ignoredLabels, l)
|
||||||
}
|
}
|
||||||
// parse provider level metadata
|
// parse provider level metadata
|
||||||
|
if len(parsedMeta.IPConversions) > 0 {
|
||||||
api.ipConversions, err = transform.DecodeTransformTable(parsedMeta.IPConversions)
|
api.ipConversions, err = transform.DecodeTransformTable(parsedMeta.IPConversions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return api, nil
|
return api, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
|
||||||
providers.RegisterDomainServiceProviderType("CLOUDFLAREAPI", newCloudflare, providers.CanUseAlias)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used on the "existing" records.
|
// Used on the "existing" records.
|
||||||
type cfRecord struct {
|
type cfRecord struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
|
@ -5,6 +5,11 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/StackExchange/dnscontrol/models"
|
"github.com/StackExchange/dnscontrol/models"
|
||||||
)
|
)
|
||||||
@ -13,6 +18,8 @@ const (
|
|||||||
baseURL = "https://api.cloudflare.com/client/v4/"
|
baseURL = "https://api.cloudflare.com/client/v4/"
|
||||||
zonesURL = baseURL + "zones/"
|
zonesURL = baseURL + "zones/"
|
||||||
recordsURL = zonesURL + "%s/dns_records/"
|
recordsURL = zonesURL + "%s/dns_records/"
|
||||||
|
pageRulesURL = zonesURL + "%s/pagerules/"
|
||||||
|
singlePageRuleURL = pageRulesURL + "%s"
|
||||||
singleRecordURL = recordsURL + "%s"
|
singleRecordURL = recordsURL + "%s"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -231,6 +238,99 @@ func (c *CloudflareApi) get(endpoint string, target interface{}) error {
|
|||||||
return decoder.Decode(target)
|
return decoder.Decode(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *CloudflareApi) getPageRules(id string, domain string) ([]*models.RecordConfig, error) {
|
||||||
|
url := fmt.Sprintf(pageRulesURL, id)
|
||||||
|
data := pageRuleResponse{}
|
||||||
|
if err := c.get(url, &data); err != nil {
|
||||||
|
return nil, fmt.Errorf("Error fetching page rule list from cloudflare: %s", err)
|
||||||
|
}
|
||||||
|
if !data.Success {
|
||||||
|
return nil, fmt.Errorf("Error fetching page rule list cloudflare: %s", stringifyErrors(data.Errors))
|
||||||
|
}
|
||||||
|
recs := []*models.RecordConfig{}
|
||||||
|
for _, pr := range data.Result {
|
||||||
|
// only interested in forwarding rules. Lets be very specific, and skip anything else
|
||||||
|
if len(pr.Actions) != 1 || len(pr.Targets) != 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if pr.Actions[0].ID != "forwarding_url" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := json.Unmarshal([]byte(pr.Actions[0].Value), &pr.ForwardingInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var thisPr = pr
|
||||||
|
recs = append(recs, &models.RecordConfig{
|
||||||
|
Name: "@",
|
||||||
|
NameFQDN: domain,
|
||||||
|
Type: "PAGE_RULE",
|
||||||
|
//$FROM,$TO,$PRIO,$CODE
|
||||||
|
Target: fmt.Sprintf("%s,%s,%d,%d", pr.Targets[0].Constraint.Value, pr.ForwardingInfo.URL, pr.Priority, pr.ForwardingInfo.StatusCode),
|
||||||
|
Original: thisPr,
|
||||||
|
TTL: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return recs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CloudflareApi) deletePageRule(recordID, domainID string) error {
|
||||||
|
endpoint := fmt.Sprintf(singlePageRuleURL, domainID, recordID)
|
||||||
|
req, err := http.NewRequest("DELETE", endpoint, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.setHeaders(req)
|
||||||
|
_, err = handleActionResponse(http.DefaultClient.Do(req))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CloudflareApi) updatePageRule(recordID, domainID string, target string) error {
|
||||||
|
if err := c.deletePageRule(recordID, domainID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.createPageRule(domainID, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CloudflareApi) createPageRule(domainID string, target string) error {
|
||||||
|
endpoint := fmt.Sprintf(pageRulesURL, domainID)
|
||||||
|
return c.sendPageRule(endpoint, "POST", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CloudflareApi) sendPageRule(endpoint, method string, data string) error {
|
||||||
|
//from to priority code
|
||||||
|
parts := strings.Split(data, ",")
|
||||||
|
priority, _ := strconv.Atoi(parts[2])
|
||||||
|
code, _ := strconv.Atoi(parts[3])
|
||||||
|
fwdInfo := &pageRuleFwdInfo{
|
||||||
|
StatusCode: code,
|
||||||
|
URL: parts[1],
|
||||||
|
}
|
||||||
|
dat, _ := json.Marshal(fwdInfo)
|
||||||
|
pr := &pageRule{
|
||||||
|
Status: "active",
|
||||||
|
Priority: priority,
|
||||||
|
Targets: []pageRuleTarget{
|
||||||
|
{Target: "url", Constraint: pageRuleConstraint{Operator: "matches", Value: parts[0]}},
|
||||||
|
},
|
||||||
|
Actions: []pageRuleAction{
|
||||||
|
{ID: "forwarding_url", Value: json.RawMessage(dat)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
if err := enc.Encode(pr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest(method, endpoint, buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.setHeaders(req)
|
||||||
|
_, err = handleActionResponse(http.DefaultClient.Do(req))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func stringifyErrors(errors []interface{}) string {
|
func stringifyErrors(errors []interface{}) string {
|
||||||
dat, err := json.Marshal(errors)
|
dat, err := json.Marshal(errors)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -244,6 +344,7 @@ type recordsResponse struct {
|
|||||||
Result []*cfRecord `json:"result"`
|
Result []*cfRecord `json:"result"`
|
||||||
ResultInfo pagingInfo `json:"result_info"`
|
ResultInfo pagingInfo `json:"result_info"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type basicResponse struct {
|
type basicResponse struct {
|
||||||
Success bool `json:"success"`
|
Success bool `json:"success"`
|
||||||
Errors []interface{} `json:"errors"`
|
Errors []interface{} `json:"errors"`
|
||||||
@ -253,6 +354,43 @@ type basicResponse struct {
|
|||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pageRuleResponse struct {
|
||||||
|
basicResponse
|
||||||
|
Result []*pageRule `json:"result"`
|
||||||
|
ResultInfo pagingInfo `json:"result_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pageRule struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
Targets []pageRuleTarget `json:"targets"`
|
||||||
|
Actions []pageRuleAction `json:"actions"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
ModifiedOn time.Time `json:"modified_on,omitempty"`
|
||||||
|
CreatedOn time.Time `json:"created_on,omitempty"`
|
||||||
|
ForwardingInfo *pageRuleFwdInfo `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pageRuleTarget struct {
|
||||||
|
Target string `json:"target"`
|
||||||
|
Constraint pageRuleConstraint `json:"constraint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pageRuleConstraint struct {
|
||||||
|
Operator string `json:"operator"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pageRuleAction struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Value json.RawMessage `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type pageRuleFwdInfo struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
StatusCode int `json:"status_code"`
|
||||||
|
}
|
||||||
|
|
||||||
type zoneResponse struct {
|
type zoneResponse struct {
|
||||||
basicResponse
|
basicResponse
|
||||||
Result []struct {
|
Result []struct {
|
||||||
|
@ -138,3 +138,24 @@ func init() {
|
|||||||
return None{}, nil
|
return None{}, nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CustomRType struct {
|
||||||
|
Name string
|
||||||
|
Provider string
|
||||||
|
RealType string
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterCustomRecordType registers a record type that is only valid for one provider.
|
||||||
|
// provider is the registered type of provider this is valid with
|
||||||
|
// name is the record type as it will appear in the js. (should be something like $PROVIDER_FOO)
|
||||||
|
// realType is the record type it will be replaced with after validation
|
||||||
|
func RegisterCustomRecordType(name, provider, realType string) {
|
||||||
|
customRecordTypes[name] = &CustomRType{Name: name, Provider: provider, RealType: realType}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCustomRecordType returns a registered custom record type, or nil if none
|
||||||
|
func GetCustomRecordType(rType string) *CustomRType {
|
||||||
|
return customRecordTypes[rType]
|
||||||
|
}
|
||||||
|
|
||||||
|
var customRecordTypes = map[string]*CustomRType{}
|
||||||
|
5
vendor/github.com/robertkrimen/otto/README.markdown
generated
vendored
5
vendor/github.com/robertkrimen/otto/README.markdown
generated
vendored
@ -88,7 +88,7 @@ Set a Go function that returns something useful
|
|||||||
```go
|
```go
|
||||||
vm.Set("twoPlus", func(call otto.FunctionCall) otto.Value {
|
vm.Set("twoPlus", func(call otto.FunctionCall) otto.Value {
|
||||||
right, _ := call.Argument(0).ToInteger()
|
right, _ := call.Argument(0).ToInteger()
|
||||||
return, _ := vm.ToValue(2 + right)
|
result, _ := vm.ToValue(2 + right)
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
@ -114,7 +114,7 @@ http://godoc.org/github.com/robertkrimen/otto/parser
|
|||||||
Parse and return an AST
|
Parse and return an AST
|
||||||
|
|
||||||
```go
|
```go
|
||||||
filenamee := "" // A filename is optional
|
filename := "" // A filename is optional
|
||||||
src := `
|
src := `
|
||||||
// Sample xyzzy example
|
// Sample xyzzy example
|
||||||
(function(){
|
(function(){
|
||||||
@ -167,6 +167,7 @@ The following are some limitations with otto:
|
|||||||
|
|
||||||
* "use strict" will parse, but does nothing.
|
* "use strict" will parse, but does nothing.
|
||||||
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
||||||
|
* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.
|
||||||
|
|
||||||
|
|
||||||
### Regular Expression Incompatibility
|
### Regular Expression Incompatibility
|
||||||
|
5
vendor/github.com/robertkrimen/otto/builtin.go
generated
vendored
5
vendor/github.com/robertkrimen/otto/builtin.go
generated
vendored
@ -70,7 +70,7 @@ func digitValue(chr rune) int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func builtinGlobal_parseInt(call FunctionCall) Value {
|
func builtinGlobal_parseInt(call FunctionCall) Value {
|
||||||
input := strings.TrimSpace(call.Argument(0).string())
|
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
|
||||||
if len(input) == 0 {
|
if len(input) == 0 {
|
||||||
return NaNValue()
|
return NaNValue()
|
||||||
}
|
}
|
||||||
@ -153,7 +153,8 @@ var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
|
|||||||
|
|
||||||
func builtinGlobal_parseFloat(call FunctionCall) Value {
|
func builtinGlobal_parseFloat(call FunctionCall) Value {
|
||||||
// Caveat emptor: This implementation does NOT match the specification
|
// Caveat emptor: This implementation does NOT match the specification
|
||||||
input := strings.TrimSpace(call.Argument(0).string())
|
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace)
|
||||||
|
|
||||||
if parseFloat_matchBadSpecial.MatchString(input) {
|
if parseFloat_matchBadSpecial.MatchString(input) {
|
||||||
return NaNValue()
|
return NaNValue()
|
||||||
}
|
}
|
||||||
|
1
vendor/github.com/robertkrimen/otto/otto.go
generated
vendored
1
vendor/github.com/robertkrimen/otto/otto.go
generated
vendored
@ -132,6 +132,7 @@ The following are some limitations with otto:
|
|||||||
|
|
||||||
* "use strict" will parse, but does nothing.
|
* "use strict" will parse, but does nothing.
|
||||||
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
* The regular expression engine (re2/regexp) is not fully compatible with the ECMA5 specification.
|
||||||
|
* Otto targets ES5. ES6 features (eg: Typed Arrays) are not supported.
|
||||||
|
|
||||||
Regular Expression Incompatibility
|
Regular Expression Incompatibility
|
||||||
|
|
||||||
|
38
vendor/github.com/robertkrimen/otto/runtime.go
generated
vendored
38
vendor/github.com/robertkrimen/otto/runtime.go
generated
vendored
@ -302,7 +302,11 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
if t.Kind() == reflect.Interface {
|
if t.Kind() == reflect.Interface {
|
||||||
iv := reflect.ValueOf(v.export())
|
e := v.export()
|
||||||
|
if e == nil {
|
||||||
|
return reflect.Zero(t)
|
||||||
|
}
|
||||||
|
iv := reflect.ValueOf(e)
|
||||||
if iv.Type().AssignableTo(t) {
|
if iv.Type().AssignableTo(t) {
|
||||||
return iv
|
return iv
|
||||||
}
|
}
|
||||||
@ -352,6 +356,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Valu
|
|||||||
|
|
||||||
tt := t.Elem()
|
tt := t.Elem()
|
||||||
|
|
||||||
|
if o.class == "Array" {
|
||||||
for i := int64(0); i < l; i++ {
|
for i := int64(0); i < l; i++ {
|
||||||
p, ok := o.property[strconv.FormatInt(i, 10)]
|
p, ok := o.property[strconv.FormatInt(i, 10)]
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -367,6 +372,37 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) reflect.Valu
|
|||||||
|
|
||||||
s.Index(int(i)).Set(ev)
|
s.Index(int(i)).Set(ev)
|
||||||
}
|
}
|
||||||
|
} else if o.class == "GoArray" {
|
||||||
|
|
||||||
|
var gslice bool
|
||||||
|
switch o.value.(type) {
|
||||||
|
case *_goSliceObject:
|
||||||
|
gslice = true
|
||||||
|
case *_goArrayObject:
|
||||||
|
gslice = false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := int64(0); i < l; i++ {
|
||||||
|
var p *_property
|
||||||
|
if gslice {
|
||||||
|
p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10))
|
||||||
|
} else {
|
||||||
|
p = goArrayGetOwnProperty(o, strconv.FormatInt(i, 10))
|
||||||
|
}
|
||||||
|
if p == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
e, ok := p.value.(Value)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ev := self.convertCallParameter(e, tt)
|
||||||
|
|
||||||
|
s.Index(int(i)).Set(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
3
vendor/github.com/robertkrimen/otto/value_boolean.go
generated
vendored
3
vendor/github.com/robertkrimen/otto/value_boolean.go
generated
vendored
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"unicode/utf16"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (value Value) bool() bool {
|
func (value Value) bool() bool {
|
||||||
@ -32,6 +33,8 @@ func (value Value) bool() bool {
|
|||||||
return true
|
return true
|
||||||
case string:
|
case string:
|
||||||
return 0 != len(value)
|
return 0 != len(value)
|
||||||
|
case []uint16:
|
||||||
|
return 0 != len(utf16.Decode(value))
|
||||||
}
|
}
|
||||||
if value.IsObject() {
|
if value.IsObject() {
|
||||||
return true
|
return true
|
||||||
|
2
vendor/github.com/robertkrimen/otto/value_number.go
generated
vendored
2
vendor/github.com/robertkrimen/otto/value_number.go
generated
vendored
@ -11,7 +11,7 @@ import (
|
|||||||
var stringToNumberParseInteger = regexp.MustCompile(`^(?:0[xX])`)
|
var stringToNumberParseInteger = regexp.MustCompile(`^(?:0[xX])`)
|
||||||
|
|
||||||
func parseNumber(value string) float64 {
|
func parseNumber(value string) float64 {
|
||||||
value = strings.TrimSpace(value)
|
value = strings.Trim(value, builtinString_trim_whitespace)
|
||||||
|
|
||||||
if value == "" {
|
if value == "" {
|
||||||
return 0
|
return 0
|
||||||
|
6
vendor/vendor.json
vendored
6
vendor/vendor.json
vendored
@ -303,10 +303,10 @@
|
|||||||
"revisionTime": "2016-04-18T18:49:04Z"
|
"revisionTime": "2016-04-18T18:49:04Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "UH75lsKCrVFdCZvJchkAPo2QXjw=",
|
"checksumSHA1": "EqyHXBcg5cWi4ERsMXN6g1opi1o=",
|
||||||
"path": "github.com/robertkrimen/otto",
|
"path": "github.com/robertkrimen/otto",
|
||||||
"revision": "7d9cbc2befca39869eb0e5bcb0f44c0692c2f8ff",
|
"revision": "21ec96599b1279b5673e4df0097dd56bb8360068",
|
||||||
"revisionTime": "2016-07-28T22:04:12Z"
|
"revisionTime": "2017-04-24T10:46:44Z"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=",
|
"checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=",
|
||||||
|
Reference in New Issue
Block a user