diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..75b9443 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.eggs/* +certbot_dns_transip.egg-info/* +*/__pycache__/* diff --git a/README.rst b/README.rst index 3e0e870..a0ad003 100644 --- a/README.rst +++ b/README.rst @@ -1,22 +1,17 @@ -certbot-dns-ispconfig +certbot-dns-transip ===================== -ISPConfig_ DNS Authenticator plugin for Certbot +transip_ DNS Authenticator plugin for Certbot This plugin automates the process of completing a ``dns-01`` challenge by -creating, and subsequently removing, TXT records using the ISPConfig Remote API. +creating, and subsequently removing, TXT records using the transip Remote API. -Configuration of ISPConfig +Configuration of transip --------------------------- -In the `System -> Remote Users` you have to have a user, with the following rights +In `https://www.transip.be/cp/account/api/` you need to have to a keypair -- Client Functions -- DNS zone functions -- DNS txt functions - - -.. _ISPConfig: https://www.ispconfig.org/ +.. _transip: https://www.transip.org/ .. _certbot: https://certbot.eff.org/ Installation @@ -24,27 +19,27 @@ Installation :: - pip install certbot-dns-ispconfig + pip install certbot-dns-transip Named Arguments --------------- -To start using DNS authentication for ispconfig, pass the following arguments on +To start using DNS authentication for transip, pass the following arguments on certbot's command line: ============================================================= ============================================== -``--authenticator certbot-dns-ispconfig:dns-ispconfig`` select the authenticator plugin (Required) +``--authenticator certbot-dns-transip:dns-transip`` select the authenticator plugin (Required) -``--certbot-dns-ispconfig:dns-ispconfig-credentials`` ispconfig Remote User credentials +``--certbot-dns-transip:dns-transip-credentials`` transip Remote User credentials INI file. (Required) -``--certbot-dns-ispconfig:dns-ispconfig-propagation-seconds`` | waiting time for DNS to propagate before asking +``--certbot-dns-transip:dns-transip-propagation-seconds`` | waiting time for DNS to propagate before asking | the ACME server to verify the DNS record. | (Default: 10, Recommended: >= 600) ============================================================= ============================================== -(Note that the verbose and seemingly redundant ``certbot-dns-ispconfig:`` prefix +(Note that the verbose and seemingly redundant ``certbot-dns-transip:`` prefix is currently imposed by certbot for external plugins.) @@ -55,17 +50,16 @@ An example ``credentials.ini`` file: .. code-block:: ini - certbot_dns_ispconfig:dns_ispconfig_username = myremoteuser - certbot_dns_ispconfig:dns_ispconfig_password = verysecureremoteuserpassword - certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080/remote/json.php + certbot_dns_transip:dns_transip_username = myremoteuser + certbot_dns_transip:dns_transip_api_key= transip_api_key The path to this file can be provided interactively or using the -``--certbot-dns-ispconfig:dns-ispconfig-credentials`` command-line argument. Certbot +``--certbot-dns-transip:dns-transip-credentials`` command-line argument. Certbot records the path to this file for use during renewal, but does not store the file's contents. **CAUTION:** You should protect these API credentials as you would the -password to your ispconfig account. Users who can read this file can use these +password to your transip account. Users who can read this file can use these credentials to issue arbitrary API calls on your behalf. Users who can cause Certbot to run using these credentials can complete a ``dns-01`` challenge to acquire new certificates or revoke existing certificates for associated @@ -88,9 +82,9 @@ To acquire a single certificate for both ``example.com`` and .. code-block:: bash certbot certonly \ - --authenticator certbot-dns-ispconfig:dns-ispconfig \ - --certbot-dns-ispconfig:dns-ispconfig-credentials /etc/letsencrypt/.secrets/domain.tld.ini \ - --certbot-dns-ispconfig:dns-ispconfig-propagation-seconds 900 \ + --authenticator certbot-dns-transip:dns-transip \ + --certbot-dns-transip:dns-transip-credentials /etc/letsencrypt/.secrets/domain.tld.ini \ + --certbot-dns-transip:dns-transip-propagation-seconds 900 \ --server https://acme-v02.api.letsencrypt.org/directory \ --agree-tos \ --rsa-key-size 4096 \ @@ -98,37 +92,6 @@ To acquire a single certificate for both ``example.com`` and -d '*.example.com' -Docker ------- - -In order to create a docker container with a certbot-dns-ispconfig installation, -create an empty directory with the following ``Dockerfile``: - -.. code-block:: docker - - FROM certbot/certbot - RUN pip install certbot-dns-ispconfig - -Proceed to build the image:: - - docker build -t certbot/dns-ispconfig . - -Once that's finished, the application can be run as follows:: - - docker run --rm \ - -v /var/lib/letsencrypt:/var/lib/letsencrypt \ - -v /etc/letsencrypt:/etc/letsencrypt \ - --cap-drop=all \ - certbot/dns-ispconfig certonly \ - --authenticator certbot-dns-ispconfig:dns-ispconfig \ - --certbot-dns-ispconfig:dns-ispconfig-propagation-seconds 900 \ - --certbot-dns-ispconfig:dns-ispconfig-credentials \ - /etc/letsencrypt/.secrets/domain.tld.ini \ - --no-self-upgrade \ - --keep-until-expiring --non-interactive --expand \ - --server https://acme-v02.api.letsencrypt.org/directory \ - -d example.com -d '*.example.com' - It is suggested to secure the folder as follows:: chown root:root /etc/letsencrypt/.secrets chmod 600 /etc/letsencrypt/.secrets diff --git a/certbot_dns_ispconfig/dns_ispconfig.py b/certbot_dns_ispconfig/dns_ispconfig.py deleted file mode 100644 index 61d11a1..0000000 --- a/certbot_dns_ispconfig/dns_ispconfig.py +++ /dev/null @@ -1,269 +0,0 @@ -"""DNS Authenticator for ISPConfig.""" -import json -import logging -import time - -import requests -import zope.interface - -from certbot import errors -from certbot import interfaces -from certbot.plugins import dns_common - -logger = logging.getLogger(__name__) - - -@zope.interface.implementer(interfaces.IAuthenticator) -@zope.interface.provider(interfaces.IPluginFactory) -class Authenticator(dns_common.DNSAuthenticator): - """DNS Authenticator for ISPConfig - - This Authenticator uses the ISPConfig Remote REST API to fulfill a dns-01 challenge. - """ - - description = "Obtain certificates using a DNS TXT record (if you are using ISPConfig for DNS)." - ttl = 60 - - def __init__(self, *args, **kwargs): - super(Authenticator, self).__init__(*args, **kwargs) - self.credentials = None - - @classmethod - def add_parser_arguments(cls, add): # pylint: disable=arguments-differ - super(Authenticator, cls).add_parser_arguments( - add, default_propagation_seconds=120 - ) - add("credentials", help="ISPConfig credentials INI file.") - - def more_info(self): # pylint: disable=missing-docstring,no-self-use - return ( - "This plugin configures a DNS TXT record to respond to a dns-01 challenge using " - + "the ISPConfig Remote REST API." - ) - - def _setup_credentials(self): - self.credentials = self._configure_credentials( - "credentials", - "ISPConfig credentials INI file", - { - "endpoint": "URL of the ISPConfig Remote API.", - "username": "Username for ISPConfig Remote API.", - "password": "Password for ISPConfig Remote API.", - }, - ) - - def _perform(self, domain, validation_name, validation): - self._get_ispconfig_client().add_txt_record( - domain, validation_name, validation, self.ttl - ) - - def _cleanup(self, domain, validation_name, validation): - self._get_ispconfig_client().del_txt_record( - domain, validation_name, validation, self.ttl - ) - - def _get_ispconfig_client(self): - return _ISPConfigClient( - self.credentials.conf("endpoint"), - self.credentials.conf("username"), - self.credentials.conf("password"), - ) - - -class _ISPConfigClient(object): - """ - Encapsulates all communication with the ISPConfig Remote REST API. - """ - - def __init__(self, endpoint, username, password): - logger.debug("creating ispconfigclient") - self.endpoint = endpoint - self.username = username - self.password = password - self.session = requests.Session() - self.session_id = None - - def _login(self): - if self.session_id is not None: - return - logger.debug("logging in") - logindata = {"username": self.username, "password": self.password} - self.session_id = self._api_request("login", logindata) - logger.debug("session id is %s", self.session_id) - - def _api_request(self, action, data): - if self.session_id is not None: - data["session_id"] = self.session_id - url = self._get_url(action) - resp = self.session.get(url, json=data) - logger.debug("API REquest to URL: %s", url) - if resp.status_code != 200: - raise errors.PluginError( - "HTTP Error during login {0}".format(resp.status_code) - ) - try: - result = resp.json() - except: - raise errors.PluginError( - "API response with non JSON: {0}".format(resp.text) - ) - if result["code"] == "ok": - return result["response"] - elif result["code"] == "remote_fault": - raise errors.PluginError( - "API response with an error: {0}".format(result["message"]) - ) - else: - raise errors.PluginError("API response unknown {0}".format(resp.text)) - - def _get_url(self, action): - return "{0}?{1}".format(self.endpoint, action) - - def _get_server_id(self, zone_id): - zone = self._api_request("dns_zone_get", {"primary_id": zone_id}) - return zone["server_id"] - - def add_txt_record(self, domain, record_name, record_content, record_ttl): - """ - Add a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the ISPConfig API - """ - self._login() - zone_id, zone_name = self._find_managed_zone_id(domain, record_name) - if zone_id is None: - raise errors.PluginError("Domain not known") - logger.debug("domain found: %s with id: %s", zone_name, zone_id) - o_record_name = record_name - record_name = record_name.replace(zone_name, "")[:-1] - logger.debug( - "using record_name: %s from original: %s", record_name, o_record_name - ) - record = self.get_existing_txt(zone_id, record_name, record_content) - if record is not None: - if record["data"] == record_content: - logger.info("already there, id {0}".format(record["id"])) - return - else: - logger.info("update {0}".format(record["id"])) - self._update_txt_record( - zone_id, record["id"], record_name, record_content, record_ttl - ) - else: - logger.info("insert new txt record") - self._insert_txt_record(zone_id, record_name, record_content, record_ttl) - - def del_txt_record(self, domain, record_name, record_content, record_ttl): - """ - Delete a TXT record using the supplied information. - - :param str domain: The domain to use to look up the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - :param str record_content: The record content (typically the challenge validation). - :param int record_ttl: The record TTL (number of seconds that the record may be cached). - :raises certbot.errors.PluginError: if an error occurs communicating with the ISPConfig API - """ - self._login() - zone_id, zone_name = self._find_managed_zone_id(domain, record_name) - if zone_id is None: - raise errors.PluginError("Domain not known") - logger.debug("domain found: %s with id: %s", zone_name, zone_id) - o_record_name = record_name - record_name = record_name.replace(zone_name, "")[:-1] - logger.debug( - "using record_name: %s from original: %s", record_name, o_record_name - ) - record = self.get_existing_txt(zone_id, record_name, record_content) - if record is not None: - if record["data"] == record_content: - logger.debug("delete TXT record: %s", record["id"]) - self._delete_txt_record(record["id"]) - - def _prepare_rr_data(self, zone_id, record_name, record_content, record_ttl): - server_id = self._get_server_id(zone_id) - data = { - "client_id": None, - "rr_type": "TXT", - "params": { - "server_id": server_id, - "name": record_name, - "active": "Y", - "type": "TXT", - "data": record_content, - "zone": zone_id, - "ttl": record_ttl, - "update_serial": False, - "stamp": time.strftime('%Y-%m-%d %H:%M:%S'), - }, - } - return data - - def _insert_txt_record(self, zone_id, record_name, record_content, record_ttl): - data = self._prepare_rr_data(zone_id, record_name, record_content, record_ttl) - logger.debug("insert with data: %s", data) - result = self._api_request("dns_txt_add", data) - - def _update_txt_record( - self, zone_id, primary_id, record_name, record_content, record_ttl - ): - data = self._prepare_rr_data(zone_id, record_name, record_content, record_ttl) - data["primary_id"] = primary_id - logger.debug("update with data: %s", data) - result = self._api_request("dns_txt_update", data) - - def _delete_txt_record(self, primary_id): - data = {"primary_id": primary_id} - logger.debug("delete with data: %s", data) - result = self._api_request("dns_txt_delete", data) - - def _find_managed_zone_id(self, domain, record_name): - """ - Find the managed zone for a given domain. - - :param str domain: The domain for which to find the managed zone. - :returns: The ID of the managed zone, if found. - :rtype: str - :raises certbot.errors.PluginError: if the managed zone cannot be found. - """ - - zone_dns_name_guesses = [record_name] + dns_common.base_domain_name_guesses(domain) - - for zone_name in zone_dns_name_guesses: - # get the zone id - try: - logger.debug("looking for zone: %s", zone_name) - zone_id = self._api_request("dns_zone_get_id", {"origin": zone_name}) - return zone_id, zone_name - except errors.PluginError as e: - pass - return None - - def get_existing_txt(self, zone_id, record_name, record_content): - """ - Get existing TXT records from the RRset for the record name. - - If an error occurs while requesting the record set, it is suppressed - and None is returned. - - :param str zone_id: The ID of the managed zone. - :param str record_name: The record name (typically beginning with '_acme-challenge.'). - - :returns: TXT record value or None - :rtype: `string` or `None` - - """ - self._login() - read_zone_data = {"zone_id": zone_id} - zone_data = self._api_request("dns_rr_get_all_by_zone", read_zone_data) - for entry in zone_data: - if ( - entry["name"] == record_name - and entry["type"] == "TXT" - and entry["data"] == record_content - ): - return entry - return None diff --git a/certbot_dns_ispconfig/dns_ispconfig_test.py b/certbot_dns_ispconfig/dns_ispconfig_test.py deleted file mode 100644 index 7c5839a..0000000 --- a/certbot_dns_ispconfig/dns_ispconfig_test.py +++ /dev/null @@ -1,168 +0,0 @@ -"""Tests for certbot_dns_ispconfig.dns_ispconfig.""" - -import unittest - -import mock -import json -import requests_mock - -from certbot import errors -from certbot.compat import os -from certbot.errors import PluginError -from certbot.plugins import dns_test_common -from certbot.plugins.dns_test_common import DOMAIN -from certbot.tests import util as test_util - -FAKE_USER = "remoteuser" -FAKE_PW = "password" -FAKE_ENDPOINT = "mock://endpoint" - - -class AuthenticatorTest( - test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest -): - def setUp(self): - super(AuthenticatorTest, self).setUp() - - from certbot_dns_ispconfig.dns_ispconfig import Authenticator - - path = os.path.join(self.tempdir, "file.ini") - dns_test_common.write( - { - "ispconfig_username": FAKE_USER, - "ispconfig_password": FAKE_PW, - "ispconfig_endpoint": FAKE_ENDPOINT, - }, - path, - ) - - super(AuthenticatorTest, self).setUp() - self.config = mock.MagicMock( - ispconfig_credentials=path, ispconfig_propagation_seconds=0 - ) # don't wait during tests - - self.auth = Authenticator(self.config, "ispconfig") - - self.mock_client = mock.MagicMock() - # _get_ispconfig_client | pylint: disable=protected-access - self.auth._get_ispconfig_client = mock.MagicMock(return_value=self.mock_client) - - def test_perform(self): - self.auth.perform([self.achall]) - - expected = [ - mock.call.add_txt_record( - DOMAIN, "_acme-challenge." + DOMAIN, mock.ANY, mock.ANY - ) - ] - self.assertEqual(expected, self.mock_client.mock_calls) - - def test_cleanup(self): - # _attempt_cleanup | pylint: disable=protected-access - self.auth._attempt_cleanup = True - self.auth.cleanup([self.achall]) - - expected = [ - mock.call.del_txt_record( - DOMAIN, "_acme-challenge." + DOMAIN, mock.ANY, mock.ANY - ) - ] - self.assertEqual(expected, self.mock_client.mock_calls) - - -class ISPConfigClientTest(unittest.TestCase): - record_name = "foo" - record_content = "bar" - record_ttl = 42 - - def setUp(self): - from certbot_dns_ispconfig.dns_ispconfig import _ISPConfigClient - - self.adapter = requests_mock.Adapter() - - self.client = _ISPConfigClient(FAKE_ENDPOINT, FAKE_USER, FAKE_PW) - self.client.session.mount("mock", self.adapter) - - def _register_response( - self, ep_id, response=None, message=None, additional_matcher=None, **kwargs - ): - resp = {"code": "ok", "message": message, "response": response} - if message is not None: - resp["code"] = "remote_failure" - - def add_matcher(request): - data = json.loads(request.text) - add_result = True - if additional_matcher is not None: - add_result = additionsal_matcher(request) - - return ( - ( - ("username" in data and data["username"] == FAKE_USER) - and ("username" in data and data["password"] == FAKE_PW) - ) - or data["session_id"] == "FAKE_SESSION" - ) and add_result - - self.adapter.register_uri( - requests_mock.ANY, - "{0}?{1}".format(FAKE_ENDPOINT, ep_id), - text=json.dumps(resp), - additional_matcher=add_matcher, - **kwargs - ) - - def test_add_txt_record(self): - self._register_response("login", response="FAKE_SESSION") - self._register_response("dns_zone_get_id", response=23) - self._register_response("dns_txt_add", response=99) - self._register_response( - "dns_zone_get", response={"zone_id": 102, "server_id": 1} - ) - self._register_response("dns_rr_get_all_by_zone", response=[]) - self.client.add_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - def test_add_txt_record_fail_to_find_domain(self): - self._register_response("login", response="FAKE_SESSION") - self._register_response("dns_zone_get_id", message="Not Found") - with self.assertRaises(errors.PluginError) as context: - self.client.add_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - def test_add_txt_record_fail_to_authenticate(self): - self._register_response("login", message="FAILED") - with self.assertRaises(errors.PluginError) as context: - self.client.add_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - def test_del_txt_record(self): - self._register_response("login", response="FAKE_SESSION") - self._register_response("dns_zone_get_id", response=23) - self._register_response("dns_rr_get_all_by_zone", response=[]) - self._register_response("dns_txt_delete", response="") - self.client.del_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - def test_del_txt_record_fail_to_find_domain(self): - self._register_response("login", response="FAKE_SESSION") - self._register_response("dns_zone_get_id", message="Not Found") - with self.assertRaises(errors.PluginError) as context: - self.client.del_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - def test_del_txt_record_fail_to_authenticate(self): - self._register_response("login", message="FAILED") - with self.assertRaises(errors.PluginError) as context: - self.client.del_txt_record( - DOMAIN, self.record_name, self.record_content, self.record_ttl - ) - - -if __name__ == "__main__": - unittest.main() # pragma: no cover diff --git a/certbot_dns_ispconfig/__init__.py b/certbot_dns_transip/__init__.py similarity index 68% rename from certbot_dns_ispconfig/__init__.py rename to certbot_dns_transip/__init__.py index 242e914..aef6413 100644 --- a/certbot_dns_ispconfig/__init__.py +++ b/certbot_dns_transip/__init__.py @@ -1,16 +1,16 @@ """ -The `~certbot_dns_ispconfig.dns_ispconfig` plugin automates the process of +The `~certbot_dns_transip.dns_transip` plugin automates the process of completing a ``dns-01`` challenge (`~acme.challenges.DNS01`) by creating, and -subsequently removing, TXT records using the ISPConfig REST API. +subsequently removing, TXT records using the transip REST API. Named Arguments --------------- ======================================== ===================================== -``--dns-ispconfig-credentials`` ISPConfig Remote API credentials_ +``--dns-transip-credentials`` transip Remote API credentials_ INI file. (Required) -``--dns-ispconfig-propagation-seconds`` The number of seconds to wait for DNS +``--dns-transip-propagation-seconds`` The number of seconds to wait for DNS to propagate before asking the ACME server to verify the DNS record. (Default: 120) @@ -20,21 +20,20 @@ Named Arguments Credentials ----------- -Use of this plugin requires a configuration file containing ISPConfig Remote API -credentials, obtained from your DNSimple -`System > Remote Users`. +Use of this plugin requires a configuration file containing transip Remote API +credentials, obtained from your transip account +`https://www.transip.be/cp/account/api/` .. code-block:: ini :name: credentials.ini :caption: Example credentials file: - # ISPCONFIG API credentials used by Certbot - dns_ispconfig_username = myispremoteuser - dns_ispconfig_password = mysecretpassword - dns_ispconfig_endpoint = https://localhost:8080 + # transip API credentials used by Certbot + dns_transip_username = myispremoteuser + dns_transip_api_key = transip_api_key The path to this file can be provided interactively or using the -``--dns-ispconfig-credentials`` command-line argument. Certbot records the path +``--dns-transip-credentials`` command-line argument. Certbot records the path to this file for use during renewal, but does not store the file's contents. .. caution:: @@ -59,8 +58,8 @@ Examples :caption: To acquire a certificate for ``example.com`` certbot certonly \\ - --dns-ispconfig \\ - --dns-ispconfig-credentials ~/.secrets/certbot/ispconfig.ini \\ + --dns-transip \\ + --dns-transip-credentials ~/.secrets/certbot/transip.ini \\ -d example.com .. code-block:: bash @@ -68,8 +67,8 @@ Examples ``www.example.com`` certbot certonly \\ - --dns-ispconfig \\ - --dns-ispconfig-credentials ~/.secrets/certbot/ispconfig.ini \\ + --dns-transip \\ + --dns-transip-credentials ~/.secrets/certbot/transip.ini \\ -d example.com \\ -d www.example.com @@ -78,9 +77,9 @@ Examples for DNS propagation certbot certonly \\ - --dns-ispconfig \\ - --dns-ispconfig-credentials ~/.secrets/certbot/ispconfig.ini \\ - --dns-ispconfig-propagation-seconds 240 \\ + --dns-transip \\ + --dns-transip-credentials ~/.secrets/certbot/transip.ini \\ + --dns-transip-propagation-seconds 240 \\ -d example.com """ diff --git a/certbot_dns_transip/dns_transip.py b/certbot_dns_transip/dns_transip.py new file mode 100644 index 0000000..81c1bf8 --- /dev/null +++ b/certbot_dns_transip/dns_transip.py @@ -0,0 +1,71 @@ +"""DNS Authenticator for transip api.""" +import logging + +import zope.interface + +from certbot import interfaces +from certbot.plugins import dns_common + +from transip.service.domain import DomainService +from transip.service.objects import DnsEntry, Domain + +logger = logging.getLogger(__name__) + + +@zope.interface.implementer(interfaces.IAuthenticator) +@zope.interface.provider(interfaces.IPluginFactory) +class Authenticator(dns_common.DNSAuthenticator): + """DNS Authenticator for transip api + + This Authenticator uses the transip Remote REST API to fulfill a dns-01 challenge. + """ + + description = "Obtain certificates using a DNS TXT record (if you are using transip for DNS)." + ttl = 60 + + def __init__(self, *args, **kwargs): + super(Authenticator, self).__init__(*args, **kwargs) + self.username = None + self.apikey = None + + @classmethod + def add_parser_arguments(cls, add): # pylint: disable=arguments-differ + super(Authenticator, cls).add_parser_arguments( + add, default_propagation_seconds=120 + ) + add("credentials", help="transip credentials INI file.") + + def _setup_credentials(self): + self.credentials = self._configure_credentials( + "credentials", + "transip credentials INI file", + { + "username": "Username for transip Remote API.", + "api-key": "Password for transip Remote API.", + }, + ) + + def more_info(self): # pylint: disable=missing-docstring,no-self-use + return ( + "This plugin configures a DNS TXT record to respond to a dns-01 challenge using " + + "the transip Remote REST API." + ) + + def _perform(self, domain, validation_name, validation): + dns_entry = self._get_dns_entry(validation_name, validation) + domain = Domain(domain) + self._get_transip_client().add_dns_entries(domain, [dns_entry]) + + def _cleanup(self, domain, validation_name, validation): + dns_entry = self._get_dns_entry(validation_name, validation) + domain = Domain(domain) + self._get_transip_client().remove_dns_entries(domain, [dns_entry]) + + def _get_dns_entry(self, validation_name, validation): + return DnsEntry(validation_name, self.ttl, 'TXT', validation) + + def _get_transip_client(self): + return DomainService( + self.credentials.conf("username"), + self.credentials.conf("api-key"), + ) diff --git a/certbot_dns_transip/dns_transip_test.py b/certbot_dns_transip/dns_transip_test.py new file mode 100644 index 0000000..2d65133 --- /dev/null +++ b/certbot_dns_transip/dns_transip_test.py @@ -0,0 +1,68 @@ +"""Tests for certbot_dns_transip.dns_transip.""" + +import unittest + +import mock + +from certbot.compat import os +from certbot.plugins import dns_test_common +from certbot.plugins.dns_test_common import DOMAIN +from certbot.tests import util as test_util + +FAKE_USER = "remoteuser" +FAKE_PW = "password" + + +class AuthenticatorTest( + test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest +): + def setUp(self): + super(AuthenticatorTest, self).setUp() + + from certbot_dns_transip.dns_transip import Authenticator + + path = os.path.join(self.tempdir, "file.ini") + dns_test_common.write( + { + "transip_username": FAKE_USER, + "transip_api_key": FAKE_PW, + }, + path, + ) + + super(AuthenticatorTest, self).setUp() + self.config = mock.MagicMock( + transip_credentials=path, transip_propagation_seconds=0 + ) # don't wait during tests + + self.auth = Authenticator(self.config, "transip") + + self.mock_client = mock.MagicMock() + # _get_transip_client | pylint: disable=protected-access + self.auth._get_transip_client = mock.MagicMock(return_value=self.mock_client) + + def test_perform(self): + self.auth.perform([self.achall]) + + expected = [ + mock.call.add_dns_entries( + mock.ANY, mock.ANY + ) + ] + self.assertEqual(expected, self.mock_client.mock_calls) + + def test_cleanup(self): + # _attempt_cleanup | pylint: disable=protected-access + self.auth._attempt_cleanup = True + self.auth.cleanup([self.achall]) + + expected = [ + mock.call.remove_dns_entries( + mock.ANY, mock.ANY + ) + ] + self.assertEqual(expected, self.mock_client.mock_calls) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover diff --git a/setup.py b/setup.py index f100835..796732e 100644 --- a/setup.py +++ b/setup.py @@ -10,6 +10,7 @@ install_requires = [ "requests", "mock", "requests-mock", + "transip", ] # read the contents of your README file @@ -20,14 +21,14 @@ with open(path.join(this_directory, "README.rst")) as f: long_description = f.read() setup( - name="certbot-dns-ispconfig", + name="certbot-dns-transip", version=version, - description="ispconfig DNS Authenticator plugin for Certbot", + description="transip DNS Authenticator plugin for Certbot", long_description=long_description, long_description_content_type="text/x-rst", - url="https://github.com/m42e/certbot-dns-ispconfig", - author="Matthias Bilger", - author_email="matthias@bilger.info", + url="https://gitea.caret.be/jens/certbot-dns-transip", + author="Jens Timmerman", + author_email="certbotdnstransip@caret.be", license="Apache License 2.0", python_requires=">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*", classifiers=[ @@ -56,8 +57,8 @@ setup( install_requires=install_requires, entry_points={ "certbot.plugins": [ - "dns-ispconfig = certbot_dns_ispconfig.dns_ispconfig:Authenticator" + "dns-transip = certbot_dns_transip.dns_transip:Authenticator" ] }, - test_suite="certbot_dns_ispconfig", + test_suite="certbot_dns_transip", )