1
0
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:
Trinopoty Biswas
2019-04-24 20:41:42 +00:00
committed by Adrien Ferrand
parent fb83a1ac09
commit 333ea90d1b
6 changed files with 146 additions and 12 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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'):

View File

@@ -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

View File

@@ -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

View File

@@ -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',