Exemplo n.º 1
0
def upgrade_packages_and_attach(
    cfg: UAConfig, upgrade_pkgs: List[str], pocket: str, dry_run: bool
) -> bool:
    """Upgrade available packages to fix a CVE.

    Upgrade all packages in upgrades_packages and, if necessary,
    prompt regarding system attach prior to upgrading UA packages.

    :return: True if package upgrade completed or unneeded, False otherwise.
    """
    if not upgrade_pkgs:
        return True

    # If we are running on --dry-run mode, we don't need to be root
    # to understand what will happen with the system
    if os.getuid() != 0 and not dry_run:
        print(messages.SECURITY_APT_NON_ROOT)
        return False

    if pocket != UBUNTU_STANDARD_UPDATES_POCKET:
        # We are now using status-cache because non-root users won't
        # have access to the private machine_token.json file. We
        # can use the status-cache as a proxy for the attached
        # information
        status_cache = cfg.read_cache("status-cache") or {}
        if not status_cache.get("attached", False):
            if not _check_attached(cfg, dry_run):
                return False
        elif _check_subscription_is_expired(
            status_cache=status_cache, cfg=cfg, dry_run=dry_run
        ):
            return False

        if not _check_subscription_for_required_service(pocket, cfg, dry_run):
            # User subscription does not have required service enabled
            return False

    print(
        colorize_commands(
            [
                ["apt", "update", "&&"]
                + ["apt", "install", "--only-upgrade", "-y"]
                + sorted(upgrade_pkgs)
            ]
        )
    )

    if not dry_run:
        apt.run_apt_update_command()
        apt.run_apt_command(
            cmd=["apt-get", "install", "--only-upgrade", "-y"] + upgrade_pkgs,
            error_msg=messages.APT_INSTALL_FAILED.msg,
            env={"DEBIAN_FRONTEND": "noninteractive"},
        )

    return True
Exemplo n.º 2
0
    def test_run_apt_command_with_invalid_repositories(self, m_subp,
                                                       error_list,
                                                       output_list):
        error_msg = "\n".join(error_list)

        m_subp.side_effect = exceptions.ProcessExecutionError(cmd="apt update",
                                                              stderr=error_msg)

        with pytest.raises(exceptions.UserFacingError) as excinfo:
            run_apt_update_command()

        expected_message = "\n".join(output_list) + "."
        assert expected_message == excinfo.value.msg
Exemplo n.º 3
0
    def test_run_update_command_clean_apt_cache_policy_cache(self, m_subp):
        m_subp.side_effect = [
            ("policy1", ""),
            ("update", ""),
            ("policy2", ""),
        ]

        assert "policy1" == run_apt_cache_policy_command()
        # Confirming that caching is happening
        assert "policy1" == run_apt_cache_policy_command()

        run_apt_update_command()

        # Confirm cache was cleared
        assert "policy2" == run_apt_cache_policy_command()
        run_apt_cache_policy_command.cache_clear()
Exemplo n.º 4
0
    def test_failed_run_update_command_clean_apt_cache_policy_cache(
            self, m_subp):
        m_subp.side_effect = [
            ("policy1", ""),
            exceptions.UserFacingError("test"),
            ("policy2", ""),
        ]

        assert "policy1" == run_apt_cache_policy_command()
        # Confirming that caching is happening
        assert "policy1" == run_apt_cache_policy_command()

        with pytest.raises(exceptions.UserFacingError):
            run_apt_update_command()

        # Confirm cache was cleared
        assert "policy2" == run_apt_cache_policy_command()
        run_apt_cache_policy_command.cache_clear()
