def test_successes(self): """Exercise most branches of letsencrypt-auto. They just happen to be the branches in which everything goes well. I violate my usual rule of having small, decoupled tests, because... 1. We shouldn't need to run a Cartesian product of the branches: the phases run in separate shell processes, containing state leakage pretty effectively. The only shared state is FS state, and it's limited to a temp dir, assuming (if we dare) all functions properly. 2. One combination of branches happens to set us up nicely for testing the next, saving code. """ NEW_LE_AUTO = build_le_auto( version='99.9.9', requirements='letsencrypt==99.9.9 --hash=sha256:1cc14d61ab424cdee446f51e50f1123f8482ec740587fe78626c933bba2873a0') NEW_LE_AUTO_SIG = signed(NEW_LE_AUTO) with ephemeral_dir() as venv_dir: # This serves a PyPI page with a higher version, a GitHub-alike # with a corresponding le-auto script, and a matching signature. resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': NEW_LE_AUTO, 'v99.9.9/letsencrypt-auto.sig': NEW_LE_AUTO_SIG} with serving(resources) as base_url: run_letsencrypt_auto = partial( run_le_auto, venv_dir, base_url, PIP_FIND_LINKS=join(tests_dir(), 'fake-letsencrypt', 'dist')) # Test when a phase-1 upgrade is needed, there's no LE binary # installed, and pip hashes verify: install_le_auto(build_le_auto(version='50.0.0'), venv_dir) out, err = run_letsencrypt_auto() ok_(re.match(r'letsencrypt \d+\.\d+\.\d+', err.strip().splitlines()[-1])) # Make a few assertions to test the validity of the next tests: self.assertIn('Upgrading certbot-auto ', out) self.assertIn('Creating virtual environment...', out) # Now we have le-auto 99.9.9 and LE 99.9.9 installed. This # conveniently sets us up to test the next 2 cases. # Test when neither phase-1 upgrade nor phase-2 upgrade is # needed (probably a common case): out, err = run_letsencrypt_auto() self.assertNotIn('Upgrading certbot-auto ', out) self.assertNotIn('Creating virtual environment...', out) # Test when a phase-1 upgrade is not needed but a phase-2 # upgrade is: set_le_script_version(venv_dir, '0.0.1') out, err = run_letsencrypt_auto() self.assertNotIn('Upgrading certbot-auto ', out) self.assertIn('Creating virtual environment...', out)
def test_successes(self): """Exercise most branches of letsencrypt-auto. They just happen to be the branches in which everything goes well. I violate my usual rule of having small, decoupled tests, because... 1. We shouldn't need to run a Cartesian product of the branches: the phases run in separate shell processes, containing state leakage pretty effectively. The only shared state is FS state, and it's limited to a temp dir, assuming (if we dare) all functions properly. 2. One combination of branches happens to set us up nicely for testing the next, saving code. """ NEW_LE_AUTO = build_le_auto( version="99.9.9", requirements="# sha256: HMFNYatCTN7kRvUeUPESP4SC7HQFh_54YmyTO7ooc6A\n" "letsencrypt==99.9.9", ) NEW_LE_AUTO_SIG = signed(NEW_LE_AUTO) with ephemeral_dir() as venv_dir: # This serves a PyPI page with a higher version, a GitHub-alike # with a corresponding le-auto script, and a matching signature. resources = { "letsencrypt/json": dumps({"releases": {"99.9.9": None}}), "v99.9.9/letsencrypt-auto": NEW_LE_AUTO, "v99.9.9/letsencrypt-auto.sig": NEW_LE_AUTO_SIG, } with serving(resources) as base_url: run_letsencrypt_auto = partial( run_le_auto, venv_dir, base_url, PIP_FIND_LINKS=join(tests_dir(), "fake-letsencrypt", "dist") ) # Test when a phase-1 upgrade is needed, there's no LE binary # installed, and peep verifies: install_le_auto(build_le_auto(version="50.0.0"), venv_dir) out, err = run_letsencrypt_auto() ok_(re.match(r"letsencrypt \d+\.\d+\.\d+", err.strip().splitlines()[-1])) # Make a few assertions to test the validity of the next tests: self.assertIn("Upgrading letsencrypt-auto ", out) self.assertIn("Creating virtual environment...", out) # Now we have le-auto 99.9.9 and LE 99.9.9 installed. This # conveniently sets us up to test the next 2 cases. # Test when neither phase-1 upgrade nor phase-2 upgrade is # needed (probably a common case): out, err = run_letsencrypt_auto() self.assertNotIn("Upgrading letsencrypt-auto ", out) self.assertNotIn("Creating virtual environment...", out) # Test when a phase-1 upgrade is not needed but a phase-2 # upgrade is: set_le_script_version(venv_dir, "0.0.1") out, err = run_letsencrypt_auto() self.assertNotIn("Upgrading letsencrypt-auto ", out) self.assertIn("Creating virtual environment...", out)
def test_pip_failure(self): """Make sure pip stops us if there is a hash mismatch.""" with temp_paths() as (le_auto_path, venv_dir): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}})} with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements='configobj==5.0.6 --hash=sha256:badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb'), le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("THESE PACKAGES DO NOT MATCH THE HASHES " "FROM THE REQUIREMENTS FILE" in exc.output) self.assertFalse( exists(venv_dir), msg="The virtualenv was left around, even though " "installation didn't succeed. We shouldn't do " "this, as it foils our detection of whether we " "need to recreate the virtualenv, which hinges " "on the presence of $VENV_BIN/letsencrypt.") else: self.fail("Pip didn't detect a bad hash and stop the " "installation.")
def test_peep_failure(self): """Make sure peep stops us if there is a hash mismatch.""" with ephemeral_dir() as venv_dir: resources = { '': '<a href="letsencrypt/">letsencrypt/</a>', 'letsencrypt/json': dumps({'releases': { '99.9.9': None }}) } with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements= '# sha256: badbadbadbadbadbadbadbadbadbadbadbadbadbadb\n' 'configobj==5.0.6'), venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn( "THE FOLLOWING PACKAGES DIDN'T MATCH THE " "HASHES SPECIFIED IN THE REQUIREMENTS", exc.output) else: self.fail("Peep didn't detect a bad hash and stop the " "installation.")
def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with ephemeral_dir() as venv_dir: # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = { '': '<a href="letsencrypt/">letsencrypt/</a>', 'letsencrypt/json': dumps({'releases': { '99.9.9': None }}), 'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'), 'v99.9.9/letsencrypt-auto.sig': signed('something else') } with serving(resources) as base_url: copy(LE_AUTO_PATH, venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn( "Couldn't verify signature of downloaded " "letsencrypt-auto.", exc.output) else: self.fail( 'Signature check on letsencrypt-auto erroneously passed.' )
def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with temp_paths() as (le_auto_path, venv_dir): # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = { '': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': { '99.9.9': None }}), 'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'), 'v99.9.9/letsencrypt-auto.sig': signed('something else') } with serving(resources) as base_url: copy_stable(LE_AUTO_PATH, le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("Couldn't verify signature of downloaded " "certbot-auto." in exc.output) else: print(out) self.fail( 'Signature check on certbot-auto erroneously passed.')
def test_peep_failure(self): """Make sure peep stops us if there is a hash mismatch.""" with ephemeral_dir() as venv_dir: resources = { "": '<a href="letsencrypt/">letsencrypt/</a>', "letsencrypt/json": dumps({"releases": {"99.9.9": None}}), } with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version="99.9.9", requirements="# sha256: badbadbadbadbadbadbadbadbadbadbadbadbadbadb\n" "configobj==5.0.6", ), venv_dir, ) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn( "THE FOLLOWING PACKAGES DIDN'T MATCH THE " "HASHES SPECIFIED IN THE REQUIREMENTS", exc.output ) ok_( not exists(join(venv_dir, "letsencrypt")), msg="The virtualenv was left around, even though " "installation didn't succeed. We shouldn't do " "this, as it foils our detection of whether we " "need to recreate the virtualenv, which hinges " "on the presence of $VENV_BIN/letsencrypt.", ) else: self.fail("Peep didn't detect a bad hash and stop the " "installation.")
def test_peep_failure(self): """Make sure peep stops us if there is a hash mismatch.""" with ephemeral_dir() as venv_dir: resources = { '': '<a href="letsencrypt/">letsencrypt/</a>', 'letsencrypt/json': dumps({'releases': { '99.9.9': None }}) } with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements= '# sha256: badbadbadbadbadbadbadbadbadbadbadbadbadbadb\n' 'configobj==5.0.6'), venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn( "THE FOLLOWING PACKAGES DIDN'T MATCH THE " "HASHES SPECIFIED IN THE REQUIREMENTS", exc.output) ok_(not exists(join(venv_dir, 'letsencrypt')), msg="The virtualenv was left around, even though " "installation didn't succeed. We shouldn't do " "this, as it foils our detection of whether we " "need to recreate the virtualenv, which hinges " "on the presence of $VENV_BIN/letsencrypt.") else: self.fail("Peep didn't detect a bad hash and stop the " "installation.")
def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with ephemeral_dir() as venv_dir: # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'), 'v99.9.9/letsencrypt-auto.sig': signed('something else')} with serving(resources) as base_url: copy(LE_AUTO_PATH, venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertTrue("Couldn't verify signature of downloaded " "certbot-auto." in exc.output) else: self.fail('Signature check on certbot-auto erroneously passed.')
def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with ephemeral_dir() as venv_dir: # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = { "": '<a href="letsencrypt/">letsencrypt/</a>', "letsencrypt/json": dumps({"releases": {"99.9.9": None}}), "v99.9.9/letsencrypt-auto": build_le_auto(version="99.9.9"), "v99.9.9/letsencrypt-auto.sig": signed("something else"), } with serving(resources) as base_url: copy(LE_AUTO_PATH, venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn("Couldn't verify signature of downloaded " "letsencrypt-auto.", exc.output) else: self.fail("Signature check on letsencrypt-auto erroneously passed.")
def test_peep_failure(self): """Make sure peep stops us if there is a hash mismatch.""" with ephemeral_dir() as venv_dir: resources = {'': '<a href="letsencrypt/">letsencrypt/</a>', 'letsencrypt/json': dumps({'releases': {'99.9.9': None}})} with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements='# sha256: badbadbadbadbadbadbadbadbadbadbadbadbadbadb\n' 'configobj==5.0.6'), venv_dir) try: out, err = run_le_auto(venv_dir, base_url) except CalledProcessError as exc: eq_(exc.returncode, 1) self.assertIn("THE FOLLOWING PACKAGES DIDN'T MATCH THE " "HASHES SPECIFIED IN THE REQUIREMENTS", exc.output) else: self.fail("Peep didn't detect a bad hash and stop the " "installation.")
class AutoTests(TestCase): """Test the major branch points of letsencrypt-auto: * An le-auto upgrade is needed. * An le-auto upgrade is not needed. * There was an out-of-date LE script installed. * There was a current LE script installed. * There was no LE script installed (less important). * Pip hash-verification passes. * Pip has a hash mismatch. * The OpenSSL sig matches. * The OpenSSL sig mismatches. For tests which get to the end, we run merely ``letsencrypt --version``. The functioning of the rest of the certbot script is covered by other test suites. """ NEW_LE_AUTO = build_le_auto( version='99.9.9', requirements='letsencrypt==99.9.9 --hash=sha256:1cc14d61ab424cdee446f51e50f1123f8482ec740587fe78626c933bba2873a0') NEW_LE_AUTO_SIG = signed(NEW_LE_AUTO) def test_successes(self): """Exercise most branches of letsencrypt-auto. They just happen to be the branches in which everything goes well. I violate my usual rule of having small, decoupled tests, because... 1. We shouldn't need to run a Cartesian product of the branches: the phases run in separate shell processes, containing state leakage pretty effectively. The only shared state is FS state, and it's limited to a temp dir, assuming (if we dare) all functions properly. 2. One combination of branches happens to set us up nicely for testing the next, saving code. """ with temp_paths() as (le_auto_path, venv_dir): # This serves a PyPI page with a higher version, a GitHub-alike # with a corresponding le-auto script, and a matching signature. resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': self.NEW_LE_AUTO, 'v99.9.9/letsencrypt-auto.sig': self.NEW_LE_AUTO_SIG} with serving(resources) as base_url: run_letsencrypt_auto = partial( run_le_auto, le_auto_path, venv_dir, base_url, PIP_FIND_LINKS=join(tests_dir(), 'fake-letsencrypt', 'dist')) # Test when a phase-1 upgrade is needed, there's no LE binary # installed, and pip hashes verify: install_le_auto(build_le_auto(version='50.0.0'), le_auto_path) out, err = run_letsencrypt_auto() self.assertTrue(re.match(r'letsencrypt \d+\.\d+\.\d+', err.strip().splitlines()[-1])) # Make a few assertions to test the validity of the next tests: self.assertTrue('Upgrading certbot-auto ' in out) self.assertTrue('Creating virtual environment...' in out) # Now we have le-auto 99.9.9 and LE 99.9.9 installed. This # conveniently sets us up to test the next 2 cases. # Test when neither phase-1 upgrade nor phase-2 upgrade is # needed (probably a common case): out, err = run_letsencrypt_auto() self.assertFalse('Upgrading certbot-auto ' in out) self.assertFalse('Creating virtual environment...' in out) def test_phase2_upgrade(self): """Test a phase-2 upgrade without a phase-1 upgrade.""" resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': self.NEW_LE_AUTO, 'v99.9.9/letsencrypt-auto.sig': self.NEW_LE_AUTO_SIG} with serving(resources) as base_url: pip_find_links=join(tests_dir(), 'fake-letsencrypt', 'dist') with temp_paths() as (le_auto_path, venv_dir): install_le_auto(self.NEW_LE_AUTO, le_auto_path) # Create venv saving the correct bootstrap script version out, err = run_le_auto(le_auto_path, venv_dir, base_url, PIP_FIND_LINKS=pip_find_links) self.assertFalse('Upgrading certbot-auto ' in out) self.assertTrue('Creating virtual environment...' in out) with open(join(venv_dir, BOOTSTRAP_FILENAME)) as f: bootstrap_version = f.read() # Create a new venv with an old letsencrypt version with temp_paths() as (le_auto_path, venv_dir): venv_bin = join(venv_dir, 'bin') makedirs(venv_bin) set_le_script_version(venv_dir, '0.0.1') with open(join(venv_dir, BOOTSTRAP_FILENAME), 'w') as f: f.write(bootstrap_version) install_le_auto(self.NEW_LE_AUTO, le_auto_path) out, err = run_le_auto(le_auto_path, venv_dir, base_url, PIP_FIND_LINKS=pip_find_links) self.assertFalse('Upgrading certbot-auto ' in out) self.assertTrue('Creating virtual environment...' in out) def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with temp_paths() as (le_auto_path, venv_dir): # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'), 'v99.9.9/letsencrypt-auto.sig': signed('something else')} with serving(resources) as base_url: copy_stable(LE_AUTO_PATH, le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("Couldn't verify signature of downloaded " "certbot-auto." in exc.output) else: print(out) self.fail('Signature check on certbot-auto erroneously passed.') def test_pip_failure(self): """Make sure pip stops us if there is a hash mismatch.""" with temp_paths() as (le_auto_path, venv_dir): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}})} with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements='configobj==5.0.6 --hash=sha256:badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb'), le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("THESE PACKAGES DO NOT MATCH THE HASHES " "FROM THE REQUIREMENTS FILE" in exc.output) self.assertFalse( exists(venv_dir), msg="The virtualenv was left around, even though " "installation didn't succeed. We shouldn't do " "this, as it foils our detection of whether we " "need to recreate the virtualenv, which hinges " "on the presence of $VENV_BIN/letsencrypt.") else: self.fail("Pip didn't detect a bad hash and stop the " "installation.") def test_permissions_warnings(self): """Make sure letsencrypt-auto properly warns about permissions problems.""" # This test assumes that only the parent of the directory containing # letsencrypt-auto (usually /tmp) may have permissions letsencrypt-auto # considers insecure. with temp_paths() as (le_auto_path, venv_dir): le_auto_path = abspath(le_auto_path) le_auto_dir = dirname(le_auto_path) le_auto_dir_parent = dirname(le_auto_dir) install_le_auto(self.NEW_LE_AUTO, le_auto_path) run_letsencrypt_auto = partial( run_le_auto, le_auto_path, venv_dir, le_auto_args_str='--install-only --no-self-upgrade', PIP_FIND_LINKS=join(tests_dir(), 'fake-letsencrypt', 'dist')) # Run letsencrypt-auto once with current permissions to avoid # potential problems when the script tries to write to temporary # directories. run_letsencrypt_auto() le_auto_dir_mode = stat(le_auto_dir).st_mode le_auto_dir_parent_mode = S_IMODE(stat(le_auto_dir_parent).st_mode) try: # Make letsencrypt-auto happy with the current permissions chmod(le_auto_dir, S_IRUSR | S_IXUSR) sudo_chmod(le_auto_dir_parent, 0o755) self._test_permissions_warnings_about_path(le_auto_path, run_letsencrypt_auto) self._test_permissions_warnings_about_path(le_auto_dir, run_letsencrypt_auto) finally: chmod(le_auto_dir, le_auto_dir_mode) sudo_chmod(le_auto_dir_parent, le_auto_dir_parent_mode) def _test_permissions_warnings_about_path(self, path, run_le_auto_func): # Test that there are no problems with the current permissions out, _ = run_le_auto_func() self.assertFalse('insecure permissions' in out) stat_result = stat(path) original_mode = stat_result.st_mode # Test world permissions chmod(path, original_mode | S_IWOTH) out, _ = run_le_auto_func() self.assertTrue('insecure permissions' in out) # Test group permissions if stat_result.st_gid >= 1000: chmod(path, original_mode | S_IWGRP) out, _ = run_le_auto_func() self.assertTrue('insecure permissions' in out) # Test owner permissions if stat_result.st_uid >= 1000: chmod(path, original_mode | S_IWUSR) out, _ = run_le_auto_func() self.assertTrue('insecure permissions' in out) # Test that permissions were properly restored chmod(path, original_mode) out, _ = run_le_auto_func() self.assertFalse('insecure permissions' in out) def test_disabled_permissions_warnings(self): """Make sure that letsencrypt-auto permissions warnings can be disabled.""" with temp_paths() as (le_auto_path, venv_dir): le_auto_path = abspath(le_auto_path) install_le_auto(self.NEW_LE_AUTO, le_auto_path) le_auto_args_str='--install-only --no-self-upgrade' pip_links=join(tests_dir(), 'fake-letsencrypt', 'dist') out, _ = run_le_auto(le_auto_path, venv_dir, le_auto_args_str=le_auto_args_str, PIP_FIND_LINKS=pip_links) self.assertTrue('insecure permissions' in out) # Test that warnings are disabled when the script isn't run as # root. out, _ = run_le_auto(le_auto_path, venv_dir, le_auto_args_str=le_auto_args_str, LE_AUTO_SUDO='', PIP_FIND_LINKS=pip_links) self.assertFalse('insecure permissions' in out) # Test that --no-permissions-check disables warnings. le_auto_args_str += ' --no-permissions-check' out, _ = run_le_auto( le_auto_path, venv_dir, le_auto_args_str=le_auto_args_str, PIP_FIND_LINKS=pip_links) self.assertFalse('insecure permissions' in out)
class AutoTests(TestCase): """Test the major branch points of letsencrypt-auto: * An le-auto upgrade is needed. * An le-auto upgrade is not needed. * There was an out-of-date LE script installed. * There was a current LE script installed. * There was no LE script installed (less important). * Pip hash-verification passes. * Pip has a hash mismatch. * The OpenSSL sig matches. * The OpenSSL sig mismatches. For tests which get to the end, we run merely ``letsencrypt --version``. The functioning of the rest of the certbot script is covered by other test suites. """ NEW_LE_AUTO = build_le_auto( version='99.9.9', requirements='letsencrypt==99.9.9 --hash=sha256:1cc14d61ab424cdee446f51e50f1123f8482ec740587fe78626c933bba2873a0') NEW_LE_AUTO_SIG = signed(NEW_LE_AUTO) def test_successes(self): """Exercise most branches of letsencrypt-auto. They just happen to be the branches in which everything goes well. I violate my usual rule of having small, decoupled tests, because... 1. We shouldn't need to run a Cartesian product of the branches: the phases run in separate shell processes, containing state leakage pretty effectively. The only shared state is FS state, and it's limited to a temp dir, assuming (if we dare) all functions properly. 2. One combination of branches happens to set us up nicely for testing the next, saving code. """ with temp_paths() as (le_auto_path, venv_dir): # This serves a PyPI page with a higher version, a GitHub-alike # with a corresponding le-auto script, and a matching signature. resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': self.NEW_LE_AUTO, 'v99.9.9/letsencrypt-auto.sig': self.NEW_LE_AUTO_SIG} with serving(resources) as base_url: run_letsencrypt_auto = partial( run_le_auto, le_auto_path, venv_dir, base_url, PIP_FIND_LINKS=join(tests_dir(), 'fake-letsencrypt', 'dist')) # Test when a phase-1 upgrade is needed, there's no LE binary # installed, and pip hashes verify: install_le_auto(build_le_auto(version='50.0.0'), le_auto_path) out, err = run_letsencrypt_auto() self.assertTrue(re.match(r'letsencrypt \d+\.\d+\.\d+', err.strip().splitlines()[-1])) # Make a few assertions to test the validity of the next tests: self.assertTrue('Upgrading certbot-auto ' in out) self.assertTrue('Creating virtual environment...' in out) # Now we have le-auto 99.9.9 and LE 99.9.9 installed. This # conveniently sets us up to test the next 2 cases. # Test when neither phase-1 upgrade nor phase-2 upgrade is # needed (probably a common case): out, err = run_letsencrypt_auto() self.assertFalse('Upgrading certbot-auto ' in out) self.assertFalse('Creating virtual environment...' in out) def test_phase2_upgrade(self): """Test a phase-2 upgrade without a phase-1 upgrade.""" with temp_paths() as (le_auto_path, venv_dir): resources = {'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': self.NEW_LE_AUTO, 'v99.9.9/letsencrypt-auto.sig': self.NEW_LE_AUTO_SIG} with serving(resources) as base_url: venv_bin = join(venv_dir, 'bin') makedirs(venv_bin) set_le_script_version(venv_dir, '0.0.1') install_le_auto(self.NEW_LE_AUTO, le_auto_path) pip_find_links=join(tests_dir(), 'fake-letsencrypt', 'dist') out, err = run_le_auto(le_auto_path, venv_dir, base_url, PIP_FIND_LINKS=pip_find_links) self.assertFalse('Upgrading certbot-auto ' in out) self.assertTrue('Creating virtual environment...' in out) def test_openssl_failure(self): """Make sure we stop if the openssl signature check fails.""" with temp_paths() as (le_auto_path, venv_dir): # Serve an unrelated hash signed with the good key (easier than # making a bad key, and a mismatch is a mismatch): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}}), 'v99.9.9/letsencrypt-auto': build_le_auto(version='99.9.9'), 'v99.9.9/letsencrypt-auto.sig': signed('something else')} with serving(resources) as base_url: copy(LE_AUTO_PATH, le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("Couldn't verify signature of downloaded " "certbot-auto." in exc.output) else: self.fail('Signature check on certbot-auto erroneously passed.') def test_pip_failure(self): """Make sure pip stops us if there is a hash mismatch.""" with temp_paths() as (le_auto_path, venv_dir): resources = {'': '<a href="certbot/">certbot/</a>', 'certbot/json': dumps({'releases': {'99.9.9': None}})} with serving(resources) as base_url: # Build a le-auto script embedding a bad requirements file: install_le_auto( build_le_auto( version='99.9.9', requirements='configobj==5.0.6 --hash=sha256:badbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadbadb'), le_auto_path) try: out, err = run_le_auto(le_auto_path, venv_dir, base_url) except CalledProcessError as exc: self.assertEqual(exc.returncode, 1) self.assertTrue("THESE PACKAGES DO NOT MATCH THE HASHES " "FROM THE REQUIREMENTS FILE" in exc.output) self.assertFalse( exists(venv_dir), msg="The virtualenv was left around, even though " "installation didn't succeed. We shouldn't do " "this, as it foils our detection of whether we " "need to recreate the virtualenv, which hinges " "on the presence of $VENV_BIN/letsencrypt.") else: self.fail("Pip didn't detect a bad hash and stop the " "installation.")