예제 #1
0
 def wrapper(*args, **kwargs):
     try:
         return func(*args, **kwargs)
     except KeyboardInterrupt:
         with util.disable_log_to_console():
             logging.exception("KeyboardInterrupt")
         print("Interrupt received; exiting.", file=sys.stderr)
         sys.exit(1)
     except util.UrlError as exc:
         with util.disable_log_to_console():
             msg_args = {"url": exc.url, "error": exc}
             if exc.url:
                 msg_tmpl = ua_status.LOG_CONNECTIVITY_ERROR_WITH_URL_TMPL
             else:
                 msg_tmpl = ua_status.LOG_CONNECTIVITY_ERROR_TMPL
             logging.exception(msg_tmpl.format(**msg_args))
         print(ua_status.MESSAGE_CONNECTIVITY_ERROR, file=sys.stderr)
         sys.exit(1)
     except exceptions.UserFacingError as exc:
         with util.disable_log_to_console():
             logging.exception(exc.msg)
         print("{}".format(exc.msg), file=sys.stderr)
         sys.exit(exc.exit_code)
     except Exception:
         with util.disable_log_to_console():
             logging.exception("Unhandled exception, please file a bug")
         print(ua_status.MESSAGE_UNEXPECTED_ERROR, file=sys.stderr)
         sys.exit(1)
예제 #2
0
 def wrapper(*args, **kwargs):
     try:
         return func(*args, **kwargs)
     except KeyboardInterrupt:
         with util.disable_log_to_console():
             logging.exception('KeyboardInterrupt')
         print('Interrupt received; exiting.', file=sys.stderr)
         sys.exit(1)
     except exceptions.UserFacingError as exc:
         with util.disable_log_to_console():
             logging.exception(exc.msg)
         print('ERROR: {}'.format(exc.msg), file=sys.stderr)
         sys.exit(1)
예제 #3
0
def _attach_with_token(cfg: config.UAConfig, token: str,
                       allow_enable: bool) -> int:
    """Common functionality to take a token and attach via contract backend"""
    try:
        contract.request_updated_contract(cfg,
                                          token,
                                          allow_enable=allow_enable)
    except util.UrlError as exc:
        with util.disable_log_to_console():
            logging.exception(exc)
        print(ua_status.MESSAGE_ATTACH_FAILURE)
        cfg.status()  # Persist updated status in the event of partial attach
        return 1
    except exceptions.UserFacingError as exc:
        logging.warning(exc.msg)
        cfg.status()  # Persist updated status in the event of partial attach
        return 1
    contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][
        "name"]
    print(
        ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format(
            contract_name=contract_name))

    action_status(args=None, cfg=cfg)
    return 0
예제 #4
0
    def test_no_error_if_console_handler_not_found(self, caplog_text):
        with mock.patch("uaclient.util.logging.getLogger") as m_getlogger:
            m_getlogger.return_value.handlers = []
            with util.disable_log_to_console():
                pass

        assert "no console handler found" in caplog_text()
예제 #5
0
def action_attach(args, cfg):
    if cfg.is_attached:
        print("This machine is already attached to '{}'.".format(
            cfg.accounts[0]["name"]))
        return 0
    if os.getuid() != 0:
        raise exceptions.NonRootUserError()
    contract_token = args.token
    if not contract_token:
        print("No valid contract token available")
        return 1
    try:
        contract.request_updated_contract(cfg,
                                          contract_token,
                                          allow_enable=args.auto_enable)
    except util.UrlError as exc:
        with util.disable_log_to_console():
            logging.exception(exc.msg)
        print(
            ua_status.MESSAGE_ATTACH_FAILURE_TMPL.format(url=cfg.contract_url))
        return 1
    except exceptions.UserFacingError as exc:
        logging.warning(exc.msg)
        action_status(args=None, cfg=cfg)
        return 1
    contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][
        "name"]
    print(
        ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format(
            contract_name=contract_name))

    action_status(args=None, cfg=cfg)
    return 0
