1# pylint: disable=too-many-lines 2"""Test for certbot_apache._internal.configurator.""" 3import copy 4import shutil 5import socket 6import tempfile 7import unittest 8 9try: 10 import mock 11except ImportError: # pragma: no cover 12 from unittest import mock # type: ignore 13 14from acme import challenges 15from certbot import achallenges 16from certbot import crypto_util 17from certbot import errors 18from certbot.compat import filesystem 19from certbot.compat import os 20from certbot.tests import acme_util 21from certbot.tests import util as certbot_util 22from certbot_apache._internal import apache_util 23from certbot_apache._internal import constants 24from certbot_apache._internal import obj 25from certbot_apache._internal import parser 26import util 27 28 29class MultipleVhostsTest(util.ApacheTest): 30 """Test two standard well-configured HTTP vhosts.""" 31 32 def setUp(self): # pylint: disable=arguments-differ 33 super().setUp() 34 35 self.config = util.get_apache_configurator( 36 self.config_path, self.vhost_path, self.config_dir, self.work_dir) 37 self.config = self.mock_deploy_cert(self.config) 38 self.vh_truth = util.get_vh_truth( 39 self.temp_dir, "debian_apache_2_4/multiple_vhosts") 40 41 def mock_deploy_cert(self, config): 42 """A test for a mock deploy cert""" 43 config.real_deploy_cert = self.config.deploy_cert 44 45 def mocked_deploy_cert(*args, **kwargs): 46 """a helper to mock a deployed cert""" 47 g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" 48 with mock.patch(g_mod): 49 config.real_deploy_cert(*args, **kwargs) 50 self.config.deploy_cert = mocked_deploy_cert 51 return self.config 52 53 @mock.patch("certbot_apache._internal.configurator.path_surgery") 54 def test_prepare_no_install(self, mock_surgery): 55 silly_path = {"PATH": "/tmp/nothingness2342"} 56 mock_surgery.return_value = False 57 with mock.patch.dict('os.environ', silly_path): 58 self.assertRaises(errors.NoInstallationError, self.config.prepare) 59 self.assertEqual(mock_surgery.call_count, 1) 60 61 @mock.patch("certbot_apache._internal.parser.ApacheParser") 62 @mock.patch("certbot_apache._internal.configurator.util.exe_exists") 63 def test_prepare_version(self, mock_exe_exists, _): 64 mock_exe_exists.return_value = True 65 self.config.version = None 66 self.config.config_test = mock.Mock() 67 self.config.get_version = mock.Mock(return_value=(1, 1)) 68 69 self.assertRaises( 70 errors.NotSupportedError, self.config.prepare) 71 72 def test_prepare_locked(self): 73 server_root = self.config.conf("server-root") 74 self.config.config_test = mock.Mock() 75 os.remove(os.path.join(server_root, ".certbot.lock")) 76 certbot_util.lock_and_call(self._test_prepare_locked, server_root) 77 78 @mock.patch("certbot_apache._internal.parser.ApacheParser") 79 @mock.patch("certbot_apache._internal.configurator.util.exe_exists") 80 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.get_parsernode_root") 81 def _test_prepare_locked(self, _node, _exists, _parser): 82 try: 83 self.config.prepare() 84 except errors.PluginError as err: 85 err_msg = str(err) 86 self.assertTrue("lock" in err_msg) 87 self.assertTrue(self.config.conf("server-root") in err_msg) 88 else: # pragma: no cover 89 self.fail("Exception wasn't raised!") 90 91 def test_add_parser_arguments(self): # pylint: disable=no-self-use 92 from certbot_apache._internal.configurator import ApacheConfigurator 93 # Weak test.. 94 ApacheConfigurator.add_parser_arguments(mock.MagicMock()) 95 96 def test_docs_parser_arguments(self): 97 os.environ["CERTBOT_DOCS"] = "1" 98 from certbot_apache._internal.configurator import ApacheConfigurator 99 mock_add = mock.MagicMock() 100 ApacheConfigurator.add_parser_arguments(mock_add) 101 parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", 102 "vhost_root", "logs_root", "challenge_location", 103 "handle_modules", "handle_sites", "ctl"] 104 exp = {} 105 106 for k in ApacheConfigurator.OS_DEFAULTS.__dict__.keys(): 107 if k in parserargs: 108 exp[k.replace("_", "-")] = getattr(ApacheConfigurator.OS_DEFAULTS, k) 109 # Special cases 110 exp["vhost-root"] = None 111 112 found = set() 113 for call in mock_add.call_args_list: 114 found.add(call[0][0]) 115 116 # Make sure that all (and only) the expected values exist 117 self.assertEqual(len(mock_add.call_args_list), len(found)) 118 for e in exp: 119 self.assertTrue(e in found) 120 121 del os.environ["CERTBOT_DOCS"] 122 123 def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use 124 from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES 125 for cls in OVERRIDE_CLASSES.values(): 126 cls.add_parser_arguments(mock.MagicMock()) 127 128 def test_all_configurators_defaults_defined(self): 129 from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES 130 from certbot_apache._internal.configurator import ApacheConfigurator 131 parameters = set(ApacheConfigurator.OS_DEFAULTS.__dict__.keys()) 132 for cls in OVERRIDE_CLASSES.values(): 133 self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.__dict__.keys()))) 134 135 def test_constant(self): 136 self.assertTrue("debian_apache_2_4/multiple_vhosts/apache" in 137 self.config.options.server_root) 138 139 @certbot_util.patch_display_util() 140 def test_get_all_names(self, mock_getutility): 141 mock_utility = mock_getutility() 142 mock_utility.notification = mock.MagicMock(return_value=True) 143 names = self.config.get_all_names() 144 self.assertEqual(names, {"certbot.demo", "ocspvhost.com", "encryption-example.demo", 145 "nonsym.link", "vhost.in.rootconf", "www.certbot.demo", 146 "duplicate.example.com"}) 147 148 @certbot_util.patch_display_util() 149 @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") 150 def test_get_all_names_addrs(self, mock_gethost, mock_getutility): 151 mock_gethost.side_effect = [("google.com", "", ""), socket.error] 152 mock_utility = mock_getutility() 153 mock_utility.notification.return_value = True 154 vhost = obj.VirtualHost( 155 "fp", "ap", 156 {obj.Addr(("8.8.8.8", "443")), 157 obj.Addr(("zombo.com",)), 158 obj.Addr(("192.168.1.2"))}, 159 True, False) 160 161 self.config.vhosts.append(vhost) 162 163 names = self.config.get_all_names() 164 self.assertEqual(len(names), 9) 165 self.assertTrue("zombo.com" in names) 166 self.assertTrue("google.com" in names) 167 self.assertTrue("certbot.demo" in names) 168 169 def test_get_bad_path(self): 170 self.assertEqual(apache_util.get_file_path(None), None) 171 self.assertEqual(apache_util.get_file_path("nonexistent"), None) 172 self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access 173 174 def test_get_aug_internal_path(self): 175 from certbot_apache._internal.apache_util import get_internal_aug_path 176 internal_paths = [ 177 "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost", 178 "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost", 179 "IfModule/VirtualHost"] 180 181 for i, internal_path in enumerate(internal_paths): 182 self.assertEqual( 183 get_internal_aug_path(self.vh_truth[i].path), internal_path) 184 185 def test_bad_servername_alias(self): 186 ssl_vh1 = obj.VirtualHost( 187 "fp1", "ap1", {obj.Addr(("*", "443"))}, 188 True, False) 189 # pylint: disable=protected-access 190 self.config._add_servernames(ssl_vh1) 191 self.assertTrue( 192 self.config._add_servername_alias("oy_vey", ssl_vh1) is None) 193 194 def test_add_servernames_alias(self): 195 self.config.parser.add_dir( 196 self.vh_truth[2].path, "ServerAlias", ["*.le.co"]) 197 # pylint: disable=protected-access 198 self.config._add_servernames(self.vh_truth[2]) 199 self.assertEqual( 200 self.vh_truth[2].get_names(), {"*.le.co", "ip-172-30-0-17"}) 201 202 def test_get_virtual_hosts(self): 203 """Make sure all vhosts are being properly found.""" 204 vhs = self.config.get_virtual_hosts() 205 self.assertEqual(len(vhs), 12) 206 found = 0 207 208 for vhost in vhs: 209 for truth in self.vh_truth: 210 if vhost == truth: 211 found += 1 212 break 213 else: 214 raise Exception("Missed: %s" % vhost) # pragma: no cover 215 216 self.assertEqual(found, 12) 217 218 # Handle case of non-debian layout get_virtual_hosts 219 with mock.patch( 220 "certbot_apache._internal.configurator.ApacheConfigurator.conf" 221 ) as mock_conf: 222 mock_conf.return_value = False 223 vhs = self.config.get_virtual_hosts() 224 self.assertEqual(len(vhs), 12) 225 226 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 227 def test_choose_vhost_none_avail(self, mock_select): 228 mock_select.return_value = None 229 self.assertRaises( 230 errors.PluginError, self.config.choose_vhost, "none.com") 231 232 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 233 def test_choose_vhost_select_vhost_ssl(self, mock_select): 234 mock_select.return_value = self.vh_truth[1] 235 self.assertEqual( 236 self.vh_truth[1], self.config.choose_vhost("none.com")) 237 238 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 239 @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") 240 def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select): 241 mock_select.return_value = self.vh_truth[0] 242 mock_conf.return_value = False 243 chosen_vhost = self.config.choose_vhost("none.com") 244 self.vh_truth[0].aliases.add("none.com") 245 self.assertEqual( 246 self.vh_truth[0].get_names(), chosen_vhost.get_names()) 247 248 # Make sure we go from HTTP -> HTTPS 249 self.assertFalse(self.vh_truth[0].ssl) 250 self.assertTrue(chosen_vhost.ssl) 251 252 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") 253 @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") 254 def test_choose_vhost_and_servername_addition(self, mock_add, mock_find): 255 ret_vh = self.vh_truth[8] 256 ret_vh.enabled = False 257 mock_find.return_value = self.vh_truth[8] 258 self.config.choose_vhost("whatever.com") 259 self.assertTrue(mock_add.called) 260 261 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 262 def test_choose_vhost_select_vhost_with_temp(self, mock_select): 263 mock_select.return_value = self.vh_truth[0] 264 chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) 265 self.assertEqual(self.vh_truth[0], chosen_vhost) 266 267 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 268 def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): 269 mock_select.return_value = self.vh_truth[3] 270 conflicting_vhost = obj.VirtualHost( 271 "path", "aug_path", {obj.Addr.fromstring("*:443")}, 272 True, True) 273 self.config.vhosts.append(conflicting_vhost) 274 275 self.assertRaises( 276 errors.PluginError, self.config.choose_vhost, "none.com") 277 278 def test_find_best_http_vhost_default(self): 279 vh = obj.VirtualHost( 280 "fp", "ap", {obj.Addr.fromstring("_default_:80")}, False, True) 281 self.config.vhosts = [vh] 282 self.assertEqual(self.config.find_best_http_vhost("foo.bar", False), vh) 283 284 def test_find_best_http_vhost_port(self): 285 port = "8080" 286 vh = obj.VirtualHost( 287 "fp", "ap", {obj.Addr.fromstring("*:" + port)}, 288 False, True, "encryption-example.demo") 289 self.config.vhosts.append(vh) 290 self.assertEqual(self.config.find_best_http_vhost("foo.bar", False, port), vh) 291 292 def test_findbest_continues_on_short_domain(self): 293 # pylint: disable=protected-access 294 chosen_vhost = self.config._find_best_vhost("purple.com") 295 self.assertEqual(None, chosen_vhost) 296 297 def test_findbest_continues_on_long_domain(self): 298 # pylint: disable=protected-access 299 chosen_vhost = self.config._find_best_vhost("green.red.purple.com") 300 self.assertEqual(None, chosen_vhost) 301 302 def test_find_best_vhost(self): 303 # pylint: disable=protected-access 304 self.assertEqual( 305 self.vh_truth[3], self.config._find_best_vhost("certbot.demo")) 306 self.assertEqual( 307 self.vh_truth[0], 308 self.config._find_best_vhost("encryption-example.demo")) 309 self.assertEqual( 310 self.config._find_best_vhost("does-not-exist.com"), None) 311 312 def test_find_best_vhost_variety(self): 313 # pylint: disable=protected-access 314 ssl_vh = obj.VirtualHost( 315 "fp", "ap", {obj.Addr(("*", "443")), 316 obj.Addr(("zombo.com",))}, 317 True, False) 318 self.config.vhosts.append(ssl_vh) 319 self.assertEqual(self.config._find_best_vhost("zombo.com"), ssl_vh) 320 321 def test_find_best_vhost_default(self): 322 # pylint: disable=protected-access 323 # Assume only the two default vhosts. 324 self.config.vhosts = [ 325 vh for vh in self.config.vhosts 326 if vh.name not in ["certbot.demo", "nonsym.link", 327 "encryption-example.demo", "duplicate.example.com", 328 "ocspvhost.com", "vhost.in.rootconf"] 329 and "*.blue.purple.com" not in vh.aliases 330 ] 331 self.assertEqual( 332 self.config._find_best_vhost("encryption-example.demo"), 333 self.vh_truth[2]) 334 335 def test_non_default_vhosts(self): 336 # pylint: disable=protected-access 337 vhosts = self.config._non_default_vhosts(self.config.vhosts) 338 self.assertEqual(len(vhosts), 10) 339 340 @mock.patch('certbot_apache._internal.configurator.display_util.notify') 341 def test_deploy_cert_enable_new_vhost(self, unused_mock_notify): 342 # Create 343 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) 344 self.config.parser.modules["ssl_module"] = None 345 self.config.parser.modules["mod_ssl.c"] = None 346 self.config.parser.modules["socache_shmcb_module"] = None 347 348 self.assertFalse(ssl_vhost.enabled) 349 self.config.deploy_cert( 350 "encryption-example.demo", "example/cert.pem", "example/key.pem", 351 "example/cert_chain.pem", "example/fullchain.pem") 352 self.assertTrue(ssl_vhost.enabled) 353 354 def test_no_duplicate_include(self): 355 def mock_find_dir(directive, argument, _): 356 """Mock method for parser.find_dir""" 357 if directive == "Include" and argument.endswith("options-ssl-apache.conf"): 358 return ["/path/to/whatever"] 359 return None # pragma: no cover 360 361 mock_add = mock.MagicMock() 362 self.config.parser.add_dir = mock_add 363 self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access 364 tried_to_add = False 365 for a in mock_add.call_args_list: 366 if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: 367 tried_to_add = True 368 # Include should be added, find_dir is not patched, and returns falsy 369 self.assertTrue(tried_to_add) 370 371 self.config.parser.find_dir = mock_find_dir 372 mock_add.reset_mock() 373 self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access 374 for a in mock_add.call_args_list: 375 if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf: 376 self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \ 377 # pragma: no cover 378 379 @mock.patch('certbot_apache._internal.configurator.display_util.notify') 380 def test_deploy_cert(self, unused_mock_notify): 381 self.config.parser.modules["ssl_module"] = None 382 self.config.parser.modules["mod_ssl.c"] = None 383 self.config.parser.modules["socache_shmcb_module"] = None 384 # Patch _add_dummy_ssl_directives to make sure we write them correctly 385 # pylint: disable=protected-access 386 orig_add_dummy = self.config._add_dummy_ssl_directives 387 def mock_add_dummy_ssl(vhostpath): 388 """Mock method for _add_dummy_ssl_directives""" 389 def find_args(path, directive): 390 """Return list of arguments in requested directive at path""" 391 f_args = [] 392 dirs = self.config.parser.find_dir(directive, None, 393 path) 394 for d in dirs: 395 f_args.append(self.config.parser.get_arg(d)) 396 return f_args 397 # Verify that the dummy directives do not exist 398 self.assertFalse( 399 "insert_cert_file_path" in find_args(vhostpath, 400 "SSLCertificateFile")) 401 self.assertFalse( 402 "insert_key_file_path" in find_args(vhostpath, 403 "SSLCertificateKeyFile")) 404 orig_add_dummy(vhostpath) 405 # Verify that the dummy directives exist 406 self.assertTrue( 407 "insert_cert_file_path" in find_args(vhostpath, 408 "SSLCertificateFile")) 409 self.assertTrue( 410 "insert_key_file_path" in find_args(vhostpath, 411 "SSLCertificateKeyFile")) 412 # pylint: disable=protected-access 413 self.config._add_dummy_ssl_directives = mock_add_dummy_ssl 414 415 # Get the default 443 vhost 416 self.config.assoc["random.demo"] = self.vh_truth[1] 417 self.config.deploy_cert( 418 "random.demo", 419 "example/cert.pem", "example/key.pem", "example/cert_chain.pem") 420 self.config.save() 421 422 # Verify ssl_module was enabled. 423 self.assertTrue(self.vh_truth[1].enabled) 424 self.assertTrue("ssl_module" in self.config.parser.modules) 425 426 loc_cert = self.config.parser.find_dir( 427 "sslcertificatefile", "example/cert.pem", self.vh_truth[1].path) 428 loc_key = self.config.parser.find_dir( 429 "sslcertificateKeyfile", "example/key.pem", self.vh_truth[1].path) 430 loc_chain = self.config.parser.find_dir( 431 "SSLCertificateChainFile", "example/cert_chain.pem", 432 self.vh_truth[1].path) 433 434 # Verify one directive was found in the correct file 435 self.assertEqual(len(loc_cert), 1) 436 self.assertEqual( 437 apache_util.get_file_path(loc_cert[0]), 438 self.vh_truth[1].filep) 439 440 self.assertEqual(len(loc_key), 1) 441 self.assertEqual( 442 apache_util.get_file_path(loc_key[0]), 443 self.vh_truth[1].filep) 444 445 self.assertEqual(len(loc_chain), 1) 446 self.assertEqual( 447 apache_util.get_file_path(loc_chain[0]), 448 self.vh_truth[1].filep) 449 450 # One more time for chain directive setting 451 self.config.deploy_cert( 452 "random.demo", 453 "two/cert.pem", "two/key.pem", "two/cert_chain.pem") 454 self.assertTrue(self.config.parser.find_dir( 455 "SSLCertificateChainFile", "two/cert_chain.pem", 456 self.vh_truth[1].path)) 457 458 def test_is_name_vhost(self): 459 addr = obj.Addr.fromstring("*:80") 460 self.assertTrue(self.config.is_name_vhost(addr)) 461 self.config.version = (2, 2) 462 self.assertFalse(self.config.is_name_vhost(addr)) 463 464 def test_add_name_vhost(self): 465 self.config.add_name_vhost(obj.Addr.fromstring("*:443")) 466 self.config.add_name_vhost(obj.Addr.fromstring("*:80")) 467 self.assertTrue(self.config.parser.find_dir( 468 "NameVirtualHost", "*:443", exclude=False)) 469 self.assertTrue(self.config.parser.find_dir( 470 "NameVirtualHost", "*:80")) 471 472 def test_add_listen_80(self): 473 mock_find = mock.Mock() 474 mock_add_dir = mock.Mock() 475 mock_find.return_value = [] 476 self.config.parser.find_dir = mock_find 477 self.config.parser.add_dir = mock_add_dir 478 self.config.ensure_listen("80") 479 self.assertTrue(mock_add_dir.called) 480 self.assertTrue(mock_find.called) 481 self.assertEqual(mock_add_dir.call_args[0][1], "Listen") 482 self.assertEqual(mock_add_dir.call_args[0][2], "80") 483 484 def test_add_listen_80_named(self): 485 mock_find = mock.Mock() 486 mock_find.return_value = ["test1", "test2", "test3"] 487 mock_get = mock.Mock() 488 mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] 489 mock_add_dir = mock.Mock() 490 491 self.config.parser.find_dir = mock_find 492 self.config.parser.get_arg = mock_get 493 self.config.parser.add_dir = mock_add_dir 494 495 self.config.ensure_listen("80") 496 self.assertEqual(mock_add_dir.call_count, 0) 497 498 # Reset return lists and inputs 499 mock_add_dir.reset_mock() 500 mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] 501 502 # Test 503 self.config.ensure_listen("8080") 504 self.assertEqual(mock_add_dir.call_count, 3) 505 self.assertTrue(mock_add_dir.called) 506 self.assertEqual(mock_add_dir.call_args[0][1], "Listen") 507 call_found = False 508 for mock_call in mock_add_dir.mock_calls: 509 if mock_call[1][2] == ['1.2.3.4:8080']: 510 call_found = True 511 self.assertTrue(call_found) 512 513 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") 514 def test_prepare_server_https(self, mock_reset): 515 mock_enable = mock.Mock() 516 self.config.enable_mod = mock_enable 517 518 mock_find = mock.Mock() 519 mock_add_dir = mock.Mock() 520 mock_find.return_value = [] 521 522 # This will test the Add listen 523 self.config.parser.find_dir = mock_find 524 self.config.parser.add_dir_to_ifmodssl = mock_add_dir 525 self.config.prepare_server_https("443") 526 # Changing the order these modules are enabled breaks the reverter 527 self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb") 528 self.assertEqual(mock_enable.call_args[0][0], "ssl") 529 self.assertEqual(mock_enable.call_args[1], {"temp": False}) 530 531 self.config.prepare_server_https("8080", temp=True) 532 # Changing the order these modules are enabled breaks the reverter 533 self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb") 534 self.assertEqual(mock_enable.call_args[0][0], "ssl") 535 # Enable mod is temporary 536 self.assertEqual(mock_enable.call_args[1], {"temp": True}) 537 538 self.assertEqual(mock_add_dir.call_count, 2) 539 540 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") 541 def test_prepare_server_https_named_listen(self, mock_reset): 542 mock_find = mock.Mock() 543 mock_find.return_value = ["test1", "test2", "test3"] 544 mock_get = mock.Mock() 545 mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] 546 mock_add_dir = mock.Mock() 547 mock_enable = mock.Mock() 548 549 self.config.parser.find_dir = mock_find 550 self.config.parser.get_arg = mock_get 551 self.config.parser.add_dir_to_ifmodssl = mock_add_dir 552 self.config.enable_mod = mock_enable 553 554 # Test Listen statements with specific ip listeed 555 self.config.prepare_server_https("443") 556 # Should be 0 as one interface already listens to 443 557 self.assertEqual(mock_add_dir.call_count, 0) 558 559 # Reset return lists and inputs 560 mock_add_dir.reset_mock() 561 mock_get.side_effect = ["1.2.3.4:80", "[::1]:80", "1.1.1.1:443"] 562 563 # Test 564 self.config.prepare_server_https("8080", temp=True) 565 self.assertEqual(mock_add_dir.call_count, 3) 566 call_args_list = [mock_add_dir.call_args_list[i][0][2] for i in range(3)] 567 self.assertEqual( 568 sorted(call_args_list), 569 sorted([["1.2.3.4:8080", "https"], 570 ["[::1]:8080", "https"], 571 ["1.1.1.1:8080", "https"]])) 572 573 # mock_get.side_effect = ["1.2.3.4:80", "[::1]:80"] 574 # mock_find.return_value = ["test1", "test2", "test3"] 575 # self.config.parser.get_arg = mock_get 576 # self.config.prepare_server_https("8080", temp=True) 577 # self.assertEqual(self.listens, 0) 578 579 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") 580 def test_prepare_server_https_needed_listen(self, mock_reset): 581 mock_find = mock.Mock() 582 mock_find.return_value = ["test1", "test2"] 583 mock_get = mock.Mock() 584 mock_get.side_effect = ["1.2.3.4:8080", "80"] 585 mock_add_dir = mock.Mock() 586 mock_enable = mock.Mock() 587 588 self.config.parser.find_dir = mock_find 589 self.config.parser.get_arg = mock_get 590 self.config.parser.add_dir_to_ifmodssl = mock_add_dir 591 self.config.enable_mod = mock_enable 592 593 self.config.prepare_server_https("443") 594 self.assertEqual(mock_add_dir.call_count, 1) 595 596 @mock.patch("certbot_apache._internal.parser.ApacheParser.reset_modules") 597 def test_prepare_server_https_mixed_listen(self, mock_reset): 598 mock_find = mock.Mock() 599 mock_find.return_value = ["test1", "test2"] 600 mock_get = mock.Mock() 601 mock_get.side_effect = ["1.2.3.4:8080", "443"] 602 mock_add_dir = mock.Mock() 603 mock_enable = mock.Mock() 604 605 self.config.parser.find_dir = mock_find 606 self.config.parser.get_arg = mock_get 607 self.config.parser.add_dir_to_ifmodssl = mock_add_dir 608 self.config.enable_mod = mock_enable 609 610 # Test Listen statements with specific ip listeed 611 self.config.prepare_server_https("443") 612 # Should only be 2 here, as the third interface 613 # already listens to the correct port 614 self.assertEqual(mock_add_dir.call_count, 0) 615 616 def test_make_vhost_ssl_with_mock_span(self): 617 # span excludes the closing </VirtualHost> tag in older versions 618 # of Augeas 619 return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142] 620 with mock.patch.object(self.config.parser.aug, 'span') as mock_span: 621 mock_span.return_value = return_value 622 self.test_make_vhost_ssl() 623 624 def test_make_vhost_ssl_with_mock_span2(self): 625 # span includes the closing </VirtualHost> tag in newer versions 626 # of Augeas 627 return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157] 628 with mock.patch.object(self.config.parser.aug, 'span') as mock_span: 629 mock_span.return_value = return_value 630 self.test_make_vhost_ssl() 631 632 def test_make_vhost_ssl_nonsymlink(self): 633 ssl_vhost_slink = self.config.make_vhost_ssl(self.vh_truth[8]) 634 self.assertTrue(ssl_vhost_slink.ssl) 635 self.assertTrue(ssl_vhost_slink.enabled) 636 self.assertEqual(ssl_vhost_slink.name, "nonsym.link") 637 638 def test_make_vhost_ssl_nonexistent_vhost_path(self): 639 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) 640 self.assertEqual(os.path.dirname(ssl_vhost.filep), 641 os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) 642 643 def test_make_vhost_ssl(self): 644 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) 645 646 self.assertEqual( 647 ssl_vhost.filep, 648 os.path.join(self.config_path, "sites-available", 649 "encryption-example-le-ssl.conf")) 650 651 self.assertEqual(ssl_vhost.path, 652 "/files" + ssl_vhost.filep + "/IfModule/Virtualhost") 653 self.assertEqual(len(ssl_vhost.addrs), 1) 654 self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs) 655 self.assertEqual(ssl_vhost.name, "encryption-example.demo") 656 self.assertTrue(ssl_vhost.ssl) 657 self.assertFalse(ssl_vhost.enabled) 658 659 self.assertEqual(self.config.is_name_vhost(self.vh_truth[0]), 660 self.config.is_name_vhost(ssl_vhost)) 661 662 self.assertEqual(len(self.config.vhosts), 13) 663 664 def test_clean_vhost_ssl(self): 665 # pylint: disable=protected-access 666 for directive in ["SSLCertificateFile", "SSLCertificateKeyFile", 667 "SSLCertificateChainFile", "SSLCACertificatePath"]: 668 for _ in range(10): 669 self.config.parser.add_dir(self.vh_truth[1].path, 670 directive, ["bogus"]) 671 self.config.save() 672 673 self.config._clean_vhost(self.vh_truth[1]) 674 self.config.save() 675 676 loc_cert = self.config.parser.find_dir( 677 'SSLCertificateFile', None, self.vh_truth[1].path, False) 678 loc_key = self.config.parser.find_dir( 679 'SSLCertificateKeyFile', None, self.vh_truth[1].path, False) 680 loc_chain = self.config.parser.find_dir( 681 'SSLCertificateChainFile', None, self.vh_truth[1].path, False) 682 loc_cacert = self.config.parser.find_dir( 683 'SSLCACertificatePath', None, self.vh_truth[1].path, False) 684 685 self.assertEqual(len(loc_cert), 1) 686 self.assertEqual(len(loc_key), 1) 687 688 self.assertEqual(len(loc_chain), 0) 689 690 self.assertEqual(len(loc_cacert), 10) 691 692 def test_deduplicate_directives(self): 693 # pylint: disable=protected-access 694 DIRECTIVE = "Foo" 695 for _ in range(10): 696 self.config.parser.add_dir(self.vh_truth[1].path, 697 DIRECTIVE, ["bar"]) 698 self.config.save() 699 700 self.config._deduplicate_directives(self.vh_truth[1].path, [DIRECTIVE]) 701 self.config.save() 702 703 self.assertEqual( 704 len(self.config.parser.find_dir( 705 DIRECTIVE, None, self.vh_truth[1].path, False)), 1) 706 707 def test_remove_directives(self): 708 # pylint: disable=protected-access 709 DIRECTIVES = ["Foo", "Bar"] 710 for directive in DIRECTIVES: 711 for _ in range(10): 712 self.config.parser.add_dir(self.vh_truth[2].path, 713 directive, ["baz"]) 714 self.config.save() 715 716 self.config._remove_directives(self.vh_truth[2].path, DIRECTIVES) 717 self.config.save() 718 719 for directive in DIRECTIVES: 720 self.assertEqual( 721 len(self.config.parser.find_dir( 722 directive, None, self.vh_truth[2].path, False)), 0) 723 724 def test_make_vhost_ssl_bad_write(self): 725 mock_open = mock.mock_open() 726 # This calls open 727 self.config.reverter.register_file_creation = mock.Mock() 728 mock_open.side_effect = IOError 729 with mock.patch("builtins.open", mock_open): 730 self.assertRaises( 731 errors.PluginError, 732 self.config.make_vhost_ssl, self.vh_truth[0]) 733 734 def test_get_ssl_vhost_path(self): 735 # pylint: disable=protected-access 736 self.assertTrue( 737 self.config._get_ssl_vhost_path("example_path").endswith(".conf")) 738 739 def test_add_name_vhost_if_necessary(self): 740 # pylint: disable=protected-access 741 self.config.add_name_vhost = mock.Mock() 742 self.config.version = (2, 2) 743 self.config._add_name_vhost_if_necessary(self.vh_truth[0]) 744 self.assertTrue(self.config.add_name_vhost.called) 745 746 new_addrs = set() 747 for addr in self.vh_truth[0].addrs: 748 new_addrs.add(obj.Addr(("_default_", addr.get_port(),))) 749 750 self.vh_truth[0].addrs = new_addrs 751 self.config._add_name_vhost_if_necessary(self.vh_truth[0]) 752 self.assertEqual(self.config.add_name_vhost.call_count, 2) 753 754 @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") 755 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") 756 def test_perform(self, mock_restart, mock_http_perform): 757 # Only tests functionality specific to configurator.perform 758 # Note: As more challenges are offered this will have to be expanded 759 account_key, achalls = self.get_key_and_achalls() 760 761 expected = [achall.response(account_key) for achall in achalls] 762 mock_http_perform.return_value = expected 763 764 responses = self.config.perform(achalls) 765 766 self.assertEqual(mock_http_perform.call_count, 1) 767 self.assertEqual(responses, expected) 768 769 self.assertEqual(mock_restart.call_count, 1) 770 771 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") 772 @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") 773 def test_cleanup(self, mock_cfg, mock_restart): 774 mock_cfg.return_value = "" 775 _, achalls = self.get_key_and_achalls() 776 777 for achall in achalls: 778 self.config._chall_out.add(achall) # pylint: disable=protected-access 779 780 for i, achall in enumerate(achalls): 781 self.config.cleanup([achall]) 782 if i == len(achalls) - 1: 783 self.assertTrue(mock_restart.called) 784 else: 785 self.assertFalse(mock_restart.called) 786 787 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") 788 @mock.patch("certbot_apache._internal.apache_util._get_runtime_cfg") 789 def test_cleanup_no_errors(self, mock_cfg, mock_restart): 790 mock_cfg.return_value = "" 791 _, achalls = self.get_key_and_achalls() 792 self.config.http_doer = mock.MagicMock() 793 794 for achall in achalls: 795 self.config._chall_out.add(achall) # pylint: disable=protected-access 796 797 self.config.cleanup([achalls[-1]]) 798 self.assertFalse(mock_restart.called) 799 800 self.config.cleanup(achalls) 801 self.assertTrue(mock_restart.called) 802 803 @mock.patch("certbot.util.run_script") 804 def test_get_version(self, mock_script): 805 mock_script.return_value = ( 806 "Server Version: Apache/2.4.2 (Debian)", "") 807 self.assertEqual(self.config.get_version(), (2, 4, 2)) 808 809 mock_script.return_value = ( 810 "Server Version: Apache/2 (Linux)", "") 811 self.assertEqual(self.config.get_version(), (2,)) 812 813 mock_script.return_value = ( 814 "Server Version: Apache (Debian)", "") 815 self.assertRaises(errors.PluginError, self.config.get_version) 816 817 mock_script.return_value = ( 818 "Server Version: Apache/2.3{0} Apache/2.4.7".format( 819 os.linesep), "") 820 self.assertRaises(errors.PluginError, self.config.get_version) 821 822 mock_script.side_effect = errors.SubprocessError("Can't find program") 823 self.assertRaises(errors.PluginError, self.config.get_version) 824 825 @mock.patch("certbot_apache._internal.configurator.util.run_script") 826 def test_restart(self, _): 827 self.config.restart() 828 829 @mock.patch("certbot_apache._internal.configurator.util.run_script") 830 def test_restart_bad_process(self, mock_run_script): 831 mock_run_script.side_effect = [None, errors.SubprocessError] 832 833 self.assertRaises(errors.MisconfigurationError, self.config.restart) 834 835 @mock.patch("certbot.util.run_script") 836 def test_config_test(self, _): 837 self.config.config_test() 838 839 @mock.patch("certbot.util.run_script") 840 def test_config_test_bad_process(self, mock_run_script): 841 mock_run_script.side_effect = errors.SubprocessError 842 843 self.assertRaises(errors.MisconfigurationError, 844 self.config.config_test) 845 846 def test_more_info(self): 847 self.assertTrue(self.config.more_info()) 848 849 def test_get_chall_pref(self): 850 self.assertTrue(isinstance(self.config.get_chall_pref(""), list)) 851 852 def test_install_ssl_options_conf(self): 853 path = os.path.join(self.work_dir, "test_it") 854 other_path = os.path.join(self.work_dir, "other_test_it") 855 self.config.install_ssl_options_conf(path, other_path) 856 self.assertTrue(os.path.isfile(path)) 857 self.assertTrue(os.path.isfile(other_path)) 858 859 # TEST ENHANCEMENTS 860 def test_supported_enhancements(self): 861 self.assertTrue(isinstance(self.config.supported_enhancements(), list)) 862 863 def test_find_http_vhost_without_ancestor(self): 864 # pylint: disable=protected-access 865 vhost = self.vh_truth[0] 866 vhost.ssl = True 867 vhost.ancestor = None 868 res = self.config._get_http_vhost(vhost) 869 self.assertEqual(self.vh_truth[0].name, res.name) 870 self.assertEqual(self.vh_truth[0].aliases, res.aliases) 871 872 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") 873 @mock.patch("certbot_apache._internal.display_ops.select_vhost") 874 @mock.patch("certbot.util.exe_exists") 875 def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get): 876 self.config.parser.modules["rewrite_module"] = None 877 mock_exe.return_value = True 878 ssl_vh1 = obj.VirtualHost( 879 "fp1", "ap1", {obj.Addr(("*", "443"))}, 880 True, False) 881 ssl_vh1.name = "satoshi.com" 882 self.config.vhosts.append(ssl_vh1) 883 mock_sel_vhost.return_value = None 884 mock_get.return_value = None 885 886 self.assertRaises( 887 errors.PluginError, 888 self.config.enhance, "satoshi.com", "redirect") 889 890 def test_enhance_unknown_enhancement(self): 891 self.assertRaises( 892 errors.PluginError, 893 self.config.enhance, "certbot.demo", "unknown_enhancement") 894 895 def test_enhance_no_ssl_vhost(self): 896 with mock.patch("certbot_apache._internal.configurator.logger.error") as mock_log: 897 self.assertRaises(errors.PluginError, self.config.enhance, 898 "certbot.demo", "redirect") 899 # Check that correct logger.warning was printed 900 self.assertTrue("not able to find" in mock_log.call_args[0][0]) 901 self.assertTrue("\"redirect\"" in mock_log.call_args[0][0]) 902 903 mock_log.reset_mock() 904 905 self.assertRaises(errors.PluginError, self.config.enhance, 906 "certbot.demo", "ensure-http-header", "Test") 907 # Check that correct logger.warning was printed 908 self.assertTrue("not able to find" in mock_log.call_args[0][0]) 909 self.assertTrue("Test" in mock_log.call_args[0][0]) 910 911 @mock.patch("certbot.util.exe_exists") 912 def test_ocsp_stapling(self, mock_exe): 913 self.config.parser.update_runtime_variables = mock.Mock() 914 self.config.parser.modules["mod_ssl.c"] = None 915 self.config.parser.modules["socache_shmcb_module"] = None 916 self.config.get_version = mock.Mock(return_value=(2, 4, 7)) 917 mock_exe.return_value = True 918 919 # This will create an ssl vhost for certbot.demo 920 self.config.choose_vhost("certbot.demo") 921 self.config.enhance("certbot.demo", "staple-ocsp") 922 923 # Get the ssl vhost for certbot.demo 924 ssl_vhost = self.config.assoc["certbot.demo"] 925 926 ssl_use_stapling_aug_path = self.config.parser.find_dir( 927 "SSLUseStapling", "on", ssl_vhost.path) 928 929 self.assertEqual(len(ssl_use_stapling_aug_path), 1) 930 931 ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) 932 stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', 933 "shmcb:/var/run/apache2/stapling_cache(128000)", 934 ssl_vhost_aug_path) 935 936 self.assertEqual(len(stapling_cache_aug_path), 1) 937 938 @mock.patch("certbot.util.exe_exists") 939 def test_ocsp_stapling_twice(self, mock_exe): 940 self.config.parser.update_runtime_variables = mock.Mock() 941 self.config.parser.modules["mod_ssl.c"] = None 942 self.config.parser.modules["socache_shmcb_module"] = None 943 self.config.get_version = mock.Mock(return_value=(2, 4, 7)) 944 mock_exe.return_value = True 945 946 # Checking the case with already enabled ocsp stapling configuration 947 self.config.choose_vhost("ocspvhost.com") 948 self.config.enhance("ocspvhost.com", "staple-ocsp") 949 950 # Get the ssl vhost for letsencrypt.demo 951 ssl_vhost = self.config.assoc["ocspvhost.com"] 952 953 ssl_use_stapling_aug_path = self.config.parser.find_dir( 954 "SSLUseStapling", "on", ssl_vhost.path) 955 956 self.assertEqual(len(ssl_use_stapling_aug_path), 1) 957 ssl_vhost_aug_path = parser.get_aug_path(ssl_vhost.filep) 958 stapling_cache_aug_path = self.config.parser.find_dir('SSLStaplingCache', 959 "shmcb:/var/run/apache2/stapling_cache(128000)", 960 ssl_vhost_aug_path) 961 962 self.assertEqual(len(stapling_cache_aug_path), 1) 963 964 965 @mock.patch("certbot.util.exe_exists") 966 def test_ocsp_unsupported_apache_version(self, mock_exe): 967 mock_exe.return_value = True 968 self.config.parser.update_runtime_variables = mock.Mock() 969 self.config.parser.modules["mod_ssl.c"] = None 970 self.config.parser.modules["socache_shmcb_module"] = None 971 self.config.get_version = mock.Mock(return_value=(2, 2, 0)) 972 self.config.choose_vhost("certbot.demo") 973 974 self.assertRaises(errors.PluginError, 975 self.config.enhance, "certbot.demo", "staple-ocsp") 976 977 978 def test_get_http_vhost_third_filter(self): 979 ssl_vh = obj.VirtualHost( 980 "fp", "ap", {obj.Addr(("*", "443"))}, 981 True, False) 982 ssl_vh.name = "satoshi.com" 983 self.config.vhosts.append(ssl_vh) 984 985 # pylint: disable=protected-access 986 http_vh = self.config._get_http_vhost(ssl_vh) 987 self.assertFalse(http_vh.ssl) 988 989 @mock.patch("certbot.util.run_script") 990 @mock.patch("certbot.util.exe_exists") 991 def test_http_header_hsts(self, mock_exe, _): 992 self.config.parser.update_runtime_variables = mock.Mock() 993 self.config.parser.modules["mod_ssl.c"] = None 994 self.config.parser.modules["headers_module"] = None 995 mock_exe.return_value = True 996 997 # This will create an ssl vhost for certbot.demo 998 self.config.choose_vhost("certbot.demo") 999 self.config.enhance("certbot.demo", "ensure-http-header", 1000 "Strict-Transport-Security") 1001 1002 # Get the ssl vhost for certbot.demo 1003 ssl_vhost = self.config.assoc["certbot.demo"] 1004 1005 # These are not immediately available in find_dir even with save() and 1006 # load(). They must be found in sites-available 1007 hsts_header = self.config.parser.find_dir( 1008 "Header", None, ssl_vhost.path) 1009 1010 # four args to HSTS header 1011 self.assertEqual(len(hsts_header), 4) 1012 1013 def test_http_header_hsts_twice(self): 1014 self.config.parser.modules["mod_ssl.c"] = None 1015 # skip the enable mod 1016 self.config.parser.modules["headers_module"] = None 1017 1018 # This will create an ssl vhost for encryption-example.demo 1019 self.config.choose_vhost("encryption-example.demo") 1020 self.config.enhance("encryption-example.demo", "ensure-http-header", 1021 "Strict-Transport-Security") 1022 1023 self.assertRaises( 1024 errors.PluginEnhancementAlreadyPresent, 1025 self.config.enhance, "encryption-example.demo", 1026 "ensure-http-header", "Strict-Transport-Security") 1027 1028 @mock.patch("certbot.util.run_script") 1029 @mock.patch("certbot.util.exe_exists") 1030 def test_http_header_uir(self, mock_exe, _): 1031 self.config.parser.update_runtime_variables = mock.Mock() 1032 self.config.parser.modules["mod_ssl.c"] = None 1033 self.config.parser.modules["headers_module"] = None 1034 1035 mock_exe.return_value = True 1036 1037 # This will create an ssl vhost for certbot.demo 1038 self.config.choose_vhost("certbot.demo") 1039 self.config.enhance("certbot.demo", "ensure-http-header", 1040 "Upgrade-Insecure-Requests") 1041 1042 self.assertTrue("headers_module" in self.config.parser.modules) 1043 1044 # Get the ssl vhost for certbot.demo 1045 ssl_vhost = self.config.assoc["certbot.demo"] 1046 1047 # These are not immediately available in find_dir even with save() and 1048 # load(). They must be found in sites-available 1049 uir_header = self.config.parser.find_dir( 1050 "Header", None, ssl_vhost.path) 1051 1052 # four args to HSTS header 1053 self.assertEqual(len(uir_header), 4) 1054 1055 def test_http_header_uir_twice(self): 1056 self.config.parser.modules["mod_ssl.c"] = None 1057 # skip the enable mod 1058 self.config.parser.modules["headers_module"] = None 1059 1060 # This will create an ssl vhost for encryption-example.demo 1061 self.config.choose_vhost("encryption-example.demo") 1062 self.config.enhance("encryption-example.demo", "ensure-http-header", 1063 "Upgrade-Insecure-Requests") 1064 1065 self.assertRaises( 1066 errors.PluginEnhancementAlreadyPresent, 1067 self.config.enhance, "encryption-example.demo", 1068 "ensure-http-header", "Upgrade-Insecure-Requests") 1069 1070 @mock.patch("certbot.util.run_script") 1071 @mock.patch("certbot.util.exe_exists") 1072 def test_redirect_well_formed_http(self, mock_exe, _): 1073 self.config.parser.modules["rewrite_module"] = None 1074 self.config.parser.update_runtime_variables = mock.Mock() 1075 mock_exe.return_value = True 1076 self.config.get_version = mock.Mock(return_value=(2, 2)) 1077 1078 # This will create an ssl vhost for certbot.demo 1079 self.config.choose_vhost("certbot.demo") 1080 self.config.enhance("certbot.demo", "redirect") 1081 1082 # These are not immediately available in find_dir even with save() and 1083 # load(). They must be found in sites-available 1084 rw_engine = self.config.parser.find_dir( 1085 "RewriteEngine", "on", self.vh_truth[3].path) 1086 rw_rule = self.config.parser.find_dir( 1087 "RewriteRule", None, self.vh_truth[3].path) 1088 1089 self.assertEqual(len(rw_engine), 1) 1090 # three args to rw_rule 1091 self.assertEqual(len(rw_rule), 3) 1092 1093 # [:-3] to remove the vhost index number 1094 self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) 1095 self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) 1096 1097 def test_rewrite_rule_exists(self): 1098 # Skip the enable mod 1099 self.config.parser.modules["rewrite_module"] = None 1100 self.config.get_version = mock.Mock(return_value=(2, 3, 9)) 1101 self.config.parser.add_dir( 1102 self.vh_truth[3].path, "RewriteRule", ["Unknown"]) 1103 # pylint: disable=protected-access 1104 self.assertTrue(self.config._is_rewrite_exists(self.vh_truth[3])) 1105 1106 def test_rewrite_engine_exists(self): 1107 # Skip the enable mod 1108 self.config.parser.modules["rewrite_module"] = None 1109 self.config.get_version = mock.Mock(return_value=(2, 3, 9)) 1110 self.config.parser.add_dir( 1111 self.vh_truth[3].path, "RewriteEngine", "on") 1112 # pylint: disable=protected-access 1113 self.assertTrue(self.config._is_rewrite_engine_on(self.vh_truth[3])) 1114 1115 @mock.patch("certbot.util.run_script") 1116 @mock.patch("certbot.util.exe_exists") 1117 def test_redirect_with_existing_rewrite(self, mock_exe, _): 1118 self.config.parser.modules["rewrite_module"] = None 1119 self.config.parser.update_runtime_variables = mock.Mock() 1120 mock_exe.return_value = True 1121 self.config.get_version = mock.Mock(return_value=(2, 2, 0)) 1122 1123 # Create a preexisting rewrite rule 1124 self.config.parser.add_dir( 1125 self.vh_truth[3].path, "RewriteRule", ["UnknownPattern", 1126 "UnknownTarget"]) 1127 self.config.save() 1128 1129 # This will create an ssl vhost for certbot.demo 1130 self.config.choose_vhost("certbot.demo") 1131 self.config.enhance("certbot.demo", "redirect") 1132 1133 # These are not immediately available in find_dir even with save() and 1134 # load(). They must be found in sites-available 1135 rw_engine = self.config.parser.find_dir( 1136 "RewriteEngine", "on", self.vh_truth[3].path) 1137 rw_rule = self.config.parser.find_dir( 1138 "RewriteRule", None, self.vh_truth[3].path) 1139 1140 self.assertEqual(len(rw_engine), 1) 1141 # three args to rw_rule + 1 arg for the pre existing rewrite 1142 self.assertEqual(len(rw_rule), 5) 1143 # [:-3] to remove the vhost index number 1144 self.assertTrue(rw_engine[0].startswith(self.vh_truth[3].path[:-3])) 1145 self.assertTrue(rw_rule[0].startswith(self.vh_truth[3].path[:-3])) 1146 1147 self.assertTrue("rewrite_module" in self.config.parser.modules) 1148 1149 @mock.patch("certbot.util.run_script") 1150 @mock.patch("certbot.util.exe_exists") 1151 def test_redirect_with_old_https_redirection(self, mock_exe, _): 1152 self.config.parser.modules["rewrite_module"] = None 1153 self.config.parser.update_runtime_variables = mock.Mock() 1154 mock_exe.return_value = True 1155 self.config.get_version = mock.Mock(return_value=(2, 2, 0)) 1156 1157 ssl_vhost = self.config.choose_vhost("certbot.demo") 1158 1159 # pylint: disable=protected-access 1160 http_vhost = self.config._get_http_vhost(ssl_vhost) 1161 1162 # Create an old (previously suppoorted) https redirectoin rewrite rule 1163 self.config.parser.add_dir( 1164 http_vhost.path, "RewriteRule", 1165 ["^", 1166 "https://%{SERVER_NAME}%{REQUEST_URI}", 1167 "[L,QSA,R=permanent]"]) 1168 1169 self.config.save() 1170 1171 try: 1172 self.config.enhance("certbot.demo", "redirect") 1173 except errors.PluginEnhancementAlreadyPresent: 1174 args_paths = self.config.parser.find_dir( 1175 "RewriteRule", None, http_vhost.path, False) 1176 arg_vals = [self.config.parser.aug.get(x) for x in args_paths] 1177 self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) 1178 1179 1180 def test_redirect_with_conflict(self): 1181 self.config.parser.modules["rewrite_module"] = None 1182 ssl_vh = obj.VirtualHost( 1183 "fp", "ap", {obj.Addr(("*", "443")), 1184 obj.Addr(("zombo.com",))}, 1185 True, False) 1186 # No names ^ this guy should conflict. 1187 1188 # pylint: disable=protected-access 1189 self.assertRaises( 1190 errors.PluginError, self.config._enable_redirect, ssl_vh, "") 1191 1192 def test_redirect_two_domains_one_vhost(self): 1193 # Skip the enable mod 1194 self.config.parser.modules["rewrite_module"] = None 1195 self.config.get_version = mock.Mock(return_value=(2, 3, 9)) 1196 1197 # Creates ssl vhost for the domain 1198 self.config.choose_vhost("red.blue.purple.com") 1199 1200 self.config.enhance("red.blue.purple.com", "redirect") 1201 verify_no_redirect = ("certbot_apache._internal.configurator." 1202 "ApacheConfigurator._verify_no_certbot_redirect") 1203 with mock.patch(verify_no_redirect) as mock_verify: 1204 self.config.enhance("green.blue.purple.com", "redirect") 1205 self.assertFalse(mock_verify.called) 1206 1207 def test_redirect_from_previous_run(self): 1208 # Skip the enable mod 1209 self.config.parser.modules["rewrite_module"] = None 1210 self.config.get_version = mock.Mock(return_value=(2, 3, 9)) 1211 self.config.choose_vhost("red.blue.purple.com") 1212 self.config.enhance("red.blue.purple.com", "redirect") 1213 # Clear state about enabling redirect on this run 1214 # pylint: disable=protected-access 1215 self.config._enhanced_vhosts["redirect"].clear() 1216 1217 self.assertRaises( 1218 errors.PluginEnhancementAlreadyPresent, 1219 self.config.enhance, "green.blue.purple.com", "redirect") 1220 1221 def test_create_own_redirect(self): 1222 self.config.parser.modules["rewrite_module"] = None 1223 self.config.get_version = mock.Mock(return_value=(2, 3, 9)) 1224 # For full testing... give names... 1225 self.vh_truth[1].name = "default.com" 1226 self.vh_truth[1].aliases = {"yes.default.com"} 1227 1228 # pylint: disable=protected-access 1229 self.config._enable_redirect(self.vh_truth[1], "") 1230 self.assertEqual(len(self.config.vhosts), 13) 1231 1232 def test_create_own_redirect_for_old_apache_version(self): 1233 self.config.parser.modules["rewrite_module"] = None 1234 self.config.get_version = mock.Mock(return_value=(2, 2)) 1235 # For full testing... give names... 1236 self.vh_truth[1].name = "default.com" 1237 self.vh_truth[1].aliases = {"yes.default.com"} 1238 1239 # pylint: disable=protected-access 1240 self.config._enable_redirect(self.vh_truth[1], "") 1241 self.assertEqual(len(self.config.vhosts), 13) 1242 1243 def test_sift_rewrite_rule(self): 1244 # pylint: disable=protected-access 1245 small_quoted_target = "RewriteRule ^ \"http://\"" 1246 self.assertFalse(self.config._sift_rewrite_rule(small_quoted_target)) 1247 1248 https_target = "RewriteRule ^ https://satoshi" 1249 self.assertTrue(self.config._sift_rewrite_rule(https_target)) 1250 1251 normal_target = "RewriteRule ^/(.*) http://www.a.com:1234/$1 [L,R]" 1252 self.assertFalse(self.config._sift_rewrite_rule(normal_target)) 1253 1254 not_rewriterule = "NotRewriteRule ^ ..." 1255 self.assertFalse(self.config._sift_rewrite_rule(not_rewriterule)) 1256 1257 def get_key_and_achalls(self): 1258 """Return testing achallenges.""" 1259 account_key = self.rsa512jwk 1260 achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( 1261 challb=acme_util.chall_to_challb( 1262 challenges.HTTP01( 1263 token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"), 1264 "pending"), 1265 domain="encryption-example.demo", account_key=account_key) 1266 achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( 1267 challb=acme_util.chall_to_challb( 1268 challenges.HTTP01( 1269 token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"), 1270 "pending"), 1271 domain="certbot.demo", account_key=account_key) 1272 achall3 = achallenges.KeyAuthorizationAnnotatedChallenge( 1273 challb=acme_util.chall_to_challb( 1274 challenges.HTTP01(token=(b'x' * 16)), "pending"), 1275 domain="example.org", account_key=account_key) 1276 1277 return account_key, (achall1, achall2, achall3) 1278 1279 def test_enable_site_nondebian(self): 1280 inc_path = "/path/to/wherever" 1281 vhost = self.vh_truth[0] 1282 vhost.enabled = False 1283 vhost.filep = inc_path 1284 self.assertFalse(self.config.parser.find_dir("Include", inc_path)) 1285 self.assertFalse( 1286 os.path.dirname(inc_path) in self.config.parser.existing_paths) 1287 self.config.enable_site(vhost) 1288 self.assertTrue(self.config.parser.find_dir("Include", inc_path)) 1289 self.assertTrue( 1290 os.path.dirname(inc_path) in self.config.parser.existing_paths) 1291 self.assertTrue( 1292 os.path.basename(inc_path) in self.config.parser.existing_paths[ 1293 os.path.dirname(inc_path)]) 1294 1295 @mock.patch('certbot_apache._internal.configurator.display_util.notify') 1296 def test_deploy_cert_not_parsed_path(self, unused_mock_notify): 1297 # Make sure that we add include to root config for vhosts when 1298 # handle-sites is false 1299 self.config.parser.modules["ssl_module"] = None 1300 self.config.parser.modules["mod_ssl.c"] = None 1301 self.config.parser.modules["socache_shmcb_module"] = None 1302 tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) 1303 filesystem.chmod(tmp_path, 0o755) 1304 mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path" 1305 mock_a = "certbot_apache._internal.parser.ApacheParser.add_include" 1306 1307 with mock.patch(mock_p) as mock_path: 1308 mock_path.return_value = os.path.join(tmp_path, "whatever.conf") 1309 with mock.patch(mock_a) as mock_add: 1310 self.config.deploy_cert( 1311 "encryption-example.demo", 1312 "example/cert.pem", "example/key.pem", 1313 "example/cert_chain.pem") 1314 # Test that we actually called add_include 1315 self.assertTrue(mock_add.called) 1316 shutil.rmtree(tmp_path) 1317 1318 def test_deploy_cert_no_mod_ssl(self): 1319 # Create 1320 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) 1321 self.config.parser.modules["socache_shmcb_module"] = None 1322 self.config.prepare_server_https = mock.Mock() 1323 1324 self.assertRaises(errors.MisconfigurationError, self.config.deploy_cert, 1325 "encryption-example.demo", "example/cert.pem", "example/key.pem", 1326 "example/cert_chain.pem", "example/fullchain.pem") 1327 1328 @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") 1329 def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed): 1330 ret_vh = self.vh_truth[8] 1331 ret_vh.enabled = True 1332 self.config.enable_site(ret_vh) 1333 # Make sure that we return early 1334 self.assertFalse(mock_parsed.called) 1335 1336 def test_enable_mod_unsupported(self): 1337 self.assertRaises(errors.MisconfigurationError, 1338 self.config.enable_mod, 1339 "whatever") 1340 1341 def test_choose_vhosts_wildcard(self): 1342 # pylint: disable=protected-access 1343 mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" 1344 with mock.patch(mock_path) as mock_select_vhs: 1345 mock_select_vhs.return_value = [self.vh_truth[3]] 1346 vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", 1347 create_ssl=True) 1348 # Check that the dialog was called with one vh: certbot.demo 1349 self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[3]) 1350 self.assertEqual(len(mock_select_vhs.call_args_list), 1) 1351 1352 # And the actual returned values 1353 self.assertEqual(len(vhs), 1) 1354 self.assertEqual(vhs[0].name, "certbot.demo") 1355 self.assertTrue(vhs[0].ssl) 1356 1357 self.assertNotEqual(vhs[0], self.vh_truth[3]) 1358 1359 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") 1360 def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): 1361 # pylint: disable=protected-access 1362 mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" 1363 with mock.patch(mock_path) as mock_select_vhs: 1364 mock_select_vhs.return_value = [self.vh_truth[1]] 1365 vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", 1366 create_ssl=False) 1367 self.assertFalse(mock_makessl.called) 1368 self.assertEqual(vhs[0], self.vh_truth[1]) 1369 1370 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") 1371 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") 1372 def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w): 1373 # pylint: disable=protected-access 1374 # Already SSL vhost 1375 mock_vh_for_w.return_value = [self.vh_truth[7]] 1376 mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" 1377 with mock.patch(mock_path) as mock_select_vhs: 1378 mock_select_vhs.return_value = [self.vh_truth[7]] 1379 vhs = self.config._choose_vhosts_wildcard("whatever", 1380 create_ssl=True) 1381 self.assertEqual(mock_select_vhs.call_args[0][0][0], self.vh_truth[7]) 1382 self.assertEqual(len(mock_select_vhs.call_args_list), 1) 1383 # Ensure that make_vhost_ssl was not called, vhost.ssl == true 1384 self.assertFalse(mock_makessl.called) 1385 1386 # And the actual returned values 1387 self.assertEqual(len(vhs), 1) 1388 self.assertTrue(vhs[0].ssl) 1389 self.assertEqual(vhs[0], self.vh_truth[7]) 1390 1391 1392 @mock.patch('certbot_apache._internal.configurator.display_util.notify') 1393 def test_deploy_cert_wildcard(self, unused_mock_notify): 1394 # pylint: disable=protected-access 1395 mock_choose_vhosts = mock.MagicMock() 1396 mock_choose_vhosts.return_value = [self.vh_truth[7]] 1397 self.config._choose_vhosts_wildcard = mock_choose_vhosts 1398 mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert" 1399 with mock.patch(mock_d) as mock_dep: 1400 self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", 1401 "/tmp/path", "/tmp/path", "/tmp/path") 1402 self.assertTrue(mock_dep.called) 1403 self.assertEqual(len(mock_dep.call_args_list), 1) 1404 self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0]) 1405 1406 @mock.patch("certbot_apache._internal.display_ops.select_vhost_multiple") 1407 def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): 1408 # pylint: disable=protected-access 1409 mock_dialog.return_value = [] 1410 self.assertRaises(errors.PluginError, 1411 self.config.deploy_cert, 1412 "*.wild.cat", "/tmp/path", "/tmp/path", 1413 "/tmp/path", "/tmp/path") 1414 1415 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") 1416 def test_enhance_wildcard_after_install(self, mock_choose): 1417 # pylint: disable=protected-access 1418 self.config.parser.modules["mod_ssl.c"] = None 1419 self.config.parser.modules["headers_module"] = None 1420 self.vh_truth[3].ssl = True 1421 self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]] 1422 self.config.enhance("*.certbot.demo", "ensure-http-header", 1423 "Upgrade-Insecure-Requests") 1424 self.assertFalse(mock_choose.called) 1425 1426 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") 1427 def test_enhance_wildcard_no_install(self, mock_choose): 1428 self.vh_truth[3].ssl = True 1429 mock_choose.return_value = [self.vh_truth[3]] 1430 self.config.parser.modules["mod_ssl.c"] = None 1431 self.config.parser.modules["headers_module"] = None 1432 self.config.enhance("*.certbot.demo", "ensure-http-header", 1433 "Upgrade-Insecure-Requests") 1434 self.assertTrue(mock_choose.called) 1435 1436 def test_add_vhost_id(self): 1437 for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]: 1438 vh_id = self.config.add_vhost_id(vh) 1439 self.assertEqual(vh, self.config.find_vhost_by_id(vh_id)) 1440 1441 def test_find_vhost_by_id_404(self): 1442 self.assertRaises(errors.PluginError, 1443 self.config.find_vhost_by_id, 1444 "nonexistent") 1445 1446 def test_add_vhost_id_already_exists(self): 1447 first_id = self.config.add_vhost_id(self.vh_truth[0]) 1448 second_id = self.config.add_vhost_id(self.vh_truth[0]) 1449 self.assertEqual(first_id, second_id) 1450 1451 def test_realpath_replaces_symlink(self): 1452 orig_match = self.config.parser.aug.match 1453 mock_vhost = copy.deepcopy(self.vh_truth[0]) 1454 mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') 1455 mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') 1456 mock_vhost.enabled = False 1457 self.config.parser.parse_file(mock_vhost.filep) 1458 1459 def mock_match(aug_expr): 1460 """Return a mocked match list of VirtualHosts""" 1461 if "/mocked/path" in aug_expr: 1462 return [self.vh_truth[1].path, self.vh_truth[0].path, mock_vhost.path] 1463 return orig_match(aug_expr) 1464 1465 self.config.parser.parser_paths = ["/mocked/path"] 1466 self.config.parser.aug.match = mock_match 1467 vhs = self.config.get_virtual_hosts() 1468 self.assertEqual(len(vhs), 2) 1469 self.assertEqual(vhs[0], self.vh_truth[1]) 1470 # mock_vhost should have replaced the vh_truth[0], because its filepath 1471 # isn't a symlink 1472 self.assertEqual(vhs[1], mock_vhost) 1473 1474 1475class AugeasVhostsTest(util.ApacheTest): 1476 """Test vhosts with illegal names dependent on augeas version.""" 1477 # pylint: disable=protected-access 1478 1479 def setUp(self): # pylint: disable=arguments-differ 1480 td = "debian_apache_2_4/augeas_vhosts" 1481 cr = "debian_apache_2_4/augeas_vhosts/apache2" 1482 vr = "debian_apache_2_4/augeas_vhosts/apache2/sites-available" 1483 super().setUp(test_dir=td, 1484 config_root=cr, 1485 vhost_root=vr) 1486 1487 self.config = util.get_apache_configurator( 1488 self.config_path, self.vhost_path, self.config_dir, 1489 self.work_dir) 1490 1491 def test_choosevhost_with_illegal_name(self): 1492 self.config.parser.aug = mock.MagicMock() 1493 self.config.parser.aug.match.side_effect = RuntimeError 1494 path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" 1495 chosen_vhost = self.config._create_vhost(path) 1496 self.assertEqual(None, chosen_vhost) 1497 1498 def test_choosevhost_works(self): 1499 path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" 1500 chosen_vhost = self.config._create_vhost(path) 1501 self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) 1502 1503 @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") 1504 def test_get_vhost_continue(self, mock_vhost): 1505 mock_vhost.return_value = None 1506 vhs = self.config.get_virtual_hosts() 1507 self.assertEqual([], vhs) 1508 1509 def test_choose_vhost_with_matching_wildcard(self): 1510 names = ( 1511 "an.example.net", "another.example.net", "an.other.example.net") 1512 for name in names: 1513 self.assertFalse(name in self.config.choose_vhost(name).aliases) 1514 1515 @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") 1516 def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): 1517 mock_conflicts.return_value = False 1518 mock_path = "certbot_apache._internal.display_ops.select_vhost" 1519 with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): 1520 for name in ("a.example.net", "other.example.net"): 1521 self.assertTrue(name in self.config.choose_vhost(name).aliases) 1522 1523 @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") 1524 def test_choose_vhost_wildcard_not_found(self, mock_conflicts): 1525 mock_conflicts.return_value = False 1526 mock_path = "certbot_apache._internal.display_ops.select_vhost" 1527 names = ( 1528 "abc.example.net", "not.there.tld", "aa.wildcard.tld" 1529 ) 1530 with mock.patch(mock_path) as mock_select: 1531 mock_select.return_value = self.config.vhosts[0] 1532 for name in names: 1533 orig_cc = mock_select.call_count 1534 self.config.choose_vhost(name) 1535 self.assertEqual(mock_select.call_count - orig_cc, 1) 1536 1537 def test_choose_vhost_wildcard_found(self): 1538 mock_path = "certbot_apache._internal.display_ops.select_vhost" 1539 names = ( 1540 "ab.example.net", "a.wildcard.tld", "yetanother.example.net" 1541 ) 1542 with mock.patch(mock_path) as mock_select: 1543 mock_select.return_value = self.config.vhosts[0] 1544 for name in names: 1545 self.config.choose_vhost(name) 1546 self.assertEqual(mock_select.call_count, 0) 1547 1548 def test_augeas_span_error(self): 1549 broken_vhost = self.config.vhosts[0] 1550 broken_vhost.path = broken_vhost.path + "/nonexistent" 1551 self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, 1552 broken_vhost) 1553 1554class MultiVhostsTest(util.ApacheTest): 1555 """Test configuration with multiple virtualhosts in a single file.""" 1556 # pylint: disable=protected-access 1557 1558 def setUp(self): # pylint: disable=arguments-differ 1559 td = "debian_apache_2_4/multi_vhosts" 1560 cr = "debian_apache_2_4/multi_vhosts/apache2" 1561 vr = "debian_apache_2_4/multi_vhosts/apache2/sites-available" 1562 super().setUp(test_dir=td, 1563 config_root=cr, 1564 vhost_root=vr) 1565 1566 self.config = util.get_apache_configurator( 1567 self.config_path, self.vhost_path, 1568 self.config_dir, self.work_dir, conf_vhost_path=self.vhost_path) 1569 self.vh_truth = util.get_vh_truth( 1570 self.temp_dir, "debian_apache_2_4/multi_vhosts") 1571 1572 def test_make_vhost_ssl(self): 1573 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) 1574 1575 self.assertEqual( 1576 ssl_vhost.filep, 1577 os.path.join(self.config_path, "sites-available", 1578 "default-le-ssl.conf")) 1579 1580 self.assertEqual(ssl_vhost.path, 1581 "/files" + ssl_vhost.filep + "/IfModule/VirtualHost") 1582 self.assertEqual(len(ssl_vhost.addrs), 1) 1583 self.assertEqual({obj.Addr.fromstring("*:443")}, ssl_vhost.addrs) 1584 self.assertEqual(ssl_vhost.name, "banana.vomit.com") 1585 self.assertTrue(ssl_vhost.ssl) 1586 self.assertFalse(ssl_vhost.enabled) 1587 1588 1589 self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]), 1590 self.config.is_name_vhost(ssl_vhost)) 1591 1592 mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" 1593 with mock.patch(mock_path) as mock_getpath: 1594 mock_getpath.return_value = None 1595 self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, 1596 self.vh_truth[1]) 1597 1598 def test_get_new_path(self): 1599 with_index_1 = ["/path[1]/section[1]"] 1600 without_index = ["/path/section"] 1601 with_index_2 = ["/path[2]/section[2]"] 1602 self.assertEqual(self.config._get_new_vh_path(without_index, 1603 with_index_1), 1604 None) 1605 self.assertEqual(self.config._get_new_vh_path(without_index, 1606 with_index_2), 1607 with_index_2[0]) 1608 1609 both = with_index_1 + with_index_2 1610 self.assertEqual(self.config._get_new_vh_path(without_index, both), 1611 with_index_2[0]) 1612 1613 @mock.patch("certbot_apache._internal.configurator.display_util.notify") 1614 def test_make_vhost_ssl_with_existing_rewrite_rule(self, mock_notify): 1615 self.config.parser.modules["rewrite_module"] = None 1616 1617 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[4]) 1618 1619 self.assertTrue(self.config.parser.find_dir( 1620 "RewriteEngine", "on", ssl_vhost.path, False)) 1621 1622 with open(ssl_vhost.filep) as the_file: 1623 conf_text = the_file.read() 1624 commented_rewrite_rule = ("# RewriteRule \"^/secrets/(.+)\" " 1625 "\"https://new.example.com/docs/$1\" [R,L]") 1626 uncommented_rewrite_rule = ("RewriteRule \"^/docs/(.+)\" " 1627 "\"http://new.example.com/docs/$1\" [R,L]") 1628 self.assertTrue(commented_rewrite_rule in conf_text) 1629 self.assertTrue(uncommented_rewrite_rule in conf_text) 1630 self.assertEqual(mock_notify.call_count, 1) 1631 self.assertIn("Some rewrite rules", mock_notify.call_args[0][0]) 1632 1633 @mock.patch("certbot_apache._internal.configurator.display_util.notify") 1634 def test_make_vhost_ssl_with_existing_rewrite_conds(self, mock_notify): 1635 self.config.parser.modules["rewrite_module"] = None 1636 1637 ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[3]) 1638 1639 with open(ssl_vhost.filep) as the_file: 1640 conf_lines = the_file.readlines() 1641 conf_line_set = [l.strip() for l in conf_lines] 1642 not_commented_cond1 = ("RewriteCond " 1643 "%{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f") 1644 not_commented_rewrite_rule = ("RewriteRule " 1645 "^(.*)$ b://u%{REQUEST_URI} [P,NE,L]") 1646 1647 commented_cond1 = "# RewriteCond %{HTTPS} !=on" 1648 commented_cond2 = "# RewriteCond %{HTTPS} !^$" 1649 commented_rewrite_rule = ("# RewriteRule ^ " 1650 "https://%{SERVER_NAME}%{REQUEST_URI} " 1651 "[L,NE,R=permanent]") 1652 1653 self.assertTrue(not_commented_cond1 in conf_line_set) 1654 self.assertTrue(not_commented_rewrite_rule in conf_line_set) 1655 1656 self.assertTrue(commented_cond1 in conf_line_set) 1657 self.assertTrue(commented_cond2 in conf_line_set) 1658 self.assertTrue(commented_rewrite_rule in conf_line_set) 1659 self.assertEqual(mock_notify.call_count, 1) 1660 self.assertIn("Some rewrite rules", mock_notify.call_args[0][0]) 1661 1662 1663class InstallSslOptionsConfTest(util.ApacheTest): 1664 """Test that the options-ssl-nginx.conf file is installed and updated properly.""" 1665 1666 def setUp(self): # pylint: disable=arguments-differ 1667 super().setUp() 1668 1669 self.config = util.get_apache_configurator( 1670 self.config_path, self.vhost_path, self.config_dir, self.work_dir) 1671 1672 def _call(self): 1673 self.config.install_ssl_options_conf(self.config.mod_ssl_conf, 1674 self.config.updated_mod_ssl_conf_digest) 1675 1676 def _current_ssl_options_hash(self): 1677 return crypto_util.sha256sum(self.config.pick_apache_config()) 1678 1679 def _assert_current_file(self): 1680 self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) 1681 self.assertEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), 1682 self._current_ssl_options_hash()) 1683 1684 def test_no_file(self): 1685 # prepare should have placed a file there 1686 self._assert_current_file() 1687 os.remove(self.config.mod_ssl_conf) 1688 self.assertFalse(os.path.isfile(self.config.mod_ssl_conf)) 1689 self._call() 1690 self._assert_current_file() 1691 1692 def test_current_file(self): 1693 self._assert_current_file() 1694 self._call() 1695 self._assert_current_file() 1696 1697 def test_prev_file_updates_to_current(self): 1698 from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES 1699 ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match") 1700 with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: 1701 mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] 1702 self._call() 1703 self._assert_current_file() 1704 1705 def test_manually_modified_current_file_does_not_update(self): 1706 with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: 1707 mod_ssl_conf.write("a new line for the wrong hash\n") 1708 with mock.patch("certbot.plugins.common.logger") as mock_logger: 1709 self._call() 1710 self.assertFalse(mock_logger.warning.called) 1711 self.assertTrue(os.path.isfile(self.config.mod_ssl_conf)) 1712 self.assertEqual(crypto_util.sha256sum( 1713 self.config.pick_apache_config()), 1714 self._current_ssl_options_hash()) 1715 self.assertNotEqual(crypto_util.sha256sum(self.config.mod_ssl_conf), 1716 self._current_ssl_options_hash()) 1717 1718 def test_manually_modified_past_file_warns(self): 1719 with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: 1720 mod_ssl_conf.write("a new line for the wrong hash\n") 1721 with open(self.config.updated_mod_ssl_conf_digest, "w") as f: 1722 f.write("hashofanoldversion") 1723 with mock.patch("certbot.plugins.common.logger") as mock_logger: 1724 self._call() 1725 self.assertEqual(mock_logger.warning.call_args[0][0], 1726 "%s has been manually modified; updated file " 1727 "saved to %s. We recommend updating %s for security purposes.") 1728 self.assertEqual(crypto_util.sha256sum( 1729 self.config.pick_apache_config()), 1730 self._current_ssl_options_hash()) 1731 # only print warning once 1732 with mock.patch("certbot.plugins.common.logger") as mock_logger: 1733 self._call() 1734 self.assertFalse(mock_logger.warning.called) 1735 1736 def test_ssl_config_files_hash_in_all_hashes(self): 1737 """ 1738 It is really critical that all TLS Apache config files have their SHA256 hash registered in 1739 constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config 1740 file has been manually edited by the user, and will refuse to update it. 1741 This test ensures that all necessary hashes are present. 1742 """ 1743 from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES 1744 import pkg_resources 1745 1746 tls_configs_dir = pkg_resources.resource_filename( 1747 "certbot_apache", os.path.join("_internal", "tls_configs")) 1748 all_files = [os.path.join(tls_configs_dir, name) for name in os.listdir(tls_configs_dir) 1749 if name.endswith('options-ssl-apache.conf')] 1750 self.assertTrue(all_files) 1751 for one_file in all_files: 1752 file_hash = crypto_util.sha256sum(one_file) 1753 self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, 1754 "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " 1755 "hash of {0} when it is updated.".format(one_file)) 1756 1757 def test_openssl_version(self): 1758 self.config._openssl_version = None 1759 some_string_contents = b""" 1760 SSLOpenSSLConfCmd 1761 OpenSSL configuration command 1762 SSLv3 not supported by this version of OpenSSL 1763 '%s': invalid OpenSSL configuration command 1764 OpenSSL 1.0.2g 1 Mar 2016 1765 OpenSSL 1766 AH02407: "SSLOpenSSLConfCmd %s %s" failed for %s 1767 AH02556: "SSLOpenSSLConfCmd %s %s" applied to %s 1768 OpenSSL 1.0.2g 1 Mar 2016 1769 """ 1770 # ssl_module as a DSO 1771 self.config.parser.modules['ssl_module'] = '/fake/path' 1772 with mock.patch("certbot_apache._internal.configurator." 1773 "ApacheConfigurator._open_module_file") as mock_omf: 1774 mock_omf.return_value = some_string_contents 1775 self.assertEqual(self.config.openssl_version(), "1.0.2g") 1776 1777 # ssl_module statically linked 1778 self.config._openssl_version = None 1779 self.config.parser.modules['ssl_module'] = None 1780 self.config.options.bin = '/fake/path/to/httpd' 1781 with mock.patch("certbot_apache._internal.configurator." 1782 "ApacheConfigurator._open_module_file") as mock_omf: 1783 mock_omf.return_value = some_string_contents 1784 self.assertEqual(self.config.openssl_version(), "1.0.2g") 1785 1786 def test_current_version(self): 1787 self.config.version = (2, 4, 10) 1788 self.config._openssl_version = '1.0.2m' 1789 self.assertTrue('old' in self.config.pick_apache_config()) 1790 1791 self.config.version = (2, 4, 11) 1792 self.config._openssl_version = '1.0.2m' 1793 self.assertTrue('current' in self.config.pick_apache_config()) 1794 1795 self.config._openssl_version = '1.0.2a' 1796 self.assertTrue('old' in self.config.pick_apache_config()) 1797 1798 def test_openssl_version_warns(self): 1799 self.config._openssl_version = '1.0.2a' 1800 self.assertEqual(self.config.openssl_version(), '1.0.2a') 1801 1802 self.config._openssl_version = None 1803 with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: 1804 self.assertEqual(self.config.openssl_version(), None) 1805 self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0]) 1806 1807 # When no ssl_module is present at all 1808 self.config._openssl_version = None 1809 self.assertTrue("ssl_module" not in self.config.parser.modules) 1810 with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: 1811 self.assertEqual(self.config.openssl_version(), None) 1812 self.assertTrue("Could not find ssl_module" in mock_log.call_args[0][0]) 1813 1814 # When ssl_module is statically linked but --apache-bin not provided 1815 self.config._openssl_version = None 1816 self.config.options.bin = None 1817 self.config.parser.modules['ssl_module'] = None 1818 with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: 1819 self.assertEqual(self.config.openssl_version(), None) 1820 self.assertTrue("ssl_module is statically linked but" in mock_log.call_args[0][0]) 1821 1822 self.config.parser.modules['ssl_module'] = "/fake/path" 1823 with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: 1824 # Check that correct logger.warning was printed 1825 self.assertEqual(self.config.openssl_version(), None) 1826 self.assertTrue("Unable to read" in mock_log.call_args[0][0]) 1827 1828 contents_missing_openssl = b"these contents won't match the regex" 1829 with mock.patch("certbot_apache._internal.configurator." 1830 "ApacheConfigurator._open_module_file") as mock_omf: 1831 mock_omf.return_value = contents_missing_openssl 1832 with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: 1833 # Check that correct logger.warning was printed 1834 self.assertEqual(self.config.openssl_version(), None) 1835 self.assertTrue("Could not find OpenSSL" in mock_log.call_args[0][0]) 1836 1837 def test_open_module_file(self): 1838 mock_open = mock.mock_open(read_data="testing 12 3") 1839 with mock.patch("builtins.open", mock_open): 1840 self.assertEqual(self.config._open_module_file("/nonsense/"), "testing 12 3") 1841 1842if __name__ == "__main__": 1843 unittest.main() # pragma: no cover 1844