diff --git a/letsencrypt/client/apache_configurator.py b/letsencrypt/client/apache/configurator.py similarity index 96% rename from letsencrypt/client/apache_configurator.py rename to letsencrypt/client/apache/configurator.py index d11caa51d..b4b5e4d20 100644 --- a/letsencrypt/client/apache_configurator.py +++ b/letsencrypt/client/apache/configurator.py @@ -67,7 +67,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): with the configuration :ivar float version: version of Apache :ivar list vhosts: All vhosts found in the configuration - (:class:`list` of :class:`letsencrypt.client.apache.obj.VH`) + (:class:`list` of :class:`letsencrypt.client.apache.obj.VirtualHost`) :ivar dict assoc: Mapping between domains and vhosts @@ -97,7 +97,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): super(ApacheConfigurator, self).__init__(direc) # See if any temporary changes need to be recovered - # This needs to occur before VH objects are setup... + # This needs to occur before VirtualHost objects are setup... # because this will change the underlying configuration and potential # vhosts self.recovery_routine() @@ -145,7 +145,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): This shouldn't happen within letsencrypt though :param vhost: ssl vhost to deploy certificate - :type vhost: :class:`letsencrypt.client.apache.obj.VH` + :type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :param str cert: certificate filename :param str key: private key filename @@ -204,7 +204,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str name: domain name :returns: ssl vhost associated with name - :rtype: :class:`letsencrypt.client.apache.obj.VH` + :rtype: :class:`letsencrypt.client.apache.obj.VirtualHost` """ # Allows for domain names to be associated with a virtual host @@ -242,7 +242,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str domain: domain name to associate :param vhost: virtual host to associate with domain - :type vhost: :class:`letsencrypt.client.apache.obj.VH` + :type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` """ self.assoc[domain] = vhost @@ -279,7 +279,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Helper function for get_virtual_hosts(). :param host: In progress vhost whose names will be added - :type host: :class:`letsencrypt.client.apache.obj.VH` + :type host: :class:`letsencrypt.client.apache.obj.VirtualHost` """ name_match = self.aug.match(("%s//*[self::directive=~regexp('%s')] | " @@ -300,7 +300,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str path: Augeas path to virtual host :returns: newly created vhost - :rtype: :class:`letsencrypt.client.apache.obj.VH` + :rtype: :class:`letsencrypt.client.apache.obj.VirtualHost` """ addrs = set() @@ -315,7 +315,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): filename = get_file_path(path) is_enabled = self.is_site_enabled(filename) - vhost = obj.VH(filename, path, addrs, is_ssl, is_enabled) + vhost = obj.VirtualHost(filename, path, addrs, is_ssl, is_enabled) self._add_servernames(vhost) return vhost @@ -323,7 +323,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def get_virtual_hosts(self): """Returns list of virtual hosts found in the Apache configuration. - :returns: List of :class:`letsencrypt.client.apache.obj.VH` objects + :returns: List of + :class:`letsencrypt.client.apache.obj.VirtualHost` objects found in configuration :rtype: list @@ -400,8 +401,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def make_server_sni_ready(self, vhost, default_addr="*:443"): """Checks to see if the server is ready for SNI challenges. - :param vhost: VHost to check SNI compatibility - :type vhost: :class:`letsencrypt.client.apache.obj.VH` + :param vhost: VirtualHostost to check SNI compatibility + :type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :param str default_addr: TODO - investigate function further @@ -431,10 +432,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): New vhost will reside as (nonssl_vhost.path) + CONFIG.LE_VHOST_EXT :param nonssl_vhost: Valid VH that doesn't have SSLEngine on - :type nonssl_vhost: :class:`letsencrypt.client.apache.obj.VH` + :type nonssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: SSL vhost - :rtype: :class:`letsencrypt.client.apache.obj.VH` + :rtype: :class:`letsencrypt.client.apache.obj.VirtualHost` """ avail_fp = nonssl_vhost.filep @@ -472,11 +473,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): addr_match % (ssl_fp, parser.case_i('VirtualHost'))) for i in range(len(ssl_addr_p)): - ssl_addr_arg = obj.Addr.fromstring( + old_addr = obj.Addr.fromstring( str(self.aug.get(ssl_addr_p[i]))) - ssl_addr_arg.set_port("443") - self.aug.set(ssl_addr_p[i], str(ssl_addr_arg)) - ssl_addrs.add(ssl_addr_arg) + ssl_addr = old_addr.get_addr_obj("443") + self.aug.set(ssl_addr_p[i], str(ssl_addr)) + ssl_addrs.add(ssl_addr) # Add directives vh_p = self.aug.match(("/files%s//* [label()=~regexp('%s')]" % @@ -528,10 +529,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): The function then adds the directive :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH` + :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, :class:`letsencrypt.client.apache.obj.VH`) + :rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`) """ # TODO: Enable check to see if it is already there @@ -577,7 +578,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): -1 is also returned in case of no redirection/rewrite directives :param vhost: vhost to check - :type vhost: :class:`letsencrypt.client.apache.obj.VH` + :type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: Success, code value... see documentation :rtype: bool, int @@ -608,10 +609,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Creates an http_vhost specifically to redirect for the ssl_vhost. :param ssl_vhost: ssl vhost - :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH` + :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: Success, vhost - :rtype: (bool, :class:`letsencrypt.client.apache.obj.VH`) + :rtype: (bool, :class:`letsencrypt.client.apache.obj.VirtualHost`) """ # Consider changing this to a dictionary check @@ -694,7 +695,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if not conflict: returns space separated list of new host addrs :param ssl_vhost: SSL Vhost to check for possible port 80 redirection - :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH` + :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: TODO :rtype: TODO @@ -727,10 +728,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): Consider changing this into a dict check :param ssl_vhost: ssl vhost to check - :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VH` + :type ssl_vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: HTTP vhost or None if unsuccessful - :rtype: :class:`letsencrypt.client.apache.obj.VH` or None + :rtype: :class:`letsencrypt.client.apache.obj.VirtualHost` or None """ # _default_:443 check @@ -826,7 +827,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. todo:: Make sure link is not broken... :param vhost: vhost to enable - :type vhost: :class:`letsencrypt.client.apache.obj.VH` + :type vhost: :class:`letsencrypt.client.apache.obj.VirtualHost` :returns: Success :rtype: bool @@ -1048,11 +1049,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): shutil.copyfile(dist_conf, CONFIG.OPTIONS_SSL_CONF) # TODO: Use ip address of existing vhost instead of relying on FQDN - config_text = " \n" + config_text = "\n" for idx, lis in enumerate(ll_addrs): config_text += self.get_config_text( list_sni_tuple[idx][2], lis, dvsni_key.file) - config_text += " \n" + config_text += "\n" self.dvsni_conf_include_check(self.parser.loc["default"]) self.register_file_creation(True, CONFIG.APACHE_CHALLENGE_CONF) diff --git a/letsencrypt/client/apache/obj.py b/letsencrypt/client/apache/obj.py index b5bc97302..aeddee443 100644 --- a/letsencrypt/client/apache/obj.py +++ b/letsencrypt/client/apache/obj.py @@ -2,10 +2,14 @@ class Addr(object): - """Represents an Apache VirtualHost address.""" - def __init__(self, addr): - """:param tuple addr: tuple of strings (ip, port)""" - self.tup = addr + """Represents an Apache VirtualHost address. + + :param str addr: addr part of vhost address + :param str port: port number or *, or "" + + """ + def __init__(self, tup): + self.tup = tup @classmethod def fromstring(cls, str_addr): @@ -14,25 +18,18 @@ class Addr(object): return cls((tup[0], tup[2])) def __str__(self): - if self.tup[1] != "": - return ':'.join(self.tup) - return str(self.tup[0]) + if self.tup[1]: + return "%s:%s" % self.tup + return self.tup[0] def __eq__(self, other): - if isinstance(other, self.__class__): - return self.tup == other.tup + if type(other) is type(self): + return self.__dict__ == other.__dict__ return False def __hash__(self): return hash(self.tup) - def set_port(self, port): - """Set the port of the address. - - :param str port: new port - """ - self.tup = (self.tup[0], port) - def get_addr(self): """Return addr part of Addr object.""" return self.tup[0] @@ -47,7 +44,7 @@ class Addr(object): # pylint: disable=too-few-public-methods -class VH(object): +class VirtualHost(object): """Represents an Apache Virtualhost. :ivar str filep: file path of VH @@ -66,7 +63,7 @@ class VH(object): self.filep = filep self.path = path self.addrs = addrs - self.names = set() if names is None else names + self.names = set() if names is None else set(names) self.ssl = ssl self.enabled = enabled @@ -75,12 +72,13 @@ class VH(object): self.names.add(name) def __str__(self): + addr_str = ", ".join(str(addr) for addr in self.addrs) return ("file: %s\n" "vh_path: %s\n" "addrs: %s\n" "names: %s\n" "ssl: %s\n" - "enabled: %s" % (self.filep, self.path, self.addrs, + "enabled: %s" % (self.filep, self.path, addr_str, self.names, self.ssl, self.enabled)) def __eq__(self, other): diff --git a/letsencrypt/client/apache/parser.py b/letsencrypt/client/apache/parser.py index 409c82b35..7a4691416 100644 --- a/letsencrypt/client/apache/parser.py +++ b/letsencrypt/client/apache/parser.py @@ -2,6 +2,8 @@ import os import re +from letsencrypt.client import errors + class ApacheParser(object): """Class handles the fine details of parsing the Apache Configuration.""" @@ -311,7 +313,7 @@ class ApacheParser(object): """ root = self._find_config_root() - default = self._set_user_config_file() + default = self._set_user_config_file(root) temp = os.path.join(self.root, "ports.conf") if os.path.isfile(temp): @@ -335,7 +337,7 @@ class ApacheParser(object): raise errors.LetsEncryptConfiguratorError( "Could not find configuration root") - def _set_user_config_file(self, filename=''): + def _set_user_config_file(self, root): """Set the appropriate user configuration file .. todo:: This will have to be updated for other distros versions @@ -344,18 +346,15 @@ class ApacheParser(object): user config """ - if filename: - return filename + # Basic check to see if httpd.conf exists and + # in heirarchy via direct include + # httpd.conf was very common as a user file in Apache 2.2 + if (os.path.isfile(self.root + 'httpd.conf') and + self.find_dir( + case_i("Include"), case_i("httpd.conf"), root)): + return os.path.join(self.root, 'httpd.conf') else: - # Basic check to see if httpd.conf exists and - # in heirarchy via direct include - # httpd.conf was very common as a user file in Apache 2.2 - if (os.path.isfile(self.root + 'httpd.conf') and - self.find_dir( - case_i("Include"), case_i("httpd.conf"))): - return os.path.join(self.root, 'httpd.conf') - else: - return os.path.join(self.root + 'apache2.conf') + return os.path.join(self.root + 'apache2.conf') def case_i(string): diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index 266fc60c5..231faa99d 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -29,7 +29,6 @@ class AugeasConfigurator(object): (used mostly for testing) """ - super(AugeasConfigurator, self).__init__() if not direc: direc = {"backup": CONFIG.BACKUP_DIR, diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index 1987edbaf..da6c55cc2 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -59,18 +59,13 @@ class Client(object): self.auth = auth self.installer = installer - def obtain_certificate(self, privkey, csr, + def obtain_certificate(self, csr, cert_path=CONFIG.CERT_PATH, chain_path=CONFIG.CHAIN_PATH): """Obtains a certificate from the ACME server. - .. todo:: Check for case when privkey is not authkey and adjust - this function accordingly. - - :param privkey: A valid private key that corresponds to the csr - :type privkey: :class:`Key` - - :param csr: A valid CSR in der format that corresponds to privkey + :param csr: A valid CSR in DER format for the certificate the client + intends to receive. :type csr: :class:`CSR` :param str cert_path: Full desired path to end certificate. @@ -699,8 +694,7 @@ def init_key(): def init_csr(privkey, names): """Initialize a CSR with the given private key.""" - csr_pem, csr_der = crypto_util.make_csr( - privkey.pem, names) + csr_pem, csr_der = crypto_util.make_csr(privkey.pem, names) # Save CSR le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0o755) diff --git a/letsencrypt/client/crypto_util.py b/letsencrypt/client/crypto_util.py index 7d80ed9a4..dd581f892 100644 --- a/letsencrypt/client/crypto_util.py +++ b/letsencrypt/client/crypto_util.py @@ -81,7 +81,6 @@ def sha256(arg): # based on M2Crypto unit test written by Toby Allsopp def make_key(bits=CONFIG.RSA_KEY_SIZE): """Returns new RSA key in PEM form with specified bits.""" - # Python Crypto module doesn't produce any stdout key = Crypto.PublicKey.RSA.generate(bits) # rsa = M2Crypto.RSA.gen_key(bits, 65537) diff --git a/letsencrypt/client/interfaces.py b/letsencrypt/client/interfaces.py index da403169b..06bb5e83e 100644 --- a/letsencrypt/client/interfaces.py +++ b/letsencrypt/client/interfaces.py @@ -1,5 +1,4 @@ """Interfaces.""" - import zope.interface diff --git a/letsencrypt/client/tests/apache_configurator_test.py b/letsencrypt/client/tests/apache_configurator_test.py index 2c8742731..7084d10c6 100644 --- a/letsencrypt/client/tests/apache_configurator_test.py +++ b/letsencrypt/client/tests/apache_configurator_test.py @@ -1,22 +1,19 @@ """Test for letsencrypt.client.apache_configurator.""" import os -import pkg_resources import re import shutil -import tempfile import unittest import mock -from letsencrypt.client import apache_configurator -from letsencrypt.client import CONFIG from letsencrypt.client import display from letsencrypt.client import errors + +from letsencrypt.client.apache import configurator from letsencrypt.client.apache import obj from letsencrypt.client.apache import parser -UBUNTU_CONFIGS = pkg_resources.resource_filename( - __name__, "testdata/debian_apache_2_4") +from letsencrypt.client.tests import config_util class TwoVhost80Test(unittest.TestCase): @@ -25,102 +22,31 @@ class TwoVhost80Test(unittest.TestCase): def setUp(self): display.set_display(display.NcursesDisplay()) - self.temp_dir = os.path.join( - tempfile.mkdtemp("temp"), "debian_apache_2_4") - self.config_dir = tempfile.mkdtemp("config") - self.work_dir = tempfile.mkdtemp("work") + self.temp_dir, self.config_dir, self.work_dir = config_util.dir_setup( + "debian_apache_2_4/two_vhost_80") - shutil.copytree(UBUNTU_CONFIGS, self.temp_dir, symlinks=True) - - temp_options = pkg_resources.resource_filename( - "letsencrypt.client", os.path.basename(CONFIG.OPTIONS_SSL_CONF)) - shutil.copyfile( - temp_options, os.path.join(self.config_dir, "options-ssl.conf")) + self.ssl_options = config_util.setup_apache_ssl_options(self.config_dir) # Final slash is currently important - self.config_path = os.path.join(self.temp_dir, "two_vhost_80/apache2/") - self.ssl_options = os.path.join(self.config_dir, "options-ssl.conf") - backups = os.path.join(self.work_dir, "backups") + self.config_path = os.path.join( + self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") - with mock.patch("letsencrypt.client.apache_configurator." - "subprocess.Popen") as mock_popen: - # This just states that the ssl module is already loaded - mock_popen().communicate.return_value = ("ssl_module", "") - self.config = apache_configurator.ApacheConfigurator( - self.config_path, - { - "backup": backups, - "temp": os.path.join(self.work_dir, "temp_checkpoint"), - "progress": os.path.join(backups, "IN_PROGRESS"), - "config": self.config_dir, - "work": self.work_dir, - }, - self.ssl_options, - (2, 4, 7)) + self.config = config_util.get_apache_configurator( + self.config_path, self.config_dir, self.work_dir, self.ssl_options) - prefix = os.path.join( - self.temp_dir, "two_vhost_80/apache2/sites-available") - aug_pre = "/files" + prefix - self.vh_truth = [ - obj.VH( - os.path.join(prefix, "encryption-example.conf"), - os.path.join(aug_pre, "encryption-example.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), - False, True, set(["encryption-example.demo"])), - obj.VH( - os.path.join(prefix, "default-ssl.conf"), - os.path.join(aug_pre, "default-ssl.conf/IfModule/VirtualHost"), - set([obj.Addr.fromstring("_default_:443")]), True, False), - obj.VH( - os.path.join(prefix, "000-default.conf"), - os.path.join(aug_pre, "000-default.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - set(["ip-172-30-0-17"])), - obj.VH( - os.path.join(prefix, "letsencrypt.conf"), - os.path.join(aug_pre, "letsencrypt.conf/VirtualHost"), - set([obj.Addr.fromstring("*:80")]), False, True, - set(["letsencrypt.demo"])), - ] + self.vh_truth = config_util.get_vh_truth( + self.temp_dir, "debian_apache_2_4/two_vhost_80") def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) - def test_parse_file(self): - """Test parse_file. - - letsencrypt.conf is chosen as the test file as it will not be - included during the normal course of execution. - - """ - file_path = os.path.join( - self.config_path, "sites-available", "letsencrypt.conf") - - # pylint: disable=protected-access - self.config.parser._parse_file(file_path) - - # search for the httpd incl - matches = self.config.aug.match( - "/augeas/load/Httpd/incl [. ='%s']" % file_path) - - self.assertTrue(matches) - def test_get_all_names(self): names = self.config.get_all_names() self.assertEqual(names, set( ['letsencrypt.demo', 'encryption-example.demo', 'ip-172-30-0-17'])) - def test_find_dir(self): - test = self.config.parser.find_dir( - parser.case_i("Listen"), "443") - # This will only look in enabled hosts - test2 = self.config.parser.find_dir( - parser.case_i("documentroot")) - self.assertEqual(len(test), 2) - self.assertEqual(len(test2), 3) - def test_get_virtual_hosts(self): vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 4) @@ -140,14 +66,6 @@ class TwoVhost80Test(unittest.TestCase): self.assertTrue(self.config.is_site_enabled(self.vh_truth[2].filep)) self.assertTrue(self.config.is_site_enabled(self.vh_truth[3].filep)) - def test_add_dir(self): - aug_default = "/files" + self.config.parser.loc["default"] - self.config.parser.add_dir( - aug_default, "AddDirective", "test") - - self.assertTrue( - self.config.parser.find_dir("AddDirective", "test", aug_default)) - def test_deploy_cert(self): self.config.deploy_cert( self.vh_truth[1], @@ -165,15 +83,15 @@ class TwoVhost80Test(unittest.TestCase): # Verify one directive was found in the correct file self.assertEqual(len(loc_cert), 1) - self.assertEqual(apache_configurator.get_file_path(loc_cert[0]), + self.assertEqual(configurator.get_file_path(loc_cert[0]), self.vh_truth[1].filep) self.assertEqual(len(loc_key), 1) - self.assertEqual(apache_configurator.get_file_path(loc_key[0]), + self.assertEqual(configurator.get_file_path(loc_key[0]), self.vh_truth[1].filep) self.assertEqual(len(loc_chain), 1) - self.assertEqual(apache_configurator.get_file_path(loc_chain[0]), + self.assertEqual(configurator.get_file_path(loc_chain[0]), self.vh_truth[1].filep) def test_is_name_vhost(self): @@ -187,21 +105,6 @@ class TwoVhost80Test(unittest.TestCase): self.assertTrue(self.config.parser.find_dir( "NameVirtualHost", re.escape("*:443"))) - def test_add_dir_to_ifmodssl(self): - """test add_dir_to_ifmodssl. - - Path must be valid before attempting to add to augeas - - """ - self.config.parser.add_dir_to_ifmodssl( - parser.get_aug_path(self.config.parser.loc["default"]), - "FakeDirective", "123") - - matches = self.config.parser.find_dir("FakeDirective", "123") - - self.assertEqual(len(matches), 1) - self.assertTrue("IfModule" in matches[0]) - def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) @@ -212,7 +115,8 @@ class TwoVhost80Test(unittest.TestCase): self.assertEqual(ssl_vhost.path, "/files" + ssl_vhost.filep + "/IfModule/VirtualHost") - self.assertEqual(ssl_vhost.addrs, set([obj.Addr.fromstring("*:443")])) + self.assertEqual(len(ssl_vhost.addrs), 1) + self.assertTrue(set([obj.Addr.fromstring("*:443")]) == ssl_vhost.addrs) self.assertEqual(ssl_vhost.names, set(["encryption-example.demo"])) self.assertTrue(ssl_vhost.ssl) self.assertFalse(ssl_vhost.enabled) @@ -229,7 +133,7 @@ class TwoVhost80Test(unittest.TestCase): self.assertEqual(len(self.config.vhosts), 5) - @mock.patch("letsencrypt.client.apache_configurator." + @mock.patch("letsencrypt.client.apache.configurator." "subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( @@ -254,6 +158,5 @@ class TwoVhost80Test(unittest.TestCase): self.assertRaises( errors.LetsEncryptConfiguratorError, self.config.get_version) - if __name__ == '__main__': unittest.main() diff --git a/letsencrypt/client/tests/apache_obj_test.py b/letsencrypt/client/tests/apache_obj_test.py new file mode 100644 index 000000000..fb13b051a --- /dev/null +++ b/letsencrypt/client/tests/apache_obj_test.py @@ -0,0 +1,62 @@ +import unittest + +from letsencrypt.client.apache import obj + + +class AddrTest(unittest.TestCase): + """Test the Addr class.""" + def setUp(self): + self.addr1 = obj.Addr.fromstring("192.168.1.1") + self.addr2 = obj.Addr.fromstring("192.168.1.1:*") + self.addr3 = obj.Addr.fromstring("192.168.1.1:80") + + def test_fromstring(self): + self.assertEqual(self.addr1.get_addr(), "192.168.1.1") + self.assertEqual(self.addr1.get_port(), "") + self.assertEqual(self.addr2.get_addr(), "192.168.1.1") + self.assertEqual(self.addr2.get_port(), "*") + self.assertEqual(self.addr3.get_addr(), "192.168.1.1") + self.assertEqual(self.addr3.get_port(), "80") + + def test_str(self): + self.assertEqual(str(self.addr1), "192.168.1.1") + self.assertEqual(str(self.addr2), "192.168.1.1:*") + self.assertEqual(str(self.addr3), "192.168.1.1:80") + + def test_get_addr_obj(self): + self.assertEqual(str(self.addr1.get_addr_obj("443")), "192.168.1.1:443") + self.assertEqual(str(self.addr2.get_addr_obj("")), "192.168.1.1") + self.assertEqual(str(self.addr1.get_addr_obj("*")), "192.168.1.1:*") + + def test_eq(self): + self.assertEqual(self.addr1, self.addr2.get_addr_obj("")) + self.assertNotEqual(self.addr1, self.addr2) + # This is specifically designed to hit line 28 but coverage denies me + # the satisfaction :( + self.assertNotEqual(self.addr1, 3333) + + def test_set_inclusion(self): + set_a = set([self.addr1, self.addr2]) + addr1b = obj.Addr.fromstring("192.168.1.1") + addr2b = obj.Addr.fromstring("192.168.1.1:*") + set_b = set([addr1b, addr2b]) + + self.assertTrue(addr1b in set_a) + self.assertEqual(set_a, set_b) + + +class VirtualHostTest(unittest.TestCase): + """Test the VirtualHost class.""" + def setUp(self): + self.vhost1 = obj.VirtualHost( + "filep", "vh_path", + set([obj.Addr.fromstring("localhost")]), False, False) + + def test_eq(self): + vhost1b = obj.VirtualHost( + "filep", "vh_path", + set([obj.Addr.fromstring("localhost")]), False, False) + + self.assertEqual(vhost1b, self.vhost1) + self.assertEqual(str(vhost1b), str(self.vhost1)) + self.assertTrue(vhost1b != 1234) diff --git a/letsencrypt/client/tests/apache_parser_test.py b/letsencrypt/client/tests/apache_parser_test.py new file mode 100644 index 000000000..259d974e1 --- /dev/null +++ b/letsencrypt/client/tests/apache_parser_test.py @@ -0,0 +1,114 @@ +import os +import shutil +import sys +import unittest + +import augeas +import mock + +from letsencrypt.client import display +from letsencrypt.client import errors +from letsencrypt.client.apache import parser +from letsencrypt.client.tests import config_util + + +class BasicParseTests(unittest.TestCase): + + def setUp(self): + display.set_display(display.FileDisplay(sys.stdout)) + + self.temp_dir, self.config_dir, self.work_dir = config_util.dir_setup( + "debian_apache_2_4/two_vhost_80") + + self.ssl_options = config_util.setup_apache_ssl_options(self.config_dir) + + # Final slash is currently important + self.config_path = os.path.join( + self.temp_dir, "debian_apache_2_4/two_vhost_80/apache2/") + + self.parser = parser.ApacheParser( + augeas.Augeas(flags=augeas.Augeas.NONE), + self.config_path, self.ssl_options) + + def tearDown(self): + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + + def test_parse_file(self): + """Test parse_file. + + letsencrypt.conf is chosen as the test file as it will not be + included during the normal course of execution. + + """ + file_path = os.path.join( + self.config_path, "sites-available", "letsencrypt.conf") + + # pylint: disable=protected-access + self.parser._parse_file(file_path) + + # search for the httpd incl + matches = self.parser.aug.match( + "/augeas/load/Httpd/incl [. ='%s']" % file_path) + + self.assertTrue(matches) + + def test_find_dir(self): + test = self.parser.find_dir( + parser.case_i("Listen"), "443") + # This will only look in enabled hosts + test2 = self.parser.find_dir( + parser.case_i("documentroot")) + self.assertEqual(len(test), 2) + self.assertEqual(len(test2), 3) + + def test_add_dir(self): + aug_default = "/files" + self.parser.loc["default"] + self.parser.add_dir( + aug_default, "AddDirective", "test") + + self.assertTrue( + self.parser.find_dir("AddDirective", "test", aug_default)) + + self.parser.add_dir(aug_default, "AddList", ["1", "2", "3", "4"]) + matches = self.parser.find_dir("AddList", None, aug_default) + for i, match in enumerate(matches): + self.assertEqual(self.parser.aug.get(match), str(i + 1)) + + def test_add_dir_to_ifmodssl(self): + """test add_dir_to_ifmodssl. + + Path must be valid before attempting to add to augeas + + """ + self.parser.add_dir_to_ifmodssl( + parser.get_aug_path(self.parser.loc["default"]), + "FakeDirective", "123") + + matches = self.parser.find_dir("FakeDirective", "123") + + self.assertEqual(len(matches), 1) + self.assertTrue("IfModule" in matches[0]) + + def test_get_aug_path(self): + self.assertEqual( + "/files/etc/apache", parser.get_aug_path("/etc/apache")) + + def test_set_locations(self): + with mock.patch("letsencrypt.client.apache.parser." + "os.path") as mock_path: + + mock_path.isfile.return_value = False + + # pylint: disable=protected-access + self.assertRaises(errors.LetsEncryptConfiguratorError, + self.parser._set_locations, self.ssl_options) + + mock_path.isfile.side_effect = [True, False, False] + + # pylint: disable=protected-access + results = self.parser._set_locations(self.ssl_options) + + self.assertEqual(results["default"], results["listen"]) + self.assertEqual(results["default"], results["name"]) diff --git a/letsencrypt/client/tests/config_util.py b/letsencrypt/client/tests/config_util.py new file mode 100644 index 000000000..4683ab0c2 --- /dev/null +++ b/letsencrypt/client/tests/config_util.py @@ -0,0 +1,94 @@ +import os +import pkg_resources +import shutil +import tempfile + +import mock + +from letsencrypt.client import CONFIG +from letsencrypt.client.apache import configurator +from letsencrypt.client.apache import obj +from letsencrypt.client.apache import parser + + +def dir_setup(test_dir="debian_apache_2_4/two_vhost_80"): + """Setup the directories necessary for the configurator.""" + temp_dir = tempfile.mkdtemp("temp") + config_dir = tempfile.mkdtemp("config") + work_dir = tempfile.mkdtemp("work") + + test_configs = pkg_resources.resource_filename( + __name__, "testdata/%s" % test_dir) + + shutil.copytree( + test_configs, os.path.join(temp_dir, test_dir), symlinks=True) + + return temp_dir, config_dir, work_dir + + +def setup_apache_ssl_options(config_dir): + """Move the ssl_options into position and return the path.""" + option_path = os.path.join(config_dir, "options-ssl.conf") + temp_options = pkg_resources.resource_filename( + "letsencrypt.client", os.path.basename(CONFIG.OPTIONS_SSL_CONF)) + shutil.copyfile( + temp_options, option_path) + + return option_path + + +def get_apache_configurator( + config_path, config_dir, work_dir, ssl_options, version=(2, 4, 7)): + """Create an Apache Configurator with the specified options.""" + + backups = os.path.join(work_dir, "backups") + + with mock.patch("letsencrypt.client.apache.configurator." + "subprocess.Popen") as mock_popen: + # This just states that the ssl module is already loaded + mock_popen().communicate.return_value = ("ssl_module", "") + config = configurator.ApacheConfigurator( + config_path, + { + "backup": backups, + "temp": os.path.join(work_dir, "temp_checkpoint"), + "progress": os.path.join(backups, "IN_PROGRESS"), + "config": config_dir, + "work": work_dir, + }, + ssl_options, + version) + + return config + + +def get_vh_truth(temp_dir, config_name): + """Return the ground truth for the specified directory.""" + if config_name == "debian_apache_2_4/two_vhost_80": + prefix = os.path.join( + temp_dir, config_name, "apache2/sites-available") + aug_pre = "/files" + prefix + vh_truth = [ + obj.VirtualHost( + os.path.join(prefix, "encryption-example.conf"), + os.path.join(aug_pre, "encryption-example.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), + False, True, set(["encryption-example.demo"])), + obj.VirtualHost( + os.path.join(prefix, "default-ssl.conf"), + os.path.join(aug_pre, "default-ssl.conf/IfModule/VirtualHost"), + set([obj.Addr.fromstring("_default_:443")]), True, False), + obj.VirtualHost( + os.path.join(prefix, "000-default.conf"), + os.path.join(aug_pre, "000-default.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + set(["ip-172-30-0-17"])), + obj.VirtualHost( + os.path.join(prefix, "letsencrypt.conf"), + os.path.join(aug_pre, "letsencrypt.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, True, + set(["letsencrypt.demo"])), + ] + return vh_truth + + return None diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py index dee5d3504..aa18541ee 100755 --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -5,9 +5,6 @@ import logging import os import sys -import zope.interface - -from letsencrypt.client import apache_configurator from letsencrypt.client import CONFIG from letsencrypt.client import client from letsencrypt.client import display @@ -15,6 +12,8 @@ from letsencrypt.client import interfaces from letsencrypt.client import errors from letsencrypt.client import log +from letsencrypt.client.apache import configurator + def main(): """Command line argument parsing and main script execution.""" @@ -82,11 +81,11 @@ def main(): display.set_display(display.FileDisplay(sys.stdout)) if args.rollback > 0: - rollback(apache_configurator.ApacheConfigurator(), args.rollback) + rollback(configurator.ApacheConfigurator(), args.rollback) sys.exit() if args.view_checkpoints: - view_checkpoints(apache_configurator.ApacheConfigurator()) + view_checkpoints(configurator.ApacheConfigurator()) sys.exit() server = args.server is None and CONFIG.ACME_SERVER or args.server @@ -122,7 +121,7 @@ def main(): # Validate the key and csr client.validate_key_csr(privkey, csr, domains) - cert_file, chain_file = acme.obtain_certificate(privkey, csr) + cert_file, chain_file = acme.obtain_certificate(csr) vhost = acme.deploy_certificate(privkey, cert_file, chain_file) acme.optimize_config(vhost, args.redirect) @@ -176,20 +175,19 @@ def get_all_names(installer): # This should be controlled by commandline parameters def determine_authenticator(): """Returns a valid authenticator.""" - try: - return apache_configurator.ApacheConfigurator() + return configurator.ApacheConfigurator() except errors.LetsEncryptConfiguratorError: - log.info("Unable to find a way to authenticate.") + logging.info("Unable to find a way to authenticate.") def determine_installer(): """Returns a valid installer if one exists.""" - try: - return apache_configurator.ApacheConfigurator() + print "shouldn't ha;ppppen!!!!!!!!!!!!!!!!!!!" + return configurator.ApacheConfigurator() except errors.LetsEncryptConfiguratorError: - log.info("Unable to find a way to install the certificate.") + logging.info("Unable to find a way to install the certificate.") def read_file(filename): diff --git a/setup.py b/setup.py index 3f8c6d5c8..f6d8f2880 100755 --- a/setup.py +++ b/setup.py @@ -37,6 +37,7 @@ setup( 'letsencrypt', 'letsencrypt.client', 'letsencrypt.client.apache', + 'letsencrypt.client.tests', 'letsencrypt.scripts', ], install_requires=install_requires, diff --git a/tox.ini b/tox.ini index 013b19c6c..4ebe69305 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ commands = [testenv:cover] commands = python setup.py dev - python setup.py nosetests --with-coverage --cover-min-percentage=44 + python setup.py nosetests --with-coverage --cover-min-percentage=47 [testenv:lint] commands =