Example #1
0
    def test_normal_path_windows(self, mock_readlink):
        # Python <3.8
        mock_readlink.return_value = "C:\\short\\path"
        self.assertEqual(filesystem.readlink("dummy"), "C:\\short\\path")

        # Python >=3.8 (os.readlink always returns the extended form)
        mock_readlink.return_value = "\\\\?\\C:\\short\\path"
        self.assertEqual(filesystem.readlink("dummy"), "C:\\short\\path")
Example #2
0
    def _update_link_to(self, kind, version):
        """Make the specified item point at the specified version.

        (Note that this method doesn't verify that the specified version
        exists.)

        :param str kind: the lineage member item ("cert", "privkey",
            "chain", or "fullchain")
        :param int version: the desired version

        """
        if kind not in ALL_FOUR:
            raise errors.CertStorageError("unknown kind of item")
        link = getattr(self, kind)
        filename = "{0}{1}.pem".format(kind, version)
        # Relative rather than absolute target directory
        target_directory = os.path.dirname(filesystem.readlink(link))
        # TODO: it could be safer to make the link first under a temporary
        #       filename, then unlink the old link, then rename the new link
        #       to the old link; this ensures that this process is able to
        #       create symlinks.
        # TODO: we might also want to check consistency of related links
        #       for the other corresponding items
        os.unlink(link)
        os.symlink(os.path.join(target_directory, filename), link)
    def test_update_live_symlinks(self):
        """Test update_live_symlinks"""
        # create files with incorrect symlinks
        from certbot._internal import cert_manager
        archive_paths = {}
        for domain in self.domains:
            custom_archive = self.domains[domain]
            if custom_archive is not None:
                archive_dir_path = custom_archive
            else:
                archive_dir_path = os.path.join(self.config.default_archive_dir, domain)
            archive_paths[domain] = {kind:
                os.path.join(archive_dir_path, kind + "1.pem") for kind in ALL_FOUR}
            for kind in ALL_FOUR:
                live_path = self.config_files[domain][kind]
                archive_path = archive_paths[domain][kind]
                open(archive_path, 'a').close()
                # path is incorrect but base must be correct
                os.symlink(os.path.join(self.config.config_dir, kind + "1.pem"), live_path)

        # run update symlinks
        cert_manager.update_live_symlinks(self.config)

        # check that symlinks go where they should
        prev_dir = os.getcwd()
        try:
            for domain in self.domains:
                for kind in ALL_FOUR:
                    os.chdir(os.path.dirname(self.config_files[domain][kind]))
                    self.assertEqual(
                        filesystem.realpath(filesystem.readlink(self.config_files[domain][kind])),
                        filesystem.realpath(archive_paths[domain][kind]))
        finally:
            os.chdir(prev_dir)
Example #4
0
    def _delete_links_and_find_target_dir(
            self, server_path: str, link_func: Callable[[str], str]) -> str:
        """Delete symlinks and return the nonsymlinked directory path.

        :param str server_path: file path based on server
        :param callable link_func: callable that returns possible links
            given a server_path

        :returns: the final, non-symlinked target
        :rtype: str

        """
        dir_path = link_func(server_path)

        # does an appropriate directory link to me? if so, make sure that's gone
        reused_servers = {}
        for k, v in constants.LE_REUSE_SERVERS.items():
            reused_servers[v] = k

        # is there a next one up?
        possible_next_link = True
        while possible_next_link:
            possible_next_link = False
            if server_path in reused_servers:
                next_server_path = reused_servers[server_path]
                next_dir_path = link_func(next_server_path)
                if os.path.islink(next_dir_path) and filesystem.readlink(
                        next_dir_path) == dir_path:
                    possible_next_link = True
                    server_path = next_server_path
                    dir_path = next_dir_path

        # if there's not a next one up to delete, then delete me
        # and whatever I link to
        while os.path.islink(dir_path):
            target = filesystem.readlink(dir_path)
            os.unlink(dir_path)
            dir_path = target

        return dir_path
Example #5
0
    def _fix_symlinks(self):
        """Fixes symlinks in the event of an incomplete version update.

        If there is no problem with the current symlinks, this function
        has no effect.

        """
        previous_symlinks = self._previous_symlinks()
        if all(os.path.exists(link[1]) for link in previous_symlinks):
            for kind, previous_link in previous_symlinks:
                current_link = getattr(self, kind)
                if os.path.lexists(current_link):
                    os.unlink(current_link)
                os.symlink(filesystem.readlink(previous_link), current_link)

        for _, link in previous_symlinks:
            if os.path.exists(link):
                os.unlink(link)