예제 #6
0
def export_gpg_key_from_keyring(
    key_id: str, source_keyring_file: str, destination_keyfile: str
) -> None:
    """Export a specific key from source_keyring_file into destination_keyfile

    :param key_id: Long fingerprint of key to export.
    :param source_keyring_file: The keyring file from which to export.
    :param destination_keyfile: The filename created with the single exported
        key.

    :raise UserFacingError: Any GPG errors or if specific key does not exist in
        the source_keyring_file.
    """
    export_cmd = [
        "gpg",
        "--output",
        destination_keyfile,
        "--yes",
        "--no-auto-check-trustdb",
        "--no-default-keyring",
        "--keyring",
        source_keyring_file,
        "--export",
        key_id,
    ]
    logging.debug("Exporting GPG key %s from %s", key_id, source_keyring_file)
    try:
        out, err = util.subp(export_cmd)
    except util.ProcessExecutionError as exc:
        with util.disable_log_to_console():
            logging.error(str(exc))
        raise exceptions.UserFacingError(
            "Unable to export GPG keys from keyring {}".format(
                source_keyring_file
            )
        )
    if "nothing exported" in err:
        raise exceptions.UserFacingError(
            "GPG key '{}' not found in {}".format(key_id, source_keyring_file)
        )
    if not os.path.exists(destination_keyfile):
        msg = "Unexpected error exporting GPG key '{}' from {}".format(
            key_id, source_keyring_file
        )
        with util.disable_log_to_console():
            logging.error(msg + " Error: {}".format(err))
        raise exceptions.UserFacingError(msg)
예제 #7
0
def process_entitlements_delta(
    past_entitlements: "Dict[str, Any]",
    new_entitlements: "Dict[str, Any]",
    allow_enable: bool,
    series_overrides: bool = True,
) -> None:
    """Iterate over all entitlements in new_entitlement and apply any delta
    found according to past_entitlements.

    :param past_entitlements: dict containing the last valid information
        regarding service entitlements.
    :param new_entitlements: dict containing the current information regarding
        service entitlements.
    :param allow_enable: Boolean set True if allowed to perform the enable
        operation. When False, a message will be logged to inform the user
        about the recommended enabled service.
    :param series_overrides: Boolean set True if series overrides should be
        applied to the new_access dict.
    """
    delta_error = False
    unexpected_error = False
    for name, new_entitlement in sorted(new_entitlements.items()):
        try:
            process_entitlement_delta(
                past_entitlements.get(name, {}),
                new_entitlement,
                allow_enable=allow_enable,
                series_overrides=series_overrides,
            )
        except exceptions.UserFacingError:
            delta_error = True
            with util.disable_log_to_console():
                logging.exception(
                    "Failed to process contract delta for {name}:"
                    " {delta}".format(name=name, delta=new_entitlement))
        except Exception:
            unexpected_error = True
            with util.disable_log_to_console():
                logging.exception(
                    "Unexpected error processing contract delta for {name}:"
                    " {delta}".format(name=name, delta=new_entitlement))
    if unexpected_error:
        raise exceptions.UserFacingError(status.MESSAGE_UNEXPECTED_ERROR)
    elif delta_error:
        raise exceptions.UserFacingError(
            status.MESSAGE_ATTACH_FAILURE_DEFAULT_SERVICES)
예제 #8
0
def action_refresh(args, cfg):
    try:
        contract.request_updated_contract(cfg)
    except util.UrlError as exc:
        with util.disable_log_to_console():
            logging.exception(exc)
        raise exceptions.UserFacingError(ua_status.MESSAGE_REFRESH_FAILURE)
    print(ua_status.MESSAGE_REFRESH_SUCCESS)
    return 0
예제 #9
0
    def test_disable_log_to_console_does_nothing_at_debug_level(
            self, logging_sandbox, capsys):
        cli.setup_logging(logging.DEBUG, logging.DEBUG)

        with util.disable_log_to_console():
            logging.error("test error")
            logging.info("test info")

        out, err = capsys.readouterr()
        combined_output = out + err
        assert "test error" in combined_output
        assert "test info" in combined_output
