예제 #1
0
    def _grab_token_via_rpc(self, auth_params, rpc_client, account_id, scopes):
        """Makes RPC to Swarming to mint a token.

    Args:
      auth_params: AuthParams tuple with configuration.
      rpc_client: instance of remote_client.RemoteClient to use for RPC.
      account_id: logical account name (e.g 'system' or 'task').
      scopes: list of OAuth scopes.

    Returns:
      (auth_server.AccessToken, <service account email>).
    """
        # Clients of AuthSystem can bombard it with parallel requests for the exact
        # same OAuth token (imagine a task launching N subprocesses in parallel,
        # each one requesting a token). We stop this concurrency here with a battery
        # of locks keyed by (account_id, sorted(scopes)). So the Swarming will see
        # only one request at a time.
        with self._rpc_locks.lock(key=(account_id, tuple(sorted(scopes)))):
            try:
                # WARNING: This call may indirectly call 'get_bot_headers', so we have
                # to use a different lock here (not self._lock).
                resp = rpc_client.mint_oauth_token(task_id=auth_params.task_id,
                                                   bot_id=auth_params.bot_id,
                                                   account_id=account_id,
                                                   scopes=scopes)
            except remote_client.InternalError as exc:
                # Raising RPCError propagates transient error status to clients, so that
                # they can decide to retry later.
                raise auth_server.RPCError(500, str(exc))
            except remote_client.MintOAuthTokenError as exc:
                # Raising fatal TokenError makes LocalAuthServer cache the error, so
                # that retrying clients get the error reply right away, without hitting
                # Swarming server.
                raise auth_server.TokenError(4, str(exc))

        # This is <email>, 'bot' or 'none'. We should handle all cases, since
        # the configuration on the server can change while the bot is running
        # the task. This is bad, but possible.
        service_account = resp.get('service_account') or 'unknown'
        if service_account == 'none':
            raise auth_server.TokenError(
                1,
                'The task has no %r account associated with it' % account_id)
        if service_account == 'bot':
            return self._grab_bot_token(auth_params), 'bot'

        # Should have a real token here now. Double check and return. It will be
        # cached by LocalAuthServer until it expires.
        access_token = resp.get('access_token')
        expiry = resp.get('expiry')
        if not access_token or not isinstance(access_token, basestring):
            raise auth_server.RPCError(
                500, 'Bad server reply, no valid token given')
        if not expiry or not isinstance(expiry, (int, long)):
            raise auth_server.RPCError(
                500, 'Bad server reply, no token expiry given')

        # Normalize types (unicode -> str, long -> int).
        tok = auth_server.AccessToken(str(access_token), int(expiry))
        return tok, str(service_account)
예제 #2
0
    def generate_token(self, scopes):
        """Generates a new access token with given scopes.

    Called by LocalAuthServer from some internal thread whenever new token is
    needed. See TokenProvider for more details.

    Returns:
      AccessToken.

    Raises:
      RPCError, TokenError, AuthSystemError.
    """
        # Grab AuthParams supplied by the main bot process.
        with self._lock:
            if not self._auth_params_reader:
                raise auth_server.RPCError(503, 'Stopped already.')
            val = self._auth_params_reader.last_value
        params = process_auth_params_json(val)

        logging.info('Getting the token for "%s", scopes %s',
                     params.task_service_account, scopes)

        # This shouldn't happen, since the local HTTP server isn't actually
        # running in this case. But handle anyway, for completeness.
        if params.task_service_account == 'none':
            raise auth_server.TokenError(
                1, 'The task is not using service accounts')

        # This works only for bots that use OAuth for authentication (e.g. GCE
        # bots). It will raise TokenError if the bot is not using OAuth.
        if params.task_service_account == 'bot':
            return self._grab_bot_oauth_token(params)

        # Using some custom service account.
        # TODO(vadimsh): Implement. This would involve sending a request to Swarming
        # backend to generate the token.
        raise auth_server.TokenError(3, 'Not implemented yet')
예제 #3
0
  def generate_token(self, account_id, scopes):
    """Generates a new access token with given scopes.

    Called by LocalAuthServer from some internal thread whenever new token is
    needed. It happens infrequently, approximately once per hour per combination
    of scopes (when the previously cached token expires).

    See TokenProvider for more details.

    Args:
      account_id: logical account name (e.g 'system' or 'task').
      scopes: list of OAuth scopes.

    Returns:
      AccessToken.

    Raises:
      RPCError, TokenError, AuthSystemError.
    """
    # Grab AuthParams supplied by the main bot process.
    with self._lock:
      if not self._auth_params_reader:
        raise auth_server.RPCError(503, 'Stopped already.')
      val = self._auth_params_reader.last_value
      rpc_client = self._remote_client
    auth_params = process_auth_params_json(val)

    # Note: 'account_id' here is "task" or "system", it's checked below.
    logging.info('Getting %r token, scopes %r', account_id, scopes)

    # Grab service account email (or 'none'/'bot' placeholders) of requested
    # logical account. This is part of the task manifest.
    service_account = None
    if account_id == 'task':
      service_account = auth_params.task_service_account
    elif account_id == 'system':
      service_account = auth_params.system_service_account
    else:
      raise auth_server.RPCError(404, 'Unknown account %r' % account_id)

    # Note: correctly behaving clients aren't supposed to hit this, since they
    # should use only accounts specified in 'accounts' section of LUCI_CONTEXT.
    if service_account == 'none':
      raise auth_server.TokenError(
          1, 'The task has no %r account associated with it' % account_id)

    if service_account == 'bot':
      # This works only for bots that use OAuth for authentication (e.g. GCE
      # bots). It will raise TokenError if the bot is not using OAuth.
      tok = self._grab_bot_token(auth_params)
    else:
      # Ask Swarming server to generate a new token for us.
      if not rpc_client:
        raise auth_server.RPCError(500, 'No RPC client, can\'t fetch token')
      tok, service_account = self._grab_token_via_rpc(
          auth_params, rpc_client, account_id, scopes)

    logging.info(
        'Got %r token (belongs to %r), expires in %d sec',
        account_id, service_account, tok.expiry - time.time())
    return tok