def test_task_account_is_none(self): task_id = self.make_task_request(service_account='none', service_account_token=None) account, tok = service_accounts.get_task_account_token( task_id, 'bot-id', ['scope1', 'scope2']) self.assertEqual('none', account) self.assertIsNone(tok)
def test_ok_with_realm(self): now = datetime.datetime(2010, 1, 2, 3, 4, 5) self.mock_now(now) self.mock(auth, 'has_permission', lambda *_args, **_kwargs: True) # Initial attempt. task_id = self.make_task_request( service_account='*****@*****.**', realm='test:realm') expiry = now + datetime.timedelta(seconds=3600) self.mock_json_request( expected_url='https://tokens.example.com/prpc/' 'tokenserver.minter.TokenMinter/MintServiceAccountToken', expected_payload={ 'tokenKind': 1, 'serviceAccount': '*****@*****.**', 'realm': 'test:realm', 'oauthScope': ['scope1', 'scope2'], 'minValidityDuration': 300, 'auditTags': [ 'swarming:gae_request_id:7357B3D7091D', 'swarming:service_version:sample-app/v1a', 'swarming:bot_id:bot-id', 'swarming:task_id:' + task_id, 'swarming:task_name:Request with [email protected]', ], }, expected_project_id='test', response={ 'token': 'totally_real_token', 'serviceVersion': 'token-server-id/ver', 'expiry': expiry.isoformat() + 'Z', }) tok = service_accounts.AccessToken('totally_real_token', int(utils.time_time() + 3600)) self.assertEqual( ('*****@*****.**', tok), service_accounts.get_task_account_token(task_id, 'bot-id', ['scope1', 'scope2']))
def test_happy_path(self): now = datetime.datetime(2010, 1, 2, 3, 4, 5) self.mock_now(now) # Initial attempt and a retry. for try_number in (1, 2): task_id = self.make_task_request( service_account='*****@*****.**', service_account_token='mocked-oauth-token-grant', try_number=try_number) expiry = now + datetime.timedelta(seconds=3600) self.mock_json_request( expected_url='https://tokens.example.com/prpc/' 'tokenserver.minter.TokenMinter/MintOAuthTokenViaGrant', expected_payload={ 'grantToken': 'mocked-oauth-token-grant', 'oauthScope': ['scope1', 'scope2'], 'minValidityDuration': 300, 'auditTags': [ 'swarming:gae_request_id:7357B3D7091D', 'swarming:service_version:sample-app/v1a', 'swarming:bot_id:bot-id', 'swarming:task_id:' + task_id, 'swarming:task_name:Request with [email protected]', ], }, response={ 'accessToken': 'totally_real_token', 'serviceVersion': 'token-server-id/ver', 'expiry': expiry.isoformat() + 'Z', }) tok = service_accounts.AccessToken('totally_real_token', int(utils.time_time() + 3600)) self.assertEqual(('*****@*****.**', tok), service_accounts.get_task_account_token( task_id, 'bot-id', ['scope1', 'scope2']))
def post(self): request = self.parse_body() logging.debug('Request body: %s', request) msg = log_unexpected_subset_keys(self.ACCEPTED_KEYS, self.REQUIRED_KEYS, request, self.request, 'bot', 'keys') if msg: self.abort_with_error(400, error=msg) account_id = request['account_id'] bot_id = request['id'] scopes = request['scopes'] task_id = request.get('task_id') # Scopes should be a list of strings, always. if (not scopes or not isinstance(scopes, list) or not all(isinstance(s, basestring) for s in scopes)): self.abort_with_error(400, error='"scopes" must be a list of strings') # Only two flavors of accounts are supported. if account_id not in ('system', 'task'): self.abort_with_error( 400, error='Unknown "account_id", expecting "task" or "system"') # If using 'task' account, task_id is required. We'll double check the bot # still executes this task (based on data in datastore), and if so, will # use a service account associated with this particular task. if account_id == 'task' and not task_id: self.abort_with_error( 400, error='"task_id" is required when using "account_id" == "task"' ) # Need machine type associated with the bot for bots.cfg query below. # BotInfo also contains ID of a task the bot currently executes (to compare # with 'task_id' request parameter). machine_type = None current_task_id = None bot_info = bot_management.get_info_key(bot_id).get() if bot_info: machine_type = bot_info.machine_type current_task_id = bot_info.task_id # Make sure bot self-reported ID matches the authentication token. Raises # auth.AuthorizationError if not. Also fetches corresponding BotGroupConfig # that contains system service account email for this bot. bot_group_cfg = bot_auth.validate_bot_id_and_fetch_config( bot_id, machine_type) # At this point, the request is valid structurally, and the bot used proper # authentication when making it. logging.info('Requesting a "%s" token with scopes %s', account_id, scopes) # This is mostly a precaution against confused bot processes. We can always # just use 'current_task_id' to look up per-task service account. Datastore # is the source of truth here, not whatever bot reports. if account_id == 'task' and task_id != current_task_id: logging.error( 'Bot %s requested "task" access token for task %s, but runs %s', bot_id, task_id, current_task_id) self.abort_with_error( 400, error='Wrong task_id: the bot is not executing this task') account = None # an email or 'bot' or 'none' token = None # service_accounts.AccessToken try: if account_id == 'task': account, token = service_accounts.get_task_account_token( task_id, bot_id, scopes) elif account_id == 'system': account, token = service_accounts.get_system_account_token( bot_group_cfg.system_service_account, scopes) else: raise AssertionError('Impossible, there is a check above') except auth.AccessTokenError as exc: # Note: no need to log this, it is already logged at the source. Also # we cautiously do not return any error details to the bot, just in case # they may contain something we don't want to disclose. if exc.transient: self.abort_with_error( 500, error='Transient error when generating the token') self.abort_with_error( 403, error='Fatal error when generating the token, see server logs') # Note: the token info is already logged by service_accounts.get_*_token. if token: self.send_response({ 'service_account': account, 'access_token': token.access_token, 'expiry': token.expiry, }) else: assert account in ('bot', 'none'), account self.send_response({'service_account': account})
def test_missing_task_id(self): with self.assertRaises(auth.AccessTokenError): service_accounts.get_task_account_token( '382b353612985111', 'bot-id', ['scope1', 'scope2'])
def test_malformed_task_id(self): with self.assertRaises(auth.AccessTokenError): service_accounts.get_task_account_token( 'bad-task-id', 'bot-id', ['scope1', 'scope2'])
def test_missing_task_id(self): with self.assertRaises(service_accounts.MisconfigurationError): service_accounts.get_task_account_token('382b353612985111', 'bot-id', ['scope1', 'scope2'])
def test_malformed_task_id(self): with self.assertRaises(service_accounts.MisconfigurationError): service_accounts.get_task_account_token('bad-task-id', 'bot-id', ['scope1', 'scope2'])