Example #1
0
    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)
Example #2
0
    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)
Example #3
0
 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.")
Example #4
0
 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.")
Example #5
0
 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.'
                 )
Example #6
0
 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.')
Example #7
0
 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.")
Example #8
0
 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.")
Example #9
0
 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.')
Example #10
0
 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.")
Example #11
0
 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.")
Example #12
0
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)
Example #13
0
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.")