-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cloudflare: handle restricted API tokens (#985)
- Loading branch information
Showing
6 changed files
with
319 additions
and
48 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,7 +27,7 @@ lego --dns cloudflare --domains my.domain.com --email [email protected] run | |
|
||
# or | ||
|
||
CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ | ||
CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ | ||
lego --dns cloudflare --domains my.domain.com --email [email protected] run | ||
``` | ||
|
||
|
@@ -40,10 +40,12 @@ lego --dns cloudflare --domains my.domain.com --email [email protected] 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. | ||
|
||
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. | ||
|
||
* `Zone:Read` | ||
* `DNS:Edit` | ||
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. | ||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,35 +11,62 @@ lego --dns cloudflare --domains my.domain.com --email [email protected] run | |
# or | ||
CLOUDFLARE_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ | ||
CLOUDFLARE_DNS_API_TOKEN=1234567890abcdefghijklmnopqrstuvwxyz \ | ||
lego --dns cloudflare --domains my.domain.com --email [email protected] 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. | ||
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. | ||
* `Zone:Read` | ||
* `DNS:Edit` | ||
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" | ||
|
Oops, something went wrong.