Skip to content

Commit

Permalink
cloudflare: add support for API tokens (#937)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeriff authored and ldez committed Sep 1, 2019
1 parent f6230a2 commit a5a2918
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 17 deletions.
4 changes: 3 additions & 1 deletion cmd/zz_gen_cmd_dnshelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,9 @@ func displayDNSHelp(name string) error {
ew.writeln(`Credentials:`)
ew.writeln(` - "CF_API_EMAIL": Account email`)
ew.writeln(` - "CF_API_KEY": API key`)
ew.writeln(` - "CLOUDFLARE_API_KEY": Alias to CLOUDFLARE_API_KEY`)
ew.writeln(` - "CF_API_TOKEN": API token`)
ew.writeln(` - "CLOUDFLARE_API_KEY": Alias to CF_API_KEY`)
ew.writeln(` - "CLOUDFLARE_API_TOKEN": Alias to CF_API_TOKEN`)
ew.writeln(` - "CLOUDFLARE_EMAIL": Alias to CF_API_EMAIL`)
ew.writeln()

Expand Down
24 changes: 22 additions & 2 deletions docs/content/dns/zz_gen_cloudflare.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ Here is an example bash command using the Cloudflare provider:
CLOUDFLARE_EMAIL=foo@bar.com \
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
lego --dns cloudflare --domains my.domain.com --email my@email.com run

# or

CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
lego --dns cloudflare --domains my.domain.com --email my@email.com run
```


Expand All @@ -35,7 +40,9 @@ lego --dns cloudflare --domains my.domain.com --email my@email.com run
|-----------------------|-------------|
| `CF_API_EMAIL` | Account email |
| `CF_API_KEY` | API key |
| `CLOUDFLARE_API_KEY` | Alias to CLOUDFLARE_API_KEY |
| `CF_API_TOKEN` | API token |
| `CLOUDFLARE_API_KEY` | Alias to CF_API_KEY |
| `CLOUDFLARE_API_TOKEN` | Alias to CF_API_TOKEN |
| `CLOUDFLARE_EMAIL` | Alias to CF_API_EMAIL |

The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
Expand All @@ -54,7 +61,20 @@ More information [here](/lego/dns/#configuration-and-credentials).
The environment variable names can be suffixed by `_FILE` to reference a file instead of a value.
More information [here](/lego/dns/#configuration-and-credentials).

The Global API Key needs to be used, not the Origin CA Key.
## Description

You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_API_TOKEN`.

### API keys

If using API keys (`CF_API_EMAIL` and `CF_API_KEY`), the Global API Key needs to be used, not the Origin CA Key.

### API tokens

If using [API tokens](https://api.cloudflare.com/#getting-started-endpoints) (`CF_API_TOKEN`), the following permissions are required:

* `Zone:Read`
* `DNS:Edit`



Expand Down
25 changes: 21 additions & 4 deletions providers/dns/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const (
type Config struct {
AuthEmail string
AuthKey string
AuthToken string
TTL int
PropagationTimeout time.Duration
PollingInterval time.Duration
Expand All @@ -47,18 +48,26 @@ type DNSProvider struct {

// NewDNSProvider returns a DNSProvider instance configured for Cloudflare.
// Credentials must be passed in the environment variables:
// CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY.
// CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY, CLOUDFLARE_API_TOKEN.
func NewDNSProvider() (*DNSProvider, error) {
values, err := env.GetWithFallback(
[]string{"CLOUDFLARE_EMAIL", "CF_API_EMAIL"},
[]string{"CLOUDFLARE_API_KEY", "CF_API_KEY"})
[]string{"CLOUDFLARE_API_KEY", "CF_API_KEY"},
)
if err != nil {
return nil, fmt.Errorf("cloudflare: %v", err)
var errT error
values, errT = env.GetWithFallback(
[]string{"CLOUDFLARE_API_TOKEN", "CF_API_TOKEN"},
)
if errT != nil {
return nil, fmt.Errorf("cloudflare: %v or %v", err, errT)
}
}

config := NewDefaultConfig()
config.AuthEmail = values["CLOUDFLARE_EMAIL"]
config.AuthKey = values["CLOUDFLARE_API_KEY"]
config.AuthToken = values["CLOUDFLARE_API_TOKEN"]

return NewDNSProviderConfig(config)
}
Expand All @@ -73,14 +82,22 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
return nil, fmt.Errorf("cloudflare: invalid TTL, TTL (%d) must be greater than %d", config.TTL, minTTL)
}

client, err := cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient))
client, err := getClient(config)
if err != nil {
return nil, err
}

return &DNSProvider{client: client, config: config}, nil
}

func getClient(config *Config) (*cloudflare.API, error) {
if config.AuthToken == "" {
return cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient))
}

