Cloudflare: get identifier automatically and supports ipv6 (#71)

This commit is contained in:
Quentin McGaw
2020-05-30 20:12:31 -04:00
committed by GitHub
parent 066bcdd3bf
commit e023aae909
3 changed files with 85 additions and 22 deletions

View File

@@ -145,7 +145,6 @@ Namecheap:
Cloudflare:
- `"zone_identifier"` is the Zone ID of your site
- `"identifier"` is the DNS record identifier as returned by the Cloudflare "List DNS Records" API (see below)
- `"domain"`
- `"host"` is your host and can be a subdomain, `"@"` or `"*"` generally
- `"ttl"` integer value for record TTL in seconds (specify 1 for automatic)
@@ -154,6 +153,7 @@ Cloudflare:
- User service key `"user_service_key"`
- API Token `"token"`, configured with DNS edit permissions for your DNS name's zone.
- *Optionally*, `"proxied"` can be `true` or `false` to use the proxy services of Cloudflare
- `"ip_version"` can be `ipv4` (A records) or `ipv6` (AAAA records), defaults to `ipv4 or ipv6`
GoDaddy:

View File

@@ -12,7 +12,7 @@ func settingsIPVersionChecks(ipVersion models.IPVersion, provider models.Provide
case constants.IPv4OrIPv6, constants.IPv4:
case constants.IPv6:
switch provider {
case constants.GODADDY, constants.CLOUDFLARE, constants.DNSPOD, constants.DREAMHOST, constants.DUCKDNS, constants.NOIP:
case constants.GODADDY, constants.DNSPOD, constants.DREAMHOST, constants.DUCKDNS, constants.NOIP:
return fmt.Errorf("IPv6 support for %s is not supported yet", provider)
}
default:

View File

@@ -6,6 +6,7 @@ import (
"net"
"net/http"
"net/url"
"strings"
"github.com/qdm12/ddns-updater/internal/constants"
"github.com/qdm12/ddns-updater/internal/models"
@@ -25,7 +26,6 @@ type cloudflare struct {
email string
userServiceKey string
zoneIdentifier string
identifier string
proxied bool
ttl uint
}
@@ -37,7 +37,6 @@ func NewCloudflare(data json.RawMessage, domain, host string, ipVersion models.I
Email string `json:"email"`
UserServiceKey string `json:"user_service_key"`
ZoneIdentifier string `json:"zone_identifier"`
Identifier string `json:"identifier"`
Proxied bool `json:"proxied"`
TTL uint `json:"ttl"`
}{}
@@ -54,7 +53,6 @@ func NewCloudflare(data json.RawMessage, domain, host string, ipVersion models.I
email: extraSettings.Email,
userServiceKey: extraSettings.UserServiceKey,
zoneIdentifier: extraSettings.ZoneIdentifier,
identifier: extraSettings.Identifier,
proxied: extraSettings.Proxied,
ttl: extraSettings.TTL,
}
@@ -85,8 +83,6 @@ func (c *cloudflare) isValid() error {
switch {
case len(c.zoneIdentifier) == 0:
return fmt.Errorf("zone identifier cannot be empty")
case len(c.identifier) == 0:
return fmt.Errorf("identifier cannot be empty")
case c.ttl == 0:
return fmt.Errorf("TTL cannot be left to 0")
}
@@ -126,9 +122,87 @@ func (c *cloudflare) HTML() models.HTMLRow {
}
}
func setHeaders(r *http.Request, token, userServiceKey, email, key string) {
r.Header.Set("User-Agent", "DDNS-Updater quentin.mcgaw@gmail.com")
switch {
case len(token) > 0:
r.Header.Set("Authorization", "Bearer "+token)
case len(userServiceKey) > 0:
r.Header.Set("X-Auth-User-Service-Key", userServiceKey)
case len(email) > 0 && len(key) > 0:
r.Header.Set("X-Auth-Email", email)
r.Header.Set("X-Auth-Key", key)
}
}
// Obtain domain identifier
// See https://api.cloudflare.com/#dns-records-for-a-zone-list-dns-records
func (c *cloudflare) getRecordIdentifier(client netlib.Client, newIP net.IP) (identifier string, upToDate bool, err error) {
recordType := "A"
if newIP.To4() == nil {
recordType = "AAAA"
}
u := url.URL{
Scheme: "https",
Host: "api.cloudflare.com",
Path: fmt.Sprintf("/client/v4/zones/%s/dns_records", c.zoneIdentifier),
}
values := url.Values{}
values.Set("type", recordType)
values.Set("name", c.BuildDomainName())
values.Set("page", "1")
values.Set("per_page", "1")
u.RawQuery = values.Encode()
r, err := http.NewRequest(http.MethodGet, u.String(), nil)
if err != nil {
return "", false, err
}
setHeaders(r, c.token, c.userServiceKey, c.email, c.key)
status, content, err := client.DoHTTPRequest(r)
if err != nil {
return "", false, err
} else if status != http.StatusOK {
return "", false, fmt.Errorf("HTTP status %d", status)
}
listRecordsResponse := struct {
Success bool `json:"success"`
Errors []string `json:"errors"`
Result []struct {
ID string `json:"id"`
Content string `json:"content"`
} `json:"result"`
}{}
if err := json.Unmarshal(content, &listRecordsResponse); err != nil {
return "", false, err
}
switch {
case len(listRecordsResponse.Errors) > 0:
return "", false, fmt.Errorf(strings.Join(listRecordsResponse.Errors, ","))
case !listRecordsResponse.Success:
return "", false, fmt.Errorf("request to Cloudflare not successful")
case len(listRecordsResponse.Result) == 0:
return "", false, fmt.Errorf("received no result from Cloudflare")
case len(listRecordsResponse.Result) > 1:
return "", false, fmt.Errorf("received %d results instead of 1 from Cloudflare", len(listRecordsResponse.Result))
case listRecordsResponse.Result[0].Content == newIP.String(): // up to date
return "", true, nil
}
return listRecordsResponse.Result[0].ID, false, nil
}
func (c *cloudflare) Update(client netlib.Client, ip net.IP) (newIP net.IP, err error) {
recordType := "A"
if newIP.To4() == nil {
recordType = "AAAA"
}
identifier, upToDate, err := c.getRecordIdentifier(client, ip)
if err != nil {
return nil, err
} else if upToDate {
return ip, nil
}
type cloudflarePutBody struct {
Type string `json:"type"` // forced to A
Type string `json:"type"` // A or AAAA depending on ip address given
Name string `json:"name"` // DNS record name i.e. example.com
Content string `json:"content"` // ip address
Proxied bool `json:"proxied"` // whether the record is receiving the performance and security benefits of Cloudflare
@@ -137,12 +211,12 @@ func (c *cloudflare) Update(client netlib.Client, ip net.IP) (newIP net.IP, err
u := url.URL{
Scheme: "https",
Host: "api.cloudflare.com",
Path: fmt.Sprintf("/client/v4/zones/%s/dns_records/%s", c.zoneIdentifier, c.identifier),
Path: fmt.Sprintf("/client/v4/zones/%s/dns_records/%s", c.zoneIdentifier, identifier),
}
r, err := network.BuildHTTPPut(
u.String(),
cloudflarePutBody{
Type: "A",
Type: recordType,
Name: c.host,
Content: ip.String(),
Proxied: c.proxied,
@@ -152,18 +226,7 @@ func (c *cloudflare) Update(client netlib.Client, ip net.IP) (newIP net.IP, err
if err != nil {
return nil, err
}
r.Header.Set("User-Agent", "DDNS-Updater quentin.mcgaw@gmail.com")
switch {
case len(c.token) > 0:
r.Header.Set("Authorization", "Bearer "+c.token)
case len(c.userServiceKey) > 0:
r.Header.Set("X-Auth-User-Service-Key", c.userServiceKey)
case len(c.email) > 0 && len(c.key) > 0:
r.Header.Set("X-Auth-Email", c.email)
r.Header.Set("X-Auth-Key", c.key)
default:
return nil, fmt.Errorf("email and key are both unset and user service key is not set and no token was provided")
}
setHeaders(r, c.token, c.userServiceKey, c.email, c.key)
status, content, err := client.DoHTTPRequest(r)
if err != nil {
return nil, err