Skip to content

Commit 1d66589

Browse files
authored
security: database admin can not administer database admins - 2 (#16679)
Database admin is not allowed to make changes to other database admins. Only cluster admins can. Database admins: - cannot change passwords or login blocking flags for other database admins - cannot change their own login blocking flags - can change their own passwords
1 parent d2c5ec0 commit 1d66589

File tree

10 files changed

+117
-20
lines changed

10 files changed

+117
-20
lines changed

ydb/core/base/local_user_token.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#include <ydb/library/aclib/aclib.h>
2+
#include <ydb/library/login/login.h>
3+
#include <ydb/library/login/protos/login.pb.h>
4+
5+
#include "local_user_token.h"
6+
7+
namespace NKikimr {
8+
9+
NACLib::TUserToken BuildLocalUserToken(const NLogin::TLoginProvider& loginProvider, const TString& user) {
10+
const auto providerGroups = loginProvider.GetGroupsMembership(user);
11+
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
12+
//NOTE: TVector vs std::vector incompatibility between TUserToken and TLoginProvider
13+
return NACLib::TUserToken(user, groups);
14+
}
15+
16+
NACLib::TUserToken BuildLocalUserToken(const NLoginProto::TSecurityState& state, const TString& user) {
17+
NLogin::TLoginProvider loginProvider;
18+
loginProvider.UpdateSecurityState(state);
19+
return BuildLocalUserToken(loginProvider, user);
20+
}
21+
22+
}

ydb/core/base/local_user_token.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <util/generic/string.h>
4+
#include <ydb/library/aclib/aclib.h>
5+
6+
7+
namespace NLoginProto {
8+
class TSecurityState;
9+
}
10+
namespace NLogin {
11+
class TLoginProvider;
12+
}
13+
14+
namespace NKikimr {
15+
16+
// Recreates user token from local user login/sid and it's database login provider or security state.
17+
// Token should be used to determine access level only (e.g. cluster/database admin status),
18+
// and not for authentication.
19+
// See methods in ydb/core/base/auth.h.
20+
NACLib::TUserToken BuildLocalUserToken(const NLogin::TLoginProvider& loginProvider, const TString& user);
21+
NACLib::TUserToken BuildLocalUserToken(const NLoginProto::TSecurityState& state, const TString& user);
22+
23+
}

ydb/core/base/ya.make

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ SRCS(
3030
group_stat.h
3131
hive.h
3232
interconnect_channels.h
33+
local_user_token.cpp
34+
local_user_token.h
3335
localdb.cpp
3436
localdb.h
3537
location.h

ydb/core/tx/schemeshard/schemeshard__login.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <ydb/library/security/util.h>
22
#include <ydb/core/protos/auth.pb.h>
33
#include <ydb/core/base/auth.h>
4+
#include <ydb/core/base/local_user_token.h>
45

56
#include "schemeshard_impl.h"
67

@@ -89,10 +90,7 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
8990
private:
9091
bool IsAdmin() const {
9192
const auto& user = Request->Get()->Record.GetUser();
92-
const auto providerGroups = Self->LoginProvider.GetGroupsMembership(user);
93-
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
94-
const auto userToken = NACLib::TUserToken(user, groups);
95-
93+
const auto userToken = NKikimr::BuildLocalUserToken(Self->LoginProvider, user);
9694
return IsAdministrator(AppData(), &userToken);
9795
}
9896

ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#include <ydb/library/security/util.h>
77
#include <ydb/core/base/auth.h>
8+
#include <ydb/core/base/local_user_token.h>
9+
810

911
#include <ydb/core/protos/auth.pb.h>
1012

@@ -293,7 +295,7 @@ class TAlterLogin: public TSubOperationBase {
293295

294296
NLogin::TLoginProvider::TBasicResponse CanRemoveSid(TOperationContext& context, const TString sid, const TString& sidType) {
295297
if (!AppData()->FeatureFlags.GetEnableStrictAclCheck()) {
296-
return {};
298+
return {};
297299
}
298300

299301
auto subTree = context.SS->ListSubTree(context.SS->RootPathId(), context.Ctx);
@@ -316,9 +318,7 @@ class TAlterLogin: public TSubOperationBase {
316318
}
317319

318320
void AddIsUserAdmin(const TString& user, NLogin::TLoginProvider& loginProvider, TParts& additionalParts) {
319-
const auto providerGroups = loginProvider.GetGroupsMembership(user);
320-
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
321-
const auto userToken = NACLib::TUserToken(user, groups);
321+
const auto userToken = NKikimr::BuildLocalUserToken(loginProvider, user);
322322

323323
if (IsAdministrator(AppData(), &userToken)) {
324324
additionalParts.emplace_back("login_user_level", "admin");

ydb/core/tx/tx_proxy/schemereq.cpp

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include <ydb/core/base/appdata.h>
44
#include <ydb/core/base/auth.h>
5+
#include <ydb/core/base/local_user_token.h>
56
#include <ydb/core/base/path.h>
67
#include <ydb/core/base/tablet_pipe.h>
78
#include <ydb/core/base/tx_processing.h>
@@ -10,6 +11,9 @@
1011
#include <ydb/core/protos/schemeshard/operations.pb.h>
1112
#include <ydb/core/tx/schemeshard/schemeshard.h>
1213

14+
#include <ydb/library/login/login.h>
15+
#include <ydb/library/login/protos/login.pb.h>
16+
1317
#include <ydb/library/aclib/aclib.h>
1418
#include <ydb/library/actors/core/hfunc.h>
1519
#include <ydb/library/protobuf_printer/security_printer.h>
@@ -63,6 +67,7 @@ struct TBaseSchemeReq: public TActorBootstrapped<TDerived> {
6367
bool IsClusterAdministrator = false;
6468
bool IsDatabaseAdministrator = false;
6569
NACLib::TSID DatabaseOwner;
70+
NLoginProto::TSecurityState DatabaseSecurityState;
6671

6772
TBaseSchemeReq(const TTxProxyServices &services, ui64 txid, TAutoPtr<TEvTxProxyReq::TEvSchemeRequest> request, const TIntrusivePtr<TTxProxyMon> &txProxyMon)
6873
: Services(services)
@@ -1141,7 +1146,8 @@ struct TBaseSchemeReq: public TActorBootstrapped<TDerived> {
11411146

11421147
allowACLBypass = (checkAdmin && isAdmin) || isUserChangesOwnPassword;
11431148

1144-
// Database admin is not allowed to manage group of database admins (its the privilege of cluster admins).
1149+
// Database admin is not allowed to manage group of database admins or change other database admins
1150+
// (its the privilege of cluster admins).
11451151
if (checkAdmin && IsDatabaseAdministrator) {
11461152
TString group;
11471153
switch (alterLogin.GetAlterCase()) {
@@ -1168,6 +1174,19 @@ struct TBaseSchemeReq: public TActorBootstrapped<TDerived> {
11681174
ReportStatus(TEvTxUserProxy::TEvProposeTransactionStatus::EStatus::AccessDenied, nullptr, &issue, ctx);
11691175
return false;
11701176
}
1177+
// Database admin still can change its own password
1178+
if (alterLogin.GetAlterCase() == NKikimrSchemeOp::TAlterLogin::kModifyUser && !isUserChangesOwnPassword) {
1179+
const auto& targetUser = alterLogin.GetModifyUser();
1180+
const auto targetUserToken = NKikimr::BuildLocalUserToken(DatabaseSecurityState, targetUser.GetUser());
1181+
if (NKikimr::IsDatabaseAdministrator(&targetUserToken, DatabaseOwner)) {
1182+
const auto errString = MakeAccessDeniedError(ctx, entry.Path, TStringBuilder()
1183+
<< "attempt to change other database admin by the database admin"
1184+
);
1185+
auto issue = MakeIssue(NKikimrIssues::TIssuesIds::ACCESS_DENIED, errString);
1186+
ReportStatus(TEvTxUserProxy::TEvProposeTransactionStatus::EStatus::AccessDenied, nullptr, &issue, ctx);
1187+
return false;
1188+
}
1189+
}
11711190
}
11721191

11731192
} else if (modifyScheme.GetOperationType() == NKikimrSchemeOp::ESchemeOpModifyACL) {
@@ -1357,9 +1376,12 @@ struct TBaseSchemeReq: public TActorBootstrapped<TDerived> {
13571376
return Die(ctx);
13581377
}
13591378

1360-
const auto& database = request.ResultSet.front();
1361-
DatabaseOwner = database.Self->Info.GetOwner();
1362-
IsDatabaseAdministrator = NKikimr::IsDatabaseAdministrator(&UserToken.value(), database.Self->Info.GetOwner());
1379+
const auto& entry = request.ResultSet.front();
1380+
1381+
DatabaseOwner = entry.Self->Info.GetOwner();
1382+
DatabaseSecurityState = entry.DomainDescription->Description.GetSecurityState();
1383+
1384+
IsDatabaseAdministrator = NKikimr::IsDatabaseAdministrator(&UserToken.value(), entry.Self->Info.GetOwner());
13631385

13641386
LOG_DEBUG_S(ctx, NKikimrServices::TX_PROXY, "Actor# " << ctx.SelfID.ToString() << " txid# " << TxId
13651387
<< " HandleResolveDatabase,"

ydb/core/tx/tx_proxy/ya.make

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ PEERDIR(
5151
ydb/core/tx/tx_allocator
5252
ydb/core/tx/tx_allocator_client
5353
ydb/library/aclib
54+
ydb/library/login
5455
ydb/library/mkql_proto/protos
5556
ydb/public/lib/base
5657
)

ydb/library/login/login.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -343,7 +343,7 @@ TLoginProvider::TRemoveGroupResponse TLoginProvider::RemoveGroup(const TString&
343343
return response;
344344
}
345345

346-
std::vector<TString> TLoginProvider::GetGroupsMembership(const TString& member) {
346+
std::vector<TString> TLoginProvider::GetGroupsMembership(const TString& member) const {
347347
std::vector<TString> groups;
348348
std::unordered_set<TString> visited;
349349
std::deque<TString> queue;

ydb/library/login/login.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ class TLoginProvider {
225225
const TCacheSettings& cacheSettings);
226226
~TLoginProvider();
227227

228-
std::vector<TString> GetGroupsMembership(const TString& member);
228+
std::vector<TString> GetGroupsMembership(const TString& member) const;
229229
static TString GetTokenAudience(const TString& token);
230230
static std::chrono::system_clock::time_point GetTokenExpiresAt(const TString& token);
231231
static TString SanitizeJwtToken(const TString& token);

ydb/tests/functional/tenants/test_user_administration.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,10 @@ def prepared_tenant_db(ydb_cluster, ydb_endpoint, ydb_database_module_scope):
9696

9797
session.execute_scheme("create group ordinarygroup")
9898
session.execute_scheme("create user dbadmin2 password '1234'")
99+
session.execute_scheme("create user dbadmin3 password '1234' nologin")
100+
session.execute_scheme("create user dbadmin4 password '1234'")
99101
session.execute_scheme("create group dbsubadmins")
100-
session.execute_scheme('alter group dbadmins add user dbadmin2, dbsubadmins')
102+
session.execute_scheme('alter group dbadmins add user dbadmin2, dbadmin3, dbadmin4, dbsubadmins')
101103

102104
# setup for database admins, second
103105
# make dbadmin the real admin of the database
@@ -114,20 +116,24 @@ def login_user(endpoint, database, user, password):
114116
return credentials._make_token_request()['access_token']
115117

116118

117-
def test_ordinaryuser_can_change_password_for_himself(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client):
119+
@pytest.mark.parametrize('subject_user', [
120+
'ordinaryuser',
121+
pytest.param('dbadmin4', id='dbadmin')
122+
])
123+
def test_user_can_change_password_for_himself(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client, subject_user):
118124
database_path = prepared_tenant_db
119125

120-
user_auth_token = login_user(ydb_endpoint, database_path, 'ordinaryuser', '1234')
126+
user_auth_token = login_user(ydb_endpoint, database_path, subject_user, '1234')
121127
credentials = ydb.AuthTokenCredentials(user_auth_token)
122128

123129
with ydb_client(database_path, credentials=credentials) as driver:
124130
driver.wait()
125131

126132
pool = ydb.SessionPool(driver)
127133
with pool.checkout() as session:
128-
session.execute_scheme("alter user ordinaryuser password '4321'")
134+
session.execute_scheme(f"alter user {subject_user} password '4321'")
129135

130-
user_auth_token = login_user(ydb_endpoint, database_path, 'ordinaryuser', '4321')
136+
user_auth_token = login_user(ydb_endpoint, database_path, subject_user, '4321')
131137

132138

133139
def test_database_admin_cant_change_database_owner(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client):
@@ -154,7 +160,6 @@ def test_database_admin_cant_change_database_owner(ydb_endpoint, prepared_root_d
154160
pytest.param('alter group dbadmins drop user dbsubadmins', id='remove-subgroup'),
155161
pytest.param('drop group dbadmins', id='remove-admin-group'),
156162
pytest.param('alter group dbadmins rename to dbadminsdemoted', id='rename-admin-group'),
157-
158163
])
159164
def test_database_admin_cant_change_database_admin_group(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client, query):
160165
database_path = prepared_tenant_db
@@ -174,6 +179,30 @@ def test_database_admin_cant_change_database_admin_group(ydb_endpoint, prepared_
174179
assert 'Access denied.' in exc_info.value.message
175180

176181

182+
@pytest.mark.parametrize('query', [
183+
pytest.param('alter user dbadmin2 password "4321"', id='change-password'),
184+
pytest.param('alter user dbadmin2 nologin', id='block'),
185+
pytest.param('alter user dbadmin3 login', id='unblock'),
186+
])
187+
def test_database_admin_cant_change_database_admin_user(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client, query):
188+
database_path = prepared_tenant_db
189+
190+
user_auth_token = login_user(ydb_endpoint, database_path, 'dbadmin', '1234')
191+
credentials = ydb.AuthTokenCredentials(user_auth_token)
192+
193+
with ydb_client(database_path, credentials=credentials) as driver:
194+
driver.wait()
195+
196+
pool = ydb.SessionPool(driver)
197+
with pool.checkout() as session:
198+
with pytest.raises(ydb.issues.Error) as exc_info:
199+
session.execute_scheme(query)
200+
201+
assert exc_info.type is ydb.issues.Unauthorized
202+
logger.debug(exc_info.value.message)
203+
assert 'Access denied.' in exc_info.value.message
204+
205+
177206
def test_database_admin_can_create_user(ydb_endpoint, prepared_root_db, prepared_tenant_db, ydb_client):
178207
database_path = prepared_tenant_db
179208

0 commit comments

Comments
 (0)