Exemplo n.º 5
0
    def remove_apt_config(self,
                          run_apt_update: bool = True,
                          silent: bool = False):
        """Remove any repository apt configuration files.

        :param run_apt_update: If after removing the apt update
            command after removing the apt files.
        """
        series = util.get_platform_info()["series"]
        repo_filename = self.repo_list_file_tmpl.format(name=self.name)
        entitlement = self.cfg.entitlements[self.name].get("entitlement", {})
        access_directives = entitlement.get("directives", {})
        repo_url = access_directives.get("aptURL")
        if not repo_url:
            raise exceptions.MissingAptURLDirective(self.name)
        if self.disable_apt_auth_only:
            # We only remove the repo from the apt auth file, because
            # UA Infra: ESM is a special-case: we want to be able to report on
            # the available UA Infra: ESM updates even when it's disabled
            apt.remove_repo_from_apt_auth_file(repo_url)
            apt.restore_commented_apt_list_file(repo_filename)
        else:
            apt.remove_auth_apt_repo(repo_filename, repo_url,
                                     self.repo_key_file)
            apt.remove_apt_list_files(repo_url, series)
        if self.repo_pin_priority:
            repo_pref_file = self.repo_pref_file_tmpl.format(name=self.name)
            if self.repo_pin_priority == "never":
                # Disable the repo with a pinning file
                apt.add_ppa_pinning(
                    repo_pref_file,
                    repo_url,
                    self.origin,
                    self.repo_pin_priority,
                )
            elif os.path.exists(repo_pref_file):
                os.unlink(repo_pref_file)

        if run_apt_update:
            if not silent:
                event.info(messages.APT_UPDATING_LISTS)
            apt.run_apt_update_command()
