- compatible with python3.6
- adapted to allow for wildcard-certs - adapted to allow for more than one Order per domain
This commit is contained in:
parent
6d423a99cb
commit
476530bd4f
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2018 Michael Porter
|
Copyright (c) 2018 Michael Porter, with modifications from Markus Pawlata
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
@ -2,6 +2,10 @@ import requests
|
|||||||
import urllib
|
import urllib
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from certbot.plugins import dns_common
|
from certbot.plugins import dns_common
|
||||||
|
try:
|
||||||
|
from urllib import quote # Python 2.X
|
||||||
|
except ImportError:
|
||||||
|
from urllib.parse import quote # Python 3+
|
||||||
|
|
||||||
|
|
||||||
_GandiConfig = namedtuple('_GandiConfig', ('api_key',))
|
_GandiConfig = namedtuple('_GandiConfig', ('api_key',))
|
||||||
@ -33,7 +37,7 @@ def _headers(cfg):
|
|||||||
|
|
||||||
def _get_url(*segs):
|
def _get_url(*segs):
|
||||||
return 'https://dns.api.gandi.net/api/v5/{}'.format(
|
return 'https://dns.api.gandi.net/api/v5/{}'.format(
|
||||||
'/'.join(urllib.quote(seg, safe='') for seg in segs)
|
'/'.join(quote(seg, safe='') for seg in segs)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -61,7 +65,10 @@ def _get_relative_name(base_domain, name):
|
|||||||
|
|
||||||
|
|
||||||
def _del_txt_record(cfg, base_domain, relative_name):
|
def _del_txt_record(cfg, base_domain, relative_name):
|
||||||
return _request(cfg, 'DELETE', ('zones', base_domain.zone_uuid, 'records', relative_name, 'TXT'))
|
return _request(
|
||||||
|
cfg,
|
||||||
|
'DELETE',
|
||||||
|
('zones', base_domain.zone_uuid, 'records', relative_name, 'TXT'))
|
||||||
|
|
||||||
|
|
||||||
def _update_record(cfg, domain, name, request_runner):
|
def _update_record(cfg, domain, name, request_runner):
|
||||||
@ -78,13 +85,36 @@ def _update_record(cfg, domain, name, request_runner):
|
|||||||
return None if response.ok else _get_response_message(response)
|
return None if response.ok else _get_response_message(response)
|
||||||
|
|
||||||
|
|
||||||
|
def get_txt_records(cfg, domain, name):
|
||||||
|
|
||||||
|
base_domain = _get_base_domain(cfg, domain)
|
||||||
|
if base_domain is None:
|
||||||
|
return 'Unable to get base domain for "{}"'.format(domain)
|
||||||
|
relative_name = _get_relative_name(base_domain, name)
|
||||||
|
if relative_name is None:
|
||||||
|
return 'Unable to derive relative name for "{}"'.format(name)
|
||||||
|
|
||||||
|
response = _request(
|
||||||
|
cfg,
|
||||||
|
'GET',
|
||||||
|
('zones', base_domain.zone_uuid, 'records', relative_name, 'TXT'))
|
||||||
|
if response.ok:
|
||||||
|
return response.json().get('rrset_values')
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def add_txt_record(cfg, domain, name, value):
|
def add_txt_record(cfg, domain, name, value):
|
||||||
|
|
||||||
def requester(base_domain, relative_name):
|
def requester(base_domain, relative_name):
|
||||||
_del_txt_record(cfg, base_domain, relative_name)
|
_del_txt_record(cfg, base_domain, relative_name)
|
||||||
return _request(cfg, 'POST',
|
return _request(
|
||||||
('zones', base_domain.zone_uuid, 'records', relative_name, 'TXT'),
|
cfg,
|
||||||
json={'rrset_values': [value]})
|
'POST',
|
||||||
|
('zones', base_domain.zone_uuid, 'records', relative_name, 'TXT'),
|
||||||
|
json={
|
||||||
|
'rrset_values': value if isinstance(value, list) else [value]
|
||||||
|
})
|
||||||
|
|
||||||
return _update_record(cfg, domain, name, requester)
|
return _update_record(cfg, domain, name, requester)
|
||||||
|
|
||||||
@ -95,5 +125,3 @@ def del_txt_record(cfg, domain, name):
|
|||||||
return _del_txt_record(cfg, base_domain, relative_name)
|
return _del_txt_record(cfg, base_domain, relative_name)
|
||||||
|
|
||||||
return _update_record(cfg, domain, name, requester)
|
return _update_record(cfg, domain, name, requester)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import zope.interface
|
import zope.interface
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from certbot import interfaces, errors
|
from certbot import interfaces
|
||||||
|
from certbot.errors import PluginError
|
||||||
from certbot.plugins import dns_common
|
from certbot.plugins import dns_common
|
||||||
|
|
||||||
from . import gandi_api
|
from . import gandi_api
|
||||||
@ -15,24 +16,21 @@ logger = logging.getLogger(__name__)
|
|||||||
class Authenticator(dns_common.DNSAuthenticator):
|
class Authenticator(dns_common.DNSAuthenticator):
|
||||||
"""DNS Authenticator for Gandi (using LiveDNS)."""
|
"""DNS Authenticator for Gandi (using LiveDNS)."""
|
||||||
|
|
||||||
description = 'Obtain certificates using a DNS TXT record (if you are using Gandi for DNS).'
|
description = ('Obtain certificates using a DNS TXT record (if you are '
|
||||||
|
'using Gandi for DNS).')
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super(Authenticator, self).__init__(*args, **kwargs)
|
super(Authenticator, self).__init__(*args, **kwargs)
|
||||||
self.credentials = None
|
self.credentials = None
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
|
def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
|
||||||
super(Authenticator, cls).add_parser_arguments(add)
|
super(Authenticator, cls).add_parser_arguments(add)
|
||||||
add('credentials', help='Gandi credentials INI file.')
|
add('credentials', help='Gandi credentials INI file.')
|
||||||
|
|
||||||
|
|
||||||
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 '
|
||||||
'the Gandi LiveDNS API.'
|
'dns-01 challenge using the Gandi LiveDNS API.')
|
||||||
|
|
||||||
|
|
||||||
def _setup_credentials(self):
|
def _setup_credentials(self):
|
||||||
self.credentials = self._configure_credentials(
|
self.credentials = self._configure_credentials(
|
||||||
@ -43,18 +41,25 @@ class Authenticator(dns_common.DNSAuthenticator):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _perform(self, domain, validation_name, validation):
|
def _perform(self, domain, validation_name, validation):
|
||||||
error = gandi_api.add_txt_record(self._get_gandi_config(), domain, validation_name, validation)
|
previous_values = gandi_api.get_txt_records(
|
||||||
|
self._get_gandi_config(), domain, validation_name)
|
||||||
|
previous_values.append(validation)
|
||||||
|
error = gandi_api.add_txt_record(
|
||||||
|
self._get_gandi_config(), domain, validation_name, previous_values)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
raise errors.PluginError('An error occurred adding the DNS TXT record: {0}'.format(error))
|
raise PluginError(
|
||||||
|
'An error occurred adding the DNS TXT record: {}'.format(
|
||||||
|
error))
|
||||||
|
|
||||||
def _cleanup(self, domain, validation_name, validation):
|
def _cleanup(self, domain, validation_name, validation):
|
||||||
error = gandi_api.del_txt_record(self._get_gandi_config(), domain, validation_name)
|
error = gandi_api.del_txt_record(
|
||||||
|
self._get_gandi_config(), domain, validation_name)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
logger.warn('Unable to find or delete the DNS TXT record: %s', error)
|
logger.warn(
|
||||||
|
('Unable to find or delete the DNS TXT record: {}, this '
|
||||||
|
'is normal if you had multiple challenges in the same '
|
||||||
|
'zone.').format(error))
|
||||||
|
|
||||||
def _get_gandi_config(self):
|
def _get_gandi_config(self):
|
||||||
return gandi_api.get_config(api_key = self.credentials.conf('api-key'))
|
return gandi_api.get_config(api_key=self.credentials.conf('api-key'))
|
||||||
|
Loading…
Reference in New Issue
Block a user