dns_dnspod.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. """DNS Authenticator for DNSPod DNS."""
  2. import logging
  3. import zope.interface
  4. from requests.exceptions import HTTPError
  5. from lexicon.providers import dnspod
  6. from certbot import errors
  7. from certbot import interfaces
  8. from certbot.plugins import dns_common
  9. from certbot.plugins import dns_common_lexicon
  10. logger = logging.getLogger(__name__)
  11. ACCOUNT_URL = 'https://www.dnspod.cn/console/user/security'
  12. @zope.interface.implementer(interfaces.IAuthenticator)
  13. @zope.interface.provider(interfaces.IPluginFactory)
  14. class Authenticator(dns_common.DNSAuthenticator):
  15. """DNS Authenticator for DNSPod DNS
  16. This Authenticator uses the DNSPod DNS API to fulfill a dns-01 challenge.
  17. """
  18. description = 'Obtain certificates using a DNS TXT record (if you are using DNSPod for DNS).'
  19. ttl = 600
  20. def __init__(self, *args, **kwargs):
  21. super(Authenticator, self).__init__(*args, **kwargs)
  22. self.credentials = None
  23. @classmethod
  24. def add_parser_arguments(cls, add): # pylint: disable=arguments-differ
  25. super(Authenticator, cls).add_parser_arguments(add, default_propagation_seconds=30)
  26. add('credentials', help='DNSPod credentials INI file.')
  27. def more_info(self): # pylint: disable=missing-docstring,no-self-use
  28. return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \
  29. 'the DNSPod API.'
  30. def _setup_credentials(self):
  31. self.credentials = self._configure_credentials(
  32. 'credentials',
  33. 'DNSPod credentials INI file',
  34. {
  35. 'api-id': 'API ID for DNSPod account, obtained from {0}'.format(ACCOUNT_URL),
  36. 'api-token': 'API Token for DNSPod account, obtained from {0}'
  37. .format(ACCOUNT_URL)
  38. }
  39. )
  40. def _perform(self, domain, validation_name, validation):
  41. self._get_dnspod_client().add_txt_record(domain, validation_name, validation)
  42. def _cleanup(self, domain, validation_name, validation):
  43. self._get_dnspod_client().del_txt_record(domain, validation_name, validation)
  44. def _get_dnspod_client(self):
  45. return _DNSPodLexiconClient(self.credentials.conf('api-id'),
  46. self.credentials.conf('api-token'),
  47. self.ttl)
  48. class _DNSPodLexiconClient(dns_common_lexicon.LexiconClient):
  49. """
  50. Encapsulates all communication with the DNSPod via Lexicon.
  51. """
  52. def __init__(self, api_id, api_token, ttl):
  53. super(_DNSPodLexiconClient, self).__init__()
  54. self.provider = dnspod.Provider({
  55. 'auth_username': api_id,
  56. 'auth_token': api_token,
  57. 'ttl': ttl,
  58. })
  59. def _find_domain_id(self, domain):
  60. """
  61. Find the domain_id for a given domain.
  62. Rewrite certbot/plugins/dns_common_lexicon.py to ensure compatibility
  63. for Lexicon 2.x and 3.x
  64. :param str domain: The domain for which to find the domain_id.
  65. :raises errors.PluginError: if the domain_id cannot be found.
  66. """
  67. domain_name_guesses = dns_common.base_domain_name_guesses(domain)
  68. for domain_name in domain_name_guesses:
  69. try:
  70. if hasattr(self.provider, 'options'):
  71. # For Lexicon 2.x
  72. self.provider.options['domain'] = domain_name
  73. else:
  74. # For Lexicon 3.x
  75. self.provider.domain = domain_name
  76. self.provider.authenticate()
  77. return # If `authenticate` doesn't throw an exception, we've found the right name
  78. except HTTPError as e:
  79. result = self._handle_http_error(e, domain_name)
  80. if result:
  81. raise result
  82. except Exception as e: # pylint: disable=broad-except
  83. result = self._handle_general_error(e, domain_name)
  84. if result:
  85. raise result
  86. raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}'
  87. .format(domain, domain_name_guesses))
  88. def _handle_http_error(self, e, domain_name):
  89. hint = None
  90. if str(e).startswith('400 Client Error:'):
  91. hint = 'Are your API ID and API Token values correct?'
  92. return errors.PluginError('Error determining zone identifier for {0}: {1}.{2}'
  93. .format(domain_name, e, ' ({0})'.format(hint) if hint else ''))
  94. def _handle_general_error(self, e, domain_name):
  95. if not (str(e).startswith('Domain name invalid') or str(e).find('域名不正确') >= 0):
  96. return errors.PluginError('Unexpected error determining zone identifier for {0}: {1}'
  97. .format(domain_name, e))