def install_packages(self, package_list: "List[str]" = None) -> None:
        """Install contract recommended packages for the entitlement.

        :param package_list: Optional package list to use instead of
            self.packages.
        """
        print("Installing {title} packages".format(title=self.title))
        if self.apt_noninteractive:
            env = {"DEBIAN_FRONTEND": "noninteractive"}
            apt_options = [
                '-o Dpkg::Options::="--force-confdef"',
                '-o Dpkg::Options::="--force-confold"',
            ]
        else:
            env = {}
            apt_options = []
        if not package_list:
            package_list = self.packages
        try:
            apt.run_apt_command(
                ["apt-get", "install", "--assume-yes"]
                + apt_options
                + package_list,
                status.MESSAGE_ENABLED_FAILED_TMPL.format(title=self.title),
                env=env,
            )
        except exceptions.UserFacingError:
            self._cleanup()
            raise
    def enable(self, *, silent_if_inapplicable: bool = False) -> bool:
        """Enable specific entitlement.

        :param silent_if_inapplicable:
            Don't emit any messages until after it has been determined that
            this entitlement is applicable to the current machine.

        @return: True on success, False otherwise.
        @raises: UserFacingError on failure to install suggested packages
        """
        if not self.can_enable(silent=silent_if_inapplicable):
            return False
        self.setup_apt_config()
        if self.packages:
            try:
                print("Installing {title} packages".format(title=self.title))
                for msg in self.messaging.get("pre_install", []):
                    print(msg)
                apt.run_apt_command(
                    ["apt-get", "install", "--assume-yes"] + self.packages,
                    status.MESSAGE_ENABLED_FAILED_TMPL.format(
                        title=self.title
                    ),
                )
            except exceptions.UserFacingError:
                self._cleanup()
                raise
        print(status.MESSAGE_ENABLED_TMPL.format(title=self.title))
        for msg in self.messaging.get("post_enable", []):
            print(msg)
        return True
Exemple #3
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
    def enable(self, *, silent_if_inapplicable: bool = False) -> bool:
        """Enable specific entitlement.

        :param silent_if_inapplicable:
            Don't emit any messages until after it has been determined that
            this entitlement is applicable to the current machine.

        @return: True on success, False otherwise.
        """
        if not self.can_enable(silent=silent_if_inapplicable):
            return False
        if not util.which("/snap/bin/canonical-livepatch"):
            if not util.which(SNAP_CMD):
                print("Installing snapd")
                print(status.MESSAGE_APT_UPDATING_LISTS)
                try:
                    apt.run_apt_command(
                        ["apt-get", "update"], status.MESSAGE_APT_UPDATE_FAILED
                    )
                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.UserFacingError(
                    "/usr/bin/snap is present but snapd is not installed;"
                    " cannot enable {}".format(self.title)
                )
            util.subp(
                [SNAP_CMD, "wait", "system", "seed.loaded"], capture=True
            )
            print("Installing canonical-livepatch snap")
            try:
                util.subp(
                    [SNAP_CMD, "install", "canonical-livepatch"],
                    capture=True,
                    retry_sleeps=SNAP_INSTALL_RETRIES,
                )
            except util.ProcessExecutionError as e:
                msg = "Unable to install Livepatch client: " + str(e)
                raise exceptions.UserFacingError(msg)
        return self.setup_livepatch_config(
            process_directives=True, process_token=True
        )
    def enable(self, *, silent_if_inapplicable: bool = False) -> bool:
        """Enable specific entitlement.

        :param silent_if_inapplicable:
            Don't emit any messages until after it has been determined that
            this entitlement is applicable to the current machine.

        @return: True on success, False otherwise.
        @raises: UserFacingError on failure to install suggested packages
        """
        msg_ops = self.messaging.get("pre_enable", [])
        if not handle_message_operations(msg_ops):
            return False
        if not self.can_enable(silent=silent_if_inapplicable):
            return False
        self.setup_apt_config()
        if self.packages:
            try:
                print("Installing {title} packages".format(title=self.title))
                msg_ops = self.messaging.get("pre_install", [])
                if not handle_message_operations(msg_ops):
                    return False

                if self.apt_noninteractive:
                    env = {"DEBIAN_FRONTEND": "noninteractive"}
                    apt_options = [
                        '-o Dpkg::Options::="--force-confdef"',
                        '-o Dpkg::Options::="--force-confold"',
                    ]
                else:
                    env = {}
                    apt_options = []
                apt.run_apt_command(
                    ["apt-get", "install", "--assume-yes"]
                    + apt_options
                    + self.packages,
                    status.MESSAGE_ENABLED_FAILED_TMPL.format(
                        title=self.title
                    ),
                    env=env,
                )
            except exceptions.UserFacingError:
                self._cleanup()
                raise
        print(status.MESSAGE_ENABLED_TMPL.format(title=self.title))
        msg_ops = self.messaging.get("post_enable", [])
        if not handle_message_operations(msg_ops):
            return False
        return True
