From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from localhost (localhost [127.0.0.1]) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTP id 5217C267EE for ; Wed, 28 Mar 2018 04:09:39 -0400 (EDT) Received: from turing.freelists.org ([127.0.0.1]) by localhost (turing.freelists.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Pd1OUJrz6g20 for ; Wed, 28 Mar 2018 04:09:39 -0400 (EDT) Received: from smtp47.i.mail.ru (smtp47.i.mail.ru [94.100.177.107]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by turing.freelists.org (Avenir Technologies Mail Multiplex) with ESMTPS id 953EB26809 for ; Wed, 28 Mar 2018 04:09:38 -0400 (EDT) From: Ilya Markov Subject: [tarantool-patches] [security 3/3] security: Refactor system space access checks Date: Wed, 28 Mar 2018 11:09:27 +0300 Message-Id: <32273d03123fd36c0b3dc8aae82594c9929fb04b.1522224470.git.imarkov@tarantool.org> In-Reply-To: References: In-Reply-To: References: Sender: tarantool-patches-bounce@freelists.org Errors-to: tarantool-patches-bounce@freelists.org Reply-To: tarantool-patches@freelists.org List-help: List-unsubscribe: List-software: Ecartis version 1.0.0 List-Id: tarantool-patches List-subscribe: List-owner: List-post: List-archive: To: georgy@tarantool.org Cc: tarantool-patches@freelists.org From: imarkov Processes of creating, dropping objects, granting contain work with system spaces which so far must be available only to admin or user who has write access to universe. This patch removes the need of special accesses to universe. Introduce virtual access_check function in space object and separate its implementation for user spaces and system ones. Move checks on write to system spaces to on_replace triggers. Follow-up #945 Closes #3250 --- src/box/alter.cc | 251 ++++++++++++++++++++++++++---------------- src/box/index.cc | 1 + src/box/lua/schema.lua | 12 +- src/box/schema.cc | 118 +++++++++++++++++++- src/box/schema.h | 15 +++ src/box/space.c | 3 +- src/box/space.h | 17 ++- src/box/sysview_engine.c | 4 + src/box/user.cc | 4 - src/box/user.h | 7 ++ test/box/access.result | 27 ++--- test/box/access.test.lua | 10 +- test/box/access_misc.result | 46 ++++++-- test/box/access_misc.test.lua | 11 +- test/box/net.box.result | 6 + test/box/net.box.test.lua | 2 + test/box/role.result | 25 ++++- test/box/role.test.lua | 13 ++- test/box/sequence.result | 17 ++- test/box/sequence.test.lua | 10 +- test/engine/truncate.result | 2 +- 21 files changed, 434 insertions(+), 167 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 8455373..cc5578a 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -64,8 +64,7 @@ static void access_check_ddl(const char *name, uint32_t owner_uid, enum schema_object_type type, - enum priv_type priv_type, - bool is_17_compat_mode) + enum priv_type priv_type, uint32_t system_space_id) { struct credentials *cr = effective_user(); user_access_t has_access = cr->universal_access; @@ -79,11 +78,15 @@ access_check_ddl(const char *name, uint32_t owner_uid, * The legacy fix does not affect sequences since they * were added in 1.7.7 only. */ - if (is_17_compat_mode && has_access & PRIV_R && has_access & PRIV_W) - has_access |= PRIV_C | PRIV_A; + struct space *space = space_by_id(system_space_id); + if (space != NULL) { + has_access |= space->access[cr->auth_token].effective; + } + + if (has_access & PRIV_W) + has_access |= PRIV_C | PRIV_A | PRIV_D; - user_access_t access = ((PRIV_U | (user_access_t) priv_type) & - ~has_access); + user_access_t access = ((user_access_t) priv_type & ~has_access); bool is_owner = owner_uid == cr->uid || cr->uid == ADMIN; /* * Only the owner of the object or someone who has @@ -91,23 +94,15 @@ access_check_ddl(const char *name, uint32_t owner_uid, * DDL. If a user has no USAGE access and is owner, * deny access as well. */ - if (access == 0 || (is_owner && !(access & PRIV_U))) + if (access == 0 || is_owner) return; /* Access granted. */ struct user *user = user_find_xc(cr->uid); - if (is_owner) { - tnt_raise(AccessDeniedError, - priv_name(PRIV_U), - schema_object_name(SC_UNIVERSE), - "", - user->def->name); - } else { - tnt_raise(AccessDeniedError, - priv_name(access), - schema_object_name(type), - name, - user->def->name); - } + tnt_raise(AccessDeniedError, + priv_name(access), + schema_object_name(type), + name, + user->def->name); } /** @@ -1496,7 +1491,9 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) struct space_def *def = space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE, region); - access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(def->name, ADMIN, SC_SPACE, PRIV_C, + BOX_SPACE_ID); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); RLIST_HEAD(empty_list); @@ -1523,7 +1520,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) txn_on_rollback(txn, on_rollback); } else if (new_tuple == NULL) { /* DELETE */ access_check_ddl(old_space->def->name, old_space->def->uid, - SC_SPACE, PRIV_D, true); + SC_SPACE, PRIV_D, BOX_SPACE_ID); /* Verify that the space is empty (has no indexes) */ if (old_space->index_count) { tnt_raise(ClientError, ER_DROP_SPACE, @@ -1562,7 +1559,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event) struct space_def *def = space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE, region); - access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, true); + access_check_ddl(def->name, def->uid, SC_SPACE, PRIV_A, + BOX_SPACE_ID); auto def_guard = make_scoped_guard([=] { space_def_delete(def); }); /* @@ -1660,7 +1658,7 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event) if (old_tuple && new_tuple) priv_type = PRIV_A; access_check_ddl(old_space->def->name, old_space->def->uid, SC_SPACE, - priv_type, true); + priv_type, BOX_SPACE_ID); struct index *old_index = space_index(old_space, iid); /* @@ -1894,10 +1892,16 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event) space_name(old_space)); /* - * Check if a write privilege was given, raise an error if not. + * Perform checks specific for space. Then perform usual check_ddl. */ - access_check_space_xc(old_space, PRIV_W); - + credentials *cr = effective_user(); + uint32_t has_access = cr->universal_access | + old_space->access[cr->auth_token].effective; + if (!(has_access & (PRIV_W | PRIV_D))) { + access_check_ddl(old_space->def->name, old_space->def->uid, + SC_SPACE, + PRIV_D, BOX_SPACE_ID); + } /* * Truncate counter is updated - truncate the space. */ @@ -2133,7 +2137,9 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) struct user *old_user = user_by_id(uid); if (new_tuple != NULL && old_user == NULL) { /* INSERT */ struct user_def *user = user_def_new_from_tuple(new_tuple); - access_check_ddl(user->name, user->owner, SC_USER, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(user->name, ADMIN, SC_USER, PRIV_C, + BOX_USER_ID); auto def_guard = make_scoped_guard([=] { free(user); }); (void) user_cache_replace(user); def_guard.is_active = false; @@ -2142,7 +2148,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) txn_on_rollback(txn, on_rollback); } else if (new_tuple == NULL) { /* DELETE */ access_check_ddl(old_user->def->name, old_user->def->owner, - SC_USER, PRIV_D, true); + SC_USER, PRIV_D, BOX_USER_ID); /* Can't drop guest or super user */ if (uid <= (uint32_t) BOX_SYSTEM_USER_ID_MAX || uid == SUPER) { tnt_raise(ClientError, ER_DROP_USER, @@ -2169,7 +2175,7 @@ on_replace_dd_user(struct trigger * /* trigger */, void *event) */ struct user_def *user = user_def_new_from_tuple(new_tuple); access_check_ddl(user->name, user->uid, SC_USER, PRIV_A, - true); + BOX_USER_ID); auto def_guard = make_scoped_guard([=] { free(user); }); struct trigger *on_commit = txn_alter_trigger_new(user_cache_alter_user, NULL); @@ -2271,7 +2277,9 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) struct func *old_func = func_by_id(fid); if (new_tuple != NULL && old_func == NULL) { /* INSERT */ struct func_def *def = func_def_new_from_tuple(new_tuple); - access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_C, true); + /* When object is not created, we assume ADMIN owns it */ + access_check_ddl(def->name, ADMIN, SC_FUNCTION, PRIV_C, + BOX_FUNC_ID); auto def_guard = make_scoped_guard([=] { free(def); }); func_cache_replace(def); def_guard.is_active = false; @@ -2285,8 +2293,8 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) * Can only delete func if you're the one * who created it or a superuser. */ - access_check_ddl(old_func->def->name, uid, SC_FUNCTION, - PRIV_D, true); + access_check_ddl(old_func->def->name, uid, SC_FUNCTION, PRIV_D, + BOX_FUNC_ID); /* Can only delete func if it has no grants. */ if (schema_find_grants("function", old_func->def->fid)) { tnt_raise(ClientError, ER_DROP_FUNCTION, @@ -2300,7 +2308,7 @@ on_replace_dd_func(struct trigger * /* trigger */, void *event) struct func_def *def = func_def_new_from_tuple(new_tuple); auto def_guard = make_scoped_guard([=] { free(def); }); access_check_ddl(def->name, def->uid, SC_FUNCTION, PRIV_A, - true); + BOX_FUNC_ID); struct trigger *on_commit = txn_alter_trigger_new(func_cache_replace_func, NULL); txn_on_commit(txn, on_commit); @@ -2442,10 +2450,6 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event) BOX_COLLATION_FIELD_ID); old_coll = coll_by_id(old_id); assert(old_coll != NULL); - access_check_ddl(old_coll->name, old_coll->owner_id, - SC_COLLATION, - new_tuple == NULL ? PRIV_D: PRIV_A, - false); struct trigger *on_commit = txn_alter_trigger_new(coll_cache_delete_coll, old_coll); @@ -2465,8 +2469,6 @@ on_replace_dd_collation(struct trigger * /* trigger */, void *event) struct coll_def new_def; coll_def_new_from_tuple(new_tuple, &new_def); - access_check_ddl(new_def.name, new_def.owner_id, SC_COLLATION, - old_tuple == NULL ? PRIV_C : PRIV_A, false); struct coll *new_coll = coll_new(&new_def); if (new_coll == NULL) diag_raise(); @@ -2509,6 +2511,35 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple) priv->access = tuple_field_u32_xc(tuple, BOX_PRIV_FIELD_ACCESS); } +/** + * Handles case, when current user is not an owner of object and not ADMIN + * and wants to add or modify privileges. + * + * The strategy is following: + * When user has write, create, drop privileges in hierarchy over object, + * she is able to pass privileges. + * This case is required for creating, dropping users. + * TODO: we have to diffirentiate granting, revoking options. As so far, + * this is security breach, user who has create privilege may delete privileges + * of other users. Moreover, we have to differentiate promotion/demotion in altering + * privillege. + */ +static inline void +priv_check_not_owner(struct user *grantor, struct priv_def *priv, + const char *name, enum priv_type priv_type) +{ + uint32_t access = schema_find_access(priv->object_type, + priv->object_id, + grantor); + if (priv->grantee_id == ADMIN || !(access & (PRIV_W | PRIV_C | PRIV_D))) { + tnt_raise(AccessDeniedError, + priv_name(priv_type), + schema_object_name(priv->object_type), + name, + grantor->def->name); + } +} + /* * This function checks that: * - a privilege is granted from an existing user to an existing @@ -2522,35 +2553,33 @@ priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple) static void priv_def_check(struct priv_def *priv, enum priv_type priv_type) { - struct user *grantor = user_find_xc(priv->grantor_id); /* May be a role */ struct user *grantee = user_by_id(priv->grantee_id); + credentials *cr = effective_user(); + struct user *grantor = user_find_xc(cr->uid); + if (grantee == NULL) { tnt_raise(ClientError, ER_NO_SUCH_USER, int2str(priv->grantee_id)); } const char *name = schema_find_name(priv->object_type, priv->object_id); - access_check_ddl(name, grantor->def->uid, priv->object_type, priv_type, - false); + switch (priv->object_type) { case SC_UNIVERSE: + { if (grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_UNIVERSE), - name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, priv_type); } break; + } case SC_SPACE: { - struct space *space = space_cache_find_xc(priv->object_id); + struct space *space = space_cache_find_xc( + priv->object_id); if (space->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_SPACE), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } @@ -2559,22 +2588,19 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) struct func *func = func_cache_find(priv->object_id); if (func->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_FUNCTION), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } case SC_SEQUENCE: { - struct sequence *seq = sequence_cache_find(priv->object_id); + struct sequence *seq = sequence_cache_find( + priv->object_id); if (seq->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_SEQUENCE), name, - grantor->def->name); + priv_check_not_owner(grantor, priv, name, + priv_type); } break; } @@ -2587,16 +2613,23 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) int2str(priv->object_id)); } /* - * Only the creator of the role can grant or revoke it. - * Everyone can grant 'PUBLIC' role. + * Only the creator or owner of grantee of the role + * or user with appropriate privileges can grant or revoke it. + * If role is 'PUBLIC' it is allowed to grant for anyone, + * Revoke 'PUBLIC' is allowed only to users who has drop access. + * The latter case is require for dropping users. */ if (role->def->owner != grantor->def->uid && - grantor->def->uid != ADMIN && - (role->def->uid != PUBLIC || priv->access != PRIV_X)) { - tnt_raise(AccessDeniedError, - priv_name(priv_type), - schema_object_name(SC_ROLE), name, - grantor->def->name); + grantor->def->uid != grantee->def->owner && + !((role->def->uid == PUBLIC) && + (((cr->universal_access & PRIV_D) + || priv_type == PRIV_GRANT) + && priv->access == PRIV_X))) { + tnt_raise(AccessDeniedError, + priv_name(priv_type), + schema_object_name(SC_ROLE), + name, + grantor->def->name); } /* Not necessary to do during revoke, but who cares. */ role_check(grantee, role); @@ -2713,6 +2746,7 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) } else { /* modify */ priv_def_create_from_tuple(&priv, new_tuple); priv_def_check(&priv, PRIV_GRANT); + /*TODO: check that modified by owner of object or ADMIN or grantor of privillege*/ struct trigger *on_commit = txn_alter_trigger_new(modify_priv, NULL); txn_on_commit(txn, on_commit); @@ -2742,31 +2776,54 @@ on_replace_dd_schema(struct trigger * /* trigger */, void *event) struct tuple *new_tuple = stmt->new_tuple; const char *key = tuple_field_cstr_xc(new_tuple ? new_tuple : old_tuple, BOX_SCHEMA_FIELD_KEY); - if (strcmp(key, "cluster") == 0) { - if (new_tuple == NULL) - tnt_raise(ClientError, ER_REPLICASET_UUID_IS_RO); - tt_uuid uu; - tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID, &uu); - REPLICASET_UUID = uu; - } else if (strcmp(key, "version") == 0) { - if (new_tuple != NULL) { - uint32_t major, minor, patch; - if (tuple_field_u32(new_tuple, 1, &major) != 0 || - tuple_field_u32(new_tuple, 2, &minor) != 0) - tnt_raise(ClientError, ER_WRONG_DD_VERSION); - /* Version can be major.minor with no patch. */ - if (tuple_field_u32(new_tuple, 3, &patch) != 0) - patch = 0; - dd_version_id = version_id(major, minor, patch); - } else { - assert(old_tuple != NULL); - /* - * _schema:delete({'version'}) for - * example, for box.internal.bootstrap(). - */ - dd_version_id = tarantool_version_id(); + credentials *cr = effective_user(); + if (strncmp(key, "max_id", 6) == 0) { + if (!(cr->universal_access & (PRIV_W | PRIV_C))) + goto error; + } else { + uint32_t access = cr->universal_access; + struct space *schema_space = space_by_id(BOX_SCHEMA_ID); + access |= schema_space->access[cr->auth_token].effective; + if (~access & PRIV_W) + goto error; + else if (strcmp(key, "cluster") == 0) { + if (new_tuple == NULL) + tnt_raise(ClientError, + ER_REPLICASET_UUID_IS_RO); + tt_uuid uu; + tuple_field_uuid_xc(new_tuple, BOX_CLUSTER_FIELD_UUID, + &uu); + REPLICASET_UUID = uu; + } else if (strcmp(key, "version") == 0) { + if (new_tuple != NULL) { + uint32_t major, minor, patch; + if (tuple_field_u32(new_tuple, 1, &major) != + 0 || + tuple_field_u32(new_tuple, 2, &minor) != 0) + tnt_raise(ClientError, + ER_WRONG_DD_VERSION); + /* Version can be major.minor with no patch. */ + if (tuple_field_u32(new_tuple, 3, &patch) != 0) + patch = 0; + dd_version_id = version_id(major, minor, patch); + } else { + assert(old_tuple != NULL); + /* + * _schema:delete({'version'}) for + * example, for box.internal.bootstrap(). + */ + dd_version_id = tarantool_version_id(); + } } } + return; +error: + struct user *user = user_find_xc(cr->uid); + tnt_raise(AccessDeniedError, + priv_name(PRIV_W), + schema_object_name(SC_SPACE), + "_schema", + user->def->name); } /** @@ -3001,7 +3058,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) struct sequence *seq = sequence_by_id(id); assert(seq != NULL); access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, - PRIV_D, false); + PRIV_D, BOX_SEQUENCE_ID); if (space_has_data(BOX_SEQUENCE_DATA_ID, 0, id)) tnt_raise(ClientError, ER_DROP_SEQUENCE, seq->def->name, "the sequence has data"); @@ -3018,7 +3075,7 @@ on_replace_dd_sequence(struct trigger * /* trigger */, void *event) struct sequence *seq = sequence_by_id(new_def->id); assert(seq != NULL); access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, - PRIV_A, false); + PRIV_A, BOX_SEQUENCE_ID); alter->old_def = seq->def; alter->new_def = new_def; } @@ -3098,10 +3155,10 @@ on_replace_dd_space_sequence(struct trigger * /* trigger */, void *event) /* Check we have the correct access type on the sequence. * */ access_check_ddl(seq->def->name, seq->def->uid, SC_SEQUENCE, priv_type, - false); + BOX_SEQUENCE_ID); /** Check we have alter access on space. */ access_check_ddl(space->def->name, space->def->uid, SC_SPACE, PRIV_A, - false); + BOX_SPACE_ID); struct trigger *on_commit = txn_alter_trigger_new(on_commit_dd_space_sequence, space); diff --git a/src/box/index.cc b/src/box/index.cc index 69fc761..a58e172 100644 --- a/src/box/index.cc +++ b/src/box/index.cc @@ -38,6 +38,7 @@ #include "txn.h" #include "rmean.h" #include "info.h" +#include "xrow.h" /* {{{ Utilities. **********************************************/ diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 5496acb..0cacbb3 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1926,10 +1926,7 @@ box.schema.user.create = function(name, opts) uid = _user:auto_increment{session.euid(), name, 'user', auth_mech_list}[1] -- grant role 'public' to the user box.schema.user.grant(uid, 'public') - -- we have to grant global privileges from setuid function, since - -- only admin has the ownership over universe and we don't have - -- grant option - box.session.su('admin', box.schema.user.grant, uid, 'session,usage', 'universe', + box.schema.user.grant(uid, 'session,usage', 'universe', nil, {if_not_exists=true}) end @@ -2047,12 +2044,9 @@ local function drop(uid, opts) for k, tuple in pairs(sequences) do box.schema.sequence.drop(tuple[1]) end - -- xxx: hack, we have to revoke session and usage privileges - -- of a user using a setuid function in absence of create/drop - -- privileges and grant option if box.space._vuser:get{uid}[4] == 'user' then - box.session.su('admin', box.schema.user.revoke, uid, - 'session,usage', 'universe', nil, {if_exists = true}) + box.schema.user.revoke(uid, 'session,usage', 'universe', + nil, {if_exists = true}) end local privs = _vpriv.index.primary:select{uid} for k, tuple in pairs(privs) do diff --git a/src/box/schema.cc b/src/box/schema.cc index 2e8533b..663d5e7 100644 --- a/src/box/schema.cc +++ b/src/box/schema.cc @@ -37,6 +37,9 @@ #include "scoped_guard.h" #include "version.h" #include "user.h" +#include "session.h" +#include "xrow.h" +#include "memtx_tuple.h" #include /** * @module Data Dictionary @@ -251,6 +254,15 @@ schema_find_id(uint32_t system_space_id, uint32_t index_id, } /** + * Access checks for system spaces, which can be modified only by admin + * _collation, _cluster + */ +static int +sys_read_write_access_check(MAYBE_UNUSED struct space *space, + user_access_t access); + + +/** * Initialize a prototype for the two mandatory data * dictionary spaces and create a cache entry for them. * When restoring data from the snapshot these spaces @@ -308,7 +320,9 @@ schema_init() sc_space_new(BOX_SEQUENCE_DATA_ID, "_sequence_data", key_def, &on_replace_sequence_data, NULL); - /* _space_seq - association space <-> sequence. */ + /* _space_seq - association space <-> sequence. + * + */ sc_space_new(BOX_SPACE_SEQUENCE_ID, "_space_sequence", key_def, &on_replace_space_sequence, NULL); @@ -520,6 +534,69 @@ sequence_cache_delete(uint32_t id) } } + +/** + * This function runs simple read, usage checks. Returns -1, 0 in + * case this checks are performed. + * All other checks are postponed. Returns 1 in thi + */ +static int +sys_read_access_check(struct space *space, user_access_t access) +{ + credentials *cr = effective_user(); + if (~cr->universal_access & PRIV_U) { + struct user *user = user_find(cr->uid); + if (user != NULL) + diag_set(AccessDeniedError, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + "", + user->def->name); + return -1; + } + if (access == PRIV_R) { + uint32_t access = cr->universal_access | + space->access[cr->auth_token].effective; + if (~access & PRIV_R) { + struct user *user = user_find(cr->uid); + if (user != NULL) + diag_set(AccessDeniedError, + priv_name(PRIV_R), + schema_object_name(SC_SPACE), + space->def->name, + user->def->name); + return -1; + } + } + /* Other checks are postponed to trigger*/ + return 0; +} + +static int +sys_read_write_access_check(struct space *space, + user_access_t access) +{ + credentials *cr = effective_user(); + if (access == PRIV_W) { + uint32_t has_access = space->access[cr->auth_token].effective + | cr->universal_access; + if (~has_access & access) { + struct user *user = user_find(cr->uid); + if (user != NULL) { + diag_set(AccessDeniedError, + priv_name(access), + schema_object_name(SC_SPACE), + space->def->name, + user->def->name); + } + return -1; + } + return 0; + } + return sys_read_access_check(space, access); +} + + const char * schema_find_name(enum schema_object_type type, uint32_t object_id) { @@ -562,3 +639,42 @@ schema_find_name(enum schema_object_type type, uint32_t object_id) return "(nil)"; } +uint32_t +schema_find_access(enum schema_object_type type, uint32_t object_id, + struct user *grantor) +{ + struct priv_def def; + def.object_type = type; + def.object_id = object_id; + def.grantor_id = grantor->def->uid; + uint32_t access = universe.access[grantor->auth_token].effective; + struct access *obj_access = access_find(&def); + if (obj_access != NULL) + access |= obj_access->effective; + return access; +} + +access_check_func_t +get_access_check_func(uint32_t space_id) +{ + switch (space_id) { + case BOX_COLLATION_ID: + case BOX_CLUSTER_ID: + return sys_read_write_access_check; + case BOX_SCHEMA_ID: + case BOX_SPACE_ID: + case BOX_TRUNCATE_ID: + case BOX_INDEX_ID: + case BOX_FUNC_ID: + case BOX_USER_ID: + case BOX_SEQUENCE_ID: + case BOX_SEQUENCE_DATA_ID: + case BOX_SPACE_SEQUENCE_ID: + case BOX_PRIV_ID: + /* Specialized access checks will be performed + * in trigger. */ + return sys_read_access_check; + default: + return access_check_user_space; + } +} diff --git a/src/box/schema.h b/src/box/schema.h index 2b87f5f..63b81f4 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -36,6 +36,8 @@ #include "error.h" #include "space.h" #include "latch.h" +#include "user.h" +#include "iproto_constants.h" #if defined(__cplusplus) extern "C" { @@ -103,6 +105,13 @@ schema_find_name(enum schema_object_type type, uint32_t object_id); */ struct sequence * sequence_by_id(uint32_t id); + +uint32_t +schema_find_access(enum schema_object_type type, uint32_t object_id, + struct user *grantor); + +int +access_check_user_space(struct space *space, user_access_t access); #if defined(__cplusplus) } /* extern "C" */ @@ -205,6 +214,12 @@ sequence_cache_delete(uint32_t id); #endif /* defined(__cplusplus) */ /** + * Utility function used to specify access_check function for system space. + */ +access_check_func_t +get_access_check_func(uint32_t space_id); + +/** * Triggers fired after committing a change in space definition. * The space is passed to the trigger callback in the event * argument. It is the new space in case of create/update or diff --git a/src/box/space.c b/src/box/space.c index cc6cbed..a7a0930 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -44,7 +44,7 @@ #include "iproto_constants.h" int -access_check_space(struct space *space, user_access_t access) +access_check_user_space(struct space *space, user_access_t access) { struct credentials *cr = effective_user(); /* Any space access also requires global USAGE privilege. */ @@ -132,6 +132,7 @@ space_create(struct space *space, struct engine *engine, rlist_create(&space->on_replace); rlist_create(&space->on_stmt_begin); space->run_triggers = true; + space->access_check = get_access_check_func(def->id); space->format = format; if (format != NULL) diff --git a/src/box/space.h b/src/box/space.h index 6408eed..5335b31 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -49,6 +49,8 @@ struct request; struct port; struct tuple; +typedef int (*access_check_func_t)(struct space *space, user_access_t access); + struct space_vtab { /** Free a space instance. */ void (*destroy)(struct space *); @@ -139,6 +141,7 @@ struct space_vtab { struct space { /** Virtual function table. */ const struct space_vtab *vtab; + access_check_func_t access_check; /** Cached runtime access information. */ struct access access[BOX_USER_MAX]; /** Engine used by this space. */ @@ -291,8 +294,11 @@ index_name_by_id(struct space *space, uint32_t id); * Check whether or not the current user can be granted * the requested access to the space. */ -int -access_check_space(struct space *space, user_access_t access); +static inline int +access_check_space(struct space *space, user_access_t access) +{ + return space->access_check(space, access); +} static inline int space_apply_initial_join_row(struct space *space, struct request *request) @@ -424,6 +430,12 @@ space_swap_index(struct space *lhs, struct space *rhs, void space_fill_index_map(struct space *space); +/* + * Utility function used to specify access_check function for system space. + */ +access_check_func_t +get_access_check_func(uint32_t space_id); + #if defined(__cplusplus) } /* extern "C" */ @@ -540,4 +552,5 @@ space_prepare_alter_xc(struct space *old_space, struct space *new_space) #endif /* defined(__cplusplus) */ + #endif /* TARANTOOL_BOX_SPACE_H_INCLUDED */ diff --git a/src/box/sysview_engine.c b/src/box/sysview_engine.c index b478e78..0f126d2 100644 --- a/src/box/sysview_engine.c +++ b/src/box/sysview_engine.c @@ -32,6 +32,9 @@ #include "sysview_index.h" #include "schema.h" #include "space.h" +#include "session.h" +#include "xrow.h" +#include "iproto_constants.h" static void sysview_space_destroy(struct space *space) @@ -230,6 +233,7 @@ sysview_engine_create_space(struct engine *engine, struct space_def *def, free(space); return NULL; } + space->access_check = access_check_user_space; return space; } diff --git a/src/box/user.cc b/src/box/user.cc index 7fa66da..3576efe 100644 --- a/src/box/user.cc +++ b/src/box/user.cc @@ -193,10 +193,6 @@ user_grant_priv(struct user *user, struct priv_def *def) } } -/** - * Find the corresponding access structure - * given object type and object id. - */ struct access * access_find(struct priv_def *priv) { diff --git a/src/box/user.h b/src/box/user.h index 07c4dc5..254f0c9 100644 --- a/src/box/user.h +++ b/src/box/user.h @@ -199,6 +199,13 @@ priv_grant(struct user *grantee, struct priv_def *priv); void priv_def_create_from_tuple(struct priv_def *priv, struct tuple *tuple); +/** + * Find the corresponding access structure + * given object type and object id. + */ +struct access * +access_find(struct priv_def *priv); + /* }}} */ #endif /* defined(__cplusplus) */ diff --git a/test/box/access.result b/test/box/access.result index 2b28f07..53816ea 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -378,7 +378,6 @@ box.schema.user.create('grantee') ... box.schema.user.grant('grantee', 'read, write, execute', 'universe') --- -- error: Grant access to universe '' is denied for user 'grantor' ... session.su('grantee') --- @@ -898,7 +897,6 @@ session.su('test') ... box.internal.collation.drop('test') -- fail --- -- error: Drop access to collation 'test' is denied for user 'test' ... session.su('admin') --- @@ -1174,11 +1172,11 @@ box.schema.space.create('test') ... box.schema.user.create('test') --- -- error: Write access to space '_user' is denied for user 'guest' +- error: Create access to user 'test' is denied for user 'guest' ... box.schema.func.create('test') --- -- error: Write access to space '_func' is denied for user 'guest' +- error: Create access to function 'test' is denied for user 'guest' ... box.session.su('admin') --- @@ -1377,21 +1375,16 @@ box.schema.space.create("test_space") ... box.schema.user.create('test_user') --- -- error: Write access to space '_user' is denied for user 'tester' +- error: Create access to user 'test_user' is denied for user 'tester' ... box.schema.func.create('test_func') --- -- error: Write access to space '_func' is denied for user 'tester' +- error: Create access to function 'test_func' is denied for user 'tester' ... box.session.su("admin") --- ... --- --- FIXME 2.0: we still need to grant 'write' on universe --- explicitly since we still use process_rw to write to system --- tables from ddl --- -box.schema.user.grant("tester", "create,write", "universe") +box.schema.user.grant("tester", "create", "universe") --- ... box.session.su("tester") @@ -1428,12 +1421,16 @@ box.schema.user.drop("test") --- - error: Revoke access to role 'public' is denied for user 'tester' ... -box.session.su("admin") +box.schema.func.drop("test") --- +- error: Drop access to function 'test' is denied for user 'tester' ... -box.session.su("tester", box.schema.func.drop, "test") +s:drop() +--- +- error: Drop access to space 'test' is denied for user 'tester' +... +box.session.su("admin") --- -- error: Drop access to function 'test' is denied for user 'tester' ... box.schema.user.grant("tester", "drop", "universe") --- diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 100dad5..6fbbaf6 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -520,12 +520,7 @@ box.schema.user.create('test_user') box.schema.func.create('test_func') box.session.su("admin") --- --- FIXME 2.0: we still need to grant 'write' on universe --- explicitly since we still use process_rw to write to system --- tables from ddl --- -box.schema.user.grant("tester", "create,write", "universe") +box.schema.user.grant("tester", "create", "universe") box.session.su("tester") -- successful create s1 = box.schema.space.create("test_space") @@ -548,9 +543,10 @@ box.schema.func.drop('test_func') -- box.session.su("tester", s.drop, s) box.schema.user.drop("test") +box.schema.func.drop("test") +s:drop() box.session.su("admin") -box.session.su("tester", box.schema.func.drop, "test") box.schema.user.grant("tester", "drop", "universe") -- successful drop diff --git a/test/box/access_misc.result b/test/box/access_misc.result index abc0be7..2f94b63 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -61,6 +61,9 @@ box.schema.user.grant('testus', 'read', 'space', 'admin_space') --- - error: User 'testus' already has read access on space 'admin_space' ... +box.schema.user.grant('testus', 'read', 'universe') +--- +... session.su('testus') --- ... @@ -78,7 +81,7 @@ s:delete(1) ... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'testus' +- error: Drop access to space 'admin_space' is denied for user 'testus' ... -- -- Check double revoke @@ -93,6 +96,9 @@ box.schema.user.revoke('testus', 'read', 'space', 'admin_space') --- - error: User 'testus' does not have read access on space 'admin_space' ... +box.schema.user.revoke('testus', 'read', 'universe') +--- +... session.su('testus') --- ... @@ -124,9 +130,18 @@ s:insert({3}) --- - [3] ... +session.su('admin') +--- +... +box.schema.user.grant('testus', 'read', 'universe') +--- +... +session.su('testus') +--- +... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'testus' +- error: Drop access to space 'admin_space' is denied for user 'testus' ... session.su('admin') --- @@ -167,9 +182,18 @@ s:delete({3}) --- - error: Write access to space 'admin_space' is denied for user 'guest' ... +session.su('admin') +--- +... +box.schema.user.grant('guest', 'read', 'universe') +--- +... +session.su('guest') +--- +... s:drop() --- -- error: Write access to space '_space_sequence' is denied for user 'guest' +- error: Drop access to space 'admin_space' is denied for user 'guest' ... gs = box.schema.space.create('guest_space') --- @@ -177,11 +201,14 @@ gs = box.schema.space.create('guest_space') ... box.schema.func.create('guest_func') --- -- error: Write access to space '_func' is denied for user 'guest' +- error: Create access to function 'guest_func' is denied for user 'guest' ... session.su('admin') --- ... +box.schema.user.revoke('guest', 'read', 'universe') +--- +... s:select() --- - - [2] @@ -277,7 +304,7 @@ session.su('admin') box.schema.user.create('someuser') --- ... -box.schema.user.grant('someuser', 'read, write, execute', 'universe') +box.schema.user.grant('someuser', 'read', 'universe') --- ... session.su('someuser') @@ -344,7 +371,7 @@ testuser_uid = session.uid() ... _ = box.space._user:delete(2) --- -- error: Drop access to user 'public' is denied for user 'testuser' +- error: 'Failed to drop user or role ''public'': the user or the role is a system' ... box.space._user:select(1) --- @@ -381,7 +408,7 @@ session.su('testuser') ... _ = box.space._user:delete(2) --- -- error: Write access to space '_user' is denied for user 'testuser' +- error: Drop access to user 'public' is denied for user 'testuser' ... box.space._user:select(1) --- @@ -389,7 +416,8 @@ box.space._user:select(1) ... box.space._user:insert{uid, session.uid(), 'someone2', 'user'} --- -- error: Write access to space '_user' is denied for user 'testuser' +- error: Tuple field count 4 is less than required by space format or defined indexes + (expected at least 5) ... session.su('admin') --- @@ -409,7 +437,7 @@ box.space._index:select(272) ... box.space._index:insert{512, 1,'owner','tree', 1, 1, 0,'unsigned'} --- -- error: Write access to space '_index' is denied for user 'testuser' +- error: 'Tuple field 5 type does not match one required by operation: expected map' ... session.su('admin') --- diff --git a/test/box/access_misc.test.lua b/test/box/access_misc.test.lua index cf6447e..e78058a 100644 --- a/test/box/access_misc.test.lua +++ b/test/box/access_misc.test.lua @@ -27,6 +27,7 @@ s:insert({2}) -- box.schema.user.grant('testus', 'read', 'space', 'admin_space') box.schema.user.grant('testus', 'read', 'space', 'admin_space') +box.schema.user.grant('testus', 'read', 'universe') session.su('testus') s:select(1) @@ -39,6 +40,7 @@ s:drop() session.su('admin') box.schema.user.revoke('testus', 'read', 'space', 'admin_space') box.schema.user.revoke('testus', 'read', 'space', 'admin_space') +box.schema.user.revoke('testus', 'read', 'universe') session.su('testus') s:select(1) @@ -52,6 +54,9 @@ session.su('testus') s:select(1) s:delete(1) s:insert({3}) +session.su('admin') +box.schema.user.grant('testus', 'read', 'universe') +session.su('testus') s:drop() session.su('admin') -- @@ -68,11 +73,15 @@ box.space._user:select(1) s:select(1) s:insert({4}) s:delete({3}) +session.su('admin') +box.schema.user.grant('guest', 'read', 'universe') +session.su('guest') s:drop() gs = box.schema.space.create('guest_space') box.schema.func.create('guest_func') session.su('admin') +box.schema.user.revoke('guest', 'read', 'universe') s:select() -- -- Create user with universe read&write grants @@ -116,7 +125,7 @@ box.schema.func.create('uniuser_func') session.su('admin') box.schema.user.create('someuser') -box.schema.user.grant('someuser', 'read, write, execute', 'universe') +box.schema.user.grant('someuser', 'read', 'universe') session.su('someuser') -- -- Check drop objects of another user diff --git a/test/box/net.box.result b/test/box/net.box.result index 46d85b3..1b62584 100644 --- a/test/box/net.box.result +++ b/test/box/net.box.result @@ -2140,6 +2140,9 @@ box.schema.user.grant('guest', 'write', 'space', '_space') box.schema.user.grant('guest', 'write', 'space', '_schema') --- ... +box.schema.user.grant('guest', 'create', 'universe') +--- +... count = 0 --- ... @@ -2188,6 +2191,9 @@ box.schema.user.revoke('guest', 'write', 'space', '_space') box.schema.user.revoke('guest', 'write', 'space', '_schema') --- ... +box.schema.user.revoke('guest', 'create', 'universe') +--- +... c:close() --- ... diff --git a/test/box/net.box.test.lua b/test/box/net.box.test.lua index 87e26f8..f9272f2 100644 --- a/test/box/net.box.test.lua +++ b/test/box/net.box.test.lua @@ -877,6 +877,7 @@ box.session.on_disconnect(nil, on_disconnect) -- box.schema.user.grant('guest', 'write', 'space', '_space') box.schema.user.grant('guest', 'write', 'space', '_schema') +box.schema.user.grant('guest', 'create', 'universe') count = 0 function create_space(name) count = count + 1 box.schema.create_space(name) return true end c = net.connect(box.cfg.listen) @@ -891,6 +892,7 @@ box.space.test2:drop() box.space.test3:drop() box.schema.user.revoke('guest', 'write', 'space', '_space') box.schema.user.revoke('guest', 'write', 'space', '_schema') +box.schema.user.revoke('guest', 'create', 'universe') c:close() -- diff --git a/test/box/role.result b/test/box/role.result index 806cea9..b8055ff 100644 --- a/test/box/role.result +++ b/test/box/role.result @@ -671,7 +671,7 @@ box.session.su('admin') _ = box.schema.space.create('test') --- ... -box.schema.user.grant('john', 'read,write,execute', 'universe') +box.schema.user.grant('john', 'read', 'universe') --- ... box.session.su('john') @@ -695,14 +695,33 @@ box.schema.user.grant('grantee', 'public') - error: User 'grantee' already has role 'public' ... -- --- revoking role 'public' is another deal - only the --- superuser can do that, and even that would be useless, +-- revoking role 'public' is another deal - the +-- superuser or creator of user can do that, and even that would be useless, -- since one can still re-grant it back to oneself. -- box.schema.user.revoke('grantee', 'public') --- - error: Revoke access to role 'public' is denied for user 'john' ... +box.session.su("admin") +--- +... +box.schema.user.grant("john", "create", 'universe') +--- +... +box.session.su('john') +--- +... +box.schema.user.create("grantee2") +--- +... +-- must be ok +box.schema.user.revoke('grantee2', 'public') +--- +... +box.schema.user.drop('grantee2') +--- +... box.session.su('admin') --- ... diff --git a/test/box/role.test.lua b/test/box/role.test.lua index e97339f..a1d6808 100644 --- a/test/box/role.test.lua +++ b/test/box/role.test.lua @@ -261,7 +261,7 @@ box.schema.user.grant('grantee', 'role') -- box.session.su('admin') _ = box.schema.space.create('test') -box.schema.user.grant('john', 'read,write,execute', 'universe') +box.schema.user.grant('john', 'read', 'universe') box.session.su('john') box.schema.user.grant('grantee', 'role') box.schema.user.grant('grantee', 'read', 'space', 'test') @@ -272,11 +272,18 @@ box.schema.user.grant('grantee', 'read', 'space', 'test') -- box.schema.user.grant('grantee', 'public') -- --- revoking role 'public' is another deal - only the --- superuser can do that, and even that would be useless, +-- revoking role 'public' is another deal - the +-- superuser or creator of user can do that, and even that would be useless, -- since one can still re-grant it back to oneself. -- box.schema.user.revoke('grantee', 'public') +box.session.su("admin") +box.schema.user.grant("john", "create", 'universe') +box.session.su('john') +box.schema.user.create("grantee2") +-- must be ok +box.schema.user.revoke('grantee2', 'public') +box.schema.user.drop('grantee2') box.session.su('admin') box.schema.user.drop('john') diff --git a/test/box/sequence.result b/test/box/sequence.result index a509207..631a8dd 100644 --- a/test/box/sequence.result +++ b/test/box/sequence.result @@ -1447,7 +1447,7 @@ box.session.su('admin') --- ... -- A user cannot alter sequences created by other users. -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read', 'universe') --- ... box.session.su('user') @@ -1518,7 +1518,7 @@ s1 = box.schema.space.create('space1') _ = s1:create_index('pk') --- ... -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read, create', 'universe') --- ... box.session.su('user') @@ -1532,15 +1532,14 @@ s2 = box.schema.space.create('space2') ... _ = s2:create_index('pk', {sequence = 'seq1'}) -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' ... s1.index.pk:alter({sequence = 'seq1'}) -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to space 'space1' is denied for user 'user' ... box.space._space_sequence:replace{s1.id, sq1.id, false} -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to space 'space1' is denied for user 'user' ... box.space._space_sequence:replace{s1.id, sq2.id, false} -- error --- @@ -1548,7 +1547,7 @@ box.space._space_sequence:replace{s1.id, sq2.id, false} -- error ... box.space._space_sequence:replace{s2.id, sq1.id, false} -- error --- -- error: Create access to sequence 'seq1' is denied for user 'user' +- error: Alter access to sequence 'seq1' is denied for user 'user' ... s2.index.pk:alter({sequence = 'seq2'}) -- ok --- @@ -1559,7 +1558,7 @@ box.session.su('admin') -- If the user owns a sequence attached to a space, -- it can use it for auto increment, otherwise it -- needs privileges. -box.schema.user.revoke('user', 'read,write', 'universe') +box.schema.user.revoke('user', 'read,create', 'universe') --- ... box.session.su('user') @@ -1697,10 +1696,10 @@ box.schema.user.create('user1') box.schema.user.create('user2') --- ... -box.schema.user.grant('user1', 'read,write', 'universe') +box.schema.user.grant('user1', 'read, create', 'universe') --- ... -box.schema.user.grant('user2', 'read,write', 'universe') +box.schema.user.grant('user2', 'read', 'universe') --- ... box.session.su('user1') diff --git a/test/box/sequence.test.lua b/test/box/sequence.test.lua index 26147bb..f6d4c29 100644 --- a/test/box/sequence.test.lua +++ b/test/box/sequence.test.lua @@ -481,7 +481,7 @@ sq:reset() -- error box.session.su('admin') -- A user cannot alter sequences created by other users. -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read', 'universe') box.session.su('user') sq:alter{step = 2} -- error sq:drop() -- error @@ -507,7 +507,7 @@ sq:drop() sq1 = box.schema.sequence.create('seq1') s1 = box.schema.space.create('space1') _ = s1:create_index('pk') -box.schema.user.grant('user', 'read,write', 'universe') +box.schema.user.grant('user', 'read, create', 'universe') box.session.su('user') sq2 = box.schema.sequence.create('seq2') s2 = box.schema.space.create('space2') @@ -522,7 +522,7 @@ box.session.su('admin') -- If the user owns a sequence attached to a space, -- it can use it for auto increment, otherwise it -- needs privileges. -box.schema.user.revoke('user', 'read,write', 'universe') +box.schema.user.revoke('user', 'read,create', 'universe') box.session.su('user') s2:insert{nil, 1} -- ok: {1, 1} box.session.su('admin') @@ -570,8 +570,8 @@ box.sequence -- to a sequence. box.schema.user.create('user1') box.schema.user.create('user2') -box.schema.user.grant('user1', 'read,write', 'universe') -box.schema.user.grant('user2', 'read,write', 'universe') +box.schema.user.grant('user1', 'read, create', 'universe') +box.schema.user.grant('user2', 'read', 'universe') box.session.su('user1') sq = box.schema.sequence.create('test') box.session.su('user2') diff --git a/test/engine/truncate.result b/test/engine/truncate.result index b6e1a99..1547b8c 100644 --- a/test/engine/truncate.result +++ b/test/engine/truncate.result @@ -506,7 +506,7 @@ con = require('net.box').connect(box.cfg.listen) ... con:eval([[box.space.access_truncate:truncate()]]) --- -- error: Write access to space 'access_truncate' is denied for user 'guest' +- error: Drop access to space 'access_truncate' is denied for user 'guest' ... con.space.access_truncate:select() --- -- 2.7.4