def get_all(self, prefix=None, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False, **kwargs): """ List all keys. Handles requests: GET /keys/ """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE scope = get_datastore_full_scope(scope) requester_user = get_requester() user = user or requester_user is_all_scope = (scope == ALL_SCOPE) is_admin = request_user_is_admin(request=pecan.request) if is_all_scope and not is_admin: msg = '"all" scope requires administrator access' raise AccessDeniedError(message=msg, user_db=requester_user) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin) # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provided( request=pecan.request, user=user) from_model_kwargs = {'mask_secrets': not decrypt} kwargs['prefix'] = prefix if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) kwargs['scope'] = scope if scope == USER_SCOPE or scope == FULL_USER_SCOPE: # Make sure we only returned values scoped to current user if kwargs['prefix']: kwargs['prefix'] = get_key_reference(name=kwargs['prefix'], scope=scope, user=requester_user) else: kwargs['prefix'] = get_key_reference(name='', scope=scope, user=user) kvp_apis = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs, **kwargs) return kvp_apis
def get_all(self, requester_user, prefix=None, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False, sort=None, offset=0, limit=None, **raw_filters): """ List all keys. Handles requests: GET /keys/ """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) is_all_scope = (scope == ALL_SCOPE) is_admin = rbac_utils.user_is_admin(user_db=requester_user) if is_all_scope and not is_admin: msg = '"all" scope requires administrator access' raise AccessDeniedError(message=msg, user_db=requester_user) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin, requester_user=requester_user) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user) from_model_kwargs = {'mask_secrets': not decrypt} if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) raw_filters['scope'] = scope if scope == USER_SCOPE or scope == FULL_USER_SCOPE: # Make sure we only returned values scoped to current user if prefix: prefix = get_key_reference(name=prefix, scope=scope, user=user) else: prefix = get_key_reference(name='', scope=scope, user=user) raw_filters['prefix'] = prefix kvp_apis = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user) return kvp_apis
def get_all(self, requester_user, prefix=None, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False, sort=None, offset=0, limit=None, **raw_filters): """ List all keys. Handles requests: GET /keys/ """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) is_all_scope = (scope == ALL_SCOPE) is_admin = rbac_utils.user_is_admin(user_db=requester_user) if is_all_scope and not is_admin: msg = '"all" scope requires administrator access' raise AccessDeniedError(message=msg, user_db=requester_user) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin, requester_user=requester_user) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user) from_model_kwargs = {'mask_secrets': not decrypt} if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) raw_filters['scope'] = scope if scope == USER_SCOPE or scope == FULL_USER_SCOPE: # Make sure we only returned values scoped to current user if prefix: prefix = get_key_reference(name=prefix, scope=scope, user=user) else: prefix = get_key_reference(name='', scope=scope, user=user) raw_filters['prefix'] = prefix kvp_apis = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters) return kvp_apis
def setUp(self): super(KeyValuesControllerRBACTestCase, self).setUp() self.kvps = {} # Insert mock users user_1_db = UserDB(name='user1') user_1_db = User.add_or_update(user_1_db) self.users['user_1'] = user_1_db user_2_db = UserDB(name='user2') user_2_db = User.add_or_update(user_2_db) self.users['user_2'] = user_2_db # Insert mock kvp objects kvp_api = KeyValuePairSetAPI(name='test_system_scope', value='value1', scope=FULL_SYSTEM_SCOPE) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_1'] = kvp_db kvp_api = KeyValuePairSetAPI(name='test_system_scope_secret', value='value_secret', scope=FULL_SYSTEM_SCOPE, secret=True) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_2'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_1', user='******') kvp_db = KeyValuePairDB(name=name, value='valueu12', scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_3'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_2', user='******') kvp_api = KeyValuePairSetAPI(name=name, value='user_secret', scope=FULL_USER_SCOPE, secret=True) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_4'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_3', user='******') kvp_db = KeyValuePairDB(name=name, value='valueu21', scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_5'] = kvp_db self.system_scoped_items_count = 2 self.user_scoped_items_count = 3 self.user_scoped_items_per_user_count = { 'user1': 2, 'user2': 1 }
def setUp(self): super(KeyValuesControllerRBACTestCase, self).setUp() self.kvps = {} # Insert mock users user_1_db = UserDB(name="user1") user_1_db = User.add_or_update(user_1_db) self.users["user_1"] = user_1_db user_2_db = UserDB(name="user2") user_2_db = User.add_or_update(user_2_db) self.users["user_2"] = user_2_db # Insert mock kvp objects kvp_api = KeyValuePairSetAPI(name="test_system_scope", value="value1", scope=FULL_SYSTEM_SCOPE) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps["kvp_1"] = kvp_db kvp_api = KeyValuePairSetAPI( name="test_system_scope_secret", value="value_secret", scope=FULL_SYSTEM_SCOPE, secret=True ) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps["kvp_2"] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name="test_user_scope_1", user="******") kvp_db = KeyValuePairDB(name=name, value="valueu12", scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps["kvp_3"] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name="test_user_scope_2", user="******") kvp_api = KeyValuePairSetAPI(name=name, value="user_secret", scope=FULL_USER_SCOPE, secret=True) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps["kvp_4"] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name="test_user_scope_3", user="******") kvp_db = KeyValuePairDB(name=name, value="valueu21", scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps["kvp_5"] = kvp_db self.system_scoped_items_count = 2 self.user_scoped_items_count = 3 self.user_scoped_items_per_user_count = {"user1": 2, "user2": 1}
def get_all(self, prefix=None, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False, **kwargs): """ List all keys. Handles requests: GET /keys/ """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE scope = get_datastore_full_scope(scope) requester_user = get_requester() user = user or requester_user is_all_scope = (scope == ALL_SCOPE) is_admin = request_user_is_admin(request=pecan.request) if is_all_scope and not is_admin: msg = '"all" scope requires administrator access' raise AccessDeniedError(message=msg, user_db=requester_user) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin) # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provider(request=pecan.request, user=user) from_model_kwargs = {'mask_secrets': not decrypt} kwargs['prefix'] = prefix if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) kwargs['scope'] = scope if scope == USER_SCOPE or scope == FULL_USER_SCOPE: # Make sure we only returned values scoped to current user if kwargs['prefix']: kwargs['prefix'] = get_key_reference(name=kwargs['prefix'], scope=scope, user=requester_user) else: kwargs['prefix'] = get_key_reference(name='', scope=scope, user=user) kvp_apis = super(KeyValuePairController, self)._get_all(from_model_kwargs=from_model_kwargs, **kwargs) return kvp_apis
def get_one(self, name, scope=SYSTEM_SCOPE, user=None, decrypt=False): """ List key by name. Handle: GET /keys/key1 """ self._validate_scope(scope=scope) if user: # Providing a user implies a user scope scope = USER_SCOPE requester_user = get_requester() user = user or requester_user is_admin = request_user_is_admin(request=pecan.request) # User needs to be either admin or requesting item for itself self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin) # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provider(request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) from_model_kwargs = {'mask_secrets': not decrypt} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs ) return kvp_api
def get_one(self, name, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False): """ List key by name. Handle: GET /keys/key1 """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) requester_user = get_requester() user = user or requester_user is_admin = request_user_is_admin(request=pecan.request) # User needs to be either admin or requesting item for itself self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin) # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provided( request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) from_model_kwargs = {'mask_secrets': not decrypt} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) return kvp_api
def put(self, kvp, name, scope=FULL_SYSTEM_SCOPE): """ Create a new entry or update an existing one. """ if not scope: scope = FULL_SYSTEM_SCOPE requester_user = get_requester() scope = getattr(kvp, 'scope', scope) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = getattr(kvp, 'user', requester_user) or requester_user # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provider(request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) LOG.debug('PUT scope: %s, name: %s', scope, name) # TODO: Custom permission check since the key doesn't need to exist here # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): try: existing_kvp_api = self._get_one_by_scope_and_name( scope=scope, name=key_ref ) except StackStormDBObjectNotFoundError: existing_kvp_api = None kvp.name = key_ref kvp.scope = scope try: kvp_db = KeyValuePairAPI.to_model(kvp) if existing_kvp_api: kvp_db.id = existing_kvp_api.id kvp_db = KeyValuePair.add_or_update(kvp_db) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for key value data=%s', kvp) abort(http_client.BAD_REQUEST, str(e)) return except CryptoKeyNotSetupException as e: LOG.exception(str(e)) abort(http_client.BAD_REQUEST, str(e)) return except InvalidScopeException as e: LOG.exception(str(e)) abort(http_client.BAD_REQUEST, str(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra) kvp_api = KeyValuePairAPI.from_model(kvp_db) return kvp_api
def put(self, kvp, name, scope=FULL_SYSTEM_SCOPE): """ Create a new entry or update an existing one. """ if not scope: scope = FULL_SYSTEM_SCOPE requester_user = get_requester() scope = getattr(kvp, 'scope', scope) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = getattr(kvp, 'user', requester_user) or requester_user # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provided( request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) LOG.debug('PUT scope: %s, name: %s', scope, name) # TODO: Custom permission check since the key doesn't need to exist here # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): try: existing_kvp_api = self._get_one_by_scope_and_name( scope=scope, name=key_ref) except StackStormDBObjectNotFoundError: existing_kvp_api = None kvp.name = key_ref kvp.scope = scope try: kvp_db = KeyValuePairAPI.to_model(kvp) if existing_kvp_api: kvp_db.id = existing_kvp_api.id kvp_db = KeyValuePair.add_or_update(kvp_db) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for key value data=%s', kvp) abort(http_client.BAD_REQUEST, str(e)) return except CryptoKeyNotSetupException as e: LOG.exception(str(e)) abort(http_client.BAD_REQUEST, str(e)) return except InvalidScopeException as e: LOG.exception(str(e)) abort(http_client.BAD_REQUEST, str(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra) kvp_api = KeyValuePairAPI.from_model(kvp_db) return kvp_api
def test_get_key_reference_user_scope(self): ref = get_key_reference(scope=USER_SCOPE, name='foo', user='******') self.assertEqual(ref, 'stanley:foo') self.assertRaises(InvalidUserException, get_key_reference, scope=USER_SCOPE, name='foo', user='')
def delete(self, name, requester_user, scope=FULL_SYSTEM_SCOPE, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope: scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): from_model_kwargs = {"mask_secrets": True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) kvp_db = KeyValuePairAPI.to_model(kvp_api) LOG.debug( "DELETE /keys/ lookup with scope=%s name=%s found object: %s", scope, name, kvp_db, ) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception( "Database delete encountered exception during " 'delete of name="%s". ', name, ) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e)) return extra = {"kvp_db": kvp_db} LOG.audit("KeyValuePair deleted. KeyValuePair.id=%s" % (kvp_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def test_get_key_reference_user_scope(self): ref = get_key_reference(scope=USER_SCOPE, name="foo", user="******") self.assertEqual(ref, "stanley:foo") self.assertRaises( InvalidUserException, get_key_reference, scope=USER_SCOPE, name="foo", user="", )
def test_admin_permissions_for_user_scoped_kvps(self): resolver = KeyValuePermissionsResolver() admin_user_db = self.users["admin"] # Setup users. No explicit grant, role, and assignment records should be # required for user to access their KVPs user_1_db = UserDB(name="user105") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db # Insert user scoped key value pairs for user1. key_1_name = "mykey5" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval5", ) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db # Admin user should have general list permissions on user1's kvps. self.assertUserHasResourceDbPermission( resolver=resolver, user_db=admin_user_db, resource_db=KeyValuePairDB(scope="%s:%s" % (FULL_USER_SCOPE, user_1_db.name)), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # Admin user should have all permissions to another user1's kvp. self.assertUserHasResourceDbPermission( resolver=resolver, user_db=admin_user_db, resource_db=kvp_1_db, permission_type=PermissionType.KEY_VALUE_PAIR_ALL, ) self.assertUserHasResourceDbPermissions( resolver=resolver, user_db=admin_user_db, resource_db=kvp_1_db, permission_types=self.read_permission_types, ) self.assertUserHasResourceDbPermissions( resolver=resolver, user_db=admin_user_db, resource_db=kvp_1_db, permission_types=self.write_permission_types, )
def delete(self, name, requester_user, scope=FULL_SYSTEM_SCOPE, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope: scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided rbac_utils = get_rbac_backend().get_utils_class() rbac_utils.assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user, require_rbac=True) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): from_model_kwargs = {'mask_secrets': True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs ) kvp_db = KeyValuePairAPI.to_model(kvp_api) LOG.debug('DELETE /keys/ lookup with scope=%s name=%s found object: %s', scope, name, kvp_db) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception('Database delete encountered exception during ' 'delete of name="%s". ', name) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra) return Response(status=http_client.NO_CONTENT)
def set_datastore_value_for_config_key(pack_name, key_name, value, secret=False, user=None): """ Set config value in the datastore. This function takes care of correctly encoding the key name, serializing the value, etc. :param pack_name: Pack name. :type pack_name: ``str`` :param key_name: Config key name. :type key_name: ``str`` :param secret: True if this value is a secret. :type secret: ``bool`` :param user: Optional username if working on a user-scoped config item. :type user: ``str`` :rtype: :class:`KeyValuePairDB` """ if user: scope = FULL_USER_SCOPE else: scope = FULL_SYSTEM_SCOPE name = get_key_reference(scope=scope, name=key_name, user=user) kvp_api = KeyValuePairAPI(name=name, value=value, scope=scope, secret=secret) kvp_db = KeyValuePairAPI.to_model(kvp_api) # TODO: Obtain a lock try: existing_kvp_db = KeyValuePair.get_by_scope_and_name(scope=scope, name=name) except StackStormDBObjectNotFoundError: existing_kvp_db = None if existing_kvp_db: kvp_db.id = existing_kvp_db.id kvp_db = KeyValuePair.add_or_update(kvp_db) return kvp_db
def delete(self, name, scope=FULL_SYSTEM_SCOPE, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope: scope = FULL_SYSTEM_SCOPE scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) requester_user = get_requester() user = user or requester_user # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provider(request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): from_model_kwargs = {'mask_secrets': True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs ) kvp_db = KeyValuePairAPI.to_model(kvp_api) LOG.debug('DELETE /keys/ lookup with scope=%s name=%s found object: %s', scope, name, kvp_db) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception('Database delete encountered exception during ' 'delete of name="%s". ', name) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
def delete(self, name, scope=FULL_SYSTEM_SCOPE, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope: scope = FULL_SYSTEM_SCOPE scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) requester_user = get_requester() user = user or requester_user # Validate that the authenticated user is admin if user query param is provided assert_request_user_is_admin_if_user_query_param_is_provided( request=pecan.request, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): from_model_kwargs = {'mask_secrets': True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) kvp_db = KeyValuePairAPI.to_model(kvp_api) LOG.debug( 'DELETE /keys/ lookup with scope=%s name=%s found object: %s', scope, name, kvp_db) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception( 'Database delete encountered exception during ' 'delete of name="%s". ', name) abort(http_client.INTERNAL_SERVER_ERROR, str(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair deleted. KeyValuePair.id=%s' % (kvp_db.id), extra=extra)
def get_one(self, name, requester_user, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False): """ List key by name. Handle: GET /keys/key1 """ if not scope: scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) is_admin = rbac_utils.user_is_admin(user_db=requester_user) # User needs to be either admin or requesting item for itself self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, is_admin=is_admin, requester_user=requester_user) user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user) key_ref = get_key_reference(scope=scope, name=name, user=user) from_model_kwargs = {'mask_secrets': not decrypt} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs ) return kvp_api
def set_datastore_value_for_config_key(pack_name, key_name, value, secret=False, user=None): """ Set config value in the datastore. This function takes care of correctly encoding the key name, serializing the value, etc. :param pack_name: Pack name. :type pack_name: ``str`` :param key_name: Config key name. :type key_name: ``str`` :param secret: True if this value is a secret. :type secret: ``bool`` :param user: Optional username if working on a user-scoped config item. :type user: ``str`` :rtype: :class:`KeyValuePairDB` """ if user: scope = USER_SCOPE else: scope = SYSTEM_SCOPE name = get_key_reference(scope=scope, name=key_name, user=user) kvp_api = KeyValuePairAPI(name=name, value=value, scope=scope, secret=secret) kvp_db = KeyValuePairAPI.to_model(kvp_api) # TODO: Obtain a lock existing_kvp_db = KeyValuePair.get_by_scope_and_name(scope=scope, name=name) if existing_kvp_db: kvp_db.id = existing_kvp_db.id kvp_db = KeyValuePair.add_or_update(kvp_db) return kvp_db
def test_get_key_reference_system_scope(self): ref = get_key_reference(scope=SYSTEM_SCOPE, name='foo') self.assertEqual(ref, 'foo')
def setUp(self): super(KeyValuesControllerRBACTestCase, self).setUp() self.kvps = {} # Insert mock users user_1_db = UserDB(name='user1') user_1_db = User.add_or_update(user_1_db) self.users['user_1'] = user_1_db user_2_db = UserDB(name='user2') user_2_db = User.add_or_update(user_2_db) self.users['user_2'] = user_2_db # Insert mock kvp objects kvp_api = KeyValuePairSetAPI(name='test_system_scope', value='value1', scope=FULL_SYSTEM_SCOPE) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_1'] = kvp_db kvp_api = KeyValuePairSetAPI(name='test_system_scope_secret', value='value_secret', scope=FULL_SYSTEM_SCOPE, secret=True) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_2'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_1', user='******') kvp_db = KeyValuePairDB(name=name, value='valueu12', scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_3'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_2', user='******') kvp_api = KeyValuePairSetAPI(name=name, value='user_secret', scope=FULL_USER_SCOPE, secret=True) kvp_db = KeyValuePairSetAPI.to_model(kvp_api) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_4'] = kvp_db name = get_key_reference(scope=FULL_USER_SCOPE, name='test_user_scope_3', user='******') kvp_db = KeyValuePairDB(name=name, value='valueu21', scope=FULL_USER_SCOPE) kvp_db = KeyValuePair.add_or_update(kvp_db) kvp_db = KeyValuePairAPI.from_model(kvp_db) self.kvps['kvp_5'] = kvp_db self.system_scoped_items_count = 2 self.user_scoped_items_count = 3 self.user_scoped_items_per_user_count = {'user1': 2, 'user2': 1}
def get_all(self, requester_user, prefix=None, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False, sort=None, offset=0, limit=None, **raw_filters): """ List all keys. Handles requests: GET /keys/ """ if not scope: # Default to system scope scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) # "all" scope can only be used by the admins (on RBAC installations) self._validate_all_scope(scope=scope, requester_user=requester_user) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, requester_user=requester_user) user_query_param_filter = bool(user) current_user = requester_user.name user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user, require_rbac=True) from_model_kwargs = {'mask_secrets': not decrypt} if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) raw_filters['scope'] = scope # Set prefix which will be used for user-scoped items. # NOTE: It's very important raw_filters['prefix'] is set when requesting user scoped items # to avoid information leakage (aka user1 retrieves items for user2) is_admin = rbac_utils.user_is_admin(user_db=requester_user) if is_admin and user_query_param_filter: # Retrieve values scoped to the provided user user_scope_prefix = get_key_reference(name=prefix or '', scope=USER_SCOPE, user=user) else: # RBAC not enabled or user is not an admin, retrieve user scoped values for the # current user user_scope_prefix = get_key_reference(name=prefix or '', scope=USER_SCOPE, user=current_user) if scope == ALL_SCOPE: # Special case for ALL_SCOPE # 1. Retrieve system scoped values raw_filters['scope'] = FULL_SYSTEM_SCOPE raw_filters['prefix'] = prefix assert 'scope' in raw_filters kvp_apis_system = super(KeyValuePairController, self)._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user) # 2. Retrieve user scoped items for current user or for all the users (depending if the # authenticated user is admin and if ?user is provided) raw_filters['scope'] = FULL_USER_SCOPE if cfg.CONF.rbac.enable and is_admin and not user_query_param_filter: # Admin user retrieving user-scoped items for all the users raw_filters['prefix'] = prefix or '' else: raw_filters['prefix'] = user_scope_prefix assert 'scope' in raw_filters assert 'prefix' in raw_filters kvp_apis_user = super(KeyValuePairController, self)._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user) # Combine the result kvp_apis = [] kvp_apis.extend(kvp_apis_system.json or []) kvp_apis.extend(kvp_apis_user.json or []) elif scope in [USER_SCOPE, FULL_USER_SCOPE]: # Make sure we only returned values scoped to current user prefix = get_key_reference(name=prefix or '', scope=scope, user=user) raw_filters['prefix'] = user_scope_prefix assert 'scope' in raw_filters assert 'prefix' in raw_filters kvp_apis = super(KeyValuePairController, self)._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user) elif scope in [SYSTEM_SCOPE, FULL_SYSTEM_SCOPE]: raw_filters['prefix'] = prefix assert 'scope' in raw_filters kvp_apis = super(KeyValuePairController, self)._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user) else: raise ValueError('Invalid scope: %s' % (scope)) return kvp_apis
def test_user_permissions_for_user_scope_kvps(self): # Insert user scoped key value pairs for user1. user_1_db = UserDB(name="user111") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db key_1_name = "mykey1" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_api = KeyValuePairSetAPI( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval1", secret=True, ) kvp_1_db = KeyValuePairSetAPI.to_model(kvp_1_api) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db # Insert user scoped key value pairs for user2. user_2_db = UserDB(name="user112") user_2_db = User.add_or_update(user_2_db) self.users[user_2_db.name] = user_2_db key_2_name = "mykey2" key_2_ref = get_key_reference(FULL_USER_SCOPE, key_2_name, user_2_db.name) kvp_2_api = KeyValuePairSetAPI( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_2_ref), scope=FULL_USER_SCOPE, name=key_2_ref, value="myval2", secret=True, ) kvp_2_db = KeyValuePairSetAPI.to_model(kvp_2_api) kvp_2_db = KeyValuePair.add_or_update(kvp_2_db) self.resources[kvp_2_db.uid] = kvp_2_db # Set context to user self.use_user(self.users[user_1_db.name]) # User should be able to list the system and user scoped kvps that user has permission to. resp = self.app.get( "/v1/keys?limit=-1") # server defaults no scope to system scope self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get( "/v1/keys/") # server defaults no scope to system scope self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get("/v1/keys?scope=all") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 1) self.assertTrue( all([item["scope"] == FULL_USER_SCOPE for item in resp.json])) resp = self.app.get("/v1/keys?scope=system") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get("/v1/keys?scope=user") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 1) self.assertTrue( all([item["scope"] == FULL_USER_SCOPE for item in resp.json])) # User should have read and write permissions to his/her own kvps. k, v = key_1_name, kvp_1_api.value resp = self.app.get("/v1/keys/%s?decrypt=True&scope=user" % k) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(resp.json["value"], v) d = { "name": key_1_ref, "value": "value for %s" % k, "scope": FULL_USER_SCOPE, "secret": True, } resp = self.app.put_json("/v1/keys/%s?scope=user" % k, d) self.assertEqual(resp.status_int, http_client.OK) resp = self.app.get("/v1/keys/%s?decrypt=True&scope=user" % k) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(resp.json["value"], "value for %s" % k) resp = self.app.delete("/v1/keys/%s?scope=user" % k) self.assertEqual(resp.status_code, http_client.NO_CONTENT) resp = self.app.get("/v1/keys/%s?scope=user" % k, expect_errors=True) self.assertEqual(resp.status_int, http_client.NOT_FOUND)
def test_user_permissions_for_another_user_kvps(self): # Setup users. user_1_db = UserDB(name="user113") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db user_2_db = UserDB(name="user114") user_2_db = User.add_or_update(user_2_db) self.users[user_2_db.name] = user_2_db # Insert user scoped key value pairs for user1. key_1_name = "mykey3" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_api = KeyValuePairSetAPI( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval3", secret=True, ) kvp_1_db = KeyValuePairSetAPI.to_model(kvp_1_api) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db # Setup bad grant, role, and assignment records where administrator # accidentally or intentionally try to grant a user's kvps to another user. grant_db = PermissionGrantDB( resource_uid=kvp_1_db.get_uid(), resource_type=ResourceType.KEY_VALUE_PAIR, permission_types=[PermissionType.KEY_VALUE_PAIR_ALL], ) grant_db = PermissionGrant.add_or_update(grant_db) role_db = RoleDB( name="custom_role_user_key3_all_grant", permission_grants=[str(grant_db.id)], ) role_db = Role.add_or_update(role_db) self.roles[role_db.name] = role_db role_assignment_db = UserRoleAssignmentDB( user=user_2_db.name, role=role_db.name, source="assignments/%s.yaml" % user_2_db.name, ) UserRoleAssignment.add_or_update(role_assignment_db) # Set context to user self.use_user(self.users[user_2_db.name]) # User2 should not be able to list user1's kvp. resp = self.app.get( "/v1/keys?limit=-1") # server defaults no scope to system scope self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get( "/v1/keys/") # server defaults no scope to system scope self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get("/v1/keys?scope=all") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get("/v1/keys?scope=system") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) resp = self.app.get("/v1/keys?scope=user") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) # User2 should not have read and write permissions on user1's kvp. k = key_1_name url = "/v1/keys/%s?scope=user&user=%s" % (k, user_1_db.name) resp = self.app.get(url, expect_errors=True) self.assertEqual(resp.status_int, http_client.FORBIDDEN) d = { "name": key_1_ref, "value": "value for %s" % k, "scope": FULL_USER_SCOPE, "user": user_1_db.name, } resp = self.app.put_json(url, d, expect_errors=True) self.assertEqual(resp.status_int, http_client.FORBIDDEN) resp = self.app.delete(url, expect_errors=True) self.assertEqual(resp.status_code, http_client.FORBIDDEN)
def delete(self, name, requester_user, scope=None, user=None): """ Delete the key value pair. Handles requests: DELETE /keys/1 """ if not scope or scope == ALL_SCOPE: # Default to system scope scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(name=cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = user or requester_user.name rbac_utils = get_rbac_backend().get_utils_class() # Validate that the authenticated user is admin if user query param is provided rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) # Set key reference for system or user scope key_ref = get_key_reference(scope=scope, name=name, user=user) extra = { "scope": scope, "name": name, "user": user, "key_ref": key_ref } LOG.debug("DELETE /v1/keys/%s", name, extra=extra) # Setup a kvp database object used for verifying permission kvp_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, scope, key_ref), scope=scope, name=key_ref, ) # Check that user has permission to the key value pair. # If RBAC is enabled, this check will verify if user has system role with all access. # If RBAC is enabled, this check guards against a user accessing another user's kvp. # If RBAC is enabled, user needs to be explicitly granted permission to delete a system kvp. # The check is sufficient to allow decryption of the system kvp. rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=kvp_db, permission_type=PermissionType.KEY_VALUE_PAIR_DELETE, ) # Acquire a lock to avoid race condition between concurrent API calls with self._coordinator.get_lock( self._get_lock_name_for_key(name=key_ref, scope=scope)): from_model_kwargs = {"mask_secrets": True} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) kvp_db = KeyValuePairAPI.to_model(kvp_api) extra["kvp_db"] = kvp_db LOG.debug("DELETE /v1/keys/%s", name, extra=extra) try: KeyValuePair.delete(kvp_db) except Exception as e: LOG.exception( "Database delete encountered exception during " 'delete of name="%s". ', name, ) abort(http_client.INTERNAL_SERVER_ERROR, six.text_type(e)) return LOG.audit("DELETE /v1/keys/%s succeeded id=%s", name, kvp_db.id, extra=extra) return Response(status=http_client.NO_CONTENT)
def setUp(self): super(KeyValueSystemScopeControllerRBACTestCase, self).setUp() # Insert system scoped key value pairs. kvp_1_api = KeyValuePairSetAPI( uid="%s:%s:key1" % (ResourceType.KEY_VALUE_PAIR, FULL_SYSTEM_SCOPE), scope=FULL_SYSTEM_SCOPE, name="key1", value="val1", secret=True, ) kvp_1_db = KeyValuePairSetAPI.to_model(kvp_1_api) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db kvp_2_api = KeyValuePairSetAPI( uid="%s:%s:key2" % (ResourceType.KEY_VALUE_PAIR, FULL_SYSTEM_SCOPE), scope=FULL_SYSTEM_SCOPE, name="key2", value="val2", secret=True, ) kvp_2_db = KeyValuePairSetAPI.to_model(kvp_2_api) kvp_2_db = KeyValuePair.add_or_update(kvp_2_db) self.resources[kvp_2_db.uid] = kvp_2_db # Setup users for user scoped KVPs. user_1_db = UserDB(name="user101") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db user_2_db = UserDB(name="user102") user_2_db = User.add_or_update(user_2_db) self.users[user_2_db.name] = user_2_db # Insert user scoped key value pairs for user1. key_1_name = "mykey1" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval1", ) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db key_2_name = "mykey2" key_2_ref = get_key_reference(FULL_USER_SCOPE, key_2_name, user_1_db.name) kvp_2_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_2_ref), scope=FULL_USER_SCOPE, name=key_2_ref, value="myval2", ) kvp_2_db = KeyValuePair.add_or_update(kvp_2_db) self.resources[kvp_2_db.uid] = kvp_2_db
def put(self, kvp, name, requester_user, scope=None): """ Create a new entry or update an existing one. """ if not scope or scope == ALL_SCOPE: # Default to system scope scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(name=cfg.CONF.system_user.user) scope = getattr(kvp, "scope", scope) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = getattr(kvp, "user", requester_user.name) or requester_user.name rbac_utils = get_rbac_backend().get_utils_class() # Validate that the authenticated user is admin if user query param is provided rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) # Set key reference for system or user scope key_ref = get_key_reference(scope=scope, name=name, user=user) extra = { "scope": scope, "name": name, "user": user, "key_ref": key_ref } LOG.debug("PUT /v1/keys/%s", name, extra=extra) # Setup a kvp database object used for verifying permission kvp_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, scope, key_ref), scope=scope, name=key_ref, ) # Check that user has permission to the key value pair. # If RBAC is enabled, this check will verify if user has system role with all access. # If RBAC is enabled, this check guards against a user accessing another user's kvp. # If RBAC is enabled, user needs to be explicitly granted permission to set a system kvp. # The check is sufficient to allow decryption of the system kvp. rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=kvp_db, permission_type=PermissionType.KEY_VALUE_PAIR_SET, ) # Validate that the pre-encrypted option can only be used by admins encrypted = getattr(kvp, "encrypted", False) self._validate_encrypted_query_parameter(encrypted=encrypted, scope=scope, requester_user=requester_user) # Acquire a lock to avoid race condition between concurrent API calls with self._coordinator.get_lock( self._get_lock_name_for_key(name=key_ref, scope=scope)): try: existing_kvp_api = self._get_one_by_scope_and_name( scope=scope, name=key_ref) except StackStormDBObjectNotFoundError: existing_kvp_api = None # st2client sends invalid id when initially setting a key so we ignore those id_ = kvp.__dict__.get("id", None) if not existing_kvp_api and id_ and not bson.ObjectId.is_valid( id_): del kvp.__dict__["id"] kvp.name = key_ref kvp.scope = scope try: kvp_db = KeyValuePairAPI.to_model(kvp) if existing_kvp_api: kvp_db.id = existing_kvp_api.id kvp_db = KeyValuePair.add_or_update(kvp_db) except (ValidationError, ValueError) as e: LOG.exception("Validation failed for key value data=%s", kvp) abort(http_client.BAD_REQUEST, six.text_type(e)) return except CryptoKeyNotSetupException as e: LOG.exception(six.text_type(e)) abort(http_client.BAD_REQUEST, six.text_type(e)) return except InvalidScopeException as e: LOG.exception(six.text_type(e)) abort(http_client.BAD_REQUEST, six.text_type(e)) return extra["kvp_db"] = kvp_db LOG.audit("PUT /v1/keys/%s succeeded id=%s", name, kvp_db.id, extra=extra) return KeyValuePairAPI.from_model(kvp_db)
def get_all( self, requester_user, prefix=None, scope=None, user=None, decrypt=False, sort=None, offset=0, limit=None, **raw_filters, ): """ List all keys. Handles requests: GET /keys/ """ if not scope: # Default to system scope scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(name=cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) if scope not in [ALL_SCOPE] + ALLOWED_SCOPES: raise ValueError("Invalid scope: %s" % (scope)) # User needs to be either admin or requesting items for themselves self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, requester_user=requester_user) current_user = requester_user.name user = user or requester_user.name rbac_utils = get_rbac_backend().get_utils_class() # Validate that the authenticated user is admin if user query param is provided rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) from_model_kwargs = {"mask_secrets": not decrypt} if scope and scope not in ALL_SCOPE: self._validate_scope(scope=scope) raw_filters["scope"] = scope # Check if user is granted one of the system roles. has_system_role = rbac_utils.user_has_system_role( user_db=requester_user) # Check that an admin user has permission to all system scoped items. if has_system_role and scope in [ ALL_SCOPE, SYSTEM_SCOPE, FULL_SYSTEM_SCOPE ]: rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=KeyValuePairDB(scope=FULL_SYSTEM_SCOPE), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # Check that user has permission to user scoped items for provided user or current user. if user and scope in [ALL_SCOPE, USER_SCOPE, FULL_USER_SCOPE]: rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=KeyValuePairDB(scope="%s:%s" % (FULL_USER_SCOPE, user)), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # Set user scope prefix for the provided user (or current user if user not provided) # NOTE: It's very important raw_filters['prefix'] is set when requesting user scoped # items to avoid information leakage (aka user1 retrieves items for user2) user_scope_prefix = get_key_reference(name=prefix or "", scope=FULL_USER_SCOPE, user=user) # Special cases for ALL_SCOPE # 1. If user is an admin, then retrieves all system scoped items else only # specific system scoped items that the user is granted permission to. # 2. Retrieves all the user scoped items that the current user owns. kvp_apis_system = [] kvp_apis_user = [] if scope in [ALL_SCOPE, SYSTEM_SCOPE, FULL_SYSTEM_SCOPE]: decrypted_keys = [] # If user has system role, then retrieve all system scoped items if has_system_role: raw_filters["scope"] = FULL_SYSTEM_SCOPE raw_filters["prefix"] = prefix items = self._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user, ) kvp_apis_system.extend(items.json or []) if decrypt and items.json: decrypted_keys.extend(kv_api["name"] for kv_api in items.json if kv_api["secret"]) else: # Otherwise if user is not an admin, then get the list of # system scoped items that user is granted permission to. for key in get_all_system_kvp_names_for_user(current_user): try: item = self._get_one_by_scope_and_name( from_model_kwargs=from_model_kwargs, scope=FULL_SYSTEM_SCOPE, name=key, ) kvp_apis_system.append(item) except Exception as e: LOG.error("Unable to get key %s: %s", key, str(e)) continue if decrypt and item.secret: decrypted_keys.append(key) if decrypted_keys: LOG.audit( "User %s decrypted the values %s ", user, decrypted_keys, extra={ "User": user, "scope": FULL_SYSTEM_SCOPE, "key_name": decrypted_keys, "operation": "decrypt", }, ) if scope in [ALL_SCOPE, USER_SCOPE, FULL_USER_SCOPE]: # Retrieves all the user scoped items that the current user owns. raw_filters["scope"] = FULL_USER_SCOPE raw_filters["prefix"] = user_scope_prefix items = self._get_all( from_model_kwargs=from_model_kwargs, sort=sort, offset=offset, limit=limit, raw_filters=raw_filters, requester_user=requester_user, ) kvp_apis_user.extend(items.json) if decrypt and items.json: decrypted_keys = [ kvp_api["name"] for kvp_api in items.json if kvp_api["secret"] ] if decrypted_keys: LOG.audit( "User %s decrypted the values %s ", user, decrypted_keys, extra={ "User": user, "scope": FULL_USER_SCOPE, "key_name": decrypted_keys, "operation": "decrypt", }, ) return kvp_apis_system + kvp_apis_user
def test_user_permissions_for_another_user_kvps(self): resolver = KeyValuePermissionsResolver() # Setup users. No explicit grant, role, and assignment records should be # required for user to access their KVPs user_1_db = UserDB(name="user103") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db user_2_db = UserDB(name="user104") user_2_db = User.add_or_update(user_2_db) self.users[user_2_db.name] = user_2_db # Insert user scoped key value pairs for user1. key_1_name = "mykey3" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval3", ) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db # Setup bad grant, role, and assignment records where administrator # accidentally or intentionally try to grant a user's kvps to another user. grant_db = PermissionGrantDB( resource_uid=kvp_1_db.get_uid(), resource_type=ResourceType.KEY_VALUE_PAIR, permission_types=[PermissionType.KEY_VALUE_PAIR_ALL], ) grant_db = PermissionGrant.add_or_update(grant_db) role_db = RoleDB( name="custom_role_user_key3_all_grant", permission_grants=[str(grant_db.id)], ) role_db = Role.add_or_update(role_db) self.roles[role_db.name] = role_db role_assignment_db = UserRoleAssignmentDB( user=user_2_db.name, role=role_db.name, source="assignments/%s.yaml" % user_2_db.name, ) UserRoleAssignment.add_or_update(role_assignment_db) # User2 should not have general list permissions on user1's kvps. self.assertUserDoesntHaveResourceDbPermission( resolver=resolver, user_db=user_2_db, resource_db=KeyValuePairDB(scope="%s:%s" % (FULL_USER_SCOPE, user_1_db.name)), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # User2 should not have any permissions on another user1's kvp. self.assertUserDoesntHaveResourceDbPermission( resolver=resolver, user_db=user_2_db, resource_db=kvp_1_db, permission_type=PermissionType.KEY_VALUE_PAIR_ALL, ) self.assertUserDoesntHaveResourceDbPermissions( resolver=resolver, user_db=user_2_db, resource_db=kvp_1_db, permission_types=self.read_permission_types, ) self.assertUserDoesntHaveResourceDbPermissions( resolver=resolver, user_db=user_2_db, resource_db=kvp_1_db, permission_types=self.write_permission_types, )
def test_user_permissions_for_user_scope_kvps(self): resolver = KeyValuePermissionsResolver() # Setup users. No explicit grant, role, and assignment records should be # required for user to access their KVPs user_1_db = UserDB(name="user101") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db user_2_db = UserDB(name="user102") user_2_db = User.add_or_update(user_2_db) self.users[user_2_db.name] = user_2_db # Insert user scoped key value pairs for user1. key_1_name = "mykey1" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval1", ) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db key_2_name = "mykey2" key_2_ref = get_key_reference(FULL_USER_SCOPE, key_2_name, user_1_db.name) kvp_2_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_2_ref), scope=FULL_USER_SCOPE, name=key_2_ref, value="myval2", ) kvp_2_db = KeyValuePair.add_or_update(kvp_2_db) self.resources[kvp_2_db.uid] = kvp_2_db # User1 should have general list permissions on user1's kvps. self.assertUserHasResourceDbPermission( resolver=resolver, user_db=user_1_db, resource_db=KeyValuePairDB(scope="%s:%s" % (FULL_USER_SCOPE, user_1_db.name)), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # User1 should have all, read, and write permissions on user1's kvps. for k in [key_1_name, key_2_name]: kvp_ref = get_key_reference(FULL_USER_SCOPE, k, user_1_db.name) kvp_uid = "%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, kvp_ref) kvp_db = self.resources[kvp_uid] self.assertUserHasResourceDbPermission( resolver=resolver, user_db=user_1_db, resource_db=kvp_db, permission_type=PermissionType.KEY_VALUE_PAIR_ALL, ) self.assertUserHasResourceDbPermissions( resolver=resolver, user_db=user_1_db, resource_db=kvp_db, permission_types=self.read_permission_types, ) self.assertUserHasResourceDbPermissions( resolver=resolver, user_db=user_1_db, resource_db=kvp_db, permission_types=self.write_permission_types, ) # User2 should not have general list permissions on user1's kvps. self.assertUserDoesntHaveResourceDbPermission( resolver=resolver, user_db=user_2_db, resource_db=KeyValuePairDB(scope="%s:%s" % (FULL_USER_SCOPE, user_1_db.name)), permission_type=PermissionType.KEY_VALUE_PAIR_LIST, ) # User2 should not have any permissions on user1's kvps. for k in [key_1_name, key_2_name]: kvp_ref = get_key_reference(FULL_USER_SCOPE, k, user_1_db.name) kvp_uid = "%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, kvp_ref) kvp_db = self.resources[kvp_uid] self.assertUserDoesntHaveResourceDbPermission( resolver=resolver, user_db=user_2_db, resource_db=kvp_db, permission_type=PermissionType.KEY_VALUE_PAIR_ALL, ) self.assertUserDoesntHaveResourceDbPermissions( resolver=resolver, user_db=user_2_db, resource_db=kvp_db, permission_types=self.read_permission_types, ) self.assertUserDoesntHaveResourceDbPermissions( resolver=resolver, user_db=user_2_db, resource_db=kvp_db, permission_types=self.write_permission_types, )
def get_one(self, name, requester_user, scope=FULL_SYSTEM_SCOPE, user=None, decrypt=False): """ List key by name. Handle: GET /keys/key1 """ if not scope: # Default to system scope scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) # User needs to be either admin or requesting item for itself self._validate_decrypt_query_parameter(decrypt=decrypt, scope=scope, requester_user=requester_user) user_query_param_filter = bool(user) current_user = requester_user.name user = user or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user, require_rbac=True) # Additional guard to ensure there is no information leakage across users is_admin = rbac_utils.user_is_admin(user_db=requester_user) if is_admin and user_query_param_filter: # Retrieve values scoped to the provided user user_scope_prefix = get_key_reference(name=name, scope=USER_SCOPE, user=user) else: # RBAC not enabled or user is not an admin, retrieve user scoped values for the # current user user_scope_prefix = get_key_reference(name=name, scope=USER_SCOPE, user=current_user) if scope == FULL_USER_SCOPE: key_ref = user_scope_prefix elif scope == FULL_SYSTEM_SCOPE: key_ref = get_key_reference(scope=FULL_SYSTEM_SCOPE, name=name, user=user) else: raise ValueError('Invalid scope: %s' % (scope)) from_model_kwargs = {'mask_secrets': not decrypt} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs ) return kvp_api
def test_admin_permissions_for_user_scoped_kvps(self): # Insert user scoped key value pairs for user1. user_1_db = UserDB(name="user115") user_1_db = User.add_or_update(user_1_db) self.users[user_1_db.name] = user_1_db key_1_name = "mykey5" key_1_ref = get_key_reference(FULL_USER_SCOPE, key_1_name, user_1_db.name) kvp_1_api = KeyValuePairSetAPI( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, FULL_USER_SCOPE, key_1_ref), scope=FULL_USER_SCOPE, name=key_1_ref, value="myval5", secret=True, ) kvp_1_db = KeyValuePairSetAPI.to_model(kvp_1_api) kvp_1_db = KeyValuePair.add_or_update(kvp_1_db) self.resources[kvp_1_db.uid] = kvp_1_db # Set context to user self.use_user(self.users["admin"]) # Admin user should have general list permissions on user1's kvps. resp = self.app.get("/v1/keys?limit=-1&scope=user&user=%s" % user_1_db.name) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 1) resp = self.app.get("/v1/keys?scope=user&user=%s" % user_1_db.name) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 1) self.assertEqual(resp.json[0]["name"], key_1_name) self.assertEqual(resp.json[0]["user"], user_1_db.name) resp = self.app.get("/v1/keys?decrypt=True&scope=user&user=%s" % user_1_db.name) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 1) self.assertEqual(resp.json[0]["name"], key_1_name) self.assertEqual(resp.json[0]["user"], user_1_db.name) self.assertEqual(resp.json[0]["value"], kvp_1_api.value) resp = self.app.get("/v1/keys?scope=all") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 2) self.assertTrue( all([item["scope"] == FULL_SYSTEM_SCOPE for item in resp.json])) resp = self.app.get("/v1/keys?scope=system") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 2) self.assertTrue( all([item["scope"] == FULL_SYSTEM_SCOPE for item in resp.json])) resp = self.app.get("/v1/keys?scope=user") self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(len(resp.json), 0) # Admin user should have read and write permissions to user1's kvps. k, v = key_1_name, kvp_1_api.value url = "/v1/keys/%s?decrypt=True&scope=user&user=%s" % (k, user_1_db.name) resp = self.app.get(url) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(resp.json["value"], v) d = { "name": key_1_ref, "value": "value for %s" % k, "scope": FULL_USER_SCOPE, "user": user_1_db.name, "secret": True, } resp = self.app.put_json(url, d) self.assertEqual(resp.status_int, http_client.OK) resp = self.app.get(url) self.assertEqual(resp.status_int, http_client.OK) self.assertEqual(resp.json["value"], "value for %s" % k) resp = self.app.delete(url) self.assertEqual(resp.status_code, http_client.NO_CONTENT) resp = self.app.get(url, expect_errors=True) self.assertEqual(resp.status_int, http_client.NOT_FOUND)
def put(self, kvp, name, requester_user, scope=FULL_SYSTEM_SCOPE): """ Create a new entry or update an existing one. """ if not scope: scope = FULL_SYSTEM_SCOPE if not requester_user: requester_user = UserDB(cfg.CONF.system_user.user) scope = getattr(kvp, 'scope', scope) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = getattr(kvp, 'user', requester_user.name) or requester_user.name # Validate that the authenticated user is admin if user query param is provided assert_user_is_admin_if_user_query_param_is_provided(user_db=requester_user, user=user, require_rbac=True) # Validate that encrypted option can only be used by admins encrypted = getattr(kvp, 'encrypted', False) self._validate_encrypted_query_parameter(encrypted=encrypted, scope=scope, requester_user=requester_user) key_ref = get_key_reference(scope=scope, name=name, user=user) lock_name = self._get_lock_name_for_key(name=key_ref, scope=scope) LOG.debug('PUT scope: %s, name: %s', scope, name) # TODO: Custom permission check since the key doesn't need to exist here # Note: We use lock to avoid a race with self._coordinator.get_lock(lock_name): try: existing_kvp_api = self._get_one_by_scope_and_name( scope=scope, name=key_ref ) except StackStormDBObjectNotFoundError: existing_kvp_api = None # st2client sends invalid id when initially setting a key so we ignore those id_ = kvp.__dict__.get('id', None) if not existing_kvp_api and id_ and not bson.ObjectId.is_valid(id_): del kvp.__dict__['id'] kvp.name = key_ref kvp.scope = scope try: kvp_db = KeyValuePairAPI.to_model(kvp) if existing_kvp_api: kvp_db.id = existing_kvp_api.id kvp_db = KeyValuePair.add_or_update(kvp_db) except (ValidationError, ValueError) as e: LOG.exception('Validation failed for key value data=%s', kvp) abort(http_client.BAD_REQUEST, six.text_type(e)) return except CryptoKeyNotSetupException as e: LOG.exception(six.text_type(e)) abort(http_client.BAD_REQUEST, six.text_type(e)) return except InvalidScopeException as e: LOG.exception(six.text_type(e)) abort(http_client.BAD_REQUEST, six.text_type(e)) return extra = {'kvp_db': kvp_db} LOG.audit('KeyValuePair updated. KeyValuePair.id=%s' % (kvp_db.id), extra=extra) kvp_api = KeyValuePairAPI.from_model(kvp_db) return kvp_api
def get_one(self, name, requester_user, scope=None, user=None, decrypt=False): """ List key by name. Handle: GET /keys/key1 """ if not scope or scope == ALL_SCOPE: # Default to system scope scope = FULL_SYSTEM_SCOPE if user: # Providing a user implies a user scope scope = FULL_USER_SCOPE if not requester_user: requester_user = UserDB(name=cfg.CONF.system_user.user) scope = get_datastore_full_scope(scope) self._validate_scope(scope=scope) user = user or requester_user.name rbac_utils = get_rbac_backend().get_utils_class() # Validate that the authenticated user is admin if user query param is provided rbac_utils.assert_user_is_admin_if_user_query_param_is_provided( user_db=requester_user, user=user, require_rbac=True) # Set key reference for system or user scope key_ref = get_key_reference(scope=scope, name=name, user=user) extra = { "scope": scope, "name": name, "user": user, "key_ref": key_ref } LOG.debug("GET /v1/keys/%s", name, extra=extra) # Setup a kvp database object used for verifying permission kvp_db = KeyValuePairDB( uid="%s:%s:%s" % (ResourceType.KEY_VALUE_PAIR, scope, key_ref), scope=scope, name=key_ref, ) # Check that user has permission to the key value pair. # If RBAC is enabled, this check will verify if user has system role with all access. # If RBAC is enabled, this check guards against a user accessing another user's kvp. # If RBAC is enabled, user needs to be explicitly granted permission to view a system kvp. # The check is sufficient to allow decryption of the system kvp. rbac_utils.assert_user_has_resource_db_permission( user_db=requester_user, resource_db=kvp_db, permission_type=PermissionType.KEY_VALUE_PAIR_VIEW, ) from_model_kwargs = {"mask_secrets": not decrypt} kvp_api = self._get_one_by_scope_and_name( name=key_ref, scope=scope, from_model_kwargs=from_model_kwargs) if decrypt and kvp_api.secret: LOG.audit( "User %s decrypted the value %s ", user, name, extra={ "user": user, "scope": scope, "key_name": name, "operation": "decrypt", }, ) return kvp_api