mirror of
https://github.com/certbot/certbot.git
synced 2026-07-28 16:54:56 +02:00
Add an option to dns_rfc2136 plugin to specify an authorative base domain. (#7029)
* Add an option to dns_rfc2136 plugin to explicitly specify an authorative base domain. * Updated CHANGELOG mentioning added base domain option * Made the comment on the new option more clear on auto-detection * Updated comment on how the authorative base domain is determined
This commit is contained in:
committed by
Adrien Ferrand
parent
9a7f774706
commit
5ab6a597b0
+3
-1
@@ -6,7 +6,9 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
* dns_rfc2136 plugin now supports explicitly specifing an authorative
|
||||
base domain for cases when the automatic method does not work (e.g.
|
||||
Split horizon DNS)
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ Credentials
|
||||
-----------
|
||||
|
||||
Use of this plugin requires a configuration file containing the target DNS
|
||||
server and optional port that supports RFC 2136 Dynamic Updates, the name
|
||||
of the TSIG key, the TSIG key secret itself and the algorithm used if it's
|
||||
server, optional authorative domain and optional port that supports RFC 2136 Dynamic Updates,
|
||||
the name of the TSIG key, the TSIG key secret itself and the algorithm used if it's
|
||||
different to HMAC-MD5.
|
||||
|
||||
.. code-block:: ini
|
||||
@@ -33,6 +33,8 @@ different to HMAC-MD5.
|
||||
dns_rfc2136_server = 192.0.2.1
|
||||
# Target DNS port
|
||||
dns_rfc2136_port = 53
|
||||
# Authorative domain (optional, will try to auto-detect if missing)
|
||||
dns_rfc2136_base_domain = example.com
|
||||
# TSIG key name
|
||||
dns_rfc2136_name = keyname.
|
||||
# TSIG key secret
|
||||
|
||||
@@ -79,25 +79,33 @@ class Authenticator(dns_common.DNSAuthenticator):
|
||||
self._get_rfc2136_client().del_txt_record(validation_name, validation)
|
||||
|
||||
def _get_rfc2136_client(self):
|
||||
key = _RFC2136Key(self.credentials.conf('name'),
|
||||
self.credentials.conf('secret'),
|
||||
self.ALGORITHMS.get(self.credentials.conf('algorithm'),
|
||||
dns.tsig.HMAC_MD5))
|
||||
return _RFC2136Client(self.credentials.conf('server'),
|
||||
int(self.credentials.conf('port') or self.PORT),
|
||||
self.credentials.conf('name'),
|
||||
self.credentials.conf('secret'),
|
||||
self.ALGORITHMS.get(self.credentials.conf('algorithm'),
|
||||
dns.tsig.HMAC_MD5))
|
||||
key,
|
||||
self.credentials.conf('base-domain'))
|
||||
|
||||
class _RFC2136Key(object):
|
||||
def __init__(self, name, secret, algorithm):
|
||||
self.name = name
|
||||
self.secret = secret
|
||||
self.algorithm = algorithm
|
||||
|
||||
class _RFC2136Client(object):
|
||||
"""
|
||||
Encapsulates all communication with the target DNS server.
|
||||
"""
|
||||
def __init__(self, server, port, key_name, key_secret, key_algorithm):
|
||||
def __init__(self, server, port, base_domain, key):
|
||||
self.server = server
|
||||
self.port = port
|
||||
self.keyring = dns.tsigkeyring.from_text({
|
||||
key_name: key_secret
|
||||
key.name: key.secret
|
||||
})
|
||||
self.algorithm = key_algorithm
|
||||
self.algorithm = key.algorithm
|
||||
self.base_domain = base_domain
|
||||
|
||||
def add_txt_record(self, record_name, record_content, record_ttl):
|
||||
"""
|
||||
@@ -171,23 +179,33 @@ class _RFC2136Client(object):
|
||||
|
||||
def _find_domain(self, record_name):
|
||||
"""
|
||||
Find the closest domain with an SOA record for a given domain name.
|
||||
If 'base_domain' option is specified check if the requested domain matches this base domain
|
||||
and return it. If not explicitly specified find the closest domain with an SOA record for
|
||||
the given domain name.
|
||||
|
||||
:param str record_name: The record name for which to find the closest SOA record.
|
||||
:param str record_name: The record name for which to find the base domain.
|
||||
:returns: The domain, if found.
|
||||
:rtype: str
|
||||
:raises certbot.errors.PluginError: if no SOA record can be found.
|
||||
"""
|
||||
|
||||
domain_name_guesses = dns_common.base_domain_name_guesses(record_name)
|
||||
if self.base_domain:
|
||||
if not record_name.endswith(self.base_domain):
|
||||
raise errors.PluginError('Requested domain {0} does not match specified base '
|
||||
'domain {1}.'
|
||||
.format(record_name, self.base_domain))
|
||||
else:
|
||||
return self.base_domain
|
||||
else:
|
||||
domain_name_guesses = dns_common.base_domain_name_guesses(record_name)
|
||||
|
||||
# Loop through until we find an authoritative SOA record
|
||||
for guess in domain_name_guesses:
|
||||
if self._query_soa(guess):
|
||||
return guess
|
||||
# Loop through until we find an authoritative SOA record
|
||||
for guess in domain_name_guesses:
|
||||
if self._query_soa(guess):
|
||||
return guess
|
||||
|
||||
raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
|
||||
.format(record_name, domain_name_guesses))
|
||||
raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
|
||||
.format(record_name, domain_name_guesses))
|
||||
|
||||
def _query_soa(self, domain_name):
|
||||
"""
|
||||
|
||||
@@ -73,9 +73,12 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
|
||||
class RFC2136ClientTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client
|
||||
from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client, _RFC2136Key
|
||||
|
||||
self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5)
|
||||
self.rfc2136_client = _RFC2136Client(SERVER,
|
||||
PORT,
|
||||
None,
|
||||
_RFC2136Key(NAME, SECRET, dns.tsig.HMAC_MD5))
|
||||
|
||||
@mock.patch("dns.query.tcp")
|
||||
def test_add_txt_record(self, query_mock):
|
||||
@@ -162,6 +165,28 @@ class RFC2136ClientTest(unittest.TestCase):
|
||||
self.rfc2136_client._find_domain,
|
||||
'foo.bar.'+DOMAIN)
|
||||
|
||||
def test_find_domain_with_base(self):
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True])
|
||||
self.rfc2136_client.base_domain = 'bar.' + DOMAIN
|
||||
|
||||
# _find_domain | pylint: disable=protected-access
|
||||
domain = self.rfc2136_client._find_domain('foo.bar.' + DOMAIN)
|
||||
|
||||
self.assertTrue(domain == 'bar.' + DOMAIN)
|
||||
|
||||
def test_find_domain_with_wrong_base(self):
|
||||
|
||||
# _query_soa | pylint: disable=protected-access
|
||||
self.rfc2136_client._query_soa = mock.MagicMock(side_effect=[False, False, True])
|
||||
self.rfc2136_client.base_domain = 'wrong.' + DOMAIN
|
||||
|
||||
self.assertRaises(
|
||||
errors.PluginError,
|
||||
# _find_domain | pylint: disable=protected-access
|
||||
self.rfc2136_client._find_domain,
|
||||
'foo.bar.' + DOMAIN)
|
||||
|
||||
@mock.patch("dns.query.udp")
|
||||
def test_query_soa_found(self, query_mock):
|
||||
query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA)
|
||||
|
||||
Reference in New Issue
Block a user