예제 #10
0
def request_updated_contract(cfg,
                             contract_token: "Optional[str]" = None,
                             allow_enable=False):
    """Request contract refresh from ua-contracts service.

    Compare original token to new token and react to entitlement deltas.

    :param cfg: Instance of UAConfig for this machine.
    :param contract_token: String contraining an optional contract token.
    :param allow_enable: Boolean set True if allowed to perform the enable
        operation. When False, a message will be logged to inform the user
        about the recommended enabled service.

    :raise UserFacingError: on failure to update contract or error processing
        contract deltas
    :raise UrlError: On failure to contact the server
    """
    orig_token = cfg.machine_token
    orig_entitlements = cfg.entitlements
    if orig_token and contract_token:
        raise RuntimeError(
            "Got unexpected contract_token on an already attached machine")
    contract_client = UAContractClient(cfg)
    if contract_token:  # We are a mid ua-attach and need to get machinetoken
        try:
            new_token = contract_client.request_contract_machine_attach(
                contract_token=contract_token)
        except util.UrlError as e:
            if isinstance(e, ContractAPIError):
                if hasattr(e, "code"):
                    if e.code == 401:
                        raise exceptions.UserFacingError(
                            status.MESSAGE_ATTACH_INVALID_TOKEN)
                    elif e.code == 403:
                        raise exceptions.UserFacingError(
                            status.MESSAGE_ATTACH_EXPIRED_TOKEN)
                raise e
            with util.disable_log_to_console():
                logging.exception(str(e))
            raise exceptions.UserFacingError(status.MESSAGE_CONNECTIVITY_ERROR)
    else:
        machine_token = orig_token["machineToken"]
        contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"]
        new_token = contract_client.request_machine_token_update(
            machine_token=machine_token, contract_id=contract_id)
    expiry = new_token["machineTokenInfo"]["contractInfo"].get("effectiveTo")
    if expiry:
        if datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") < datetime.utcnow():
            raise exceptions.UserFacingError(
                status.MESSAGE_CONTRACT_EXPIRED_ERROR)

    process_entitlements_delta(orig_entitlements, cfg.entitlements,
                               allow_enable)
예제 #11
0
def action_attach(args, cfg):
    if cfg.is_attached:
        print("This machine is already attached to '%s'." %
              cfg.accounts[0]['name'])
        return 0
    if os.getuid() != 0:
        raise exceptions.NonRootUserError()
    contract_client = contract.UAContractClient(cfg)
    if not args.token:
        with util.disable_log_to_console():
            bound_macaroon_bytes = sso.discharge_root_macaroon(contract_client)
        if bound_macaroon_bytes is None:
            print('Could not attach machine. Unable to obtain authenticated'
                  ' user token')
            return 1
        bound_macaroon = bound_macaroon_bytes.decode('utf-8')
        cfg.write_cache('bound-macaroon', bound_macaroon)
        try:
            contract_client.request_accounts(macaroon_token=bound_macaroon)
            contract_token = contract.get_contract_token_for_account(
                contract_client, bound_macaroon, cfg.accounts[0]['id'])
        except (sso.SSOAuthError, util.UrlError) as e:
            logging.error(str(e))
            print('Could not attach machine. Unable to obtain authenticated'
                  ' contract token')
            return 1
    else:
        contract_token = args.token
    if not contract_token:
        print('No valid contract token available')
        return 1
    if not contract.request_updated_contract(
            cfg, contract_token, allow_enable=args.auto_enable):
        print(
            ua_status.MESSAGE_ATTACH_FAILURE_TMPL.format(url=cfg.contract_url))
        return 1
    contract_name = (
        cfg.machine_token['machineTokenInfo']['contractInfo']['name'])
    print(
        ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format(
            contract_name=contract_name))

    action_status(args=None, cfg=cfg)
    return 0
