From 2d2c98aa9df307844328de4cd5c9b22e71d24ceb Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Fri, 19 Feb 2016 13:47:10 -0800 Subject: [PATCH 1/3] add a check for wildcards --- .../letsencrypt_apache/configurator.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index cbc451ac94e..6cb7c12d4ae 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -342,6 +342,14 @@ def _choose_vhost_from_list(self, target_name, temp=False): self.assoc[target_name] = vhost return vhost + def included_in_wildcard(self, names, target_name): + """Helper function to see if alias is covered by wildcard""" + wildcards = [domain for domain in names if domain.startswith("*")] + for wildcard in wildcards: + if wildcard.split(".")[1] == target_name.split(".")[1]: + return True + return False + def _find_best_vhost(self, target_name): """Finds the best vhost for a target_name. @@ -360,7 +368,10 @@ def _find_best_vhost(self, target_name): for vhost in self.vhosts: if vhost.modmacro is True: continue - if target_name in vhost.get_names(): + names = vhost.get_names() + if target_name in names: + points = 3 + elif self.included_in_wildcard(names, target_name): points = 2 elif any(addr.get_addr() == target_name for addr in vhost.addrs): points = 1 From ca56a31132b46a4fe36eef58909ac0d4f1e049b1 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 23 Feb 2016 15:27:30 -0800 Subject: [PATCH 2/3] reverse domain matching for wildcards --- .../letsencrypt_apache/configurator.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 6cb7c12d4ae..47f2ef38200 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -344,9 +344,16 @@ def _choose_vhost_from_list(self, target_name, temp=False): def included_in_wildcard(self, names, target_name): """Helper function to see if alias is covered by wildcard""" - wildcards = [domain for domain in names if domain.startswith("*")] + target_name = target_name.split(".")[::-1] + wildcards = [domain.split(".")[1:] for domain in names if domain.startswith("*")] for wildcard in wildcards: - if wildcard.split(".")[1] == target_name.split(".")[1]: + if len(wildcard) > len(target_name): + continue + for idx, segment in enumerate(wildcard[::-1]): + if segment != target_name[idx]: + break + else: + # https://docs.python.org/2/tutorial/controlflow.html#break-and-continue-statements-and-else-clauses-on-loops return True return False @@ -359,9 +366,11 @@ def _find_best_vhost(self, target_name): :returns: VHost or None """ - # Points 4 - Servername SSL - # Points 3 - Address name with SSL - # Points 2 - Servername no SSL + # Points 6 - Servername SSL + # Points 5 - Wildcard SSL + # Points 4 - Address name with SSL + # Points 3 - Servername no SSL + # Points 2 - Wildcard no SSL # Points 1 - Address name with no SSL best_candidate = None best_points = 0 @@ -381,7 +390,7 @@ def _find_best_vhost(self, target_name): continue # pragma: no cover if vhost.ssl: - points += 2 + points += 3 if points > best_points: best_points = points From e46ce3028f6a633cfd3d66c5755211ff279446c7 Mon Sep 17 00:00:00 2001 From: Noah Swartz Date: Tue, 23 Feb 2016 17:31:41 -0800 Subject: [PATCH 3/3] coverage --- .../tests/configurator_test.py | 33 ++++++++++++++----- .../apache2/sites-available/wildcard.conf | 13 ++++++++ .../letsencrypt_apache/tests/util.py | 7 +++- 3 files changed, 43 insertions(+), 10 deletions(-) create mode 100644 letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/wildcard.conf diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 5b15a20d1f8..ef2c1b0b66c 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py @@ -85,7 +85,7 @@ def test_get_all_names(self, mock_getutility): mock_getutility.notification = mock.MagicMock(return_value=True) names = self.config.get_all_names() self.assertEqual(names, set( - ["letsencrypt.demo", "encryption-example.demo", "ip-172-30-0-17"])) + ["letsencrypt.demo", "encryption-example.demo", "ip-172-30-0-17", "*.blue.purple.com"])) @mock.patch("zope.component.getUtility") @mock.patch("letsencrypt_apache.configurator.socket.gethostbyaddr") @@ -103,7 +103,7 @@ def test_get_all_names_addrs(self, mock_gethost, mock_getutility): self.config.vhosts.append(vhost) names = self.config.get_all_names() - self.assertEqual(len(names), 5) + self.assertEqual(len(names), 6) self.assertTrue("zombo.com" in names) self.assertTrue("google.com" in names) self.assertTrue("letsencrypt.demo" in names) @@ -124,7 +124,7 @@ def test_get_virtual_hosts(self): """ vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 6) + self.assertEqual(len(vhs), 7) found = 0 for vhost in vhs: @@ -135,7 +135,7 @@ def test_get_virtual_hosts(self): else: raise Exception("Missed: %s" % vhost) # pragma: no cover - self.assertEqual(found, 6) + self.assertEqual(found, 7) # Handle case of non-debian layout get_virtual_hosts with mock.patch( @@ -143,7 +143,7 @@ def test_get_virtual_hosts(self): ) as mock_conf: mock_conf.return_value = False vhs = self.config.get_virtual_hosts() - self.assertEqual(len(vhs), 6) + self.assertEqual(len(vhs), 7) @mock.patch("letsencrypt_apache.display_ops.select_vhost") def test_choose_vhost_none_avail(self, mock_select): @@ -186,6 +186,20 @@ def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): self.assertRaises( errors.PluginError, self.config.choose_vhost, "none.com") + def test_choosevhost_select_vhost_with_wildcard(self): + chosen_vhost = self.config.choose_vhost("blue.purple.com", temp=True) + self.assertEqual(self.vh_truth[6], chosen_vhost) + + def test_findbest_continues_on_short_domain(self): + # pylint: disable=protected-access + chosen_vhost = self.config._find_best_vhost("purple.com") + self.assertEqual(None, chosen_vhost) + + def test_findbest_continues_on_long_domain(self): + # pylint: disable=protected-access + chosen_vhost = self.config._find_best_vhost("green.red.purple.com") + self.assertEqual(None, chosen_vhost) + def test_find_best_vhost(self): # pylint: disable=protected-access self.assertEqual( @@ -211,6 +225,7 @@ def test_find_best_vhost_default(self): self.config.vhosts = [ vh for vh in self.config.vhosts if vh.name not in ["letsencrypt.demo", "encryption-example.demo"] + and "*.blue.purple.com" not in vh.aliases ] self.assertEqual( @@ -218,7 +233,7 @@ def test_find_best_vhost_default(self): def test_non_default_vhosts(self): # pylint: disable=protected-access - self.assertEqual(len(self.config._non_default_vhosts()), 4) + self.assertEqual(len(self.config._non_default_vhosts()), 5) def test_is_site_enabled(self): """Test if site is enabled. @@ -524,7 +539,7 @@ def test_make_vhost_ssl(self): self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]), self.config.is_name_vhost(ssl_vhost)) - self.assertEqual(len(self.config.vhosts), 7) + self.assertEqual(len(self.config.vhosts), 8) def test_clean_vhost_ssl(self): # pylint: disable=protected-access @@ -942,7 +957,7 @@ def test_create_own_redirect(self): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 7) + self.assertEqual(len(self.config.vhosts), 8) def test_create_own_redirect_for_old_apache_version(self): self.config.parser.modules.add("rewrite_module") @@ -953,7 +968,7 @@ def test_create_own_redirect_for_old_apache_version(self): # pylint: disable=protected-access self.config._enable_redirect(self.vh_truth[1], "") - self.assertEqual(len(self.config.vhosts), 7) + self.assertEqual(len(self.config.vhosts), 8) def test_sift_line(self): # pylint: disable=protected-access diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/wildcard.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/wildcard.conf new file mode 100644 index 00000000000..33e30a63bb6 --- /dev/null +++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/wildcard.conf @@ -0,0 +1,13 @@ + + + ServerName ip-172-30-0-17 + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + ServerAlias *.blue.purple.com + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + + +# vim: syntax=apache ts=4 sw=4 sts=4 sr noet diff --git a/letsencrypt-apache/letsencrypt_apache/tests/util.py b/letsencrypt-apache/letsencrypt_apache/tests/util.py index 97a11e85162..fb1e1442d56 100644 --- a/letsencrypt-apache/letsencrypt_apache/tests/util.py +++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py @@ -150,7 +150,12 @@ def get_vh_truth(temp_dir, config_name): os.path.join(prefix, "default-ssl-port-only.conf"), os.path.join(aug_pre, ("default-ssl-port-only.conf/" "IfModule/VirtualHost")), - set([obj.Addr.fromstring("_default_:443")]), True, False) + set([obj.Addr.fromstring("_default_:443")]), True, False), + obj.VirtualHost( + os.path.join(prefix, "wildcard.conf"), + os.path.join(aug_pre, "wildcard.conf/VirtualHost"), + set([obj.Addr.fromstring("*:80")]), False, False, + "ip-172-30-0-17", aliases=["*.blue.purple.com"]) ] return vh_truth