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): ...@@ -27,33 +27,32 @@ class Authenticator(dns_common.DNSAuthenticator):
description = ('Obtain certificates using a DNS TXT record (if you are using OVH for ' description = ('Obtain certificates using a DNS TXT record (if you are using OVH for '
'DNS).') 'DNS).')
ttl = 60 ttl = 60
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs) super(Authenticator, self).__init__(*args, **kwargs)
self.ovh = ovh.Client()
def more_info(self): # pylint: disable=missing-docstring,no-self-use 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 ' + \ return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \
'the OVH API.' 'the OVH API.'
def _setup_credentials(self):
pass
def _perform(self, domain, validation_name, validation): 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): def _cleanup(self, domain, validation_name, validation):
self._get_ovh_client().del_txt_record(domain, validation_name, validation) domain = self._get_zone_from_fqdn(domain)
validation_name = validation_name[0:validation_name.find(domain)-1]
def _get_ovh_client(self): logger.debug('Run cleanup on domain: {} - validation_name: {} - validation: {}'.format(domain,validation_name,validation))
return _OvhClient() self._del_txt_record(domain, validation_name, validation)
class _OvhClient(object):
"""
Encapsulates all communication with the OVH API.
"""
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. Add a TXT record using the supplied information.
Wait for the record to be visible in domain NS servers. Wait for the record to be visible in domain NS servers.
...@@ -64,20 +63,19 @@ class _OvhClient(object): ...@@ -64,20 +63,19 @@ class _OvhClient(object):
:param int record_ttl: The record TTL (number of seconds that the record may be cached). :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 :raises certbot.errors.PluginError: if an error occurs communicating with the Ovh API
""" """
try: try:
logger.debug('Attempting to add record to zone {0}: {1} {2} IN TXT {3}'.format(domain, record_name, record_ttl, record_content)) 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', res = self.ovh.post("/domain/zone/{0}/record".format(domain), fieldType='TXT',
subDomain=record_name, target=record_content, ttl=record_ttl) subDomain=record_name, target=record_content, ttl=record_ttl)
except ovh.exceptions.APIError as e: 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)) raise errors.PluginError('Error communicating with the Ovh API: {0}'.format(e))
record_id = res['id'] record_id = res['id']
logger.debug('Successfully added TXT record with record_id: %s', record_id) logger.debug('Successfully added TXT record with record_id: {}'.format(record_id))
self.refresh_zone(domain) self._refresh_zone(domain)
self.waitfor_record(domain, record_name, record_content, 10, 30) 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. Delete a TXT record using the supplied information.
...@@ -90,7 +88,6 @@ class _OvhClient(object): ...@@ -90,7 +88,6 @@ class _OvhClient(object):
:param str record_name: The record name (typically beginning with '_acme-challenge.'). :param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation). :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) records = self.ovh.get("/domain/zone/{0}/record".format(domain), fieldType='TXT', subDomain=record_name)
if len(records) < 1: if len(records) < 1:
raise Exception("No record found for {0}".format(record_name)) raise Exception("No record found for {0}".format(record_name))
...@@ -99,19 +96,18 @@ class _OvhClient(object): ...@@ -99,19 +96,18 @@ class _OvhClient(object):
logger.debug(" + Deleting TXT record name: {0}".format(record_name)) logger.debug(" + Deleting TXT record name: {0}".format(record_name))
self.ovh.delete('/domain/zone/{0}/record/{1}'.format(domain, records[0])) 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 Refresh the DNS zone
:param str domain: The domain to refresh :param str domain: The domain to refresh
""" """
self.ovh.post('/domain/zone/{0}/refresh'.format(domain)) self.ovh.post('/domain/zone/{0}/refresh'.format(domain))
logger.info("+ Zone refreshed on OVH side") logger.info("+ Zone refreshed on OVH side")
soa = self.ovh.get('/domain/zone/{0}/soa'.format(domain)) soa = self.ovh.get('/domain/zone/{0}/soa'.format(domain))
logger.debug("+ SOA SERIAL of zone: {0}".format(soa['serial'])) 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 Wait for the visibility of the record in NS server
...@@ -121,7 +117,6 @@ class _OvhClient(object): ...@@ -121,7 +117,6 @@ class _OvhClient(object):
:param int check_interval: Sleep time between check :param int check_interval: Sleep time between check
:param int check_limit: Limit of check loops before raising exception :param int check_limit: Limit of check loops before raising exception
""" """
nameservers = dns.resolver.query(domain, 'NS') nameservers = dns.resolver.query(domain, 'NS')
resolver = dns.resolver.Resolver() resolver = dns.resolver.Resolver()
resolver.nameservers = [] resolver.nameservers = []
...@@ -151,3 +146,30 @@ class _OvhClient(object): ...@@ -151,3 +146,30 @@ class _OvhClient(object):
time.sleep(check_interval) time.sleep(check_interval)
raise Exception('Timeout waiting for record visibility in domain NS. Domain: {} - Record: {} - Value: {}'.format(domain, record_name, record_value)) 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 = [ ...@@ -6,6 +6,7 @@ install_requires = [
'certbot', 'certbot',
'zope.interface', 'zope.interface',
'dnspython', 'dnspython',
'ovh',
] ]
setup( setup(
......
Supports Markdown
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