Commit 1100c8ed authored by Gael's avatar Gael
Browse files

- Separate OVH client class merged in Authenticator

- Try to get OVH zone to detect real OVH managed dns zone
- Detect record depending of the detected zone
- Global cleanup
parent fc2d0be7
......@@ -27,33 +27,32 @@ class Authenticator(dns_common.DNSAuthenticator):
description = ('Obtain certificates using a DNS TXT record (if you are using OVH for '
'DNS).')
ttl = 60
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
self.ovh = ovh.Client()
def more_info(self): # pylint: disable=missing-docstring,no-self-use
return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \
'the OVH API.'
def _setup_credentials(self):
pass
def _perform(self, domain, validation_name, validation):
self._get_ovh_client().add_txt_record(domain, validation_name, validation, self.ttl)
domain = self._get_zone_from_fqdn(domain)
validation_name = validation_name[0:validation_name.find(domain)-1]
logger.debug('Run perform on domain: {} - validation_name: {} - validation: {}'.format(domain,validation_name,validation))
self._add_txt_record(domain, validation_name, validation, self.ttl)
def _cleanup(self, domain, validation_name, validation):
self._get_ovh_client().del_txt_record(domain, validation_name, validation)
def _get_ovh_client(self):
return _OvhClient()
class _OvhClient(object):
"""
Encapsulates all communication with the OVH API.
"""
domain = self._get_zone_from_fqdn(domain)
validation_name = validation_name[0:validation_name.find(domain)-1]
logger.debug('Run cleanup on domain: {} - validation_name: {} - validation: {}'.format(domain,validation_name,validation))
self._del_txt_record(domain, validation_name, validation)
def __init__(self):
self.ovh = ovh.Client()
def add_txt_record(self, domain, record_name, record_content, record_ttl):
def _add_txt_record(self, domain, record_name, record_content, record_ttl):
"""
Add a TXT record using the supplied information.
Wait for the record to be visible in domain NS servers.
......@@ -64,20 +63,19 @@ class _OvhClient(object):
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the Ovh API
"""
try:
logger.debug('Attempting to add record to zone {0}: {1} {2} IN TXT {3}'.format(domain, record_name, record_ttl, record_content))
res = self.ovh.post("/domain/zone/{0}/record".format(domain), fieldType='TXT',
subDomain=record_name, target=record_content, ttl=record_ttl)
except ovh.exceptions.APIError as e:
logger.error('Encountered OVH APIError adding TXT record: %d %s', e, e)
logger.error('Encountered OVH APIError adding TXT record: {}'.format(e))
raise errors.PluginError('Error communicating with the Ovh API: {0}'.format(e))
record_id = res['id']
logger.debug('Successfully added TXT record with record_id: %s', record_id)
self.refresh_zone(domain)
self.waitfor_record(domain, record_name, record_content, 10, 30)
logger.debug('Successfully added TXT record with record_id: {}'.format(record_id))
self._refresh_zone(domain)
self._waitfor_record(domain, record_name, record_content, 10, 30)
def del_txt_record(self, domain, record_name, record_content):
def _del_txt_record(self, domain, record_name, record_content):
"""
Delete a TXT record using the supplied information.
......@@ -90,7 +88,6 @@ class _OvhClient(object):
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
"""
records = self.ovh.get("/domain/zone/{0}/record".format(domain), fieldType='TXT', subDomain=record_name)
if len(records) < 1:
raise Exception("No record found for {0}".format(record_name))
......@@ -99,19 +96,18 @@ class _OvhClient(object):
logger.debug(" + Deleting TXT record name: {0}".format(record_name))
self.ovh.delete('/domain/zone/{0}/record/{1}'.format(domain, records[0]))
def refresh_zone(self, domain):
def _refresh_zone(self, domain):
"""
Refresh the DNS zone
:param str domain: The domain to refresh
"""
self.ovh.post('/domain/zone/{0}/refresh'.format(domain))
logger.info("+ Zone refreshed on OVH side")
soa = self.ovh.get('/domain/zone/{0}/soa'.format(domain))
logger.debug("+ SOA SERIAL of zone: {0}".format(soa['serial']))
def waitfor_record(self, domain, record_name, record_content, check_interval, check_limit):
def _waitfor_record(self, domain, record_name, record_content, check_interval, check_limit):
"""
Wait for the visibility of the record in NS server
......@@ -121,7 +117,6 @@ class _OvhClient(object):
:param int check_interval: Sleep time between check
:param int check_limit: Limit of check loops before raising exception
"""
nameservers = dns.resolver.query(domain, 'NS')
resolver = dns.resolver.Resolver()
resolver.nameservers = []
......@@ -151,3 +146,30 @@ class _OvhClient(object):
time.sleep(check_interval)
raise Exception('Timeout waiting for record visibility in domain NS. Domain: {} - Record: {} - Value: {}'.format(domain, record_name, record_value))
def _get_zone_from_fqdn(self, fqdn):
"""
Return the ovh managed dns zone for the given FQDN
:param str domain: The FQDN
:return str zone: The dns zone
"""
zone = None
parts = fqdn.split('.')
for i in range(len(parts)):
z = '.'.join(parts[i:])
try:
res = self.ovh.get('/domain/zone/{0}'.format(z))
except ovh.exceptions.ResourceNotFoundError:
logger.debug('get zone from fqdn {}: zone {} not managed in ovh'.format(z, fqdn))
continue
except ovh.exceptions.APIError as e:
logger.error('Encountered OVH APIError trying to get zone {} from fqdn {}: {}'.format(z, fqdn, e))
raise errors.PluginError('Error while trying to detect zone')
logger.debug('OVH Managed zone found for fqdn {}: {}'.format(fqdn, z))
zone = z
break
if zone is None:
raise errors.PluginError('No OVH managed zone found for {}'.format(fqdn))
return z
......@@ -6,6 +6,7 @@ install_requires = [
'certbot',
'zope.interface',
'dnspython',
'ovh',
]
setup(
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment