From 74ab44ae7657989a233abfda05a30fd4738afae6 Mon Sep 17 00:00:00 2001 From: Ilya <markovilya197@gmail.com> Date: Sat, 2 Dec 2017 22:57:33 +0300 Subject: [PATCH] box: introduce system privileges Add system privileges 'session' and 'usage' * 'session' privilege lets user connect to database server * 'usage' privilege lets user use his/her rights on database objects * Both privileges are assigned to all users by default. Implementation details: * system privileges are special grant rights to 'universe'. Therefore, they can be granted only by admin. Because of this fact, during creation or deletion of user, we have to switch to 'admin' to grant or revoke these rights. Important changes: * changed bootstrap.snap due to need to start admin with new privileges * added auto upgrade script for 1.7.7 Fixes gh-2898. With contributions by @kostja. --- src/box/alter.cc | 28 ++++++- src/box/authentication.cc | 2 + src/box/bootstrap.snap | Bin 1476 -> 1488 bytes src/box/call.cc | 27 +++++-- src/box/lua/schema.lua | 47 ++++++++++-- src/box/lua/session.c | 3 + src/box/lua/upgrade.lua | 22 +++++- src/box/sequence.c | 23 ++++-- src/box/session.cc | 48 +++++++++++- src/box/session.h | 33 ++++---- src/box/space.c | 28 +++++-- src/box/space.h | 4 +- test/box-py/bootstrap.result | 5 +- test/box-tap/session.test.lua | 12 ++- test/box/access.result | 140 ++++++++++++++++++++++++++++++++-- test/box/access.test.lua | 48 ++++++++++++ test/box/access_misc.result | 9 ++- test/box/access_misc.test.lua | 2 + test/box/role.result | 11 ++- test/box/role.test.lua | 2 +- test/box/sequence.result | 3 + test/xlog/upgrade.result | 6 +- 22 files changed, 435 insertions(+), 68 deletions(-) diff --git a/src/box/alter.cc b/src/box/alter.cc index 061647c4fe..ecc43a83a3 100644 --- a/src/box/alter.cc +++ b/src/box/alter.cc @@ -72,11 +72,20 @@ access_check_ddl(uint32_t owner_uid, enum schema_object_type type) * since Tarantool lacks separate CREATE/DROP/GRANT OPTION * privileges. */ - if (owner_uid != cr->uid && cr->uid != ADMIN) { + user_access_t access = PRIV_U & ~cr->universal_access; + if (access || (owner_uid != cr->uid && cr->uid != ADMIN)) { struct user *user = user_find_xc(cr->uid); - tnt_raise(ClientError, ER_ACCESS_DENIED, - "Create, drop or alter", schema_object_name(type), - user->def->name); + if (access) { + tnt_raise(ClientError, ER_ACCESS_DENIED, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + user->def->name); + } else { + tnt_raise(ClientError, ER_ACCESS_DENIED, + "Create, drop or alter", + schema_object_name(type), + user->def->name); + } } } @@ -2451,6 +2460,17 @@ on_replace_dd_priv(struct trigger * /* trigger */, void *event) if (new_tuple != NULL && old_tuple == NULL) { /* grant */ priv_def_create_from_tuple(&priv, new_tuple); + /* + * Add system privileges explicitly to the + * universe grant issued prior to 1.7.7 in + * case upgrade script has not been invoked. + */ + if (priv.object_type == SC_UNIVERSE && + dd_version_id < version_id(1, 7, 7)) { + + priv.access |= PRIV_S; + priv.access |= PRIV_U; + } priv_def_check(&priv); grant_or_revoke(&priv); struct trigger *on_rollback = diff --git a/src/box/authentication.cc b/src/box/authentication.cc index d2c96c254d..daf638523f 100644 --- a/src/box/authentication.cc +++ b/src/box/authentication.cc @@ -56,6 +56,8 @@ authenticate(const char *user_name, uint32_t len, goto ok; } + access_check_session_xc(user); + if (part_count < 2) { /* Expected at least: authentication mechanism and data. */ tnt_raise(ClientError, ER_INVALID_MSGPACK, diff --git a/src/box/bootstrap.snap b/src/box/bootstrap.snap index ecb047196e3383961b6837f8e7e764fdea45b617..b1ec58e4174747b1135ff6841e2fc8b1a7c99e19 100644 GIT binary patch delta 1483 zcmV;+1vL7^3(yOY7k@S_F)}tSXE8HmG&N>o3Q2BrbYX5|WjY`^HZU?XF*q?TVq<1B zEi^JPWG!JiW;HE1IAvlmHZWp2VlXfYRzqxWV{1Afdoem7FntON)w&D1%>`5d&W&D? zucZJ00000ewJ-euP+ckjR=dwh@RU@{z%%d+Eb^3-w}J2M1AjM?%3`rrP<Nj1B8tO^ zM5fY^l29<a`I}D6VUiU9tW^bRW<&@BS_;Zz$x{geBe$3UFF8Xgr4+jWvH-*YBCPM% zIAN)J&B|PlH&y<z)1$-OF9?SC142^*yp7qruBNec-ksN8gmu~u5007BUGFNd>sy$A zHi|-lcyJ*Awtr3%wy!I1(bnoL{k!v<PR1XQYGUgoarlr?V#SQUD#TfjHrm6bSX(D~ zazb^O`Tmz}hpm$gqCl$r<y=>d+B(UY&ciaD>Hby0l<?`^62_^R(n|-GSHdS5+&#=o z%aZ+_k07?iI21Z(5|kZ}Mvee4c$Cl}F{yJWEZZw@@qa6+r$Dx!k0umbC)w)bmF?%_ zSJE!Q4y?!3>#I=`0$V3J_UAjW9#>yUM+bU11N(|)p7rFsO6RXeSqj)X$q~G-GS;u^ z6GB%A*g8pl9}L43h+*Ht7ngnC&dZ#uvun1W^<8J1_?^>w=pS!9|9q&*0G!V+4&%1h zXQLtkcYp6&Ue^v+HP!`JW!GbfKNa|S^`kROe~riEEAp>KLnpRQQoODi?_x*;FAl+4 z`}yeNk`tzgP%7g_B5w31#0`j=4;1AAbvk*c4yTz|t$H%ki?MZ*Hm1XaF};{frmo}A zxpXDc29gF+h7oBrvUQR?BMjVy7-xZ{CB{;r#eZ*Wg@OvF!lE)kBtf(yQmHi*fyQ`@ z&=7=>h|-X)lT3LqV?0NGypi=-)A`?>Hc=>0569v__5JSG&-!={@}px2y=h?UBxC(z zJ?{Sb(3%umCppHx8$~gA<t?5T*gDB)J(yp4i#}h+Un3R%{D)oN9bK0?rMj6e^`2Lf zS$~~wN!7i?lwwvmRWf-dn3rzm)Ut%eI1~)}0%cv)nieM(CL2->w}OgP*gDCYLV?Pf zg6c4LtjE=@QW@u=2nq^HLQmK_$<Tbh|CRN4Ghyo_P3ojF&MSd4DE|JTmc)bMrr`{x zMzKb#jO%E0cByVjDazJKhG4B)B8-DHHh(Cvh8Qy*2j-%8q7BZ!TCXCe+u|khwy=-R zem>GV|B+SFDdV~kwoZ~p28$UDS30`{fextASY(hAm;nF)0Z;$|21hB!ZXObVz&MJd zFbZK924n~@2o4wl0KtKffC!)#0JIn_=~Ao19!$xNwXrtVhT-(^%yon1Lh&YtO@Hg6 zZFXr30&zj+Lx=pwfKiVzKBy#1qM{rTtZuVU8xk}7CF$))U#kYix8?l^<U!u!d@>FS z|8;(W#945ed_G)Tj!}u9#3Smf*7qV1wJ-=H^?A30ln*Dy@p!IgqsNL$Zv%fG9RA4d z1A>fgBV+=aY$Id>ny`&-YwMFb4u6zU){w$S3t}1#ov;K5l43$MJ{m5F=d~$n4R{01 zVhwl$Z5R>qY-8DPFRgq?xgDh5`b|i1e+i{JDzmNoj=SQDA^5fQHr(n>umm!i{<Ndf zf!|D-p@gPU<oCeLHn)sVe+=+;-Gi6}Y2Y9n{L&T=8pAbiRWM8zr@EALXnzP1C+;Uz zdjO!fD_`i@7?PZ&Yw1UsFp{++gx%&MK@g6jcSw$);>U|RJ3S;`7~TdP0n@P?gvTX; zW&mCU>~e)>%J8M!*~_#a*9f+So`@XaxnNX?Tv%BEq%aHzkPK;>^Kga&!NV}>gxR#X zw^D7(z%TNecVt4Klkbt8Eks^A=~O()->dW=36J4BLKO(qWJO{#*$-v70&MC<Uzycf ljnKcFItic9Y9P|Tp>-?~)Ua&o2qJt%)|2Fm%K_C8t?jsJw#EPe delta 1471 zcmV;w1wi`H3&abM7k@P^GB-CZXJjxkW-u~1F*6EDZgX^DZewLSAT(uWGBh$aF)d>- zF=Q<?Gc-0WVKz1}EoEV3HaR&pVPiNsH40WkY;R+0Iv{&7Iv_B83JTS_3%bn(MgYz+ zG4|=D0000004TLD{QywyDFDWL&Pw2vH~}!sFvAQp0Qm;<hkr2c<m*tg1yVu|(<5QC z5#=_N>{O1FM6sBqmo1?a3x#{UR>XJ-C5WoAqW4(xEdXrw<q}?(8Yrcd0=fXP0KowJ zb<KE&uUVPv@)l+uI68XF9gE@!emyEmfS)mY7uGbE&b#y4ldw+1$+@96-Sw^$yS@eT z&sI-r4-W1F;D1_b!Zvp0P1;(WrGIyRtC7L4hb_65nm0ROkWewJAC6$wrLF34DEC@w z1~935%zXdLcH>%VCaFDP{%o+TR<EU|3k=Mu^StoAk~hMtlQ$}@I@3tCQgx#eh5=VD z;fV#w{?1Dgw8glSN~w_(9A3hNjsiDFVv3YliWA>iJ%0tV{k$ZdTuaSXFR^StFTb;P z32uP8T)n<p6(P8mnq_}}1Jvc}J8S5G4rgFv^2{?Gn-6vVYIUW+wbU#{{5oSDhu@E? zLf~3zYW(6jszea>Ef9le-?;PB2J7sa?Pq=0*+zcnv>f_N8qdF-nlhl~^NYi{?e*Cz zNC4g&pMTi3$5oAm#nst$If7p(eqQnD%+g=O@gOGoSF0bBYpF?M*Nk^^q=^_D#Tp6g z0YPe&aiI{mN&|xCgN%oo>X1UAJQNJ-6suY9g?grIsd-~IJQq@lL?U$^hpwd?i8hEd zh%$skq0qI|%n4!OF2pzsD=e{<3N4OXDil(<lz)|!2_Oj|6;O(`p9nRUGo*eXd_c8+ zuB9f*0~*UA@+FO|%UX^9-D&is_ULdc4#Mwvzkb%ovkx8{Kq^gxYpEIQ7}VwN-%gc5 zxt5w`?7P(zgIC@pYQeSCeAbKkl{dNbb?{}VMxOt%YrI*uSKeeVC=bLhDAXy$Kk7~= zJbyZwTuh^o)PW<?sbngx-o%2W#<-M=+9GvXSeX^58Ip`h1(%|NP+Ut*i&A^e6D3uT zxr4e~-K>dgscAx=?|)@o-n<gmQnSR;D&ss7U>3>WJoS!vaojX8i>i{WRV(AV7@b`q zK5I=~OHB~0Rf~jiF=pljsNqG7uYtAb9e+7f^RL!R2no0Nh{P>$r^bF>(mMZ=byh0l zIuX}WGX}<rSshn8x9sO-ON-5@3V{o#(O_hz1ZDsL0Rd0|0R~4Y$8H7^ps+X!q%aC$ z7zShrFa{170RX{)kbnrF767!sZEkuEz>9%M44O*QC~DDoSim%~fPNJTlZTcJOMhqL zM}@<W@ja+z$hR69I9M$Vglecs`-M8(j~*ce6rJMbY~(>+ad|QhN-sOUpyI56Og_er zt;bf*BeRP3I<#ISL>C58r2gG@m|4rGaU8@I<LEI_xq76ti9;tl@ezrO73hUD)DrXp zni4Czjb8r&YKVrif)qkJh-mcagnxaZJH>?27&P1<2oDvjCFlh-r4{6Xno6v>*v7Ko zTUt2^ejgyc=uK*H&<VwTRHUr?{9Sy-bbc+dZJBx#EP;%s50+(g(>D`al#oA)*&gJ! zeJn%M9|N*oqgf_F4KfHpd<i(6#!w9aCBm>L*DyKV1am{;|D>=F&h+-|3x73dMv`9| zF1{x7$FOjOc(;#8CHhh5&g}?N{1B<mD2Bv4=CskHXF9e~I9!<(2Jnc(E@Ws94BwMG zFPQdY8o;*diO~Vh7mVB>7XugIBaF_$C4-x09)6+_<}e^TMQ@tXTdBrm;1_wxNiyNj z$@j?47QdWy!aT{ctMnfUk2q;NvI<1hWG^u@xreg2!eHv1zMj?U8sWd291}gX)IhC& Zi|g1T$YGh-5p?;AQcn^FJ_pqht?e{Rtq1@B diff --git a/src/box/call.cc b/src/box/call.cc index 4d74160b0b..70eee018e4 100644 --- a/src/box/call.cc +++ b/src/box/call.cc @@ -65,20 +65,31 @@ access_check_func(const char *name, uint32_t name_len, struct func **funcp) * No special check for ADMIN user is necessary * since ADMIN has universal access. */ - if ((credentials->universal_access & PRIV_X) == PRIV_X) { + if ((credentials->universal_access & (PRIV_X | PRIV_U)) == + (PRIV_X | PRIV_U)) { + *funcp = func; return 0; } - - user_access_t access = PRIV_X & ~credentials->universal_access; + user_access_t access = PRIV_X | PRIV_U; + user_access_t func_access = access & ~credentials->universal_access; if (func == NULL || (func->def->uid != credentials->uid && - access & ~func->access[credentials->auth_token].effective)) { + func_access & ~func->access[credentials->auth_token].effective)) { /* Access violation, report error. */ struct user *user = user_find(credentials->uid); - if (user != NULL) - diag_set(ClientError, ER_FUNCTION_ACCESS_DENIED, - priv_name(access), user->def->name, - tt_cstr(name, name_len)); + if (user != NULL) { + if (!(access & credentials->universal_access)) { + diag_set(ClientError, ER_ACCESS_DENIED, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + user->def->name); + } else { + diag_set(ClientError, + ER_FUNCTION_ACCESS_DENIED, + priv_name(PRIV_X), user->def->name, + tt_cstr(name, name_len)); + } + } return -1; } diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua index 21863d58a2..2e5b4b4dd6 100644 --- a/src/box/lua/schema.lua +++ b/src/box/lua/schema.lua @@ -1900,6 +1900,11 @@ 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', + nil, {if_not_exists=true}) end box.schema.user.exists = function(name) @@ -1964,11 +1969,11 @@ local function revoke(uid, name, privilege, object_type, object_name, options) privilege = 'execute' end local privilege_hex = checked_privilege(privilege, object_type) - options = options or {} local oid = object_resolve(object_type, object_name) local _priv = box.space[box.schema.PRIV_ID] local tuple = _priv:get{uid, object_type, oid} + -- system privileges of admin and guest can't be revoked if tuple == nil then if options.if_exists then return @@ -1997,10 +2002,6 @@ end local function drop(uid, opts) -- recursive delete of user data local _priv = box.space[box.schema.PRIV_ID] - local privs = _priv.index.primary:select{uid} - for k, tuple in pairs(privs) do - revoke(uid, uid, tuple[5], tuple[3], tuple[4]) - end local spaces = box.space[box.schema.SPACE_ID].index.owner:select{uid} for k, tuple in pairs(spaces) do box.space[tuple[1]]:drop() @@ -2018,6 +2019,17 @@ 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._user:get{uid}[4] == 'user' then + box.session.su('admin', box.schema.user.revoke, uid, + 'session,usage', 'universe', nil, {if_exists = true}) + end + local privs = _priv.index.primary:select{uid} + for k, tuple in pairs(privs) do + revoke(uid, uid, tuple[5], tuple[3], tuple[4]) + end box.space[box.schema.USER_ID]:delete{uid} end @@ -2037,6 +2049,16 @@ box.schema.user.revoke = function(user_name, ...) return revoke(uid, user_name, ...) end +box.schema.user.enable = function(user) + box.schema.user.grant(user, "session,usage", "universe", nil, + {if_not_exists = true}) +end + +box.schema.user.disable = function(user) + box.schema.user.revoke(user, "session,usage", "universe", nil, + {if_exists = true}) +end + box.schema.user.drop = function(name, opts) opts = opts or {} check_param_table(opts, { if_exists = 'boolean' }) @@ -2048,6 +2070,10 @@ box.schema.user.drop = function(name, opts) box.error(box.error.DROP_USER, name, "the user or the role is a system") end + if uid == box.session.uid() or uid == box.session.euid() then + box.error(box.error.DROP_USER, name, + "the user is active in the current session") + end return drop(uid, opts) end if not opts.if_exists then @@ -2123,11 +2149,21 @@ box.schema.role.drop = function(name, opts) end return drop(uid) end + +function role_check_grant_revoke_of_sys_priv(priv) + priv = string.lower(priv) + if (type(priv) == 'string' and (priv:match("session") or priv:match("usage"))) or + (type(priv) == "number" and (bit.band(priv, 8) ~= 0 or bit.band(priv, 16) ~= 0)) then + box.error(box.error.GRANT, "system privilege can not be granted to role") + end +end + box.schema.role.grant = function(user_name, ...) local uid = role_resolve(user_name) if uid == nil then box.error(box.error.NO_SUCH_ROLE, user_name) end + role_check_grant_revoke_of_sys_priv(...) return grant(uid, user_name, ...) end box.schema.role.revoke = function(user_name, ...) @@ -2135,6 +2171,7 @@ box.schema.role.revoke = function(user_name, ...) if uid == nil then box.error(box.error.NO_SUCH_ROLE, user_name) end + role_check_grant_revoke_of_sys_priv(...) return revoke(uid, user_name, ...) end box.schema.role.info = function(role_name) diff --git a/src/box/lua/session.c b/src/box/lua/session.c index 382b0c533b..5b5f83d224 100644 --- a/src/box/lua/session.c +++ b/src/box/lua/session.c @@ -165,6 +165,9 @@ lbox_session_su(struct lua_State *L) } if (user == NULL) luaT_error(L); + if (access_check_session(user) < 0) + luaT_error(L); + if (top == 1) { credentials_init(&session->credentials, user->auth_token, user->def->uid); diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua index 9a6eaf1c5e..eb5efbc5f0 100644 --- a/src/box/lua/upgrade.lua +++ b/src/box/lua/upgrade.lua @@ -11,7 +11,6 @@ local PUBLIC = 2 -- role 'REPLICATION' local REPLICATION = 3 - -------------------------------------------------------------------------------- -- Utils -------------------------------------------------------------------------------- @@ -911,6 +910,26 @@ local function upgrade_to_1_7_6() end -------------------------------------------------------------------------------- +--- Tarantool 1.7.7 +-------------------------------------------------------------------------------- +local function upgrade_to_1_7_7() + local _priv = box.space[box.schema.PRIV_ID] + local _user = box.space[box.schema.USER_ID] + -- + -- grant 'session' and 'usage' to all existing users + -- + for _, v in _user:pairs() do + if v[4] ~= "role" then + _priv:upsert({ADMIN, v[1], "universe", 0, 24}, {{"|", 5, 24}}) + end + end + -- + -- grant admin all new privileges (session, usage, grant option, + -- create, alter, drop and anything that might come up in the future + -- + _priv:upsert({ADMIN, ADMIN, 'universe', 0, 4294967295}, + {{ "|", 5, 4294967295}}) +end local function get_version() local version = box.space._schema:get{'version'} @@ -936,6 +955,7 @@ local function upgrade(options) {version = mkversion(1, 7, 2), func = upgrade_to_1_7_2, auto = false}, {version = mkversion(1, 7, 5), func = upgrade_to_1_7_5, auto = true}, {version = mkversion(1, 7, 6), func = upgrade_to_1_7_6, auto = false}, + {version = mkversion(1, 7, 7), func = upgrade_to_1_7_7, auto = true}, } for _, handler in ipairs(handlers) do diff --git a/src/box/sequence.c b/src/box/sequence.c index c7d7056dbd..881cf61ee9 100644 --- a/src/box/sequence.c +++ b/src/box/sequence.c @@ -247,15 +247,26 @@ access_check_sequence(struct sequence *seq) * No special check for ADMIN user is necessary since ADMIN has * universal access. */ - uint8_t access = PRIV_W & ~cr->universal_access; + + user_access_t access = PRIV_U | PRIV_W; + user_access_t sequence_access = access & ~cr->universal_access; if (seq->def->uid != cr->uid && - access & ~seq->access[cr->auth_token].effective) { + sequence_access & ~seq->access[cr->auth_token].effective) { /* Access violation, report error. */ struct user *user = user_find(cr->uid); - if (user != NULL) - diag_set(ClientError, ER_SEQUENCE_ACCESS_DENIED, - priv_name(access), user->def->name, - seq->def->name); + if (user != NULL) { + if (!(cr->universal_access & PRIV_U)) { + diag_set(ClientError, ER_ACCESS_DENIED, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + user->def->name); + } else { + diag_set(ClientError, + ER_SEQUENCE_ACCESS_DENIED, + priv_name(access), user->def->name, + seq->def->name); + } + } return -1; } return 0; diff --git a/src/box/session.cc b/src/box/session.cc index 33b1feb4cc..aff8657c78 100644 --- a/src/box/session.cc +++ b/src/box/session.cc @@ -131,7 +131,7 @@ session_create_on_demand(int fd) * At bootstrap, admin user access is not loaded yet (is * 0), force global access. @sa comment in session_init() */ - s->credentials.universal_access = PRIV_ALL; + s->credentials.universal_access = ~(user_access_t) 0; fiber_set_session(fiber(), s); fiber_set_user(fiber(), &s->credentials); return s; @@ -223,7 +223,7 @@ session_init() * When session_init() is called, admin user access is not * loaded yet (is 0), force global access. */ - admin_credentials.universal_access = PRIV_ALL; + admin_credentials.universal_access = ~((user_access_t) 0); } void @@ -232,3 +232,47 @@ session_free() if (session_registry) mh_i64ptr_delete(session_registry); } + +int +access_check_session(struct user *user) +{ + /* + * Can't use here access_check_universe + * 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), + schema_object_name(SC_UNIVERSE), + user->def->name); + return -1; + } + return 0; +} + +void +access_check_session_xc(struct user *user) +{ + if (access_check_session(user) < 0) { + diag_raise(); + } +} + +void +access_check_universe(user_access_t access) +{ + struct credentials *credentials = effective_user(); + access |= PRIV_U; + if ((credentials->universal_access & access) ^ access) { + /* + * Access violation, report error. + * The user may not exist already, if deleted + * from a different connection. + */ + struct user *user = user_find_xc(credentials->uid); + int denied_access = access & ((credentials->universal_access + & access) ^ access); + tnt_raise(ClientError, ER_ACCESS_DENIED, + priv_name(denied_access), + schema_object_name(SC_UNIVERSE), user->def->name); + } +} diff --git a/src/box/session.h b/src/box/session.h index 29efe2b092..f7bfc9c5b0 100644 --- a/src/box/session.h +++ b/src/box/session.h @@ -241,26 +241,25 @@ session_run_on_disconnect_triggers(struct session *session); int session_run_on_auth_triggers(const char *user_name); +/** + * Check whether or not the current user is authorized to connect + */ +int +access_check_session(struct user *user); + +void +access_check_session_xc(struct user *user); + +/** + * Check whether or not the current user can be granted + * the requested access to the universe. + */ +void +access_check_universe(user_access_t access); + #if defined(__cplusplus) } /* extern "C" */ -static inline void -access_check_universe(uint8_t access) -{ - struct credentials *credentials = effective_user(); - if (!(credentials->universal_access & access)) { - /* - * Access violation, report error. - * The user may not exist already, if deleted - * from a different connection. - */ - struct user *user = user_find_xc(credentials->uid); - tnt_raise(ClientError, ER_ACCESS_DENIED, - priv_name(access), schema_object_name(SC_UNIVERSE), - user->def->name); - } -} - #endif /* defined(__cplusplus) */ #endif /* INCLUDES_TARANTOOL_SESSION_H */ diff --git a/src/box/space.c b/src/box/space.c index 359728aaf0..d49538a50e 100644 --- a/src/box/space.c +++ b/src/box/space.c @@ -41,9 +41,11 @@ #include "sequence.h" int -access_check_space(struct space *space, uint8_t access) +access_check_space(struct space *space, user_access_t access) { struct credentials *cr = effective_user(); + /* Any space access also requires global USAGE privilege. */ + access |= PRIV_U; /* * If a user has a global permission, clear the respective * privilege from the list of privileges required @@ -51,9 +53,10 @@ access_check_space(struct space *space, uint8_t access) * No special check for ADMIN user is necessary * since ADMIN has universal access. */ - access &= ~cr->universal_access; - if (access && space->def->uid != cr->uid && - access & ~space->access[cr->auth_token].effective) { + user_access_t space_access = access & ~cr->universal_access; + + if (space_access && space->def->uid != cr->uid && + space_access & ~space->access[cr->auth_token].effective) { /* * Report access violation. Throw "no such user" * error if there is no user with this id. @@ -61,10 +64,19 @@ access_check_space(struct space *space, uint8_t access) * from a different connection. */ struct user *user = user_find(cr->uid); - if (user != NULL) - diag_set(ClientError, ER_SPACE_ACCESS_DENIED, - priv_name(access), user->def->name, - space->def->name); + if (user != NULL) { + if (!(cr->universal_access & PRIV_U)) { + diag_set(ClientError, ER_ACCESS_DENIED, + priv_name(PRIV_U), + schema_object_name(SC_UNIVERSE), + user->def->name); + } else { + diag_set(ClientError, + ER_SPACE_ACCESS_DENIED, + priv_name(access), user->def->name, + space->def->name); + } + } return -1; } return 0; diff --git a/src/box/space.h b/src/box/space.h index 17ecbb31c5..678817a101 100644 --- a/src/box/space.h +++ b/src/box/space.h @@ -305,7 +305,7 @@ space_def_check_compatibility(const struct space_def *old_def, * the requested access to the space. */ int -access_check_space(struct space *space, uint8_t access); +access_check_space(struct space *space, user_access_t access); static inline int space_apply_initial_join_row(struct space *space, struct request *request) @@ -469,7 +469,7 @@ space_new_xc(struct space_def *space_def, struct rlist *key_list) } static inline void -access_check_space_xc(struct space *space, uint8_t access) +access_check_space_xc(struct space *space, user_access_t access) { if (access_check_space(space, access) != 0) diag_raise(); diff --git a/test/box-py/bootstrap.result b/test/box-py/bootstrap.result index d5fa6579fb..ae8fb487cb 100644 --- a/test/box-py/bootstrap.result +++ b/test/box-py/bootstrap.result @@ -5,7 +5,7 @@ box.space._schema:select{} --- - - ['cluster', '<cluster uuid>'] - ['max_id', 511] - - ['version', 1, 7, 6] + - ['version', 1, 7, 7] ... box.space._cluster:select{} --- @@ -123,7 +123,8 @@ box.space._func:select{} box.space._priv:select{} --- - - [1, 0, 'role', 2, 4] - - [1, 1, 'universe', 0, 7] + - [1, 0, 'universe', 0, 24] + - [1, 1, 'universe', 0, 4294967295] - [1, 2, 'function', 1, 4] - [1, 2, 'space', 276, 2] - [1, 2, 'space', 281, 1] diff --git a/test/box-tap/session.test.lua b/test/box-tap/session.test.lua index 844d406bb6..a9b5610dbb 100755 --- a/test/box-tap/session.test.lua +++ b/test/box-tap/session.test.lua @@ -15,7 +15,7 @@ session = box.session space = box.schema.space.create('tweedledum') index = space:create_index('primary', { type = 'hash' }) -test:plan(49) +test:plan(53) --- --- Check that Tarantool creates ADMIN session for #! script @@ -141,6 +141,16 @@ test:is(on_disconnect_user, 'admin', "check triggers permissions, on_disconnect" box.session.on_connect(nil, on_connect) box.session.on_disconnect(nil, on_disconnect) + +-- check Session privilege +ok, err = pcall(function() net.box.connect("tester:tester@" ..HOST..':'..PORT) end) +test:ok(ok, "session privilege") +box.schema.user.revoke('tester', 'session', 'universe') +conn = net.box.connect("tester:tester@" ..HOST..':'..PORT) +test:is(conn.state, "error", "session privilege state") +test:ok(conn.error:match("Session"), "sesssion privilege errmsg") +ok, err = pcall(box.session.su, "user1") +test:ok(not ok, "session.su on revoked") box.schema.user.drop('tester') local test_run = require('test_run') diff --git a/test/box/access.result b/test/box/access.result index 790dbdc56e..cb830159ff 100644 --- a/test/box/access.result +++ b/test/box/access.result @@ -132,6 +132,13 @@ box.schema.user.revoke('rich', 'read,write', 'universe') box.schema.user.revoke('rich', 'public') --- ... +box.schema.user.disable("rich") +--- +... +-- test double disable is a no op +box.schema.user.disable("rich") +--- +... box.space['_user']:delete{uid} --- - [5, 1, 'rich', 'user', {}] @@ -196,14 +203,19 @@ session.user() -- drop current user session.su('admin', function() return box.schema.user.drop('tester') end) --- +- error: 'Failed to drop user or role ''tester'': the user is active in the current + session' ... session.user() --- -- null +- tester ... session.su('admin') --- ... +box.schema.user.drop('tester') +--- +... session.user() --- - admin @@ -361,7 +373,8 @@ box.schema.user.drop('grantee') -- fails, can't kill oneself box.schema.user.drop('grantor') --- -- error: Create, drop or alter access on role is denied for user 'grantor' +- error: 'Failed to drop user or role ''grantor'': the user is active in the current + session' ... session.su('admin') --- @@ -462,7 +475,7 @@ box.schema.user.grant('user', 'read,write', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] - - [1, 4, 'universe', 0, 3] + - [1, 4, 'universe', 0, 27] ... box.schema.user.grant('user', 'read', 'universe') --- @@ -471,7 +484,7 @@ box.schema.user.grant('user', 'read', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] - - [1, 4, 'universe', 0, 3] + - [1, 4, 'universe', 0, 27] ... box.schema.user.revoke('user', 'write', 'universe') --- @@ -479,7 +492,7 @@ box.schema.user.revoke('user', 'write', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] - - [1, 4, 'universe', 0, 1] + - [1, 4, 'universe', 0, 25] ... box.schema.user.revoke('user', 'read', 'universe') --- @@ -487,6 +500,7 @@ box.schema.user.revoke('user', 'read', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] + - [1, 4, 'universe', 0, 24] ... box.schema.user.grant('user', 'write', 'universe') --- @@ -494,7 +508,7 @@ box.schema.user.grant('user', 'write', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] - - [1, 4, 'universe', 0, 2] + - [1, 4, 'universe', 0, 26] ... box.schema.user.grant('user', 'read', 'universe') --- @@ -502,7 +516,7 @@ box.schema.user.grant('user', 'read', 'universe') box.space._priv:select{id} --- - - [1, 4, 'role', 2, 4] - - [1, 4, 'universe', 0, 3] + - [1, 4, 'universe', 0, 27] ... box.schema.user.drop('user') --- @@ -659,6 +673,9 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe', '', { if_not_ex box.schema.user.revoke('guest', 'read,write,execute', 'universe') --- ... +box.schema.user.revoke('guest', 'usage,session', 'universe') +--- +... box.schema.user.revoke('guest', 'read,write,execute', 'universe') --- - error: User 'guest' does not have read,write,execute access on universe 'nil' @@ -666,6 +683,9 @@ box.schema.user.revoke('guest', 'read,write,execute', 'universe') box.schema.user.revoke('guest', 'read,write,execute', 'universe', '', { if_exists = true }) --- ... +box.schema.user.grant('guest', 'usage,session', 'universe') +--- +... box.schema.func.create('dummy', { if_not_exists = true }) --- ... @@ -870,6 +890,9 @@ box.schema.user.info('test_user') - - read - space - test_space + - - session,usage + - universe + - ... box.schema.role.info('test_role') --- @@ -897,6 +920,9 @@ box.schema.user.info('test_user') - - - execute - role - public + - - session,usage + - universe + - ... box.schema.role.info('test_role') --- @@ -930,3 +956,103 @@ box.session.su('guest', uids) - uid: 1 euid: 0 ... +-- +-- gh-2898 System privileges +-- +s = box.schema.create_space("tweed") +--- +... +_ = s:create_index('primary', {type = 'hash', parts = {1, 'unsigned'}}) +--- +... +box.schema.user.create('test', {password="pass"}) +--- +... +box.schema.user.grant('test', 'read,write', 'universe') +--- +... +-- other users can't disable +box.schema.user.create('test1') +--- +... +session.su("test1") +--- +... +box.schema.user.disable("test") +--- +- error: Read access is denied for user 'test1' to space '_user' +... +session.su("admin") +--- +... +box.schema.user.disable("test") +--- +... +-- test double disable is a no op +box.schema.user.disable("test") +--- +... +session.su("test") +--- +- error: Session access on universe is denied for user 'test' +... +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) +--- +... +c.state +--- +- error +... +c.error +--- +- Session access on universe is denied for user 'test' +... +session.su("test1") +--- +... +box.schema.user.grant("test", "usage", "universe") +--- +- error: Read access is denied for user 'test1' to space '_user' +... +session.su('admin') +--- +... +box.schema.user.grant("test", "session", "universe") +--- +... +session.su("test") +--- +... +s:select{} +--- +- error: Usage access on universe is denied for user 'test' +... +session.su('admin') +--- +... +box.schema.user.enable("test") +--- +... +-- check enable not fails on double enabling +box.schema.user.enable("test") +--- +... +session.su("test") +--- +... +s:select{} +--- +- [] +... +session.su("admin") +--- +... +box.schema.user.drop('test') +--- +... +box.schema.user.drop('test1') +--- +... +s:drop() +--- +... diff --git a/test/box/access.test.lua b/test/box/access.test.lua index c353aad00b..c1d041520e 100644 --- a/test/box/access.test.lua +++ b/test/box/access.test.lua @@ -59,6 +59,9 @@ box.schema.func.drop('dummy') box.space['_user']:delete{uid} box.schema.user.revoke('rich', 'read,write', 'universe') box.schema.user.revoke('rich', 'public') +box.schema.user.disable("rich") +-- test double disable is a no op +box.schema.user.disable("rich") box.space['_user']:delete{uid} box.schema.user.drop('test') @@ -89,6 +92,7 @@ session.user() session.su('admin', function() return box.schema.user.drop('tester') end) session.user() session.su('admin') +box.schema.user.drop('tester') session.user() -------------------------------------------------------------------------------- @@ -258,8 +262,10 @@ box.schema.user.grant('guest', 'read,write,execute', 'universe') box.schema.user.grant('guest', 'read,write,execute', 'universe') box.schema.user.grant('guest', 'read,write,execute', 'universe', '', { if_not_exists = true }) box.schema.user.revoke('guest', 'read,write,execute', 'universe') +box.schema.user.revoke('guest', 'usage,session', 'universe') box.schema.user.revoke('guest', 'read,write,execute', 'universe') box.schema.user.revoke('guest', 'read,write,execute', 'universe', '', { if_exists = true }) +box.schema.user.grant('guest', 'usage,session', 'universe') box.schema.func.create('dummy', { if_not_exists = true }) box.schema.func.create('dummy', { if_not_exists = true }) box.schema.func.drop('dummy') @@ -356,3 +362,45 @@ box.session.su('guest') uids() box.session.su('admin') box.session.su('guest', uids) + +-- +-- gh-2898 System privileges +-- +s = box.schema.create_space("tweed") +_ = s:create_index('primary', {type = 'hash', parts = {1, 'unsigned'}}) + +box.schema.user.create('test', {password="pass"}) +box.schema.user.grant('test', 'read,write', 'universe') + +-- other users can't disable +box.schema.user.create('test1') +session.su("test1") +box.schema.user.disable("test") + +session.su("admin") +box.schema.user.disable("test") +-- test double disable is a no op +box.schema.user.disable("test") +session.su("test") +c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test", password="pass"}) +c.state +c.error + +session.su("test1") +box.schema.user.grant("test", "usage", "universe") +session.su('admin') +box.schema.user.grant("test", "session", "universe") +session.su("test") +s:select{} + +session.su('admin') +box.schema.user.enable("test") +-- check enable not fails on double enabling +box.schema.user.enable("test") +session.su("test") +s:select{} + +session.su("admin") +box.schema.user.drop('test') +box.schema.user.drop('test1') +s:drop() diff --git a/test/box/access_misc.result b/test/box/access_misc.result index d190c0e41c..5646068986 100644 --- a/test/box/access_misc.result +++ b/test/box/access_misc.result @@ -209,7 +209,8 @@ uid = session.uid() -- box.schema.user.drop('uniuser') --- -- error: Create, drop or alter access on role is denied for user 'uniuser' +- error: 'Failed to drop user or role ''uniuser'': the user is active in the current + session' ... -- --Check create, call and drop function @@ -414,10 +415,16 @@ box.space._index:insert{512, 1,'owner','tree', 1, 1, 0,'unsigned'} session.su('admin') --- ... +box.schema.user.revoke('testuser', 'usage,session', 'universe') +--- +... box.schema.user.revoke('testuser', 'read, write, execute', 'universe') --- - error: User 'testuser' does not have read, write, execute access on universe 'nil' ... +box.schema.user.grant('testuser', 'usage,session', 'universe') +--- +... -- -- Check that itertors check privileges -- diff --git a/test/box/access_misc.test.lua b/test/box/access_misc.test.lua index a667e2885a..c23a021731 100644 --- a/test/box/access_misc.test.lua +++ b/test/box/access_misc.test.lua @@ -172,7 +172,9 @@ box.space._index:insert{512, 1,'owner','tree', 1, 1, 0,'unsigned'} session.su('admin') +box.schema.user.revoke('testuser', 'usage,session', 'universe') box.schema.user.revoke('testuser', 'read, write, execute', 'universe') +box.schema.user.grant('testuser', 'usage,session', 'universe') -- -- Check that itertors check privileges -- diff --git a/test/box/role.result b/test/box/role.result index 4aa59b738c..8f6ff5d5ea 100644 --- a/test/box/role.result +++ b/test/box/role.result @@ -46,6 +46,9 @@ box.schema.user.info('tester') - - - execute - role - public + - - session,usage + - universe + - ... box.schema.user.grant('tester', 'execute', 'role', 'iddqd') --- @@ -58,6 +61,9 @@ box.schema.user.info('tester') - - execute - role - iddqd + - - session,usage + - universe + - ... -- test granting user to a user box.schema.user.grant('tester', 'execute', 'role', 'tester') @@ -789,7 +795,7 @@ box.schema.user.drop('john') --- ... -- test ER_GRANT -box.space._priv:insert{1, 0, 'universe', 0, 0} +box.space._priv:replace{1, 0, 'universe', 0, 0} --- - error: 'Incorrect grant arguments: the grant tuple has no privileges' ... @@ -926,6 +932,9 @@ box.schema.user.info('test_user') - - - execute - role - public + - - session,usage + - universe + - ... box.schema.role.info('test_user') --- diff --git a/test/box/role.test.lua b/test/box/role.test.lua index 639d8adc19..c85a26d995 100644 --- a/test/box/role.test.lua +++ b/test/box/role.test.lua @@ -314,7 +314,7 @@ box.space.test:drop() box.schema.user.drop('john') -- test ER_GRANT -box.space._priv:insert{1, 0, 'universe', 0, 0} +box.space._priv:replace{1, 0, 'universe', 0, 0} -- role.exists() -- -- true if the role is present diff --git a/test/box/sequence.result b/test/box/sequence.result index 807401426b..3564a52235 100644 --- a/test/box/sequence.result +++ b/test/box/sequence.result @@ -1355,6 +1355,9 @@ box.schema.user.info() - - read - space - _priv + - - session,usage + - universe + - ... sq:set(100) -- ok --- diff --git a/test/xlog/upgrade.result b/test/xlog/upgrade.result index 891126728b..3802f0e3e4 100644 --- a/test/xlog/upgrade.result +++ b/test/xlog/upgrade.result @@ -36,7 +36,7 @@ box.space._schema:select() --- - - ['cluster', '<server_uuid>'] - ['max_id', 513] - - ['version', 1, 7, 6] + - ['version', 1, 7, 7] ... box.space._space:select() --- @@ -166,7 +166,8 @@ box.space._collation:select() box.space._priv:select() --- - - [1, 0, 'role', 2, 4] - - [1, 1, 'universe', 0, 7] + - [1, 0, 'universe', 0, 24] + - [1, 1, 'universe', 0, 4294967295] - [1, 2, 'function', 1, 4] - [1, 2, 'function', 2, 4] - [1, 2, 'space', 276, 2] @@ -182,6 +183,7 @@ box.space._priv:select() - [1, 4, 'role', 2, 4] - [1, 4, 'role', 5, 4] - [1, 4, 'space', 513, 3] + - [1, 4, 'universe', 0, 24] - [1, 5, 'space', 512, 3] ... box.space._vspace ~= nil -- GitLab