Repository: certbot/certbot

Issue:
"SSLEngine on" does not appear to be an exhausitve/correct test for SSL vhosts
Over in #1423 @ArchimedesPi noticed that the [value set for `is_ssl`](https://github.com/letsencrypt/letsencrypt/blob/master/letsencrypt-apache/letsencrypt_apache/configurator.py#L433), based on whether `SSLEngine on` is in the vhost entry, is wrong.  In fact, it seems to be wrong for the SSL vhosts that `letsencrypt-apache` creates itself!

Probably we should be also checking for whether the vhost ends in :443!



Discussion:
It seems as though the way we set `SSLEngine on` in our server-wide `options-ssl-apache.conf` file, rather than within each vhost.  The docs imply [that shouldn't work](https://httpd.apache.org/docs/2.4/mod/mod_ssl.html#sslengine), but evidently it does.


Resolving patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/configurator.py b/letsencrypt-apache/letsencrypt_apache/configurator.py
--- a/letsencrypt-apache/letsencrypt_apache/configurator.py
+++ b/letsencrypt-apache/letsencrypt_apache/configurator.py
@@ -445,6 +445,12 @@ def _create_vhost(self, path):
         if self.parser.find_dir("SSLEngine", "on", start=path, exclude=False):
             is_ssl = True
 
+        # "SSLEngine on" might be set outside of <VirtualHost>
+        # Treat vhosts with port 443 as ssl vhosts
+        for addr in addrs:
+            if addr.get_port() == "443":
+                is_ssl = True
+
         filename = get_file_path(path)
         is_enabled = self.is_site_enabled(filename)
 


Test patch:
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
--- a/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/configurator_test.py
@@ -103,7 +103,7 @@ def test_get_virtual_hosts(self):
 
         """
         vhs = self.config.get_virtual_hosts()
-        self.assertEqual(len(vhs), 5)
+        self.assertEqual(len(vhs), 6)
         found = 0
 
         for vhost in vhs:
@@ -114,7 +114,7 @@ def test_get_virtual_hosts(self):
             else:
                 raise Exception("Missed: %s" % vhost)  # pragma: no cover
 
-        self.assertEqual(found, 5)
+        self.assertEqual(found, 6)
 
     @mock.patch("letsencrypt_apache.display_ops.select_vhost")
     def test_choose_vhost_none_avail(self, mock_select):
@@ -409,7 +409,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), 6)
+        self.assertEqual(len(self.config.vhosts), 7)
 
     def test_clean_vhost_ssl(self):
         # pylint: disable=protected-access
@@ -597,14 +597,14 @@ def test_config_test_bad_process(self, mock_run_script):
     def test_get_all_certs_keys(self):
         c_k = self.config.get_all_certs_keys()
 
-        self.assertEqual(len(c_k), 1)
+        self.assertEqual(len(c_k), 2)
         cert, key, path = next(iter(c_k))
         self.assertTrue("cert" in cert)
         self.assertTrue("key" in key)
-        self.assertTrue("default-ssl.conf" in path)
+        self.assertTrue("default-ssl" in path)
 
     def test_get_all_certs_keys_malformed_conf(self):
-        self.config.parser.find_dir = mock.Mock(side_effect=[["path"], []])
+        self.config.parser.find_dir = mock.Mock(side_effect=[["path"], [], ["path"], []])
         c_k = self.config.get_all_certs_keys()
 
         self.assertFalse(c_k)
@@ -710,7 +710,7 @@ def test_create_own_redirect(self):
         self.vh_truth[1].aliases = set(["yes.default.com"])
 
         self.config._enable_redirect(self.vh_truth[1], "")  # pylint: disable=protected-access
-        self.assertEqual(len(self.config.vhosts), 6)
+        self.assertEqual(len(self.config.vhosts), 7)
 
     def get_achalls(self):
         """Return testing achallenges."""
diff --git a/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl-port-only.conf b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl-port-only.conf
new file mode 100644
--- /dev/null
+++ b/letsencrypt-apache/letsencrypt_apache/tests/testdata/debian_apache_2_4/two_vhost_80/apache2/sites-available/default-ssl-port-only.conf
@@ -0,0 +1,36 @@
+<IfModule mod_ssl.c>
+	<VirtualHost _default_:443>
+		ServerAdmin webmaster@localhost
+
+		DocumentRoot /var/www/html
+
+		ErrorLog ${APACHE_LOG_DIR}/error.log
+		CustomLog ${APACHE_LOG_DIR}/access.log combined
+
+		#   A self-signed (snakeoil) certificate can be created by installing
+		#   the ssl-cert package. See
+		#   /usr/share/doc/apache2/README.Debian.gz for more info.
+		#   If both key and certificate are stored in the same file, only the
+		#   SSLCertificateFile directive is needed.
+		SSLCertificateFile	/etc/apache2/certs/letsencrypt-cert_5.pem
+		SSLCertificateKeyFile /etc/apache2/ssl/key-letsencrypt_15.pem
+
+
+		#SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire
+		<FilesMatch "\.(cgi|shtml|phtml|php)$">
+				SSLOptions +StdEnvVars
+		</FilesMatch>
+		<Directory /usr/lib/cgi-bin>
+				SSLOptions +StdEnvVars
+		</Directory>
+
+		BrowserMatch "MSIE [2-6]" \
+				nokeepalive ssl-unclean-shutdown \
+				downgrade-1.0 force-response-1.0
+		# MSIE 7 and newer should be able to use keepalive
+		BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown
+
+	</VirtualHost>
+</IfModule>
+
+# 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
--- a/letsencrypt-apache/letsencrypt_apache/tests/util.py
+++ b/letsencrypt-apache/letsencrypt_apache/tests/util.py
@@ -128,7 +128,11 @@ def get_vh_truth(temp_dir, config_name):
                 os.path.join(prefix, "mod_macro-example.conf"),
                 os.path.join(aug_pre,
                              "mod_macro-example.conf/Macro/VirtualHost"),
-                set([obj.Addr.fromstring("*:80")]), False, True, modmacro=True)
+                set([obj.Addr.fromstring("*:80")]), False, True, modmacro=True),
+            obj.VirtualHost(
+                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),
         ]
         return vh_truth