mirror of
https://github.com/qdm12/ddns-updater.git
synced 2026-07-31 17:48:39 -04:00
Cloudflare: get identifier automatically and supports ipv6 (#71)
This commit is contained in:
@@ -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:
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user