diff --git a/src/box/alter.cc b/src/box/alter.cc index 5b3f7348203ce78608edd9af74712f9d08560547..680a051f79375c9754a7b20a22fa7f04f39e2a5b 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -78,13 +78,13 @@ access_check_ddl(const char *name, uint32_t owner_uid, if (access || (owner_uid != cr->uid && cr->uid != ADMIN)) { struct user *user = user_find_xc(cr->uid); if (access) { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(PRIV_U), schema_object_name(SC_UNIVERSE), "", user->def->name); } else { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(type), name, @@ -907,7 +907,8 @@ ModifySpace::~ModifySpace() { /** DropIndex - remove an index from space. */ -class DropIndex: public AlterSpaceOp { +class DropIndex: public AlterSpaceOp +{ public: DropIndex(struct alter_space *alter, struct index_def *def_arg) :AlterSpaceOp(alter), old_index_def(def_arg) {} @@ -1059,7 +1060,8 @@ ModifyIndex::~ModifyIndex() } /** CreateIndex - add a new index to the space. */ -class CreateIndex: public AlterSpaceOp { +class CreateIndex: public AlterSpaceOp +{ public: CreateIndex(struct alter_space *alter) :AlterSpaceOp(alter), @@ -1135,7 +1137,8 @@ CreateIndex::~CreateIndex() * from by reading the primary key. Used when key_def of * an index is changed. */ -class RebuildIndex: public AlterSpaceOp { +class RebuildIndex: public AlterSpaceOp +{ public: RebuildIndex(struct alter_space *alter, struct index_def *new_index_def_arg, @@ -2338,7 +2341,7 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) switch (priv->object_type) { case SC_UNIVERSE: if (grantor->def->uid != ADMIN) { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(SC_UNIVERSE), name, @@ -2350,7 +2353,7 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) struct space *space = space_cache_find_xc(priv->object_id); if (space->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(SC_SPACE), name, grantor->def->name); @@ -2362,7 +2365,7 @@ 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(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(SC_FUNCTION), name, grantor->def->name); @@ -2374,7 +2377,7 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) struct sequence *seq = sequence_cache_find(priv->object_id); if (seq->def->uid != grantor->def->uid && grantor->def->uid != ADMIN) { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(SC_SEQUENCE), name, grantor->def->name); @@ -2396,7 +2399,7 @@ priv_def_check(struct priv_def *priv, enum priv_type priv_type) if (role->def->owner != grantor->def->uid && grantor->def->uid != ADMIN && (role->def->uid != PUBLIC || priv->access < PRIV_X)) { - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(priv_type), schema_object_name(SC_ROLE), name, grantor->def->name);; diff --git a/src/box/call.cc b/src/box/call.cc index aca87146742a397274de71065b4a837ef1fa6e9c..b597dac3ba4ffe329e77913b806ef5e99d287ccf 100644 --- a/src/box/call.cc +++ b/src/box/call.cc @@ -79,12 +79,12 @@ access_check_func(const char *name, uint32_t name_len, struct func **funcp) struct user *user = user_find(credentials->uid); if (user != NULL) { if (!(access & credentials->universal_access)) { - diag_set(ClientError, ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(PRIV_U), schema_object_name(SC_UNIVERSE), "", user->def->name); } else { - diag_set(ClientError, ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(PRIV_X), schema_object_name(SC_FUNCTION), tt_cstr(name, name_len), diff --git a/src/box/error.cc b/src/box/error.cc index 53ffcfbff7202936a4936d7130ef12056947cd47..647b17e6f87db3927ff225233ef64971c10ae9e4 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -99,17 +99,23 @@ static struct method_info clienterror_methods[] = { const struct type_info type_ClientError = make_type("ClientError", &type_Exception, clienterror_methods); +ClientError::ClientError(const type_info *type, const char *file, unsigned line, + uint32_t errcode) + :Exception(type, file, line) +{ + m_errcode = errcode; + if (rmean_error) + rmean_collect(rmean_error, RMEAN_ERROR, 1); +} + ClientError::ClientError(const char *file, unsigned line, uint32_t errcode, ...) - : Exception(&type_ClientError, file, line) + :ClientError(&type_ClientError, file, line, errcode) { - m_errcode = errcode; va_list ap; va_start(ap, errcode); error_vformat_msg(this, tnt_errcode_desc(m_errcode), ap); va_end(ap); - if (rmean_error) - rmean_collect(rmean_error, RMEAN_ERROR, 1); } struct error * @@ -165,3 +171,57 @@ BuildXlogError(const char *file, unsigned line, const char *format, ...) } } +#include "schema.h" +#include "trigger.h" + +struct rlist on_access_denied = RLIST_HEAD_INITIALIZER(on_access_denied); + +static struct method_info accessdeniederror_methods[] = { + make_method(&type_AccessDeniedError, "access_type", &AccessDeniedError::access_type), + make_method(&type_AccessDeniedError, "object_type", &AccessDeniedError::object_type), + make_method(&type_AccessDeniedError, "object_name", &AccessDeniedError::object_name), + METHODS_SENTINEL +}; + +const struct type_info type_AccessDeniedError = + make_type("AccessDeniedError", &type_ClientError, + accessdeniederror_methods); + +AccessDeniedError::AccessDeniedError(const char *file, unsigned int line, + const char *access_type, + const char *object_type, + const char *object_name, + const char *user_name) + :ClientError(&type_AccessDeniedError, file, line, ER_ACCESS_DENIED) +{ + error_format_msg(this, tnt_errcode_desc(m_errcode), + access_type, object_type, object_name, user_name); + + struct on_access_denied_ctx ctx = {access_type, object_type, object_name}; + trigger_run(&on_access_denied, (void *) &ctx); + /* + * We want to use ctx parameters as error parameters + * later, so we have to alloc space for it. + * As m_access_type and m_object_type are constant + * literals they are statically allocated. We must copy + * only m_object_name. + */ + m_object_type = object_type; + m_access_type = access_type; + m_object_name = strdup(object_name); +} + +struct error * +BuildAccessDeniedError(const char *file, unsigned int line, + const char *access_type, const char *object_type, + const char *object_name, + const char *user_name) +{ + try { + return new AccessDeniedError(file, line, access_type, + object_type, object_name, + user_name); + } catch (OutOfMemory *e) { + return e; + } +} diff --git a/src/box/error.h b/src/box/error.h index 63b4a7d6c501ec680900e0b242ef7dfe4b23ac6e..c791e6c6a0b580892a4841a99b9b395cd9adc4df 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -39,6 +39,12 @@ extern "C" { struct error * BuildClientError(const char *file, unsigned line, uint32_t errcode, ...); +struct error * +BuildAccessDeniedError(const char *file, unsigned int line, + const char *access_type, const char *object_type, + const char *object_name, const char *user_name); + + /** \cond public */ struct error; @@ -125,6 +131,7 @@ box_error_set(const char *file, unsigned line, uint32_t code, extern const struct type_info type_ClientError; extern const struct type_info type_XlogError; +extern const struct type_info type_AccessDeniedError; #if defined(__cplusplus) } /* extern "C" */ @@ -139,7 +146,8 @@ enum rmean_error_name { }; extern const char *rmean_error_strings[RMEAN_ERROR_LAST]; -class ClientError: public Exception { +class ClientError: public Exception +{ public: virtual void raise() { @@ -159,9 +167,13 @@ class ClientError: public Exception { static uint32_t get_errcode(const struct error *e); /* client errno code */ int m_errcode; +protected: + ClientError(const type_info *type, const char *file, unsigned line, + uint32_t errcode); }; -class LoggedError: public ClientError { +class LoggedError: public ClientError +{ public: template <typename ... Args> LoggedError(const char *file, unsigned line, uint32_t errcode, Args ... args) @@ -172,6 +184,49 @@ class LoggedError: public ClientError { } }; +/** + * A special type of exception which must be used + * for all access denied errors, since it invokes audit triggers. + */ +class AccessDeniedError: public ClientError +{ +public: + AccessDeniedError(const char *file, unsigned int line, + const char *access_type, const char *object_type, + const char *object_name, const char *user_name); + + ~AccessDeniedError() + { + free(m_object_name); + } + + const char * + object_type() + { + return m_object_type; + } + + const char * + object_name() + { + return m_object_name?:"(nil)"; + } + + const char * + access_type() + { + return m_access_type; + } + +private: + /** Type of object the required access was denied to */ + const char *m_object_type; + /** Name of object the required access was denied to */ + char *m_object_name; + /** Type of declined access */ + const char *m_access_type; +}; + /** * XlogError is raised when there is an error with contents * of the data directory or a log file. A special subclass diff --git a/src/box/lua/session.c b/src/box/lua/session.c index 54baca1e563f86f0324bdb82049395f9f8e7a766..0bdfb8c77a14504821cdcdbf5fb608aac81931ae 100644 --- a/src/box/lua/session.c +++ b/src/box/lua/session.c @@ -40,6 +40,7 @@ #include "box/box.h" #include "box/session.h" #include "box/user.h" +#include "box/schema.h" static const char *sessionlib_name = "box.session"; @@ -344,6 +345,27 @@ lbox_session_run_on_auth(struct lua_State *L) return 0; } +static int +lbox_push_on_access_denied_event(struct lua_State *L, void *event) +{ + struct on_access_denied_ctx *ctx = (struct on_access_denied_ctx *) event; + lua_pushstring(L, ctx->access_type); + lua_pushstring(L, ctx->object_type); + lua_pushstring(L, ctx->object_name); + return 3; +} + +/** + * Sets trigger on_access_denied. + * For test purposes only. + */ +static int +lbox_session_on_access_denied(struct lua_State *L) +{ + return lbox_trigger_reset(L, 2, &on_access_denied, + lbox_push_on_access_denied_event); +} + void session_storage_cleanup(int sid) { @@ -403,6 +425,7 @@ box_lua_session_init(struct lua_State *L) {"on_connect", lbox_session_on_connect}, {"on_disconnect", lbox_session_on_disconnect}, {"on_auth", lbox_session_on_auth}, + {"on_access_denied", lbox_session_on_access_denied}, {NULL, NULL} }; luaL_register_module(L, sessionlib_name, sessionlib); diff --git a/src/box/schema.h b/src/box/schema.h index 7a1cbbf99be24381e9d8c1b823fd04524299295a..56f39b3fe084e8a2e17174e20531c7bc7ea2640d 100644 --- a/src/box/schema.h +++ b/src/box/schema.h @@ -219,4 +219,21 @@ extern struct rlist on_alter_space; */ extern struct rlist on_alter_sequence; +/** + * Triggers fired after access denied error is created. + */ +extern struct rlist on_access_denied; + +/** + * Context passed to on_access_denied trigger. + */ +struct on_access_denied_ctx { + /** Type of declined access */ + const char *access_type; + /** Type of object the required access was denied to */ + const char *object_type; + /** Name of object the required access was denied to */ + const char *object_name; +}; + #endif /* INCLUDES_TARANTOOL_BOX_SCHEMA_H */ diff --git a/src/box/sequence.c b/src/box/sequence.c index b549e09d5aa9ccf7f54f56476f2085b3e5b3091e..0f6a8ca974e1af94978a2920b78fc568c167fde8 100644 --- a/src/box/sequence.c +++ b/src/box/sequence.c @@ -256,13 +256,12 @@ access_check_sequence(struct sequence *seq) struct user *user = user_find(cr->uid); if (user != NULL) { if (!(cr->universal_access & PRIV_U)) { - diag_set(ClientError, ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(PRIV_U), schema_object_name(SC_UNIVERSE), "", user->def->name); } else { - diag_set(ClientError, - ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(access), schema_object_name(SC_SEQUENCE), seq->def->name, user->def->name); diff --git a/src/box/session.cc b/src/box/session.cc index 758e9236790ff8966c749fbbc69f9750f51498be..cb31cc5fd6b5e22bab566b3cfff3da4799b50be0 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -241,7 +241,7 @@ access_check_session(struct user *user) * as current_user is not assigned yet */ if (!(universe.access[user->auth_token].effective & PRIV_S)) { - diag_set(ClientError, ER_ACCESS_DENIED, priv_name(PRIV_S), + diag_set(AccessDeniedError, priv_name(PRIV_S), schema_object_name(SC_UNIVERSE), "", user->def->name); return -1; @@ -271,7 +271,7 @@ access_check_universe(user_access_t access) struct user *user = user_find_xc(credentials->uid); int denied_access = access & ((credentials->universal_access & access) ^ access); - tnt_raise(ClientError, ER_ACCESS_DENIED, + tnt_raise(AccessDeniedError, priv_name(denied_access), schema_object_name(SC_UNIVERSE), "", user->def->name); diff --git a/src/box/space.c b/src/box/space.c index 212aa1cd779aa7df3bb7913cd9c44865ee2e531d..c02eb886326d732fe5129900d44965c5546ae647 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -66,13 +66,12 @@ access_check_space(struct space *space, user_access_t access) struct user *user = user_find(cr->uid); if (user != NULL) { if (!(cr->universal_access & PRIV_U)) { - diag_set(ClientError, ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(PRIV_U), schema_object_name(SC_UNIVERSE), "", user->def->name); } else { - diag_set(ClientError, - ER_ACCESS_DENIED, + diag_set(AccessDeniedError, priv_name(access), schema_object_name(SC_SPACE), space->def->name, user->def->name); diff --git a/test/box/access.result b/test/box/access.result index c8cc9f09238ff33163ba1580b0142f034ed2601c..d0beb0451a3721c78cb07b97d658f1ac68cb5341 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -1117,3 +1117,166 @@ box.schema.func.create('test') box.session.su('admin') --- ... +-- +-- gh-2911 on_access_denied trigger +-- +obj_type = nil +--- +... +obj_name = nil +--- +... +op_type = nil +--- +... +euid = nil +--- +... +auid = nil +--- +... +function access_denied_trigger(op, type, name) obj_type = type; obj_name = name; op_type = op end +--- +... +function uid() euid = box.session.euid(); auid = box.session.uid() end +--- +... +_ = box.session.on_access_denied(access_denied_trigger) +--- +... +_ = box.session.on_access_denied(uid) +--- +... +s = box.schema.space.create('admin_space', {engine="vinyl"}) +--- +... +seq = box.schema.sequence.create('test_sequence') +--- +... +index = s:create_index('primary', {type = 'tree', parts = {1, 'unsigned'}}) +--- +... +box.schema.user.create('test_user', {password="pass"}) +--- +... +box.session.su("test_user") +--- +... +s:select{} +--- +- error: Read access to space 'admin_space' is denied for user 'test_user' +... +obj_type, obj_name, op_type +--- +- space +- admin_space +- Read +... +euid, auid +--- +- 32 +- 32 +... +seq:set(1) +--- +- error: Write access to sequence 'test_sequence' is denied for user 'test_user' +... +obj_type, obj_name, op_type +--- +- sequence +- test_sequence +- Write +... +euid, auid +--- +- 32 +- 32 +... +box.session.su("admin") +--- +... +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +--- +... +function func() end +--- +... +st, e = pcall(c.call, c, func) +--- +... +obj_type, op_type +--- +- function +- Execute +... +euid, auid +--- +- 32 +- 32 +... +obj_name:match("function") +--- +- function +... +box.schema.user.revoke("test_user", "usage", "universe") +--- +... +box.session.su("test_user") +--- +... +st, e = pcall(s.select, s, {}) +--- +... +e = e:unpack() +--- +... +e.type, e.access_type, e.object_type, e.message +--- +- AccessDeniedError +- Usage +- universe +- Usage access to universe '' is denied for user 'test_user' +... +obj_type, obj_name, op_type +--- +- universe +- +- Usage +... +euid, auid +--- +- 32 +- 32 +... +box.session.su("admin") +--- +... +box.schema.user.revoke("test_user", "session", "universe") +--- +... +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +--- +... +obj_type, obj_name, op_type +--- +- universe +- +- Session +... +euid, auid +--- +- 0 +- 0 +... +box.session.on_access_denied(nil, access_denied_trigger) +--- +... +box.session.on_access_denied(nil, uid) +--- +... +box.schema.user.drop("test_user") +--- +... +s:drop() +--- +... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index 8f366ddde729a88dd1b44fdf1ff5d42fdd00fb3a..0d5690a4251db99c6008952d336c310d5d4e3f76 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -426,3 +426,50 @@ box.schema.space.create('test') box.schema.user.create('test') box.schema.func.create('test') box.session.su('admin') + +-- +-- gh-2911 on_access_denied trigger +-- +obj_type = nil +obj_name = nil +op_type = nil +euid = nil +auid = nil +function access_denied_trigger(op, type, name) obj_type = type; obj_name = name; op_type = op end +function uid() euid = box.session.euid(); auid = box.session.uid() end +_ = box.session.on_access_denied(access_denied_trigger) +_ = box.session.on_access_denied(uid) +s = box.schema.space.create('admin_space', {engine="vinyl"}) +seq = box.schema.sequence.create('test_sequence') +index = s:create_index('primary', {type = 'tree', parts = {1, 'unsigned'}}) +box.schema.user.create('test_user', {password="pass"}) +box.session.su("test_user") +s:select{} +obj_type, obj_name, op_type +euid, auid +seq:set(1) +obj_type, obj_name, op_type +euid, auid +box.session.su("admin") +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +function func() end +st, e = pcall(c.call, c, func) +obj_type, op_type +euid, auid +obj_name:match("function") +box.schema.user.revoke("test_user", "usage", "universe") +box.session.su("test_user") +st, e = pcall(s.select, s, {}) +e = e:unpack() +e.type, e.access_type, e.object_type, e.message +obj_type, obj_name, op_type +euid, auid +box.session.su("admin") +box.schema.user.revoke("test_user", "session", "universe") +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user", password="pass"}) +obj_type, obj_name, op_type +euid, auid +box.session.on_access_denied(nil, access_denied_trigger) +box.session.on_access_denied(nil, uid) +box.schema.user.drop("test_user") +s:drop()