Exemplo n.º 6
0
    def setup_apt_config(self, silent: bool = False) -> None:
        """Setup apt config based on the resourceToken and directives.
        Also sets up apt proxy if necessary.

        :raise UserFacingError: on failure to setup any aspect of this apt
           configuration
        """
        http_proxy = None  # type: Optional[str]
        https_proxy = None  # type: Optional[str]
        scope = None  # type: Optional[apt.AptProxyScope]
        if self.cfg.global_apt_http_proxy or self.cfg.global_apt_https_proxy:
            http_proxy = util.validate_proxy(
                "http",
                self.cfg.global_apt_http_proxy,
                util.PROXY_VALIDATION_APT_HTTP_URL,
            )
            https_proxy = util.validate_proxy(
                "https",
                self.cfg.global_apt_https_proxy,
                util.PROXY_VALIDATION_APT_HTTPS_URL,
            )
            scope = apt.AptProxyScope.GLOBAL
        elif self.cfg.ua_apt_http_proxy or self.cfg.ua_apt_https_proxy:
            http_proxy = util.validate_proxy(
                "http",
                self.cfg.ua_apt_http_proxy,
                util.PROXY_VALIDATION_APT_HTTP_URL,
            )
            https_proxy = util.validate_proxy(
                "https",
                self.cfg.ua_apt_https_proxy,
                util.PROXY_VALIDATION_APT_HTTPS_URL,
            )
            scope = apt.AptProxyScope.UACLIENT

        apt.setup_apt_proxy(http_proxy=http_proxy,
                            https_proxy=https_proxy,
                            proxy_scope=scope)
        repo_filename = self.repo_list_file_tmpl.format(name=self.name)
        resource_cfg = self.cfg.entitlements.get(self.name)
        directives = resource_cfg["entitlement"].get("directives", {})
        obligations = resource_cfg["entitlement"].get("obligations", {})
        token = resource_cfg.get("resourceToken")
        if not token:
            machine_token = self.cfg.machine_token["machineToken"]
            if not obligations.get("enableByDefault"):
                # services that are not enableByDefault need to obtain specific
                # resource access for tokens. We want to refresh this every
                # enable call because it is not refreshed by `ua refresh`.
                client = contract.UAContractClient(self.cfg)
                machine_access = client.request_resource_machine_access(
                    machine_token, self.name)
                if machine_access:
                    token = machine_access.get("resourceToken")
            if not token:
                token = machine_token
                logging.warning(
                    "No resourceToken present in contract for service %s."
                    " Using machine token as credentials",
                    self.title,
                )
        aptKey = directives.get("aptKey")
        if not aptKey:
            raise exceptions.UserFacingError(
                "Ubuntu Advantage server provided no aptKey directive for"
                " {}.".format(self.name))
        repo_url = directives.get("aptURL")
        if not repo_url:
            raise exceptions.MissingAptURLDirective(self.name)
        repo_suites = directives.get("suites")
        if not repo_suites:
            raise exceptions.UserFacingError(
                "Empty {} apt suites directive from {}".format(
                    self.name, self.cfg.contract_url))
        if self.repo_pin_priority:
            if not self.origin:
                raise exceptions.UserFacingError(
                    "Cannot setup apt pin. Empty apt repo origin value '{}'.\n"
                    "{}".format(
                        self.origin,
                        messages.ENABLED_FAILED.format(title=self.title).msg,
                    ))
            repo_pref_file = self.repo_pref_file_tmpl.format(name=self.name)
            if self.repo_pin_priority != "never":
                apt.add_ppa_pinning(
                    repo_pref_file,
                    repo_url,
                    self.origin,
                    self.repo_pin_priority,
                )
            elif os.path.exists(repo_pref_file):
                os.unlink(repo_pref_file)  # Remove disabling apt pref file

        prerequisite_pkgs = []
        if not os.path.exists(apt.APT_METHOD_HTTPS_FILE):
            prerequisite_pkgs.append("apt-transport-https")
        if not os.path.exists(apt.CA_CERTIFICATES_FILE):
            prerequisite_pkgs.append("ca-certificates")

        if prerequisite_pkgs:
            if not silent:
                event.info("Installing prerequisites: {}".format(
                    ", ".join(prerequisite_pkgs)))
            try:
                apt.run_apt_install_command(packages=prerequisite_pkgs)
            except exceptions.UserFacingError:
                self.remove_apt_config()
                raise
        apt.add_auth_apt_repo(repo_filename, repo_url, token, repo_suites,
                              self.repo_key_file)
        # Run apt-update on any repo-entitlement enable because the machine
        # probably wants access to the repo that was just enabled.
        # Side-effect is that apt policy will now report the repo as accessible
        # which allows ua status to report correct info
        if not silent:
            event.info(messages.APT_UPDATING_LISTS)
        try:
            apt.run_apt_update_command()
        except exceptions.UserFacingError:
            self.remove_apt_config(run_apt_update=False)
            raise
    def _perform_enable(self, silent: bool = False) -> bool:
        """Enable specific entitlement.

        @return: True on success, False otherwise.
        """
        if not util.which(snap.SNAP_CMD):
            event.info("Installing snapd")
            event.info(messages.APT_UPDATING_LISTS)
            try:
                apt.run_apt_update_command()
            except exceptions.UserFacingError as e:
                logging.debug(
                    "Trying to install snapd."
                    " Ignoring apt-get update failure: %s",
                    str(e),
                )
            util.subp(
                ["apt-get", "install", "--assume-yes", "snapd"],
                capture=True,
                retry_sleeps=apt.APT_RETRIES,
            )
        elif "snapd" not in apt.get_installed_packages():
            raise exceptions.SnapdNotProperlyInstalledError(
                snap_cmd=snap.SNAP_CMD, service=self.title
            )

        try:
            util.subp(
                [snap.SNAP_CMD, "wait", "system", "seed.loaded"], capture=True
            )
        except exceptions.ProcessExecutionError as e:
            if re.search(r"unknown command .*wait", str(e).lower()):
                logging.warning(messages.SNAPD_DOES_NOT_HAVE_WAIT_CMD)
            else:
                raise

        http_proxy = util.validate_proxy(
            "http", self.cfg.http_proxy, util.PROXY_VALIDATION_SNAP_HTTP_URL
        )
        https_proxy = util.validate_proxy(
            "https", self.cfg.https_proxy, util.PROXY_VALIDATION_SNAP_HTTPS_URL
        )
        snap.configure_snap_proxy(
            http_proxy=http_proxy,
            https_proxy=https_proxy,
            retry_sleeps=snap.SNAP_INSTALL_RETRIES,
        )

        if not util.which(LIVEPATCH_CMD):
            event.info("Installing canonical-livepatch snap")
            try:
                util.subp(
                    [snap.SNAP_CMD, "install", "canonical-livepatch"],
                    capture=True,
                    retry_sleeps=snap.SNAP_INSTALL_RETRIES,
                )
            except exceptions.ProcessExecutionError as e:
                raise exceptions.ErrorInstallingLivepatch(error_msg=str(e))

        configure_livepatch_proxy(http_proxy, https_proxy)

        return self.setup_livepatch_config(
            process_directives=True, process_token=True
        )