mirror of
https://github.com/certbot/certbot.git
synced 2026-01-26 07:41:33 +03:00
Added support for linode version 4 tokens (#6588)
* certbot-dns-linode : Added support for linode version 4 tokens * certbot-dns-linode : Added credentials ini option to override automatic api version detection * certbot-dns-linode : Added clearer messages and documentation based on review * certbot-dns-linode : Added check for empty 'linode_version' config instead of missing * certbot-dns-linode : Fix rebase on master * certbot-dns-linode : Updated local-oldest-requirements.txt * Updated CHANGELOG to indicate Linode v4 API key support
This commit is contained in:
committed by
Adrien Ferrand
parent
fb83a1ac09
commit
333ea90d1b
@@ -20,6 +20,8 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response
|
||||
with a `keyAuthorization` if sending the response without this field caused a
|
||||
`malformed` error to be received from the ACME server.
|
||||
* Linode DNS plugin now supports api keys created from their new panel
|
||||
at [cloud.linode.com](https://cloud.linode.com)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ Credentials
|
||||
|
||||
Use of this plugin requires a configuration file containing Linode API
|
||||
credentials, obtained from your Linode account's `Applications & API
|
||||
Tokens page <https://manager.linode.com/profile/api>`_.
|
||||
Tokens page (legacy) <https://manager.linode.com/profile/api>`_ or `Applications
|
||||
& API Tokens page (new) <https://cloud.linode.com/profile/tokens>`_.
|
||||
|
||||
.. code-block:: ini
|
||||
:name: credentials.ini
|
||||
@@ -35,6 +36,7 @@ Tokens page <https://manager.linode.com/profile/api>`_.
|
||||
|
||||
# Linode API credentials used by Certbot
|
||||
dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
|
||||
dns_linode_version = [<blank>|3|4]
|
||||
|
||||
The path to this file can be provided interactively or using the
|
||||
``--dns-linode-credentials`` command-line argument. Certbot records the path
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
"""DNS Authenticator for Linode."""
|
||||
import logging
|
||||
import re
|
||||
|
||||
import zope.interface
|
||||
from lexicon.providers import linode
|
||||
from lexicon.providers import linode4
|
||||
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
@@ -12,6 +14,7 @@ from certbot.plugins import dns_common_lexicon
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
API_KEY_URL = 'https://manager.linode.com/profile/api'
|
||||
API_KEY_URL_V4 = 'https://cloud.linode.com/profile/tokens'
|
||||
|
||||
@zope.interface.implementer(interfaces.IAuthenticator)
|
||||
@zope.interface.provider(interfaces.IPluginFactory)
|
||||
@@ -41,7 +44,8 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
'credentials',
|
||||
'Linode credentials INI file',
|
||||
{
|
||||
'key': 'API key for Linode account, obtained from {0}'.format(API_KEY_URL)
|
||||
'key': 'API key for Linode account, obtained from {0} or {1}'
|
||||
.format(API_KEY_URL, API_KEY_URL_V4)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -52,7 +56,23 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
self._get_linode_client().del_txt_record(domain, validation_name, validation)
|
||||
|
||||
def _get_linode_client(self):
|
||||
return _LinodeLexiconClient(self.credentials.conf('key'))
|
||||
api_key = self.credentials.conf('key')
|
||||
api_version = self.credentials.conf('version')
|
||||
if api_version == '':
|
||||
api_version = None
|
||||
|
||||
if not api_version:
|
||||
api_version = 3
|
||||
|
||||
# Match for v4 api key
|
||||
regex_v4 = re.compile('^[0-9a-f]{64}$')
|
||||
regex_match = regex_v4.match(api_key)
|
||||
if regex_match:
|
||||
api_version = 4
|
||||
else:
|
||||
api_version = int(api_version)
|
||||
|
||||
return _LinodeLexiconClient(api_key, api_version)
|
||||
|
||||
|
||||
class _LinodeLexiconClient(dns_common_lexicon.LexiconClient):
|
||||
@@ -60,14 +80,26 @@ class _LinodeLexiconClient(dns_common_lexicon.LexiconClient):
|
||||
Encapsulates all communication with the Linode API.
|
||||
"""
|
||||
|
||||
def __init__(self, api_key):
|
||||
def __init__(self, api_key, api_version):
|
||||
super(_LinodeLexiconClient, self).__init__()
|
||||
|
||||
config = dns_common_lexicon.build_lexicon_config('linode', {}, {
|
||||
'auth_token': api_key,
|
||||
})
|
||||
self.api_version = api_version
|
||||
|
||||
self.provider = linode.Provider(config)
|
||||
if api_version == 3:
|
||||
config = dns_common_lexicon.build_lexicon_config('linode', {}, {
|
||||
'auth_token': api_key,
|
||||
})
|
||||
|
||||
self.provider = linode.Provider(config)
|
||||
elif api_version == 4:
|
||||
config = dns_common_lexicon.build_lexicon_config('linode4', {}, {
|
||||
'auth_token': api_key,
|
||||
})
|
||||
|
||||
self.provider = linode4.Provider(config)
|
||||
else:
|
||||
raise errors.PluginError('Invalid api version specified: {0}. (Supported: 3, 4)'
|
||||
.format(api_version))
|
||||
|
||||
def _handle_general_error(self, e, domain_name):
|
||||
if not str(e).startswith('Domain not found'):
|
||||
|
||||
@@ -4,12 +4,16 @@ import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot import errors
|
||||
from certbot.compat import os
|
||||
from certbot.plugins import dns_test_common
|
||||
from certbot.plugins import dns_test_common_lexicon
|
||||
from certbot.tests import util as test_util
|
||||
from certbot_dns_linode.dns_linode import Authenticator
|
||||
|
||||
TOKEN = 'a-token'
|
||||
TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64'
|
||||
TOKEN_V4 = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
|
||||
class AuthenticatorTest(test_util.TempDirTestCase,
|
||||
dns_test_common_lexicon.BaseLexiconAuthenticatorTest):
|
||||
@@ -17,8 +21,6 @@ class AuthenticatorTest(test_util.TempDirTestCase,
|
||||
def setUp(self):
|
||||
super(AuthenticatorTest, self).setUp()
|
||||
|
||||
from certbot_dns_linode.dns_linode import Authenticator
|
||||
|
||||
path = os.path.join(self.tempdir, 'file.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN}, path)
|
||||
|
||||
@@ -31,6 +33,89 @@ class AuthenticatorTest(test_util.TempDirTestCase,
|
||||
# _get_linode_client | pylint: disable=protected-access
|
||||
self.auth._get_linode_client = mock.MagicMock(return_value=self.mock_client)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_3_detection(self):
|
||||
path = os.path.join(self.tempdir, 'file_3_auto.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V3}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(3, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_4_detection(self):
|
||||
path = os.path.join(self.tempdir, 'file_4_auto.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V4}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(4, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_3_detection_empty_version(self):
|
||||
path = os.path.join(self.tempdir, 'file_3_auto_empty.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": ""}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(3, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_4_detection_empty_version(self):
|
||||
path = os.path.join(self.tempdir, 'file_4_auto_empty.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": ""}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(4, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_3_manual(self):
|
||||
path = os.path.join(self.tempdir, 'file_3_manual.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V4, "linode_version": 3}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(3, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_4_manual(self):
|
||||
path = os.path.join(self.tempdir, 'file_4_manual.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 4}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
client = auth._get_linode_client()
|
||||
self.assertEqual(4, client.api_version)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
def test_api_version_error(self):
|
||||
path = os.path.join(self.tempdir, 'file_version_error.ini')
|
||||
dns_test_common.write({"linode_key": TOKEN_V3, "linode_version": 5}, path)
|
||||
|
||||
config = mock.MagicMock(linode_credentials=path,
|
||||
linode_propagation_seconds=0)
|
||||
auth = Authenticator(config, "linode")
|
||||
auth._setup_credentials()
|
||||
self.assertRaises(errors.PluginError, auth._get_linode_client)
|
||||
|
||||
class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest):
|
||||
|
||||
DOMAIN_NOT_FOUND = Exception('Domain not found')
|
||||
@@ -38,7 +123,19 @@ class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex
|
||||
def setUp(self):
|
||||
from certbot_dns_linode.dns_linode import _LinodeLexiconClient
|
||||
|
||||
self.client = _LinodeLexiconClient(TOKEN)
|
||||
self.client = _LinodeLexiconClient(TOKEN, 3)
|
||||
|
||||
self.provider_mock = mock.MagicMock()
|
||||
self.client.provider = self.provider_mock
|
||||
|
||||
class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest):
|
||||
|
||||
DOMAIN_NOT_FOUND = Exception('Domain not found')
|
||||
|
||||
def setUp(self):
|
||||
from certbot_dns_linode.dns_linode import _LinodeLexiconClient
|
||||
|
||||
self.client = _LinodeLexiconClient(TOKEN, 4)
|
||||
|
||||
self.provider_mock = mock.MagicMock()
|
||||
self.client.provider = self.provider_mock
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Remember to update setup.py to match the package versions below.
|
||||
acme[dev]==0.31.0
|
||||
-e .[dev]
|
||||
dns-lexicon==2.2.3
|
||||
|
||||
@@ -7,7 +7,7 @@ version = '0.34.0.dev0'
|
||||
install_requires = [
|
||||
'acme>=0.31.0',
|
||||
'certbot>=0.34.0.dev0',
|
||||
'dns-lexicon>=2.2.1',
|
||||
'dns-lexicon>=2.2.3',
|
||||
'mock',
|
||||
'setuptools',
|
||||
'zope.interface',
|
||||
|
||||
Reference in New Issue
Block a user