Beispiel #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)
Beispiel #2
0
  def _grab_bot_token(self, auth_params):
    """Extracts OAuth token from 'Authorization' header used by the bot itself.

    This works only for bots that use OAuth for authentication (e.g. GCE bots).
    Also it totally ignores scopes. It relies on bot_main to keep the bot OAuth
    token sufficiently fresh. See remote_client.AUTH_HEADERS_EXPIRATION_SEC.

    Args:
      auth_params: AuthParams tuple with configuration.

    Returns:
      auth_server.AccessToken.
    """
    bot_auth_hdr = auth_params.swarming_http_headers.get('Authorization') or ''
    if not bot_auth_hdr.startswith('Bearer '):
      raise auth_server.TokenError(2, 'The bot is not using OAuth')
    tok = bot_auth_hdr[len('Bearer '):]

    # Default to some safe small expiration in case bot_main doesn't report it
    # to us. This may happen if get_authentication_header bot hook is not
    # reporting expiration time.
    exp = auth_params.swarming_http_headers_exp or (time.time() + 4*60)

    # TODO(vadimsh): For GCE bots specifically we can pass a list of OAuth
    # scopes granted to the GCE token and verify it contains all the requested
    # scopes.
    return auth_server.AccessToken(tok, exp)
Beispiel #3
0
    def _grab_bot_oauth_token(self, auth_params):
        # Piggyback on the bot own credentials for now. This works only for bots
        # that use OAuth for authentication (e.g. GCE bots). Also it totally ignores
        # scopes or expiration time. It relies on bot_main to keep the bot OAuth
        # token sufficiently fresh. See remote_client.AUTH_HEADERS_EXPIRATION_SEC.
        bot_auth_hdr = auth_params.swarming_http_headers.get(
            'Authorization') or ''
        if not bot_auth_hdr.startswith('Bearer '):
            raise auth_server.TokenError(2,
                                         'The bot is not using OAuth',
                                         fatal=True)
        tok = bot_auth_hdr[len('Bearer '):]

        # bot_main guarantees swarming_http_headers are usable for at least 6 min.
        # (see AUTH_HEADERS_EXPIRATION_SEC). task_runner grabs these headers from
        # bot_main asynchronously with some delay. To account for that delay make
        # expiration time shorter (4 min instead of 6 min).
        #
        # TODO(vadimsh): The real token expiration time can be passed from
        # bot_main.py via --auth-params-file mechanism (same way as
        # 'swarming_http_headers' are passed).
        #
        # TODO(vadimsh): For GCE bots specifically we can pass a list of OAuth
        # scopes granted to the GCE token and verify it contains all the requested
        # scopes.
        return auth_server.AccessToken(tok, int(time.time()) + 4 * 60)
Beispiel #4
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')
Beispiel #5
0
 def token_gen(_account_id, _scopes):
     calls.append(1)
     raise auth_server.TokenError(123, 'error message')
Beispiel #6
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
Beispiel #7
0
 def token_gen(_scopes):
     raise auth_server.TokenError(code, 'error message', fatal=fatal)