cloudflare: handle restricted API tokens (#985)
This commit is contained in:
parent
415e5343da
commit
828b0f3420
6 changed files with 319 additions and 48 deletions
|
@ -220,10 +220,12 @@ func displayDNSHelp(name string) error {
|
|||
ew.writeln(`Credentials:`)
|
||||
ew.writeln(` - "CF_API_EMAIL": Account email`)
|
||||
ew.writeln(` - "CF_API_KEY": API key`)
|
||||
ew.writeln(` - "CF_API_TOKEN": API token`)
|
||||
ew.writeln(` - "CF_DNS_API_TOKEN": API token with DNS:Edit permission (since v3.1.0)`)
|
||||
ew.writeln(` - "CF_ZONE_API_TOKEN": API token with Zone:Read permission (since v3.1.0)`)
|
||||
ew.writeln(` - "CLOUDFLARE_API_KEY": Alias to CF_API_KEY`)
|
||||
ew.writeln(` - "CLOUDFLARE_API_TOKEN": Alias to CF_API_TOKEN`)
|
||||
ew.writeln(` - "CLOUDFLARE_DNS_API_TOKEN": Alias to CF_DNS_API_TOKEN`)
|
||||
ew.writeln(` - "CLOUDFLARE_EMAIL": Alias to CF_API_EMAIL`)
|
||||
ew.writeln(` - "CLOUDFLARE_ZONE_API_TOKEN": Alias to CF_ZONE_API_TOKEN`)
|
||||
ew.writeln()
|
||||
|
||||
ew.writeln(`Additional Configuration:`)
|
||||
|
|
|
@ -27,7 +27,7 @@ lego --dns cloudflare --domains my.domain.com --email my@email.com run
|
|||
|
||||
# or
|
||||
|
||||
CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
|
||||
CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
|
||||
lego --dns cloudflare --domains my.domain.com --email my@email.com run
|
||||
```
|
||||
|
||||
|
@ -40,10 +40,12 @@ lego --dns cloudflare --domains my.domain.com --email my@email.com run
|
|||
|-----------------------|-------------|
|
||||
| `CF_API_EMAIL` | Account email |
|
||||
| `CF_API_KEY` | API key |
|
||||
| `CF_API_TOKEN` | API token |
|
||||
| `CF_DNS_API_TOKEN` | API token with DNS:Edit permission (since v3.1.0) |
|
||||
| `CF_ZONE_API_TOKEN` | API token with Zone:Read permission (since v3.1.0) |
|
||||
| `CLOUDFLARE_API_KEY` | Alias to CF_API_KEY |
|
||||
| `CLOUDFLARE_API_TOKEN` | Alias to CF_API_TOKEN |
|
||||
| `CLOUDFLARE_DNS_API_TOKEN` | Alias to CF_DNS_API_TOKEN |
|
||||
| `CLOUDFLARE_EMAIL` | Alias to CF_API_EMAIL |
|
||||
| `CLOUDFLARE_ZONE_API_TOKEN` | Alias to CF_ZONE_API_TOKEN |
|
||||
|
||||
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).
|
||||
|
@ -63,18 +65,43 @@ More information [here](/lego/dns/#configuration-and-credentials).
|
|||
|
||||
## Description
|
||||
|
||||
You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_API_TOKEN`.
|
||||
You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_DNS_API_TOKEN`, or `CF_DNS_API_TOKEN` and `CF_ZONE_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.
|
||||
|
||||
Please be aware, that this in principle allows Lego to read and change *everything* related to this account.
|
||||
|
||||
### API tokens
|
||||
|
||||
If using [API tokens](https://api.cloudflare.com/#getting-started-endpoints) (`CF_API_TOKEN`), the following permissions are required:
|
||||
With API tokens (`CF_DNS_API_TOKEN`, and optionally `CF_ZONE_API_TOKEN`),
|
||||
very specific access can be granted to your resources at Cloudflare.
|
||||
See this [Cloudflare announcement](https://blog.cloudflare.com/api-tokens-general-availability/) for details.
|
||||
|
||||
* `Zone:Read`
|
||||
* `DNS:Edit`
|
||||
The main resources Lego cares for are the DNS entries for your Zones.
|
||||
It also need to resolve a domain name to an internal Zone ID in order to manipulate DNS entries.
|
||||
|
||||
Hence, you should create an API token with the following permissions:
|
||||
|
||||
* Zone / Zone / Read
|
||||
* Zone / DNS / Edit
|
||||
|
||||
You also need to scope the access to all your domains for this to work.
|
||||
Then pass the API token as `CF_DNS_API_TOKEN` to Lego.
|
||||
|
||||
**Alternatively,** if you prefer a more strict set of privileges,
|
||||
you can split the access tokens:
|
||||
|
||||
* Create one with *Zone / Zone / Read* permissions and scope it to all your zones.
|
||||
This is needed to resolve domain names to Zone IDs and can be shared among multiple Lego installations.
|
||||
Pass this API token as `CF_ZONE_API_TOKEN` to Lego.
|
||||
* Create another API token with *Zone / DNS / Edit* permissions and set the scope to the domains you want to manage with a single Lego installation.
|
||||
Pass this token as `CF_DNS_API_TOKEN` to Lego.
|
||||
* Repeat the previous step for each host you want to run Lego on.
|
||||
|
||||
This "paranoid" setup is mainly interesting for users who manage many zones/domains with a single Cloudflare account.
|
||||
It follows the principle of least privilege and limits the possible damage, should one of the hosts become compromised.
|
||||
|
||||
|
||||
|
||||
|
|
91
providers/dns/cloudflare/client.go
Normal file
91
providers/dns/cloudflare/client.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package cloudflare
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/cloudflare/cloudflare-go"
|
||||
"github.com/go-acme/lego/v3/challenge/dns01"
|
||||
)
|
||||
|
||||
type metaClient struct {
|
||||
clientEdit *cloudflare.API // needs Zone/DNS/Edit permissions
|
||||
clientRead *cloudflare.API // needs Zone/Zone/Read permissions
|
||||
|
||||
zones map[string]string // caches calls to ZoneIDByName, see lookupZoneID()
|
||||
zonesMu *sync.RWMutex
|
||||
}
|
||||
|
||||
func newClient(config *Config) (*metaClient, error) {
|
||||
// with AuthKey/AuthEmail we can access all available APIs
|
||||
if config.AuthToken == "" {
|
||||
client, err := cloudflare.New(config.AuthKey, config.AuthEmail, cloudflare.HTTPClient(config.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metaClient{
|
||||
clientEdit: client,
|
||||
clientRead: client,
|
||||
zones: make(map[string]string),
|
||||
zonesMu: &sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
dns, err := cloudflare.NewWithAPIToken(config.AuthToken, cloudflare.HTTPClient(config.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if config.ZoneToken == "" || config.ZoneToken == config.AuthToken {
|
||||
return &metaClient{
|
||||
clientEdit: dns,
|
||||
clientRead: dns,
|
||||
zones: make(map[string]string),
|
||||
zonesMu: &sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
zone, err := cloudflare.NewWithAPIToken(config.ZoneToken, cloudflare.HTTPClient(config.HTTPClient))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &metaClient{
|
||||
clientEdit: dns,
|
||||
clientRead: zone,
|
||||
zones: make(map[string]string),
|
||||
zonesMu: &sync.RWMutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *metaClient) CreateDNSRecord(zoneID string, rr cloudflare.DNSRecord) (*cloudflare.DNSRecordResponse, error) {
|
||||
return m.clientEdit.CreateDNSRecord(zoneID, rr)
|
||||
}
|
||||
|
||||
func (m *metaClient) DNSRecords(zoneID string, rr cloudflare.DNSRecord) ([]cloudflare.DNSRecord, error) {
|
||||
return m.clientEdit.DNSRecords(zoneID, rr)
|
||||
}
|
||||
|
||||
func (m *metaClient) DeleteDNSRecord(zoneID, recordID string) error {
|
||||
return m.clientEdit.DeleteDNSRecord(zoneID, recordID)
|
||||
}
|
||||
|
||||
func (m *metaClient) ZoneIDByName(fdqn string) (string, error) {
|
||||
m.zonesMu.RLock()
|
||||
id := m.zones[fdqn]
|
||||
m.zonesMu.RUnlock()
|
||||
|
||||
if id != "" {
|
||||
return id, nil
|
||||
}
|
||||
|
||||
id, err := m.clientRead.ZoneIDByName(dns01.UnFqdn(fdqn))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
m.zonesMu.Lock()
|
||||
m.zones[fdqn] = id
|
||||
m.zonesMu.Unlock()
|
||||
return id, nil
|
||||
}
|
|
@ -19,9 +19,12 @@ const (
|
|||
|
||||
// Config is used to configure the creation of the DNSProvider
|
||||
type Config struct {
|
||||
AuthEmail string
|
||||
AuthKey string
|
||||
AuthToken string
|
||||
AuthEmail string
|
||||
AuthKey string
|
||||
|
||||
AuthToken string
|
||||
ZoneToken string
|
||||
|
||||
TTL int
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
|
@ -42,13 +45,22 @@ func NewDefaultConfig() *Config {
|
|||
|
||||
// DNSProvider is an implementation of the challenge.Provider interface
|
||||
type DNSProvider struct {
|
||||
client *cloudflare.API
|
||||
client *metaClient
|
||||
config *Config
|
||||
}
|
||||
|
||||
// NewDNSProvider returns a DNSProvider instance configured for Cloudflare.
|
||||
// Credentials must be passed in the environment variables:
|
||||
// CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY, CLOUDFLARE_API_TOKEN.
|
||||
// Credentials must be passed in as environment variables:
|
||||
//
|
||||
// Either provide CLOUDFLARE_EMAIL and CLOUDFLARE_API_KEY,
|
||||
// or a CLOUDFLARE_DNS_API_TOKEN.
|
||||
//
|
||||
// For a more paranoid setup, provide CLOUDFLARE_DNS_API_TOKEN and CLOUDFLARE_ZONE_API_TOKEN.
|
||||
//
|
||||
// The email and API key should be avoided, if possible.
|
||||
// Instead setup a API token with both Zone:Read and DNS:Edit permission, and pass the CLOUDFLARE_DNS_API_TOKEN environment variable.
|
||||
// You can split the Zone:Read and DNS:Edit permissions across multiple API tokens:
|
||||
// in this case pass both CLOUDFLARE_ZONE_API_TOKEN and CLOUDFLARE_DNS_API_TOKEN accordingly.
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.GetWithFallback(
|
||||
[]string{"CLOUDFLARE_EMAIL", "CF_API_EMAIL"},
|
||||
|
@ -57,7 +69,8 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
if err != nil {
|
||||
var errT error
|
||||
values, errT = env.GetWithFallback(
|
||||
[]string{"CLOUDFLARE_API_TOKEN", "CF_API_TOKEN"},
|
||||
[]string{"CLOUDFLARE_DNS_API_TOKEN", "CF_DNS_API_TOKEN"},
|
||||
[]string{"CLOUDFLARE_ZONE_API_TOKEN", "CF_ZONE_API_TOKEN", "CLOUDFLARE_DNS_API_TOKEN", "CF_DNS_API_TOKEN"},
|
||||
)
|
||||
if errT != nil {
|
||||
return nil, fmt.Errorf("cloudflare: %v or %v", err, errT)
|
||||
|
@ -67,7 +80,8 @@ func NewDNSProvider() (*DNSProvider, error) {
|
|||
config := NewDefaultConfig()
|
||||
config.AuthEmail = values["CLOUDFLARE_EMAIL"]
|
||||
config.AuthKey = values["CLOUDFLARE_API_KEY"]
|
||||
config.AuthToken = values["CLOUDFLARE_API_TOKEN"]
|
||||
config.AuthToken = values["CLOUDFLARE_DNS_API_TOKEN"]
|
||||
config.ZoneToken = values["CLOUDFLARE_ZONE_API_TOKEN"]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
@ -82,22 +96,14 @@ 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 := getClient(config)
|
||||
client, err := newClient(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("cloudflare: %v", 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) {
|
||||
|
@ -113,7 +119,7 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
zoneID, err := d.client.ZoneIDByName(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
@ -148,7 +154,7 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|||
return fmt.Errorf("cloudflare: %v", err)
|
||||
}
|
||||
|
||||
zoneID, err := d.client.ZoneIDByName(dns01.UnFqdn(authZone))
|
||||
zoneID, err := d.client.ZoneIDByName(authZone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cloudflare: failed to find zone %s: %v", authZone, err)
|
||||
}
|
||||
|
|
|
@ -11,35 +11,62 @@ lego --dns cloudflare --domains my.domain.com --email my@email.com run
|
|||
|
||||
# or
|
||||
|
||||
CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
|
||||
CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \
|
||||
lego --dns cloudflare --domains my.domain.com --email my@email.com run
|
||||
'''
|
||||
|
||||
Additional = '''
|
||||
## Description
|
||||
|
||||
You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_API_TOKEN`.
|
||||
You may use `CF_API_EMAIL` and `CF_API_KEY` to authenticate, or `CF_DNS_API_TOKEN`, or `CF_DNS_API_TOKEN` and `CF_ZONE_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.
|
||||
|
||||
Please be aware, that this in principle allows Lego to read and change *everything* related to this account.
|
||||
|
||||
### API tokens
|
||||
|
||||
If using [API tokens](https://api.cloudflare.com/#getting-started-endpoints) (`CF_API_TOKEN`), the following permissions are required:
|
||||
With API tokens (`CF_DNS_API_TOKEN`, and optionally `CF_ZONE_API_TOKEN`),
|
||||
very specific access can be granted to your resources at Cloudflare.
|
||||
See this [Cloudflare announcement](https://blog.cloudflare.com/api-tokens-general-availability/) for details.
|
||||
|
||||
* `Zone:Read`
|
||||
* `DNS:Edit`
|
||||
The main resources Lego cares for are the DNS entries for your Zones.
|
||||
It also need to resolve a domain name to an internal Zone ID in order to manipulate DNS entries.
|
||||
|
||||
Hence, you should create an API token with the following permissions:
|
||||
|
||||
* Zone / Zone / Read
|
||||
* Zone / DNS / Edit
|
||||
|
||||
You also need to scope the access to all your domains for this to work.
|
||||
Then pass the API token as `CF_DNS_API_TOKEN` to Lego.
|
||||
|
||||
**Alternatively,** if you prefer a more strict set of privileges,
|
||||
you can split the access tokens:
|
||||
|
||||
* Create one with *Zone / Zone / Read* permissions and scope it to all your zones.
|
||||
This is needed to resolve domain names to Zone IDs and can be shared among multiple Lego installations.
|
||||
Pass this API token as `CF_ZONE_API_TOKEN` to Lego.
|
||||
* Create another API token with *Zone / DNS / Edit* permissions and set the scope to the domains you want to manage with a single Lego installation.
|
||||
Pass this token as `CF_DNS_API_TOKEN` to Lego.
|
||||
* Repeat the previous step for each host you want to run Lego on.
|
||||
|
||||
This "paranoid" setup is mainly interesting for users who manage many zones/domains with a single Cloudflare account.
|
||||
It follows the principle of least privilege and limits the possible damage, should one of the hosts become compromised.
|
||||
'''
|
||||
|
||||
[Configuration]
|
||||
[Configuration.Credentials]
|
||||
CF_API_EMAIL = "Account email"
|
||||
CF_API_KEY = "API key"
|
||||
CF_API_TOKEN = "API token"
|
||||
CF_DNS_API_TOKEN = "API token with DNS:Edit permission (since v3.1.0)"
|
||||
CF_ZONE_API_TOKEN = "API token with Zone:Read permission (since v3.1.0)"
|
||||
CLOUDFLARE_EMAIL = "Alias to CF_API_EMAIL"
|
||||
CLOUDFLARE_API_KEY = "Alias to CF_API_KEY"
|
||||
CLOUDFLARE_API_TOKEN = "Alias to CF_API_TOKEN"
|
||||
CLOUDFLARE_DNS_API_TOKEN = "Alias to CF_DNS_API_TOKEN"
|
||||
CLOUDFLARE_ZONE_API_TOKEN = "Alias to CF_ZONE_API_TOKEN"
|
||||
[Configuration.Additional]
|
||||
CLOUDFLARE_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||
CLOUDFLARE_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||
|
|
|
@ -12,7 +12,8 @@ import (
|
|||
var envTest = tester.NewEnvTest(
|
||||
"CLOUDFLARE_EMAIL",
|
||||
"CLOUDFLARE_API_KEY",
|
||||
"CLOUDFLARE_API_TOKEN").
|
||||
"CLOUDFLARE_DNS_API_TOKEN",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN").
|
||||
WithDomain("CLOUDFLARE_DOMAIN")
|
||||
|
||||
func TestNewDNSProvider(t *testing.T) {
|
||||
|
@ -31,17 +32,24 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
{
|
||||
desc: "success API token",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_API_TOKEN": "012345abcdef",
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "012345abcdef",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "success separate API tokens",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "012345abcdef",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN": "abcdef012345",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_EMAIL": "",
|
||||
"CLOUDFLARE_API_KEY": "",
|
||||
"CLOUDFLARE_API_TOKEN": "",
|
||||
"CLOUDFLARE_EMAIL": "",
|
||||
"CLOUDFLARE_API_KEY": "",
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "",
|
||||
},
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_API_TOKEN",
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN",
|
||||
},
|
||||
{
|
||||
desc: "missing email",
|
||||
|
@ -49,7 +57,7 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
"CLOUDFLARE_EMAIL": "",
|
||||
"CLOUDFLARE_API_KEY": "key",
|
||||
},
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL or some credentials information are missing: CLOUDFLARE_API_TOKEN",
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN",
|
||||
},
|
||||
{
|
||||
desc: "missing api key",
|
||||
|
@ -57,7 +65,7 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
"CLOUDFLARE_EMAIL": "awesome@possum.com",
|
||||
"CLOUDFLARE_API_KEY": "",
|
||||
},
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_API_TOKEN",
|
||||
expected: "cloudflare: some credentials information are missing: CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN,CLOUDFLARE_ZONE_API_TOKEN",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -82,6 +90,116 @@ func TestNewDNSProvider(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewDNSProviderWithToken(t *testing.T) {
|
||||
type expected struct {
|
||||
dnsToken string
|
||||
zoneToken string
|
||||
sameClient bool
|
||||
error string
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
||||
// test input
|
||||
envVars map[string]string
|
||||
|
||||
// expectations
|
||||
expected expected
|
||||
}{
|
||||
{
|
||||
desc: "same client when zone token is missing",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "123",
|
||||
},
|
||||
expected: expected{
|
||||
dnsToken: "123",
|
||||
zoneToken: "123",
|
||||
sameClient: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "same client when zone token equals dns token",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "123",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN": "123",
|
||||
},
|
||||
expected: expected{
|
||||
dnsToken: "123",
|
||||
zoneToken: "123",
|
||||
sameClient: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "failure when only zone api given",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_ZONE_API_TOKEN": "123",
|
||||
},
|
||||
expected: expected{
|
||||
error: "cloudflare: some credentials information are missing: CLOUDFLARE_EMAIL,CLOUDFLARE_API_KEY or some credentials information are missing: CLOUDFLARE_DNS_API_TOKEN",
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "different clients when zone and dns token differ",
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "123",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN": "abc",
|
||||
},
|
||||
expected: expected{
|
||||
dnsToken: "123",
|
||||
zoneToken: "abc",
|
||||
sameClient: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "aliases work as expected", // CLOUDFLARE_* takes precedence over CF_*
|
||||
envVars: map[string]string{
|
||||
"CLOUDFLARE_DNS_API_TOKEN": "123",
|
||||
"CF_DNS_API_TOKEN": "456",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN": "abc",
|
||||
"CF_ZONE_API_TOKEN": "def",
|
||||
},
|
||||
expected: expected{
|
||||
dnsToken: "123",
|
||||
zoneToken: "abc",
|
||||
sameClient: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
defer envTest.RestoreEnv()
|
||||
localEnvTest := tester.NewEnvTest(
|
||||
"CLOUDFLARE_DNS_API_TOKEN", "CF_DNS_API_TOKEN",
|
||||
"CLOUDFLARE_ZONE_API_TOKEN", "CF_ZONE_API_TOKEN",
|
||||
).WithDomain("CLOUDFLARE_DOMAIN")
|
||||
envTest.ClearEnv()
|
||||
|
||||
for _, test := range testCases {
|
||||
t.Run(test.desc, func(t *testing.T) {
|
||||
defer localEnvTest.RestoreEnv()
|
||||
localEnvTest.ClearEnv()
|
||||
localEnvTest.Apply(test.envVars)
|
||||
|
||||
p, err := NewDNSProvider()
|
||||
|
||||
if test.expected.error != "" {
|
||||
require.EqualError(t, err, test.expected.error)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, p)
|
||||
assert.Equal(t, test.expected.dnsToken, p.config.AuthToken)
|
||||
assert.Equal(t, test.expected.zoneToken, p.config.ZoneToken)
|
||||
if test.expected.sameClient {
|
||||
assert.Equal(t, p.client.clientRead, p.client.clientEdit)
|
||||
} else {
|
||||
assert.NotEqual(t, p.client.clientRead, p.client.clientEdit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewDNSProviderConfig(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
@ -107,22 +225,22 @@ func TestNewDNSProviderConfig(t *testing.T) {
|
|||
},
|
||||
{
|
||||
desc: "missing credentials",
|
||||
expected: "invalid credentials: key & email must not be empty",
|
||||
expected: "cloudflare: invalid credentials: key & email must not be empty",
|
||||
},
|
||||
{
|
||||
desc: "missing email",
|
||||
authKey: "123",
|
||||
expected: "invalid credentials: key & email must not be empty",
|
||||
expected: "cloudflare: invalid credentials: key & email must not be empty",
|
||||
},
|
||||
{
|
||||
desc: "missing api key",
|
||||
authEmail: "test@example.com",
|
||||
expected: "invalid credentials: key & email must not be empty",
|
||||
expected: "cloudflare: 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",
|
||||
expected: "cloudflare: invalid credentials: key & email must not be empty",
|
||||
},
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue