mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 18:56:55 +02:00
Refactor Lexicon-based DNS plugins (#9746)
* Refactor Lexicon-based DNS plugins and upgrade minimal version of Lexicon * Relax filterwarning to comply with envs where boto3 is not installed * Update pinned dependencies * Use our previous method to deprecate part of modules * Safe import internally * Add changelog Co-authored-by: Brad Warren <bmw@users.noreply.github.com>
This commit is contained in:
co-authored by
Brad Warren
parent
694c758db7
commit
732a3ac962
@@ -2,23 +2,19 @@
|
||||
import logging
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Optional
|
||||
|
||||
from lexicon.providers import sakuracloud
|
||||
from requests import HTTPError
|
||||
|
||||
from certbot import errors
|
||||
from certbot.plugins import dns_common
|
||||
from certbot.plugins import dns_common_lexicon
|
||||
from certbot.plugins.dns_common import CredentialsConfiguration
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
APIKEY_URL = "https://secure.sakura.ad.jp/cloud/#!/apikey/top/"
|
||||
|
||||
|
||||
class Authenticator(dns_common.DNSAuthenticator):
|
||||
class Authenticator(dns_common_lexicon.LexiconDNSAuthenticator):
|
||||
"""DNS Authenticator for Sakura Cloud DNS
|
||||
|
||||
This Authenticator uses the Sakura Cloud API to fulfill a dns-01 challenge.
|
||||
@@ -26,11 +22,15 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
|
||||
description = 'Obtain certificates using a DNS TXT record ' + \
|
||||
'(if you are using Sakura Cloud for DNS).'
|
||||
ttl = 60
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
self.credentials: Optional[CredentialsConfiguration] = None
|
||||
self._add_provider_option('api-token',
|
||||
f'API token for Sakura Cloud API obtained from {APIKEY_URL}',
|
||||
'auth_token')
|
||||
self._add_provider_option('api-secret',
|
||||
f'API secret for Sakura Cloud API obtained from {APIKEY_URL}',
|
||||
'auth_secret')
|
||||
|
||||
@classmethod
|
||||
def add_parser_arguments(cls, add: Callable[..., None],
|
||||
@@ -43,50 +43,9 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
return 'This plugin configures a DNS TXT record to respond to a dns-01 challenge using ' + \
|
||||
'the Sakura Cloud API.'
|
||||
|
||||
def _setup_credentials(self) -> None:
|
||||
self.credentials = self._configure_credentials(
|
||||
'credentials',
|
||||
'Sakura Cloud credentials file',
|
||||
{
|
||||
'api-token': f'API token for Sakura Cloud API obtained from {APIKEY_URL}',
|
||||
'api-secret': f'API secret for Sakura Cloud API obtained from {APIKEY_URL}',
|
||||
}
|
||||
)
|
||||
|
||||
def _perform(self, domain: str, validation_name: str, validation: str) -> None:
|
||||
self._get_sakuracloud_client().add_txt_record(
|
||||
domain, validation_name, validation)
|
||||
|
||||
def _cleanup(self, domain: str, validation_name: str, validation: str) -> None:
|
||||
self._get_sakuracloud_client().del_txt_record(
|
||||
domain, validation_name, validation)
|
||||
|
||||
def _get_sakuracloud_client(self) -> "_SakuraCloudLexiconClient":
|
||||
if not self.credentials: # pragma: no cover
|
||||
raise errors.Error("Plugin has not been prepared.")
|
||||
return _SakuraCloudLexiconClient(
|
||||
cast(str, self.credentials.conf('api-token')),
|
||||
cast(str, self.credentials.conf('api-secret')),
|
||||
self.ttl
|
||||
)
|
||||
|
||||
|
||||
class _SakuraCloudLexiconClient(dns_common_lexicon.LexiconClient):
|
||||
"""
|
||||
Encapsulates all communication with the Sakura Cloud via Lexicon.
|
||||
"""
|
||||
|
||||
def __init__(self, api_token: str, api_secret: str, ttl: int) -> None:
|
||||
super().__init__()
|
||||
|
||||
config = dns_common_lexicon.build_lexicon_config('sakuracloud', {
|
||||
'ttl': ttl,
|
||||
}, {
|
||||
'auth_token': api_token,
|
||||
'auth_secret': api_secret,
|
||||
})
|
||||
|
||||
self.provider = sakuracloud.Provider(config)
|
||||
@property
|
||||
def _provider_name(self) -> str:
|
||||
return 'sakuracloud'
|
||||
|
||||
def _handle_http_error(self, e: HTTPError, domain_name: str) -> Optional[errors.PluginError]:
|
||||
if domain_name in str(e) and (str(e).startswith('404 Client Error: Not Found for url:')):
|
||||
|
||||
+5
-21
@@ -1,7 +1,5 @@
|
||||
"""Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud."""
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import pytest
|
||||
@@ -16,8 +14,12 @@ from certbot.tests import util as test_util
|
||||
API_TOKEN = '00000000-0000-0000-0000-000000000000'
|
||||
API_SECRET = 'MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw'
|
||||
|
||||
|
||||
class AuthenticatorTest(test_util.TempDirTestCase,
|
||||
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
|
||||
dns_test_common_lexicon.BaseLexiconDNSAuthenticatorTest):
|
||||
|
||||
DOMAIN_NOT_FOUND = HTTPError(f'404 Client Error: Not Found for url: {DOMAIN}.')
|
||||
LOGIN_ERROR = HTTPError(f'401 Client Error: Unauthorized for url: {DOMAIN}.')
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
@@ -35,24 +37,6 @@ class AuthenticatorTest(test_util.TempDirTestCase,
|
||||
|
||||
self.auth = Authenticator(self.config, "sakuracloud")
|
||||
|
||||
self.mock_client = mock.MagicMock()
|
||||
# _get_sakuracloud_client | pylint: disable=protected-access
|
||||
self.auth._get_sakuracloud_client = mock.MagicMock(return_value=self.mock_client)
|
||||
|
||||
|
||||
class SakuraCloudLexiconClientTest(unittest.TestCase,
|
||||
dns_test_common_lexicon.BaseLexiconClientTest):
|
||||
DOMAIN_NOT_FOUND = HTTPError('404 Client Error: Not Found for url: {0}.'.format(DOMAIN))
|
||||
LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN))
|
||||
|
||||
def setUp(self):
|
||||
from certbot_dns_sakuracloud._internal.dns_sakuracloud import _SakuraCloudLexiconClient
|
||||
|
||||
self.client = _SakuraCloudLexiconClient(API_TOKEN, API_SECRET, 0)
|
||||
|
||||
self.provider_mock = mock.MagicMock()
|
||||
self.client.provider = self.provider_mock
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main(sys.argv[1:] + [__file__])) # pragma: no cover
|
||||
|
||||
@@ -7,7 +7,7 @@ from setuptools import setup
|
||||
version = '2.7.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'dns-lexicon>=3.2.1',
|
||||
'dns-lexicon>=3.14.1',
|
||||
'setuptools>=41.6.0',
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user