コード例 #1
0
def status(cfg: UAConfig, show_beta: bool = False) -> Dict[str, Any]:
    """Return status as a dict, using a cache for non-root users

    When unattached, get available resources from the contract service
    to report detailed availability of different resources for this
    machine.

    Write the status-cache when called by root.
    """
    if os.getuid() != 0:
        response = cast("Dict[str, Any]", cfg.read_cache("status-cache"))
        if not response:
            response = _unattached_status(cfg)
    elif not cfg.is_attached:
        response = _unattached_status(cfg)
    else:
        response = _attached_status(cfg)

    response.update(_get_config_status(cfg))

    if os.getuid() == 0:
        cfg.write_cache("status-cache", response)

        # Try to remove fix reboot notices if not applicable
        if not util.should_reboot():
            cfg.remove_notice(
                "",
                messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
                    operation="fix operation"
                ),
            )

    response = _handle_beta_resources(cfg, show_beta, response)

    return response
コード例 #2
0
 def test_read_cache_returns_none_when_data_path_absent(
     self, tmpdir, key, path_basename
 ):
     """Return None when the specified key data_path is not cached."""
     cfg = UAConfig({"data_dir": tmpdir.strpath})
     assert None is cfg.read_cache(key)
     assert not tmpdir.join(path_basename).check()
コード例 #3
0
 def test_read_cache_returns_none_when_data_path_absent(
         self, key, path_basename):
     """Return None when the specified key data_path is not cached."""
     tmp_dir = self.tmp_dir()
     cfg = UAConfig({'data_dir': tmp_dir})
     assert None is cfg.read_cache(key)
     assert False is os.path.exists(os.path.join(tmp_dir, path_basename))
コード例 #4
0
    def test_read_cache_returns_content_when_data_path_present(
            self, tmpdir, key, path_basename):
        cfg = UAConfig({'data_dir': tmpdir.strpath})
        data_path = tmpdir.join(path_basename)
        with open(data_path.strpath, 'w') as f:
            f.write('content%s' % key)

        assert 'content%s' % key == cfg.read_cache(key)
コード例 #5
0
    def test_read_cache_returns_stuctured_content_when_json_data_path_present(
            self, tmpdir, key, path_basename):
        cfg = UAConfig({'data_dir': tmpdir.strpath})
        data_path = tmpdir.join(path_basename)
        expected = {key: 'content%s' % key}
        with open(data_path.strpath, 'w') as f:
            f.write(json.dumps(expected))

        assert expected == cfg.read_cache(key)
コード例 #6
0
    def test_read_cache_returns_content_when_data_path_present(
            self, key, path_basename):
        tmp_dir = self.tmp_dir()
        cfg = UAConfig({'data_dir': tmp_dir})
        data_path = self.tmp_path(path_basename, tmp_dir)
        with open(data_path, 'w') as f:
            f.write('content%s' % key)

        assert 'content%s' % key == cfg.read_cache(key)
コード例 #7
0
    def test_datetimes_are_unserialised(self, tmpdir):
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath)
        data_path = tmpdir.join(PRIVATE_SUBDIR, "dt_test")
        with open(data_path.strpath, "w") as f:
            f.write('{"dt": "2019-07-25T14:35:51"}')

        actual = cfg.read_cache("dt_test")
        assert {"dt": datetime.datetime(2019, 7, 25, 14, 35, 51)} == actual
コード例 #8
0
    def test_write_cache_writes_non_private_dir_when_private_is_false(
            self, tmpdir):
        """When content is not a string, write a json string."""
        cfg = UAConfig({'data_dir': tmpdir.strpath})

        assert None is cfg.write_cache('key', 'value', private=False)
        with open(tmpdir.join('key').strpath, 'r') as stream:
            assert 'value' == stream.read()
        assert 'value' == cfg.read_cache('key')
コード例 #9
0
    def test_read_cache_returns_content_when_data_path_present(
            self, tmpdir, key, path_basename):
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath)
        data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename)
        with open(data_path.strpath, "w") as f:
            f.write("content{}".format(key))

        assert "content{}".format(key) == cfg.read_cache(key)
コード例 #10
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
コード例 #11
0
    def test_read_cache_returns_stuctured_content_when_json_data_path_present(
            self, tmpdir, key, path_basename):
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        os.makedirs(tmpdir.join(PRIVATE_SUBDIR).strpath)
        data_path = tmpdir.join(PRIVATE_SUBDIR, path_basename)
        expected = {key: "content{}".format(key)}
        with open(data_path.strpath, "w") as f:
            f.write(json.dumps(expected))

        assert expected == cfg.read_cache(key)
