Add an option to dns_rfc2136 plugin to specify an authorative base domain.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ac7f34a09e8..2f699feecb3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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
 
diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py
index 12b360959ff..cebff2841d3 100644
--- a/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py
+++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/__init__.py
@@ -21,8 +21,8 @@
 -----------
 
 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 @@
    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
diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py
index 2061374e0e8..5db8c3020a3 100644
--- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py
+++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py
@@ -79,25 +79,33 @@ def _cleanup(self, _domain, validation_name, validation):
         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 @@ def del_txt_record(self, record_name, record_content):
 
     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):
         """
diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py
index d800f1ec7c2..bed3445b69e 100644
--- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py
+++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py
@@ -73,9 +73,12 @@ def test_valid_algorithm_passes(self):
 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 @@ def test_find_domain_wraps_errors(self):
             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)

Added an option to dns_rfc2136 plugin to explicitly specify the base domain for which the DNS server is authorative. This solves problems with split horizon DNS configurations which do not correctly report authorative SOA records when queried without a TSIG key. Could be useful for other use cases.