diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py index 11886ea54..d59862a3c 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py @@ -21,8 +21,8 @@ Credentials ----------- Use of this plugin requires a configuration file containing Cloudflare API -credentials, obtained from your Cloudflare -`account page `_. +credentials, obtained from your +`Cloudflare dashboard `_. Previously, Cloudflare's "Global API Key" was used for authentication, however this key can access the entire Cloudflare API for all domains in your account, @@ -31,11 +31,8 @@ meaning it could cause a lot of damage if leaked. Cloudflare's newer API Tokens can be restricted to specific domains and operations, and are therefore now the recommended authentication option. -However, due to some shortcomings in Cloudflare's implementation of Tokens, -Tokens created for Certbot currently require ``Zone:Zone:Read`` and ``Zone:DNS:Edit`` -permissions for **all** zones in your account. While this is not ideal, your Token -will still have fewer permission than the Global key, so it's still worth doing. -Hopefully Cloudflare will improve this in the future. +The Token needed by Certbot requires ``Zone:DNS:Edit`` permissions for only the +zones you need certificates for. Using Cloudflare Tokens also requires at least version 2.3.1 of the ``cloudflare`` python module. If the version that automatically installed with this plugin is diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index 22dbcfa1f..5978af85c 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -14,7 +14,7 @@ from certbot.plugins import dns_common logger = logging.getLogger(__name__) -ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens' +ACCOUNT_URL = 'https://dash.cloudflare.com/?to=/:account/profile/api-tokens' @zope.interface.implementer(interfaces.IAuthenticator) @@ -118,7 +118,7 @@ class _CloudflareClient(object): code = int(e) hint = None - if code == 9109: + if code == 1009: hint = 'Does your API token have "Zone:DNS:Edit" permissions?' logger.error('Encountered CloudFlareAPIError adding TXT record: %d %s', e, e) @@ -210,11 +210,22 @@ class _CloudflareClient(object): logger.debug('Found zone_id of %s for %s using name %s', zone_id, domain, zone_name) return zone_id - raise errors.PluginError('Unable to determine zone_id for {0} using zone names: {1}. ' - 'Please confirm that the domain name has been entered correctly ' - 'and is already associated with the supplied Cloudflare account.{2}' - .format(domain, zone_name_guesses, ' The error from Cloudflare was:' - ' {0} {1}'.format(code, msg) if code is not None else '')) + if msg is not None: + if 'com.cloudflare.api.account.zone.list' in msg: + raise errors.PluginError('Unable to determine zone_id for {0} using zone names: ' + '{1}. Please confirm that the domain name has been ' + 'entered correctly and your Cloudflare Token has access ' + 'to the domain.'.format(domain, zone_name_guesses)) + else: + raise errors.PluginError('Unable to determine zone_id for {0} using zone names: ' + '{1}. The error from Cloudflare was: {2} {3}.' + .format(domain, zone_name_guesses, code, msg)) + else: + raise errors.PluginError('Unable to determine zone_id for {0} using zone names: ' + '{1}. Please confirm that the domain name has been ' + 'entered correctly and is already associated with the ' + 'supplied Cloudflare account.' + .format(domain, zone_name_guesses)) def _find_txt_record_id(self, zone_id, record_name, record_content): """ diff --git a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py index 4d2dcf4ca..e9adf4ed9 100644 --- a/certbot-dns-cloudflare/tests/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -133,7 +133,7 @@ class CloudflareClientTest(unittest.TestCase): def test_add_txt_record_error(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] - self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(9109, '', '') + self.cf.zones.dns_records.post.side_effect = CloudFlare.exceptions.CloudFlareAPIError(1009, '', '') self.assertRaises( errors.PluginError, @@ -175,6 +175,12 @@ class CloudflareClientTest(unittest.TestCase): self.cloudflare_client.add_txt_record, DOMAIN, self.record_name, self.record_content, self.record_ttl) + self.cf.zones.get.side_effect = CloudFlare.exceptions.CloudFlareAPIError(0, 'com.cloudflare.api.account.zone.list', '') + self.assertRaises( + errors.PluginError, + self.cloudflare_client.add_txt_record, + DOMAIN, self.record_name, self.record_content, self.record_ttl) + def test_del_txt_record(self): self.cf.zones.get.return_value = [{'id': self.zone_id}] self.cf.zones.dns_records.get.return_value = [{'id': self.record_id}] diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md index 60a5d2aaa..a91efd7f4 100644 --- a/certbot/CHANGELOG.md +++ b/certbot/CHANGELOG.md @@ -23,7 +23,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). ### Fixed -* +* Cloudflare API Tokens may now be restricted to individual zones. More details about these changes can be found on our GitHub repo.