Fallback without dns.resourceRecordSets.list permission (#5678)

* Add rrset list fallback

* List dns.resourceRecordSets.list as required

* Handle list failures differently for add and del

* Quote record content

* disable not-callable for iter_entry_points

* List update permission
This commit is contained in:
Brad Warren 2018-03-06 15:32:22 -08:00 committed by GitHub
parent d62c56f9c9
commit 6357e051f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 37 additions and 9 deletions

View file

@ -29,6 +29,8 @@ for an account with the following permissions:
* ``dns.managedZones.list``
* ``dns.resourceRecordSets.create``
* ``dns.resourceRecordSets.delete``
* ``dns.resourceRecordSets.list``
* ``dns.resourceRecordSets.update``
Google provides instructions for `creating a service account <https://developers
.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount>`_ and

View file

@ -108,6 +108,8 @@ class _GoogleClient(object):
zone_id = self._find_managed_zone_id(domain)
record_contents = self.get_existing_txt_rrset(zone_id, record_name)
if record_contents is None:
record_contents = []
add_records = record_contents[:]
if "\""+record_content+"\"" in record_contents:
@ -176,6 +178,8 @@ class _GoogleClient(object):
return
record_contents = self.get_existing_txt_rrset(zone_id, record_name)
if record_contents is None:
record_contents = ["\"" + record_content + "\""]
data = {
"kind": "dns#change",
@ -216,23 +220,32 @@ class _GoogleClient(object):
"""
Get existing TXT records from the RRset for the record name.
If an error occurs while requesting the record set, it is suppressed
and None is returned.
:param str zone_id: The ID of the managed zone.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:returns: List of TXT record values
:rtype: `list` of `string`
:returns: List of TXT record values or None
:rtype: `list` of `string` or `None`
"""
rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member
request = rrs_request.list(managedZone=zone_id, project=self.project_id)
response = request.execute()
# Add dot as the API returns absolute domains
record_name += "."
if response:
for rr in response["rrsets"]:
if rr["name"] == record_name and rr["type"] == "TXT":
return rr["rrdatas"]
return []
try:
response = request.execute()
except googleapiclient_errors.Error:
logger.info("Unable to list existing records. If you're "
"requesting a wildcard certificate, this might not work.")
logger.debug("Error was:", exc_info=True)
else:
if response:
for rr in response["rrsets"]:
if rr["name"] == record_name and rr["type"] == "TXT":
return rr["rrdatas"]
return None
def _find_managed_zone_id(self, domain):
"""

View file

@ -270,7 +270,19 @@ class GoogleClientTest(unittest.TestCase):
found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org")
self.assertEquals(found, ["\"example-txt-contents\""])
not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld")
self.assertEquals(not_found, [])
self.assertEquals(not_found, None)
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
@mock.patch('certbot_dns_google.dns_google.open',
mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True)
def test_get_existing_fallback(self, unused_credential_mock):
client, unused_changes = self._setUp_client_with_mock(
[{'managedZones': [{'id': self.zone}]}])
mock_execute = client.dns.resourceRecordSets.return_value.list.return_value.execute
mock_execute.side_effect = API_ERROR
rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org")
self.assertFalse(rrset)
def test_get_project_id(self):
from certbot_dns_google.dns_google import _GoogleClient

View file

@ -190,6 +190,7 @@ class PluginsRegistry(collections.Mapping):
def find_all(cls):
"""Find plugins using setuptools entry points."""
plugins = {}
# pylint: disable=not-callable
entry_points = itertools.chain(
pkg_resources.iter_entry_points(
constants.SETUPTOOLS_PLUGINS_ENTRY_POINT),