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

Side by Side Diff: appengine/auth_service/delegation.py

Issue 2164733003: auth: Keep audit log of all generated delegation tokens. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/luci-py@master
Patch Set: index services too Created 4 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
« no previous file with comments | « appengine/auth_service/config_test.py ('k') | appengine/auth_service/delegation_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2015 The LUCI Authors. All rights reserved. 1 # Copyright 2015 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 """API handler to mint delegation tokens.""" 5 """API handler to mint delegation tokens."""
6 6
7 import logging 7 import logging
8 import webapp2 8 import webapp2
9 9
10 from google.appengine.ext import ndb
11
10 from components import auth 12 from components import auth
11 from components import utils 13 from components import utils
12 14
13 from components.auth import delegation 15 from components.auth import delegation
16 from components.auth import ipaddr
14 from components.auth.proto import delegation_pb2 17 from components.auth.proto import delegation_pb2
15 18
16 from proto import config_pb2 19 from proto import config_pb2
17 20
18 import config 21 import config
19 22
20 23
21 # Minimum accepted value for 'validity_duration'. 24 # Minimum accepted value for 'validity_duration'.
22 MIN_VALIDITY_DURATION_SEC = 30 25 MIN_VALIDITY_DURATION_SEC = 30
23 26
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
71 @auth.require(lambda: not auth.get_current_identity().is_anonymous) 74 @auth.require(lambda: not auth.get_current_identity().is_anonymous)
72 def post(self): 75 def post(self):
73 # Forbid usage of delegation tokens for this particular call. Using 76 # Forbid usage of delegation tokens for this particular call. Using
74 # delegation when creating delegation tokens is too deep. Redelegation will 77 # delegation when creating delegation tokens is too deep. Redelegation will
75 # be done as separate explicit API call that accept existing delegation 78 # be done as separate explicit API call that accept existing delegation
76 # token via request body, not via headers. 79 # token via request body, not via headers.
77 if auth.get_current_identity() != auth.get_peer_identity(): 80 if auth.get_current_identity() != auth.get_peer_identity():
78 raise auth.AuthorizationError( 81 raise auth.AuthorizationError(
79 'This API call must not be used with active delegation token') 82 'This API call must not be used with active delegation token')
80 83
81 # Convert request body to proto (with validation). 84 # Convert request body to proto (with validation). Verify IP format.
82 try: 85 try:
83 subtoken = subtoken_from_jsonish(self.parse_body()) 86 subtoken = subtoken_from_jsonish(self.parse_body())
87 caller_ip = ipaddr.ip_from_string(self.request.remote_addr)
nodir 2016/07/20 18:04:22 ip is not provided by the user, so perhaps it shou
Vadim Sh. 2016/07/20 20:07:07 Done. Replaced with get_peer_ip, it is already val
84 except (TypeError, ValueError) as exc: 88 except (TypeError, ValueError) as exc:
85 self.abort_with_error(400, text=str(exc)) 89 self.abort_with_error(400, text=str(exc))
86 90
87 # Fill in defaults. 91 # Fill in defaults.
88 assert not subtoken.impersonator_id 92 assert not subtoken.impersonator_id
89 user_id = auth.get_current_identity().to_bytes() 93 user_id = auth.get_current_identity().to_bytes()
90 if not subtoken.issuer_id: 94 if not subtoken.issuer_id:
91 subtoken.issuer_id = user_id 95 subtoken.issuer_id = user_id
92 if subtoken.issuer_id != user_id: 96 if subtoken.issuer_id != user_id:
93 subtoken.impersonator_id = user_id 97 subtoken.impersonator_id = user_id
94 subtoken.creation_time = int(utils.time_time()) 98 subtoken.creation_time = int(utils.time_time())
95 if not subtoken.validity_duration: 99 if not subtoken.validity_duration:
96 subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC 100 subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC
97 if not subtoken.services or '*' in subtoken.services: 101 if not subtoken.services or '*' in subtoken.services:
98 subtoken.services[:] = get_default_allowed_services(user_id) 102 subtoken.services[:] = get_default_allowed_services(user_id)
99 103
100 # Check ACL (raises auth.AuthorizationError on errors). 104 # Check ACL (raises auth.AuthorizationError on errors).
101 check_can_create_token(user_id, subtoken) 105 rule = check_can_create_token(user_id, subtoken)
106
107 # Register the token in the datastore, generate its ID.
108 subtoken.subtoken_id = register_subtoken(subtoken, rule, caller_ip)
102 109
103 # Create and sign the token. 110 # Create and sign the token.
104 try: 111 try:
105 token = delegation.serialize_token( 112 token = delegation.serialize_token(
106 delegation.seal_token( 113 delegation.seal_token(
107 delegation_pb2.SubtokenList(subtokens=[subtoken]))) 114 delegation_pb2.SubtokenList(subtokens=[subtoken])))
108 except delegation.BadTokenError as exc: 115 except delegation.BadTokenError as exc:
109 # This happens if resulting token is too large. 116 # This happens if resulting token is too large.
110 self.abort_with_error(400, text=str(exc)) 117 self.abort_with_error(400, text=str(exc))
111 118
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
176 try: 183 try:
177 auth.Identity.from_bytes(imp) 184 auth.Identity.from_bytes(imp)
178 except ValueError as exc: 185 except ValueError as exc:
179 raise ValueError( 186 raise ValueError(
180 'Invalid identity name "%s" in "impersonate": %s' % (imp, exc)) 187 'Invalid identity name "%s" in "impersonate": %s' % (imp, exc))
181 msg.issuer_id = str(imp) 188 msg.issuer_id = str(imp)
182 189
183 return msg 190 return msg
184 191
185 192
193 ################################################################################
194
195
186 # Fallback rule returned if nothing else matches. 196 # Fallback rule returned if nothing else matches.
187 DEFAULT_RULE = config_pb2.DelegationConfig.Rule( 197 DEFAULT_RULE = config_pb2.DelegationConfig.Rule(
188 user_id=['*'], 198 user_id=['*'],
189 target_service=['*'], 199 target_service=['*'],
190 max_validity_duration=MAX_VALIDITY_DURATION_SEC) 200 max_validity_duration=MAX_VALIDITY_DURATION_SEC)
191 201
192 202
193 def get_delegation_rule(user_id, services): 203 def get_delegation_rule(user_id, services):
194 """Returns first matching rule from delegation.cfg DelegationConfig rules. 204 """Returns first matching rule from delegation.cfg DelegationConfig rules.
195 205
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 return False 246 return False
237 247
238 248
239 def check_can_create_token(user_id, subtoken): 249 def check_can_create_token(user_id, subtoken):
240 """Checks that caller is allowed to mint a given root token. 250 """Checks that caller is allowed to mint a given root token.
241 251
242 Args: 252 Args:
243 user_id: identity string of a current caller. 253 user_id: identity string of a current caller.
244 subtoken: instance of delegation_pb2.Subtoken describing root token. 254 subtoken: instance of delegation_pb2.Subtoken describing root token.
245 255
256 Returns:
257 config_pb2.DelegationConfig.Rule that allows the operation.
258
246 Raises: 259 Raises:
247 auth.AuthorizationError if such token is not allowed for the caller. 260 auth.AuthorizationError if such token is not allowed for the caller.
248 """ 261 """
249 rule = get_delegation_rule(user_id, subtoken.services) 262 rule = get_delegation_rule(user_id, subtoken.services)
250 263
251 if subtoken.validity_duration > rule.max_validity_duration: 264 if subtoken.validity_duration > rule.max_validity_duration:
252 raise auth.AuthorizationError( 265 raise auth.AuthorizationError(
253 'Maximum allowed validity_duration is %d sec, %d requested.' % 266 'Maximum allowed validity_duration is %d sec, %d requested.' %
254 (rule.max_validity_duration, subtoken.validity_duration)) 267 (rule.max_validity_duration, subtoken.validity_duration))
255 268
256 # Just delegating one's own identity (not impersonating someone else)? Allow. 269 # Just delegating one's own identity (not impersonating someone else)? Allow.
257 if subtoken.issuer_id == user_id: 270 if subtoken.issuer_id == user_id:
258 return 271 return rule
259 272
260 # Verify it's OK to impersonate a given user. 273 # Verify it's OK to impersonate a given user.
261 impersonated = auth.Identity.from_bytes(subtoken.issuer_id) 274 impersonated = auth.Identity.from_bytes(subtoken.issuer_id)
262 for principal_set in rule.allowed_to_impersonate: 275 for principal_set in rule.allowed_to_impersonate:
263 if is_identity_in_principal_set(impersonated, principal_set): 276 if is_identity_in_principal_set(impersonated, principal_set):
264 return 277 return rule
265 278
266 raise auth.AuthorizationError( 279 raise auth.AuthorizationError(
267 '"%s" is not allowed to impersonate "%s" on %s' % 280 '"%s" is not allowed to impersonate "%s" on %s' %
268 (user_id, subtoken.issuer_id, subtoken.services or ['*'])) 281 (user_id, subtoken.issuer_id, subtoken.services or ['*']))
269 282
270 283
271 def get_default_allowed_services(user_id): 284 def get_default_allowed_services(user_id):
272 """Returns the list of services defined by a first matching rule. 285 """Returns the list of services defined by a first matching rule.
273 286
274 Args: 287 Args:
275 user_id: identity string of a current caller. 288 user_id: identity string of a current caller.
276 """ 289 """
277 rule = get_delegation_rule(user_id, ['*']) 290 rule = get_delegation_rule(user_id, ['*'])
278 return rule.target_service 291 return rule.target_service
279 292
293
294 ################################################################################
295
296
297 class AuthDelegationSubtoken(ndb.Model):
298 """Represents a delegation subtoken.
299
300 Used to track what tokens are issued. Root entity. ID is autogenerated.
301 """
302 # Serialized delegation_pb2.Subtoken proto.
303 subtoken = ndb.BlobProperty()
304 # Serialized config_pb2.DelegationConfig.Rule that allowed this token.
305 rule = ndb.BlobProperty()
306 # IP address the minting request came from.
307 caller_ip = ndb.StringProperty()
308 # Version of the auth_service that created the subtoken.
309 auth_service_version = ndb.StringProperty()
310
311 # Fields below are extracted from 'subtoken', for indexing purposes.
312
313 # Whose authority the token conveys.
314 issuer_id = ndb.StringProperty()
315 # When the token was created.
316 creation_time = ndb.DateTimeProperty()
317 # List of service that accept the token (or empty list if all).
nodir 2016/07/20 18:04:22 services
Vadim Sh. 2016/07/20 20:07:06 Done.
318 services = ndb.StringProperty(repeated=True)
319 # Who initiated the minting request if it is an impersonation token.
320 impersonator_id = ndb.StringProperty()
321
322
323 def register_subtoken(subtoken, rule, caller_ip):
324 """Creates new AuthDelegationSubtoken entity in the datastore, returns its ID.
325
326 Args:
327 subtoken: delegation_pb2.Subtoken describing the token.
328 rule: config_pb2.DelegationConfig.Rule that allows the operation
329 caller_ip: ipaddr.IP of the caller.
330
331 Returns:
332 int64 with ID of the new entity.
333 """
334 entity = AuthDelegationSubtoken(
335 subtoken=subtoken.SerializeToString(),
336 rule=rule.SerializeToString(),
337 caller_ip=ipaddr.ip_to_string(caller_ip),
338 auth_service_version=utils.get_app_version(),
339 issuer_id=subtoken.issuer_id,
340 creation_time=utils.timestamp_to_datetime(subtoken.creation_time*1e6),
nodir 2016/07/20 18:04:22 using datetime.fromtimestamp is probably more stra
Vadim Sh. 2016/07/20 20:07:06 It returns local time, not UTC.
341 services=list(subtoken.services or []),
342 impersonator_id=subtoken.impersonator_id)
343 entity.put(use_cache=False, use_memcache=False)
344 subtoken_id = entity.key.integer_id()
345
346 # Keep a logging entry (extractable via BigQuery) too.
347 logging.info(
348 'subtoken: subtoken_id=%d caller_ip=%s issuer_id=%s impersonator_id=%s',
349 subtoken_id, entity.caller_ip, entity.issuer_id, entity.impersonator_id)
350
351 return subtoken_id
OLDNEW
« no previous file with comments | « appengine/auth_service/config_test.py ('k') | appengine/auth_service/delegation_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698