コード例 #12
0
    def test_write_cache_writes_json_string_when_content_not_a_string(
            self, tmpdir, key, value):
        """When content is not a string, write a json string."""
        cfg = UAConfig({"data_dir": tmpdir.strpath})

        expected_json_content = json.dumps(value)
        assert None is cfg.write_cache(key, value)
        with open(tmpdir.join(PRIVATE_SUBDIR, key).strpath, "r") as stream:
            assert expected_json_content == stream.read()
        assert value == cfg.read_cache(key)
コード例 #13
0
    def test_read_cache_returns_stuctured_content_when_json_data_path_present(
            self, key, path_basename):
        tmp_dir = self.tmp_dir()
        cfg = UAConfig({'data_dir': tmp_dir})
        data_path = self.tmp_path(path_basename, tmp_dir)
        expected = {key: 'content%s' % key}
        with open(data_path, 'w') as f:
            f.write(json.dumps(expected))

        assert expected == cfg.read_cache(key)
コード例 #14
0
    def test_write_cache_writes_json_string_when_content_not_a_string(
            self, key, value):
        """When content is not a string, write a json string."""
        tmp_dir = self.tmp_dir()
        cfg = UAConfig({'data_dir': tmp_dir})

        expected_json_content = json.dumps(value)
        assert None is cfg.write_cache(key, value)
        with open(self.tmp_path(key, tmp_dir), 'r') as stream:
            assert expected_json_content == stream.read()
        assert value == cfg.read_cache(key)
コード例 #15
0
    def test_write_cache_creates_dir_when_data_dir_does_not_exist(self):
        """When data_dir doesn't exist, create it."""
        tmp_subdir = self.tmp_path('does/not/exist')
        cfg = UAConfig({'data_dir': tmp_subdir})

        assert False is os.path.isdir(tmp_subdir), (
            'Found unexpected directory %s' % tmp_subdir)
        assert None is cfg.write_cache('somekey', 'someval')
        assert True is os.path.isdir(tmp_subdir), (
            'Missing expected directory %s' % tmp_subdir)
        assert 'someval' == cfg.read_cache('somekey')
コード例 #16
0
    def test_write_cache_write_key_name_in_data_dir_when_data_path_absent(
            self, tmpdir, key, content):
        """When key is not in data_paths, write content to data_dir/key."""
        cfg = UAConfig({"data_dir": tmpdir.strpath})
        expected_path = tmpdir.join(PRIVATE_SUBDIR, key)

        assert not expected_path.check(), "Found unexpected file {}".format(
            expected_path)
        assert None is cfg.write_cache(key, content)
        assert expected_path.check(), "Missing expected file {}".format(
            expected_path)
        assert content == cfg.read_cache(key)
コード例 #17
0
    def test_write_cache_write_key_name_in_data_dir_when_data_path_absent(
            self, tmpdir, key, content):
        """When key is not in data_paths, write content to data_dir/key."""
        cfg = UAConfig({'data_dir': tmpdir.strpath})
        expected_path = tmpdir.join(key)

        assert not expected_path.check(), (
            'Found unexpected file %s' % expected_path)
        assert None is cfg.write_cache(key, content)
        assert expected_path.check(), (
            'Missing expected file %s' % expected_path)
        assert content == cfg.read_cache(key)
コード例 #18
0
    def test_write_cache_write_key_name_in_data_dir_when_data_path_absent(
            self, key, content):
        """When key is not in data_paths, write content to data_dir/key."""
        tmp_dir = self.tmp_dir()
        cfg = UAConfig({'data_dir': tmp_dir})
        expected_path = os.path.join(tmp_dir, key)

        assert False is os.path.exists(expected_path), (
            'Found unexpected file %s' % expected_path)
        assert None is cfg.write_cache(key, content)
        assert True is os.path.exists(expected_path), (
            'Missing expected file %s' % expected_path)
        assert content == cfg.read_cache(key)
コード例 #19
0
    def test_write_cache_creates_dir_when_data_dir_does_not_exist(
            self, tmpdir):
        """When data_dir doesn't exist, create it."""
        tmp_subdir = tmpdir.join("does/not/exist")
        cfg = UAConfig({"data_dir": tmp_subdir.strpath})

        assert False is os.path.isdir(
            tmp_subdir.strpath), "Found unexpected directory {}".format(
                tmp_subdir)
        assert None is cfg.write_cache("somekey", "someval")
        assert True is os.path.isdir(
            tmp_subdir.strpath), "Missing expected directory {}".format(
                tmp_subdir)
        assert "someval" == cfg.read_cache("somekey")
