Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(253)

Side by Side Diff: appengine/swarming/swarming_bot/bot_code/bot_auth.py

Issue 2958853002: Propagate name of system service account to the bot. (Closed)
Patch Set: Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2016 The LUCI Authors. All rights reserved. 1 # Copyright 2016 The LUCI Authors. All rights reserved.
2 # Use of this source code is governed under the Apache License, Version 2.0 2 # Use of this source code is governed under the Apache License, Version 2.0
3 # that can be found in the LICENSE file. 3 # that can be found in the LICENSE file.
4 4
5 import collections 5 import collections
6 import logging 6 import logging
7 import threading 7 import threading
8 import time 8 import time
9 9
10 from utils import auth_server 10 from utils import auth_server
11 11
12 import file_reader 12 import file_reader
13 13
14 14
15 class AuthSystemError(Exception): 15 class AuthSystemError(Exception):
16 """Fatal errors raised by AuthSystem class.""" 16 """Fatal errors raised by AuthSystem class."""
17 17
18 18
19 # Parsed value of JSON at path specified by --auth-params-file task_runner arg. 19 # Parsed value of JSON at path specified by --auth-params-file task_runner arg.
20 AuthParams = collections.namedtuple('AuthParams', [ 20 AuthParams = collections.namedtuple('AuthParams', [
21 # Dict with HTTP headers to use when calling Swarming backend (specifically). 21 # Dict with HTTP headers to use when calling Swarming backend (specifically).
22 # They identify the bot to the Swarming backend. Ultimately generated by 22 # They identify the bot to the Swarming backend. Ultimately generated by
23 # 'get_authentication_headers' in bot_config.py. 23 # 'get_authentication_headers' in bot_config.py.
24 'swarming_http_headers', 24 'swarming_http_headers',
25 25
26 # Unix timestamp of when swarming_http_headers expire, or 0 if unknown. 26 # Unix timestamp of when swarming_http_headers expire, or 0 if unknown.
27 'swarming_http_headers_exp', 27 'swarming_http_headers_exp',
28 28
29 # Indicates the service account the task runs as. One of: 29 # Indicates the service account to use for internal bot processes. One of:
30 # - 'none' if the task shouldn't use any authentication at all. 30 # - 'none' to not use authentication at all.
31 # - 'bot' if the task should use bot's own service account. 31 # - 'bot' to use whatever bot is using to authenticate itself to Swarming.
32 # - <email> if the task is using service acccount via delegation token. 32 # - <email> to get tokens through API calls to Swarming.
33 'system_service_account',
34
35 # Indicates the service account the task runs as. Same range of values as for
36 # 'system_service_account'.
37 #
38 # It is distinct from 'system_service_account' to allow user-supplied payloads
39 # to use a service account also supplied by the user (and not the one used
40 # internally by the bot).
33 'task_service_account', 41 'task_service_account',
34 ]) 42 ])
35 43
36 44
37 def prepare_auth_params_json(bot, manifest): 45 def prepare_auth_params_json(bot, manifest):
38 """Returns a dict to put into JSON file passed to task_runner. 46 """Returns a dict to put into JSON file passed to task_runner.
39 47
40 This JSON file contains various tokens and configuration parameters that allow 48 This JSON file contains various tokens and configuration parameters that allow
41 task_runner to make HTTP calls authenticated by bot's own credentials. 49 task_runner to make HTTP calls authenticated by bot's own credentials.
42 50
43 The file is managed by bot_main.py (main Swarming bot process) and consumed by 51 The file is managed by bot_main.py (main Swarming bot process) and consumed by
44 task_runner.py. 52 task_runner.py.
45 53
46 It lives it the task work directory. 54 It lives it the task work directory.
47 55
48 Args: 56 Args:
49 bot: instance of bot.Bot. 57 bot: instance of bot.Bot.
50 manifest: dict with the task manifest, as generated by the backend in /poll. 58 manifest: dict with the task manifest, as generated by the backend in /poll.
51 """ 59 """
60 def account(acc_id):
61 acc = (manifest.get('service_accounts') or {}).get(acc_id) or {}
62 return acc.get('service_account') or 'none'
52 return { 63 return {
53 'swarming_http_headers': bot.remote.get_authentication_headers(), 64 'swarming_http_headers': bot.remote.get_authentication_headers(),
54 'swarming_http_headers_exp': bot.remote.authentication_headers_expiration, 65 'swarming_http_headers_exp': bot.remote.authentication_headers_expiration,
55 'task_service_account': manifest.get('service_account') or 'none', 66 'system_service_account': account('system'),
67 'task_service_account': account('task'),
56 } 68 }
57 69
58 70
59 def process_auth_params_json(val): 71 def process_auth_params_json(val):
60 """Takes a dict loaded from auth params JSON file and validates it. 72 """Takes a dict loaded from auth params JSON file and validates it.
61 73
62 Args: 74 Args:
63 val: decoded JSON value read from auth params JSON file. 75 val: decoded JSON value read from auth params JSON file.
64 76
65 Returns: 77 Returns:
(...skipping 13 matching lines...) Expand all
79 exp = val.get('swarming_http_headers_exp') or 0 91 exp = val.get('swarming_http_headers_exp') or 0
80 if not isinstance(exp, (int, long)): 92 if not isinstance(exp, (int, long)):
81 raise ValueError( 93 raise ValueError(
82 'Expecting "swarming_http_headers_exp" to be int, got %r' % (exp,)) 94 'Expecting "swarming_http_headers_exp" to be int, got %r' % (exp,))
83 95
84 # The headers must be ASCII for sure, so don't bother with picking the 96 # The headers must be ASCII for sure, so don't bother with picking the
85 # correct unicode encoding, default would work. If not, it'll raise 97 # correct unicode encoding, default would work. If not, it'll raise
86 # UnicodeEncodeError, which is subclass of ValueError. 98 # UnicodeEncodeError, which is subclass of ValueError.
87 headers = {str(k): str(v) for k, v in headers.iteritems()} 99 headers = {str(k): str(v) for k, v in headers.iteritems()}
88 100
89 acc = val.get('task_service_account') or 'none' 101 def read_account(key):
90 if not isinstance(acc, basestring): 102 acc = val.get(key) or 'none'
91 raise ValueError( 103 if not isinstance(acc, basestring):
92 'Expecting "task_service_account" to be a string, got %r' % (acc,)) 104 raise ValueError('Expecting "%s" to be a string, got %r' % (key, acc))
105 return str(acc)
93 106
94 return AuthParams(headers, exp, str(acc)) 107 return AuthParams(
108 swarming_http_headers=headers,
109 swarming_http_headers_exp=exp,
110 system_service_account=read_account('system_service_account'),
111 task_service_account=read_account('task_service_account'))
95 112
96 113
97 class AuthSystem(object): 114 class AuthSystem(object):
98 """Authentication subsystem used by task_runner. 115 """Authentication subsystem used by task_runner.
99 116
100 Contains two threads: 117 Contains two threads:
101 * One thread periodically rereads the file with bots own authentication 118 * One thread periodically rereads the file with bots own authentication
102 information (auth_params_file). This file is generated by bot_main. 119 information (auth_params_file). This file is generated by bot_main.
103 * Another thread hosts local HTTP server that servers authentication tokens 120 * Another thread hosts local HTTP server that servers authentication tokens
104 to local processes. This is enabled only if the task is running in a 121 to local processes. This is enabled only if the task is running in a
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after
147 except file_reader.FatalReadError as e: 164 except file_reader.FatalReadError as e:
148 raise AuthSystemError('Cannot start FileReaderThread: %s' % e) 165 raise AuthSystemError('Cannot start FileReaderThread: %s' % e)
149 166
150 # Initial validation. 167 # Initial validation.
151 try: 168 try:
152 params = process_auth_params_json(reader.last_value) 169 params = process_auth_params_json(reader.last_value)
153 except ValueError as e: 170 except ValueError as e:
154 reader.stop() 171 reader.stop()
155 raise AuthSystemError('Cannot parse bot_auth_params.json: %s' % e) 172 raise AuthSystemError('Cannot parse bot_auth_params.json: %s' % e)
156 173
157 # If using task auth, launch local HTTP server that serves tokens (let OS 174 logging.info('Using following service accounts:')
158 # assign the port). 175 logging.info(' system: %s', params.system_service_account)
176 logging.info(' task: %s', params.task_service_account)
177
178 # If using service accounts, launch local HTTP server that serves tokens
179 # (let OS assign the port).
180 #
181 # TODO(vadimsh): Launch local auth server if using 'system' account (or both
182 # 'system' and 'task') too. This can be done only once all processes that
183 # inherit LUCI_CONTEXT know about 'system' and 'task' accounts distinction.
159 server = None 184 server = None
160 local_auth_context = None 185 local_auth_context = None
161 if params.task_service_account != 'none': 186 if params.task_service_account != 'none':
162 try: 187 try:
163 server = auth_server.LocalAuthServer() 188 server = auth_server.LocalAuthServer()
164 local_auth_context = server.start(token_provider=self) 189 local_auth_context = server.start(token_provider=self)
165 except Exception as exc: 190 except Exception as exc:
166 reader.stop() # cleanup 191 reader.stop() # cleanup
167 raise AuthSystemError('Failed to start local auth server - %s' % exc) 192 raise AuthSystemError('Failed to start local auth server - %s' % exc)
168 193
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
270 # Default to some safe small expiration in case bot_main doesn't report it 295 # Default to some safe small expiration in case bot_main doesn't report it
271 # to us. This may happen if get_authentication_header bot hook is not 296 # to us. This may happen if get_authentication_header bot hook is not
272 # reporting expiration time. 297 # reporting expiration time.
273 exp = auth_params.swarming_http_headers_exp or (time.time() + 4*60) 298 exp = auth_params.swarming_http_headers_exp or (time.time() + 4*60)
274 logging.info('Bot token expires in %d sec', exp - time.time()) 299 logging.info('Bot token expires in %d sec', exp - time.time())
275 300
276 # TODO(vadimsh): For GCE bots specifically we can pass a list of OAuth 301 # TODO(vadimsh): For GCE bots specifically we can pass a list of OAuth
277 # scopes granted to the GCE token and verify it contains all the requested 302 # scopes granted to the GCE token and verify it contains all the requested
278 # scopes. 303 # scopes.
279 return auth_server.AccessToken(tok, exp) 304 return auth_server.AccessToken(tok, exp)
OLDNEW
« no previous file with comments | « appengine/swarming/handlers_bot_test.py ('k') | appengine/swarming/swarming_bot/bot_code/bot_auth_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698