Example #6
0
 def test_update_link_to(self):
     for ver in range(1, 6):
         for kind in ALL_FOUR:
             self._write_out_kind(kind, ver)
             self.assertEqual(ver, self.test_rc.current_version(kind))
     # pylint: disable=protected-access
     self.test_rc._update_link_to("cert", 3)
     self.test_rc._update_link_to("privkey", 2)
     self.assertEqual(3, self.test_rc.current_version("cert"))
     self.assertEqual(2, self.test_rc.current_version("privkey"))
     self.assertEqual(5, self.test_rc.current_version("chain"))
     self.assertEqual(5, self.test_rc.current_version("fullchain"))
     # Currently we are allowed to update to a version that doesn't exist
     self.test_rc._update_link_to("chain", 3000)
     # However, current_version doesn't allow querying the resulting
     # version (because it's a broken link).
     self.assertEqual(os.path.basename(filesystem.readlink(self.test_rc.chain)),
                      "chain3000.pem")
Example #7
0
def get_link_target(link):
    """Get an absolute path to the target of link.

    :param str link: Path to a symbolic link

    :returns: Absolute path to the target of link
    :rtype: str

    :raises .CertStorageError: If link does not exists.

    """
    try:
        target = filesystem.readlink(link)
    except OSError:
        raise errors.CertStorageError(
            "Expected {0} to be a symlink".format(link))

    if not os.path.isabs(target):
        target = os.path.join(os.path.dirname(link), target)
    return os.path.abspath(target)
Example #8
0
    def test_save_successor(self, mock_rv):
        # Mock relevant_values() to claim that all values are relevant here
        # (to avoid instantiating parser)
        mock_rv.side_effect = lambda x: x

        for ver in range(1, 6):
            for kind in ALL_FOUR:
                self._write_out_kind(kind, ver)
        self.test_rc.update_all_links_to(3)
        self.assertEqual(
            6,
            self.test_rc.save_successor(3, b'new cert', None, b'new chain',
                                        self.config))
        with open(self.test_rc.version("cert", 6)) as f:
            self.assertEqual(f.read(), "new cert")
        with open(self.test_rc.version("chain", 6)) as f:
            self.assertEqual(f.read(), "new chain")
        with open(self.test_rc.version("fullchain", 6)) as f:
            self.assertEqual(f.read(), "new cert" + "new chain")
        # version 6 of the key should be a link back to version 3
        self.assertFalse(os.path.islink(self.test_rc.version("privkey", 3)))
        self.assertTrue(os.path.islink(self.test_rc.version("privkey", 6)))
        # Let's try two more updates
        self.assertEqual(
            7,
            self.test_rc.save_successor(6, b'again', None, b'newer chain',
                                        self.config))
        self.assertEqual(
            8,
            self.test_rc.save_successor(7, b'hello', None, b'other chain',
                                        self.config))
        # All of the subsequent versions should link directly to the original
        # privkey.
        for i in (6, 7, 8):
            self.assertTrue(os.path.islink(self.test_rc.version("privkey", i)))
            self.assertEqual(
                "privkey3.pem",
                os.path.basename(
                    filesystem.readlink(self.test_rc.version("privkey", i))))

        for kind in ALL_FOUR:
            self.assertEqual(self.test_rc.available_versions(kind),
                             list(range(1, 9)))
            self.assertEqual(self.test_rc.current_version(kind), 3)
        # Test updating from latest version rather than old version
        self.test_rc.update_all_links_to(8)
        self.assertEqual(
            9,
            self.test_rc.save_successor(8, b'last', None, b'attempt',
                                        self.config))
        for kind in ALL_FOUR:
            self.assertEqual(self.test_rc.available_versions(kind),
                             list(range(1, 10)))
            self.assertEqual(self.test_rc.current_version(kind), 8)
        with open(self.test_rc.version("fullchain", 9)) as f:
            self.assertEqual(f.read(), "last" + "attempt")
        temp_config_file = os.path.join(self.config.renewal_configs_dir,
                                        self.test_rc.lineagename) + ".conf.new"
        with open(temp_config_file, "w") as f:
            f.write("We previously crashed while writing me :(")
        # Test updating when providing a new privkey.  The key should
        # be saved in a new file rather than creating a new symlink.
        self.assertEqual(
            10,
            self.test_rc.save_successor(9, b'with', b'a', b'key', self.config))
        self.assertTrue(os.path.exists(self.test_rc.version("privkey", 10)))
        self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10)))
        self.assertFalse(os.path.exists(temp_config_file))
