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

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: auth: Keep audit log of all generates delegation tokens. 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())
84 except (TypeError, ValueError) as exc: 87 except (TypeError, ValueError) as exc:
85 self.abort_with_error(400, text=str(exc)) 88 self.abort_with_error(400, text=str(exc))
86 89
87 # Fill in defaults. 90 # Fill in defaults.
88 assert not subtoken.impersonator_id 91 assert not subtoken.impersonator_id
89 user_id = auth.get_current_identity().to_bytes() 92 user_id = auth.get_current_identity().to_bytes()
90 if not subtoken.issuer_id: 93 if not subtoken.issuer_id:
91 subtoken.issuer_id = user_id 94 subtoken.issuer_id = user_id
92 if subtoken.issuer_id != user_id: 95 if subtoken.issuer_id != user_id:
93 subtoken.impersonator_id = user_id 96 subtoken.impersonator_id = user_id
94 subtoken.creation_time = int(utils.time_time()) 97 subtoken.creation_time = int(utils.time_time())
95 if not subtoken.validity_duration: 98 if not subtoken.validity_duration:
96 subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC 99 subtoken.validity_duration = DEF_VALIDITY_DURATION_SEC
97 if not subtoken.services or '*' in subtoken.services: 100 if not subtoken.services or '*' in subtoken.services:
98 subtoken.services[:] = get_default_allowed_services(user_id) 101 subtoken.services[:] = get_default_allowed_services(user_id)
99 102
100 # Check ACL (raises auth.AuthorizationError on errors). 103 # Check ACL (raises auth.AuthorizationError on errors).
101 check_can_create_token(user_id, subtoken) 104 rule = check_can_create_token(user_id, subtoken)
105
106 # Register the token in the datastore, generate its ID.
107 subtoken.subtoken_id = register_subtoken(subtoken, rule, auth.get_peer_ip())
102 108
103 # Create and sign the token. 109 # Create and sign the token.
104 try: 110 try:
105 token = delegation.serialize_token( 111 token = delegation.serialize_token(
106 delegation.seal_token( 112 delegation.seal_token(
107 delegation_pb2.SubtokenList(subtokens=[subtoken]))) 113 delegation_pb2.SubtokenList(subtokens=[subtoken])))
108 except delegation.BadTokenError as exc: 114 except delegation.BadTokenError as exc:
109 # This happens if resulting token is too large. 115 # This happens if resulting token is too large.
110 self.abort_with_error(400, text=str(exc)) 116 self.abort_with_error(400, text=str(exc))
111 117
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
176 try: 182 try:
177 auth.Identity.from_bytes(imp) 183 auth.Identity.from_bytes(imp)
178 except ValueError as exc: 184 except ValueError as exc:
179 raise ValueError( 185 raise ValueError(
180 'Invalid identity name "%s" in "impersonate": %s' % (imp, exc)) 186 'Invalid identity name "%s" in "impersonate": %s' % (imp, exc))
181 msg.issuer_id = str(imp) 187 msg.issuer_id = str(imp)
182 188
183 return msg 189 return msg
184 190
185 191
192 ################################################################################
193
194
186 # Fallback rule returned if nothing else matches. 195 # Fallback rule returned if nothing else matches.
187 DEFAULT_RULE = config_pb2.DelegationConfig.Rule( 196 DEFAULT_RULE = config_pb2.DelegationConfig.Rule(
188 user_id=['*'], 197 user_id=['*'],
189 target_service=['*'], 198 target_service=['*'],
190 max_validity_duration=MAX_VALIDITY_DURATION_SEC) 199 max_validity_duration=MAX_VALIDITY_DURATION_SEC)
191 200
192 201
193 def get_delegation_rule(user_id, services): 202 def get_delegation_rule(user_id, services):
194 """Returns first matching rule from delegation.cfg DelegationConfig rules. 203 """Returns first matching rule from delegation.cfg DelegationConfig rules.
195 204
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
236 return False 245 return False
237 246
238 247
239 def check_can_create_token(user_id, subtoken): 248 def check_can_create_token(user_id, subtoken):
240 """Checks that caller is allowed to mint a given root token. 249 """Checks that caller is allowed to mint a given root token.
241 250
242 Args: 251 Args:
243 user_id: identity string of a current caller. 252 user_id: identity string of a current caller.
244 subtoken: instance of delegation_pb2.Subtoken describing root token. 253 subtoken: instance of delegation_pb2.Subtoken describing root token.
245 254
255 Returns:
256 config_pb2.DelegationConfig.Rule that allows the operation.
257
246 Raises: 258 Raises:
247 auth.AuthorizationError if such token is not allowed for the caller. 259 auth.AuthorizationError if such token is not allowed for the caller.
248 """ 260 """
249 rule = get_delegation_rule(user_id, subtoken.services) 261 rule = get_delegation_rule(user_id, subtoken.services)
250 262
251 if subtoken.validity_duration > rule.max_validity_duration: 263 if subtoken.validity_duration > rule.max_validity_duration:
252 raise auth.AuthorizationError( 264 raise auth.AuthorizationError(
253 'Maximum allowed validity_duration is %d sec, %d requested.' % 265 'Maximum allowed validity_duration is %d sec, %d requested.' %
254 (rule.max_validity_duration, subtoken.validity_duration)) 266 (rule.max_validity_duration, subtoken.validity_duration))
255 267
256 # Just delegating one's own identity (not impersonating someone else)? Allow. 268 # Just delegating one's own identity (not impersonating someone else)? Allow.
257 if subtoken.issuer_id == user_id: 269 if subtoken.issuer_id == user_id:
258 return 270 return rule
259 271
260 # Verify it's OK to impersonate a given user. 272 # Verify it's OK to impersonate a given user.
261 impersonated = auth.Identity.from_bytes(subtoken.issuer_id) 273 impersonated = auth.Identity.from_bytes(subtoken.issuer_id)
262 for principal_set in rule.allowed_to_impersonate: 274 for principal_set in rule.allowed_to_impersonate:
263 if is_identity_in_principal_set(impersonated, principal_set): 275 if is_identity_in_principal_set(impersonated, principal_set):
264 return 276 return rule
265 277
266 raise auth.AuthorizationError( 278 raise auth.AuthorizationError(
267 '"%s" is not allowed to impersonate "%s" on %s' % 279 '"%s" is not allowed to impersonate "%s" on %s' %
268 (user_id, subtoken.issuer_id, subtoken.services or ['*'])) 280 (user_id, subtoken.issuer_id, subtoken.services or ['*']))
269 281
270 282
271 def get_default_allowed_services(user_id): 283 def get_default_allowed_services(user_id):
272 """Returns the list of services defined by a first matching rule. 284 """Returns the list of services defined by a first matching rule.
273 285
274 Args: 286 Args:
275 user_id: identity string of a current caller. 287 user_id: identity string of a current caller.
276 """ 288 """
277 rule = get_delegation_rule(user_id, ['*']) 289 rule = get_delegation_rule(user_id, ['*'])
278 return rule.target_service 290 return rule.target_service
279 291
292
293 ################################################################################
294
295
296 class AuthDelegationSubtoken(ndb.Model):
297 """Represents a delegation subtoken.
298
299 Used to track what tokens are issued. Root entity. ID is autogenerated.
300 """
301 # Serialized delegation_pb2.Subtoken proto.
302 subtoken = ndb.BlobProperty()
303 # Serialized config_pb2.DelegationConfig.Rule that allowed this token.
304 rule = ndb.BlobProperty()
305 # IP address the minting request came from.
306 caller_ip = ndb.StringProperty()
307 # Version of the auth_service that created the subtoken.
308 auth_service_version = ndb.StringProperty()
309
310 # Fields below are extracted from 'subtoken', for indexing purposes.
311
312 # Whose authority the token conveys.
313 issuer_id = ndb.StringProperty()
314 # When the token was created.
315 creation_time = ndb.DateTimeProperty()
316 # List of services that accept the token (or ['*'] if all).
317 services = ndb.StringProperty(repeated=True)
318 # Who initiated the minting request if it is an impersonation token.
319 impersonator_id = ndb.StringProperty()
320
321
322 def register_subtoken(subtoken, rule, caller_ip):
323 """Creates new AuthDelegationSubtoken entity in the datastore, returns its ID.
324
325 Args:
326 subtoken: delegation_pb2.Subtoken describing the token.
327 rule: config_pb2.DelegationConfig.Rule that allows the operation
328 caller_ip: ipaddr.IP of the caller.
329
330 Returns:
331 int64 with ID of the new entity.
332 """
333 entity = AuthDelegationSubtoken(
334 subtoken=subtoken.SerializeToString(),
335 rule=rule.SerializeToString(),
336 caller_ip=ipaddr.ip_to_string(caller_ip),
337 auth_service_version=utils.get_app_version(),
338 issuer_id=subtoken.issuer_id,
339 creation_time=utils.timestamp_to_datetime(subtoken.creation_time*1e6),
340 services=list(subtoken.services or ['*']),
341 impersonator_id=subtoken.impersonator_id)
342 entity.put(use_cache=False, use_memcache=False)
343 subtoken_id = entity.key.integer_id()
344
345 # Keep a logging entry (extractable via BigQuery) too.
346 logging.info(
347 'subtoken: subtoken_id=%d caller_ip=%s issuer_id=%s impersonator_id=%s',
348 subtoken_id, entity.caller_ip, entity.issuer_id, entity.impersonator_id)
349
350 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