コード例 #20
0
def _get_contract_token_from_cloud_identity(cfg: config.UAConfig) -> str:
    """Detect cloud_type and request a contract token from identity info.

    :param cfg: a ``config.UAConfig`` instance

    :raise NonAutoAttachImageError: When not on an auto-attach image type.
    :raise UrlError: On unexpected connectivity issues to contract
        server or inability to access identity doc from metadata service.
    :raise ContractAPIError: On unexpected errors when talking to the contract
        server.
    :raise NonAutoAttachImageError: If this cloud type does not have
        auto-attach support.

    :return: contract token obtained from identity doc
    """
    try:
        instance = identity.cloud_instance_factory()
    except exceptions.UserFacingError as e:
        if cfg.is_attached:
            # We are attached on non-Pro Image, just report already attached
            raise exceptions.AlreadyAttachedError(cfg)
        # Unattached on non-Pro return UserFacing error msg details
        raise e
    current_iid = identity.get_instance_id()
    if cfg.is_attached:
        prev_iid = cfg.read_cache("instance-id")
        if current_iid == prev_iid:
            raise exceptions.AlreadyAttachedError(cfg)
        print("Re-attaching Ubuntu Advantage subscription on new instance")
        if _detach(cfg, assume_yes=True) != 0:
            raise exceptions.UserFacingError(
                ua_status.MESSAGE_DETACH_AUTOMATION_FAILURE
            )
    contract_client = contract.UAContractClient(cfg)
    try:
        tokenResponse = contract_client.request_auto_attach_contract_token(
            instance=instance
        )
    except contract.ContractAPIError as e:
        if e.code and 400 <= e.code < 500:
            raise exceptions.NonAutoAttachImageError(
                ua_status.MESSAGE_UNSUPPORTED_AUTO_ATTACH
            )
        raise e
    if current_iid:
        cfg.write_cache("instance-id", current_iid)

    return tokenResponse["contractToken"]
コード例 #21
0
def prompt_request_macaroon(cfg: UAConfig, caveat_id: str) -> dict:
    discharge_macaroon = cfg.read_cache('macaroon')
    if discharge_macaroon:
        # TODO(invalidate cached macaroon on root-macaroon or discharge expiry)
        return discharge_macaroon
    email = input('Email: ')
    password = getpass.getpass('Password: '******'email': email, 'password': password, 'caveat_id': caveat_id}
    sso_client = UbuntuSSOClient(cfg)
    content = None
    twofactor_retries = 0
    while True:
        try:
            content = sso_client.request_discharge_macaroon(**args)
        except SSOAuthError as e:
            if API_ERROR_2FA_REQUIRED in e:
                args['otp'] = input('Second-factor auth: ')
                continue
            elif API_ERROR_INVALID_CREDENTIALS in e:
                # This is arguably bug in canonical-identity-provider code
                # that the error 'code' is 'invalid-credentials' when docs
                # clearly designates a 'twofactor-error' code that should be
                # emitted when the 2FA token is invalid. There are no plans for
                # changes to the error codes or messages as it might break
                # existing clients. As a result, we have to distinguish
                # email/password invalid-credentials errors from 2-factor
                # errors by searching the attached error 'message' field for
                # 2-factor.
                if '2-factor' in e[API_ERROR_INVALID_CREDENTIALS]:
                    if twofactor_retries < TWOFACTOR_RETRIES:
                        args['otp'] = input(
                            'Invalid second-factor auth, try again: ')
                        twofactor_retries += 1
                        continue
            raise exceptions.UserFacingError(str(e))
        break
    if not content:
        raise exceptions.UserFacingError('SSO server returned empty content')
    return content
コード例 #22
0
def run_jobs(cfg: UAConfig, current_time: datetime):
    """Run jobs in order when next_run is before current_time.

    Persist jobs-status with calculated next_run values to aid in timer
    state introspection for jobs which have not yet run.
    """
    jobs_status = cfg.read_cache("jobs-status") or {}
    for job in UACLIENT_JOBS:
        if job.name in jobs_status:
            next_run = jobs_status[job.name]["next_run"]
            if next_run > current_time:
                continue  # Skip job as expected next_run hasn't yet passed
        if job.run(cfg):
            # Persist last_run and next_run UTC-based times on job success.
            jobs_status[job.name] = {
                "last_run":
                current_time,
                "next_run":
                current_time +
                timedelta(seconds=job.run_interval_seconds(cfg)),
            }
    cfg.write_cache(key="jobs-status", content=jobs_status)