Example #9
0
    def save_successor(self, prior_version, new_cert, new_privkey, new_chain,
                       cli_config):
        """Save new cert and chain as a successor of a prior version.

        Returns the new version number that was created.

        .. note:: this function does NOT update links to deploy this
                  version

        :param int prior_version: the old version to which this version
            is regarded as a successor (used to choose a privkey, if the
            key has not changed, but otherwise this information is not
            permanently recorded anywhere)
        :param bytes new_cert: the new certificate, in PEM format
        :param bytes new_privkey: the new private key, in PEM format,
            or ``None``, if the private key has not changed
        :param bytes new_chain: the new chain, in PEM format
        :param .NamespaceConfig cli_config: parsed command line
            arguments

        :returns: the new version number that was created
        :rtype: int

        """
        # XXX: assumes official archive location rather than examining links
        # XXX: consider using os.open for availability of os.O_EXCL
        # XXX: ensure file permissions are correct; also create directories
        #      if needed (ensuring their permissions are correct)
        # Figure out what the new version is and hence where to save things

        self.cli_config = cli_config
        target_version = self.next_free_version()
        target = {
            kind: os.path.join(self.archive_dir,
                               "{0}{1}.pem".format(kind, target_version))
            for kind in ALL_FOUR
        }

        old_privkey = os.path.join(self.archive_dir,
                                   "privkey{0}.pem".format(prior_version))

        # Distinguish the cases where the privkey has changed and where it
        # has not changed (in the latter case, making an appropriate symlink
        # to an earlier privkey version)
        if new_privkey is None:
            # The behavior below keeps the prior key by creating a new
            # symlink to the old key or the target of the old key symlink.
            if os.path.islink(old_privkey):
                old_privkey = filesystem.readlink(old_privkey)
            else:
                old_privkey = "privkey{0}.pem".format(prior_version)
            logger.debug("Writing symlink to old private key, %s.",
                         old_privkey)
            os.symlink(old_privkey, target["privkey"])
        else:
            with util.safe_open(target["privkey"],
                                "wb",
                                chmod=BASE_PRIVKEY_MODE) as f:
                logger.debug("Writing new private key to %s.",
                             target["privkey"])
                f.write(new_privkey)
            # Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS)
            # from previous privkey in this lineage.
            mode = filesystem.compute_private_key_mode(old_privkey,
                                                       BASE_PRIVKEY_MODE)
            filesystem.copy_ownership_and_apply_mode(old_privkey,
                                                     target["privkey"],
                                                     mode,
                                                     copy_user=False,
                                                     copy_group=True)

        # Save everything else
        with open(target["cert"], "wb") as f:
            logger.debug("Writing certificate to %s.", target["cert"])
            f.write(new_cert)
        with open(target["chain"], "wb") as f:
            logger.debug("Writing chain to %s.", target["chain"])
            f.write(new_chain)
        with open(target["fullchain"], "wb") as f:
            logger.debug("Writing full chain to %s.", target["fullchain"])
            f.write(new_cert + new_chain)

        symlinks = {kind: self.configuration[kind] for kind in ALL_FOUR}
        # Update renewal config file
        self.configfile = update_configuration(self.lineagename,
                                               self.archive_dir, symlinks,
                                               cli_config)
        self.configuration = config_with_defaults(self.configfile)

        return target_version
Example #10
0
 def test_extended_path_windows(self, mock_readlink):
     # Following path is largely over the 260 characters permitted in the normal form.
     mock_readlink.return_value = "\\\\?\\C:\\long" + 1000 * "\\path"
     with self.assertRaises(ValueError):
         filesystem.readlink("dummy")
Example #11
0
 def test_path_posix(self, mock_readlink):
     mock_readlink.return_value = "/normal/path"
     self.assertEqual(filesystem.readlink("dummy"), "/normal/path")