Exemple #6
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 = util.ProcessExecutionError(
            cmd="apt update", stderr=error_msg
        )

        with pytest.raises(exceptions.UserFacingError) as excinfo:
            run_apt_command(
                cmd=["apt", "update"],
                error_msg=status.MESSAGE_APT_UPDATE_FAILED,
            )

        expected_message = "\n".join(output_list)
        assert expected_message == excinfo.value.msg
    def remove_apt_config(self, run_apt_update=True):
        """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 ESM Infra
            # is a special-case: we want to be able to report on the
            # available ESM Infra 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:
            print(status.MESSAGE_APT_UPDATING_LISTS)
            apt.run_apt_command(
                ["apt-get", "update"], status.MESSAGE_APT_UPDATE_FAILED
            )
    def setup_apt_config(self, silent: bool = False) -> None:
        """Setup apt config based on the resourceToken and directives.

        FIPS-specifically handle apt-mark unhold

        :raise UserFacingError: on failure to setup any aspect of this apt
           configuration
        """
        cmd = ["apt-mark", "showholds"]
        holds = apt.run_apt_command(cmd, " ".join(cmd) + " failed.")
        unholds = []
        for hold in holds.splitlines():
            if hold in self.fips_pro_package_holds:
                unholds.append(hold)
        if unholds:
            unhold_cmd = ["apt-mark", "unhold"] + unholds
            holds = apt.run_apt_command(
                unhold_cmd, " ".join(unhold_cmd) + " failed."
            )
        super().setup_apt_config(silent=silent)
Exemple #9
0
 def application_status(self) -> 'Tuple[ApplicationStatus, str]':
     entitlement_cfg = self.cfg.entitlements.get(self.name, {})
     directives = entitlement_cfg.get(
         'entitlement', {}).get('directives', {})
     repo_url = directives.get('aptURL')
     if not repo_url:
         repo_url = self.repo_url
     protocol, repo_path = repo_url.split('://')
     policy = apt.run_apt_command(
         ['apt-cache', 'policy'], status.MESSAGE_APT_POLICY_FAILED)
     match = re.search(r'(?P<pin>(-)?\d+) %s[^-]' % repo_url, policy)
     if match and match.group('pin') != APT_DISABLED_PIN:
         return ApplicationStatus.ENABLED, '%s is active' % self.title
     return ApplicationStatus.DISABLED, '%s is not configured' % self.title
    def remove_packages(self) -> None:
        """Remove fips meta package to disable the service.

        FIPS meta-package will unset grub config options which will deactivate
        FIPS on any related packages.
        """
        installed_packages = set(apt.get_installed_packages())
        fips_metapackage = set(self.packages).difference(
            set(self.conditional_packages)
        )
        remove_packages = fips_metapackage.intersection(installed_packages)
        if remove_packages:
            env = {"DEBIAN_FRONTEND": "noninteractive"}
            apt_options = [
                '-o Dpkg::Options::="--force-confdef"',
                '-o Dpkg::Options::="--force-confold"',
            ]
            apt.run_apt_command(
                ["apt-get", "remove", "--assume-yes"]
                + apt_options
                + list(remove_packages),
                messages.DISABLE_FAILED_TMPL.format(title=self.title),
                env=env,
            )
 def application_status(self) -> "Tuple[ApplicationStatus, str]":
     entitlement_cfg = self.cfg.entitlements.get(self.name, {})
     directives = entitlement_cfg.get("entitlement", {}).get(
         "directives", {}
     )
     repo_url = directives.get("aptURL")
     if not repo_url:
         repo_url = self.repo_url
     protocol, repo_path = repo_url.split("://")
     policy = apt.run_apt_command(
         ["apt-cache", "policy"], status.MESSAGE_APT_POLICY_FAILED
     )
     match = re.search(r"(?P<pin>(-)?\d+) {}[^-]".format(repo_url), policy)
     if match and match.group("pin") != APT_DISABLED_PIN:
         return ApplicationStatus.ENABLED, "{} is active".format(self.title)
     return (
         ApplicationStatus.DISABLED,
         "{} is not configured".format(self.title),
     )
    def setup_apt_config(self) -> None:
        """Setup apt config based on the resourceToken and  directives.

        :raise UserFacingError: on failure to setup any aspect of this apt
           configuration
        """
        series = util.get_platform_info()["series"]
        repo_filename = self.repo_list_file_tmpl.format(
            name=self.name, series=series
        )
        resource_cfg = self.cfg.entitlements.get(self.name)
        directives = resource_cfg["entitlement"].get("directives", {})
        token = resource_cfg.get("resourceToken")
        if not token:
            logging.debug(
                "No specific resourceToken present. Using machine token"
                " as %s credentials",
                self.title,
            )
            token = self.cfg.machine_token["machineToken"]
        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,
                        status.MESSAGE_ENABLED_FAILED_TMPL.format(
                            title=self.title
                        ),
                    )
                )
            repo_pref_file = self.repo_pref_file_tmpl.format(
                name=self.name, series=series
            )
            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:
            print(
                "Installing prerequisites: {}".format(
                    ", ".join(prerequisite_pkgs)
                )
            )
            try:
                apt.run_apt_command(
                    ["apt-get", "install", "--assume-yes"] + prerequisite_pkgs,
                    status.MESSAGE_APT_INSTALL_FAILED,
                )
            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
        print(status.MESSAGE_APT_UPDATING_LISTS)
        try:
            apt.run_apt_command(
                ["apt-get", "update"], status.MESSAGE_APT_UPDATE_FAILED
            )
        except exceptions.UserFacingError:
            self.remove_apt_config()
            raise
Exemple #13
0
    def setup_apt_config(self) -> None:
        """Setup apt config based on the resourceToken and  directives.

        :raise UserFacingError: on failure to setup any aspect of this apt
           configuration
        """
        series = util.get_platform_info()['series']
        repo_filename = self.repo_list_file_tmpl.format(
            name=self.name, series=series)
        resource_cfg = self.cfg.entitlements.get(self.name)
        directives = resource_cfg['entitlement'].get('directives', {})
        token = resource_cfg.get('resourceToken')
        if not token:
            logging.debug(
                'No specific resourceToken present. Using machine token'
                ' as %s credentials', self.title)
            token = self.cfg.machine_token['machineToken']
        if directives.get('aptKey'):
            logging.debug(
                "Ignoring aptKey directive '%s'", directives.get('aptKey'))
        keyring_file = os.path.join(apt.KEYRINGS_DIR, self.repo_key_file)
        repo_url = directives.get('aptURL')
        if not repo_url:
            repo_url = self.repo_url
        repo_suites = directives.get('suites')
        if not repo_suites:
            raise exceptions.UserFacingError(
                'Empty %s apt suites directive from %s' %
                (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 '%s'.\n"
                    "%s" % (self.origin,
                            status.MESSAGE_ENABLED_FAILED_TMPL.format(
                                title=self.title)))
            repo_pref_file = self.repo_pref_file_tmpl.format(
                name=self.name, series=series)
            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:
            print('Installing prerequisites: {}'.format(
                ', '.join(prerequisite_pkgs)))
            try:
                apt.run_apt_command(
                    ['apt-get', 'install', '--assume-yes'] + prerequisite_pkgs,
                    status.MESSAGE_APT_INSTALL_FAILED)
            except exceptions.UserFacingError:
                self.remove_apt_config()
                raise
        apt.add_auth_apt_repo(repo_filename, repo_url, token, repo_suites,
                              keyring_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
        print('Updating package lists')
        try:
            apt.run_apt_command(
                ['apt-get', 'update'], status.MESSAGE_APT_UPDATE_FAILED)
        except exceptions.UserFacingError:
            self.remove_apt_config()
            raise