diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py index 6a92296cd8a..07c145bfc11 100644 --- a/letsencrypt-apache/letsencrypt_apache/configurator.py +++ b/letsencrypt-apache/letsencrypt_apache/configurator.py @@ -342,6 +342,21 @@ 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""" + target_name = target_name.split(".")[::-1] + wildcards = [domain.split(".")[1:] for domain in names if domain.startswith("*")] + for wildcard in wildcards: + 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 + def _find_best_vhost(self, target_name): """Finds the best vhost for a target_name. @@ -351,16 +366,21 @@ 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 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 @@ -370,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 diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py index 58ec3fe21d7..81e237be3c2 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