CLOUDFLARE: Support API tokens (#555)

Cloudflare API tokens are a new way to authenticate to Cloudflare API.
Unlike the Global API key, tokens can be given specific permissions to
only access parts of the API. See [1] for details.

[1] https://blog.cloudflare.com/api-tokens-general-availability/

This commit introduces a new credential for cloudflare called
`apitoken`, which is mutually exclusive with `apiuser` and `apikey`.

In order for DNSControl to work with this token, it should have the
right to read DNS zones and edit DNS records.

Closes #534
This commit is contained in:
Benoît Knecht 2019-10-23 17:48:00 +02:00 committed by Tom Limoncelli
parent 96583a9188
commit 4e6d05b716
4 changed files with 31 additions and 11 deletions

View file

@ -11,7 +11,21 @@ jsId: CLOUDFLAREAPI
* When using `SPF()` or the `SPF_BUILDER()` the records are converted to RecordType `TXT` as Cloudflare API fails otherwise. See more [here](https://github.com/StackExchange/dnscontrol/issues/446).
## Configuration
In the credentials file you must provide your Cloudflare API username and access token:
In the credentials file you must provide a [Cloudflare API token](https://dash.cloudflare.com/profile/api-tokens):
{% highlight json %}
{
"cloudflare": {
"apitoken": "your-cloudflare-api-token"
}
}
{% endhighlight %}
Make sure the token has at least the right read zones and edit DNS records (i.e. `Zone → Zone → Read` and `Zone → DNS → Edit`);
checkout [Cloudflare's documentation](https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys) for instructions on how to generate and configure permissions on API tokens.
Or you can provide your Cloudflare API username and access key instead, but it isn't recommended because those credentials give DNSControl access to the complete Cloudflare API rather:
{% highlight json %}
{
@ -27,8 +41,7 @@ If your Cloudflare account has access to multiple Cloudflare accounts, you can s
{% highlight json %}
{
"cloudflare": {
"apikey": "...",
"apiuser": "...",
"apitoken": "...",
"accountid": "your-cloudflare-account-id",
"accountname": "your-cloudflare-account-name"
}

View file

@ -66,8 +66,7 @@ If you are using other providers, you will likely need to make a `creds.json` fi
{% highlight js %}
{
"cloudflare":{ // provider name to be used in dnsconfig.js
"apikey": "key", // API key
"apiuser": "username" // username for cloudflare
"apitoken": "token" // API token
},
"namecom":{ // provider name to be used in dnsconfig.js
"apikey": "key", // API Key
@ -259,4 +258,4 @@ If you are going to use this in production, we highly recommend the following:
* Store the configuration files in Git.
* Encrypt the `creds.json` file before storing it in Git.
* Use a CI/CD tool like Jenkins to automatically push DNS changes.
* Join the DNSControl community. File [issues and PRs](https://github.com/StackExchange/dnscontrol).
* Join the DNSControl community. File [issues and PRs](https://github.com/StackExchange/dnscontrol).

View file

@ -58,6 +58,7 @@ func init() {
// CloudflareApi is the handle for API calls.
type CloudflareApi struct {
ApiKey string `json:"apikey"`
ApiToken string `json:apitoken`
ApiUser string `json:"apiuser"`
AccountID string `json:"accountid"`
AccountName string `json:"accountname"`
@ -370,10 +371,13 @@ func (c *CloudflareApi) preprocessConfig(dc *models.DomainConfig) error {
func newCloudflare(m map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
api := &CloudflareApi{}
api.ApiUser, api.ApiKey = m["apiuser"], m["apikey"]
api.ApiUser, api.ApiKey, api.ApiToken = m["apiuser"], m["apikey"], m["apitoken"]
// check api keys from creds json file
if api.ApiKey == "" || api.ApiUser == "" {
return nil, errors.Errorf("cloudflare apikey and apiuser must be provided")
if api.ApiToken == "" && (api.ApiKey == "" || api.ApiUser == "") {
return nil, errors.Errorf("if cloudflare apitoken is not set, apikey and apiuser must be provided")
}
if api.ApiToken != "" && (api.ApiKey != "" || api.ApiUser != "") {
return nil, errors.Errorf("if cloudflare apitoken is set, apikey and apiuser should not be provided")
}
// Check account data if set

View file

@ -355,8 +355,12 @@ func handleActionResponse(resp *http.Response, err error) (id string, e error) {
}
func (c *CloudflareApi) setHeaders(req *http.Request) {
req.Header.Set("X-Auth-Key", c.ApiKey)
req.Header.Set("X-Auth-Email", c.ApiUser)
if len(c.ApiToken) > 0 {
req.Header.Set("Authorization", "Bearer "+c.ApiToken)
} else {
req.Header.Set("X-Auth-Key", c.ApiKey)
req.Header.Set("X-Auth-Email", c.ApiUser)
}
}
// generic get handler. makes request and unmarshalls response to given interface