예제 #12
0
def request_updated_contract(cfg,
                             contract_token: "Optional[str]" = None,
                             allow_enable=False):
    """Request contract refresh from ua-contracts service.

    Compare original token to new token and react to entitlement deltas.

    :param cfg: Instance of UAConfig for this machine.
    :param contract_token: String contraining an optional contract token.
    :param allow_enable: Boolean set True if allowed to perform the enable
        operation. When False, a message will be logged to inform the user
        about the recommended enabled service.

    :raise UserFacingError: on failure to update contract or error processing
        contract deltas
    :raise UrlError: On failure to contact the server
    """
    orig_token = cfg.machine_token
    orig_entitlements = cfg.entitlements
    if orig_token and contract_token:
        raise RuntimeError(
            "Got unexpected contract_token on an already attached machine")
    contract_client = UAContractClient(cfg)
    if contract_token:  # We are a mid ua-attach and need to get machinetoken
        try:
            new_token = contract_client.request_contract_machine_attach(
                contract_token=contract_token)
        except util.UrlError as e:
            if isinstance(e, ContractAPIError):
                if API_ERROR_INVALID_TOKEN in e:
                    raise exceptions.UserFacingError(
                        status.MESSAGE_ATTACH_INVALID_TOKEN)
                raise e
            with util.disable_log_to_console():
                logging.exception(str(e))
            raise exceptions.UserFacingError(status.MESSAGE_CONNECTIVITY_ERROR)
    else:
        machine_token = orig_token["machineToken"]
        contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"]
        new_token = contract_client.request_machine_token_refresh(
            machine_token=machine_token, contract_id=contract_id)
    expiry = new_token["machineTokenInfo"]["contractInfo"].get("effectiveTo")
    if expiry:
        if datetime.strptime(expiry, "%Y-%m-%dT%H:%M:%SZ") < datetime.utcnow():
            raise exceptions.UserFacingError(
                status.MESSAGE_CONTRACT_EXPIRED_ERROR)
    user_errors = []
    for name, entitlement in sorted(cfg.entitlements.items()):
        if entitlement["entitlement"].get("entitled"):
            # Obtain each entitlement's accessContext for this machine
            new_access = contract_client.request_resource_machine_access(
                new_token["machineToken"], name)
        else:
            new_access = entitlement
        try:
            process_entitlement_delta(
                orig_entitlements.get(name, {}),
                new_access,
                allow_enable=allow_enable,
            )
        except exceptions.UserFacingError as e:
            user_errors.append(e)
        except Exception as e:
            with util.disable_log_to_console():
                logging.exception(str(e))
            raise exceptions.UserFacingError(
                "Unexpected error handling Ubuntu Advantage contract changes")
    if user_errors:
        raise exceptions.UserFacingError(
            status.MESSAGE_ATTACH_FAILURE_DEFAULT_SERVICES)
def request_updated_contract(
    cfg, contract_token: "Optional[str]" = None, allow_enable=False
):
    """Request contract refresh from ua-contracts service.

    Compare original token to new token and react to entitlement deltas.

    :param cfg: Instance of UAConfig for this machine.
    :param contract_token: String contraining an optional contract token.
    :param allow_enable: Boolean set True if allowed to perform the enable
        operation. When False, a message will be logged to inform the user
        about the recommended enabled service.

    :raise UserFacingError: on failure to update contract or error processing
        contract deltas
    :raise UrlError: On failure to contact the server
    """
    orig_token = cfg.machine_token
    orig_entitlements = cfg.entitlements
    if orig_token and contract_token:
        raise RuntimeError(
            "Got unexpected contract_token on an already attached machine"
        )
    contract_client = UAContractClient(cfg)
    if contract_token:  # We are a mid ua-attach and need to get machinetoken
        new_token = contract_client.request_contract_machine_attach(
            contract_token=contract_token
        )
    else:
        machine_token = orig_token["machineToken"]
        contract_id = orig_token["machineTokenInfo"]["contractInfo"]["id"]
        new_token = contract_client.request_machine_token_refresh(
            machine_token=machine_token, contract_id=contract_id
        )
    user_errors = []
    for name, entitlement in sorted(cfg.entitlements.items()):
        if entitlement["entitlement"].get("entitled"):
            # Obtain each entitlement's accessContext for this machine
            new_access = contract_client.request_resource_machine_access(
                new_token["machineToken"], name
            )
        else:
            new_access = entitlement
        try:
            process_entitlement_delta(
                orig_entitlements.get(name, {}),
                new_access,
                allow_enable=allow_enable,
            )
        except exceptions.UserFacingError as e:
            user_errors.append(e)
        except Exception as e:
            with util.disable_log_to_console():
                logging.exception(str(e))
            raise exceptions.UserFacingError(
                "Unexpected error handling Ubuntu Advantage contract changes"
            )
    if user_errors:
        error_lines = ["Failure processing Ubuntu Advantage contract changes."]
        error_lines.extend(["- {}".format(error) for error in user_errors])
        raise exceptions.UserFacingError("\n".join(error_lines))