return cloudflare.NewWithAPIToken(config.AuthToken, cloudflare.HTTPClient(config.HTTPClient))
}

// Timeout returns the timeout and interval to use when checking for DNS propagation.
// Adjusting here to cope with spikes in propagation times.
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
Expand Down
24 changes: 22 additions & 2 deletions providers/dns/cloudflare/cloudflare.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,38 @@ Example = '''
CLOUDFLARE_EMAIL=foo@bar.com \
CLOUDFLARE_API_KEY=b9841238feb177a84330febba8a83208921177bffe733 \
lego --dns cloudflare --domains my.domain.com --email my@email.com run
# or
CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
lego --dns cloudflare --domains my.domain.com --email my@email.com run
'''

Additional = '''
The Global API Key needs to be used, not the Origin CA Key.
## Description
You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_API_TOKEN`.
### API keys
If using API keys (`CF_API_EMAIL` and `CF_API_KEY`), the Global API Key needs to be used, not the Origin CA Key.
### API tokens
If using [API tokens](https://api.cloudflare.com/#getting-started-endpoints) (`CF_API_TOKEN`), the following permissions are required:
* `Zone:Read`
* `DNS:Edit`
'''

[Configuration]
[Configuration.Credentials]
CF_API_EMAIL = "Account email"
CF_API_KEY = "API key"
CF_API_TOKEN = "API token"
CLOUDFLARE_EMAIL = "Alias to CF_API_EMAIL"
CLOUDFLARE_API_KEY = "Alias to CLOUDFLARE_API_KEY"
CLOUDFLARE_API_KEY = "Alias to CF_API_KEY"
CLOUDFLARE_API_TOKEN = "Alias to CF_API_TOKEN"
[Configuration.Additional]
CLOUDFLARE_POLLING_INTERVAL = "Time between DNS propagation check"
CLOUDFLARE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
Expand Down
41 changes: 33 additions & 8 deletions providers/dns/cloudflare/cloudflare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import (

var envTest = tester.NewEnvTest(
"CLOUDFLARE_EMAIL",
"CLOUDFLARE_API_KEY").
"CLOUDFLARE_API_KEY",
"CLOUDFLARE_API_TOKEN").
WithDomain("CLOUDFLARE_DOMAIN")

func TestNewDNSProvider(t *testing.T) {
Expand All @@ -21,35 +22,42 @@ func TestNewDNSProvider(t *testing.T) {
expected string
}{
{
desc: "success",
desc: "success email, API key",
envVars: map[string]string{
"CLOUDFLARE_EMAIL": "test@example.com",
"CLOUDFLARE_API_KEY": "123",
},
},
{
desc: "success API token",
envVars: map[string]string{
"CLOUDFLARE_API_TOKEN": "012345abcdef",
},
},
{
desc: "missing credentials",
envVars: map[string]string{
"CLOUDFLARE_EMAIL": "",
"CLOUDFLARE_API_KEY": "",
"CLOUDFLARE_EMAIL": "",
"CLOUDFLARE_API_KEY": "",
"CLOUDFLARE_API_TOKEN": "",
},
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY",
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_API_TOKEN",
},
{
desc: "missing email",
envVars: map[string]string{
"CLOUDFLARE_EMAIL": "",
"CLOUDFLARE_API_KEY": "key",
},
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL",
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL or some credentials information are missing: CLOUDFLARE_API_TOKEN",
},
{
desc: "missing api key",
envVars: map[string]string{
"CLOUDFLARE_EMAIL": "awesome@possum.com",
"CLOUDFLARE_API_KEY": "",
},
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_API_KEY",
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_API_TOKEN",
},
}

Expand Down Expand Up @@ -79,10 +87,21 @@ func TestNewDNSProviderConfig(t *testing.T) {
desc string
authEmail string
authKey string
authToken string
expected string
}{
{
desc: "success",
desc: "success with email and api key",
authEmail: "test@example.com",
authKey: "123",
},
{
desc: "success with api token",
authToken: "012345abcdef",
},
{
desc: "prefer api token",
authToken: "012345abcdef",
authEmail: "test@example.com",
authKey: "123",
},
Expand All @@ -100,13 +119,19 @@ func TestNewDNSProviderConfig(t *testing.T) {
authEmail: "test@example.com",
expected: "invalid credentials: key & email must not be empty",
},
{
desc: "missing api token, fallback to api key/email",
authToken: "",
expected: "invalid credentials: key & email must not be empty",
},
}

for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
config := NewDefaultConfig()
config.AuthEmail = test.authEmail
config.AuthKey = test.authKey
config.AuthToken = test.authToken

p, err := NewDNSProviderConfig(config)

Expand Down

0 comments on commit a5a2918

Please sign in to comment.