Skip to content

Commit 66801f1

Browse files
authored
Audit log for ModifyScheme::ESchemeOpAlterLogin (#13108)
1 parent 046bf73 commit 66801f1

File tree

5 files changed

+145
-52
lines changed

5 files changed

+145
-52
lines changed

ydb/core/tx/schemeshard/schemeshard__operation.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,7 @@ struct TSchemeShard::TTxOperationPropose: public NTabletFlatExecutor::TTransacti
346346
TProposeRequest::TPtr Request;
347347
THolder<TProposeResponse> Response = nullptr;
348348

349+
TString PeerName;
349350
TString UserSID;
350351
TString SanitizedToken;
351352

@@ -378,10 +379,12 @@ struct TSchemeShard::TTxOperationPropose: public NTabletFlatExecutor::TTransacti
378379
UserSID = userToken->GetUserSID();
379380
SanitizedToken = userToken->GetSanitizedToken();
380381
}
382+
PeerName = Request->Get()->Record.GetPeerName();
381383

382384
TMemoryChanges memChanges;
383385
TStorageChanges dbChanges;
384386
TOperationContext context{Self, txc, ctx, OnComplete, memChanges, dbChanges, std::move(userToken)};
387+
context.PeerName = PeerName;
385388

386389
//NOTE: Successful IgniteOperation will leave created operation in Self->Operations and accumulated changes in the context.
387390
// Unsuccessful IgniteOperation will leave no operation and context will also be clean.
@@ -444,7 +447,7 @@ struct TSchemeShard::TTxOperationPropose: public NTabletFlatExecutor::TTransacti
444447
<< ", response: " << Response->Record.ShortDebugString()
445448
<< ", at schemeshard: " << Self->TabletID());
446449

447-
AuditLogModifySchemeTransaction(record, Response->Record, Self, UserSID, SanitizedToken);
450+
AuditLogModifySchemeTransaction(record, Response->Record, Self, PeerName, UserSID, SanitizedToken);
448451

449452
//NOTE: Double audit output into the common log as a way to ease
450453
// transition to a new auditlog stream.

ydb/core/tx/schemeshard/schemeshard__operation_alter_login.cpp

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
#include "schemeshard_audit_log.h"
12
#include "schemeshard__operation_part.h"
23
#include "schemeshard__operation_common.h"
34
#include "schemeshard_impl.h"
@@ -16,19 +17,24 @@ class TAlterLogin: public TSubOperationBase {
1617
THolder<TProposeResponse> Propose(const TString&, TOperationContext& context) override {
1718
NIceDb::TNiceDb db(context.GetTxc().DB); // do not track is there are direct writes happen
1819
TTabletId ssId = context.SS->SelfTabletId();
19-
auto result = MakeHolder<TProposeResponse>(OperationId.GetTxId(), ssId);
20+
const auto txId = OperationId.GetTxId();
21+
auto result = MakeHolder<TProposeResponse>(txId, ssId);
2022
if (!AppData()->AuthConfig.GetEnableLoginAuthentication()) {
2123
result->SetStatus(NKikimrScheme::StatusPreconditionFailed, "Login authentication is disabled");
2224
} else if (Transaction.GetWorkingDir() != context.SS->LoginProvider.Audience) {
2325
result->SetStatus(NKikimrScheme::StatusPreconditionFailed, "Wrong working dir");
2426
} else {
2527
const NKikimrConfig::TSecurityConfig& securityConfig = context.SS->GetSecurityConfig();
2628
const NKikimrSchemeOp::TAlterLogin& alterLogin = Transaction.GetAlterLogin();
29+
30+
TParts additionalParts;
31+
2732
switch (alterLogin.GetAlterCase()) {
2833
case NKikimrSchemeOp::TAlterLogin::kCreateUser: {
2934
const auto& createUser = alterLogin.GetCreateUser();
3035
auto response = context.SS->LoginProvider.CreateUser(
3136
{.User = createUser.GetUser(), .Password = createUser.GetPassword()});
37+
3238
if (response.Error) {
3339
result->SetStatus(NKikimrScheme::StatusPreconditionFailed, response.Error);
3440
} else {
@@ -46,6 +52,8 @@ class TAlterLogin: public TSubOperationBase {
4652
}
4753
}
4854
result->SetStatus(NKikimrScheme::StatusSuccess);
55+
56+
AddIsUserAdmin(createUser.GetUser(), context.SS->LoginProvider, additionalParts);
4957
}
5058
break;
5159
}
@@ -58,16 +66,27 @@ class TAlterLogin: public TSubOperationBase {
5866
auto& sid = context.SS->LoginProvider.Sids[modifyUser.GetUser()];
5967
db.Table<Schema::LoginSids>().Key(sid.Name).Update<Schema::LoginSids::SidType, Schema::LoginSids::SidHash>(sid.Type, sid.Hash);
6068
result->SetStatus(NKikimrScheme::StatusSuccess);
69+
70+
AddIsUserAdmin(modifyUser.GetUser(), context.SS->LoginProvider, additionalParts);
71+
AddLastSuccessfulLogin(sid, additionalParts);
6172
}
6273
break;
6374
}
6475
case NKikimrSchemeOp::TAlterLogin::kRemoveUser: {
6576
const auto& removeUser = alterLogin.GetRemoveUser();
77+
78+
auto sid = context.SS->LoginProvider.Sids.find(removeUser.GetUser());
79+
if (context.SS->LoginProvider.Sids.end() != sid) {
80+
AddLastSuccessfulLogin(sid->second, additionalParts);
81+
}
82+
6683
auto response = RemoveUser(context, removeUser, db);
6784
if (response.Error) {
6885
result->SetStatus(NKikimrScheme::StatusPreconditionFailed, response.Error);
6986
} else {
7087
result->SetStatus(NKikimrScheme::StatusSuccess);
88+
89+
AddIsUserAdmin(removeUser.GetUser(), context.SS->LoginProvider, additionalParts);
7190
}
7291
break;
7392
}
@@ -162,6 +181,15 @@ class TAlterLogin: public TSubOperationBase {
162181
break;
163182
}
164183
}
184+
185+
TString userSID, sanitizedToken;
186+
if (context.UserToken) {
187+
userSID = context.UserToken->GetUserSID();
188+
sanitizedToken = context.UserToken->GetSanitizedToken();
189+
}
190+
const auto status = result->Record.GetStatus();
191+
const auto reason = result->Record.HasReason() ? result->Record.GetReason() : TString();
192+
AuditLogModifySchemeOperation(Transaction, status, reason, context.SS, context.PeerName, userSID, sanitizedToken, ui64(txId), additionalParts);
165193
}
166194

167195
if (result->Record.GetStatus() == NKikimrScheme::StatusSuccess) {
@@ -246,6 +274,32 @@ class TAlterLogin: public TSubOperationBase {
246274

247275
return {}; // success
248276
}
277+
278+
void AddIsUserAdmin(const TString& user, NLogin::TLoginProvider& loginProvider, TParts& additionalParts) {
279+
const auto& adminSids = AppData()->AdministrationAllowedSIDs;
280+
bool isAdmin = adminSids.empty();
281+
if (!isAdmin) {
282+
const auto providerGroups = loginProvider.GetGroupsMembership(user);
283+
const TVector<NACLib::TSID> groups(providerGroups.begin(), providerGroups.end());
284+
const auto userToken = NACLib::TUserToken(user, groups);
285+
auto hasSid = [&userToken](const TString& sid) -> bool {
286+
return userToken.IsExist(sid);
287+
};
288+
isAdmin = std::find_if(adminSids.begin(), adminSids.end(), hasSid) != adminSids.end();
289+
}
290+
291+
if (isAdmin) {
292+
additionalParts.emplace_back("login_user_level", "admin");
293+
}
294+
}
295+
296+
void AddLastSuccessfulLogin(NLogin::TLoginProvider::TSidRecord& sid, TParts& additionalParts) {
297+
const auto duration = sid.LastSuccessfulLogin.time_since_epoch();
298+
const auto time = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
299+
if (time) {
300+
additionalParts.emplace_back("last_login", TInstant::MicroSeconds(time).ToString());
301+
}
302+
}
249303
};
250304

251305
}

ydb/core/tx/schemeshard/schemeshard__operation_part.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct TOperationContext {
9595
TStorageChanges& DbChanges;
9696

9797
TMaybe<NACLib::TUserToken> UserToken;
98+
TString PeerName;
9899
bool IsAllowedPrivateTables = false;
99100

100101
private:

ydb/core/tx/schemeshard/schemeshard_audit_log.cpp

Lines changed: 69 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -81,57 +81,77 @@ TPath DatabasePathFromWorkingDir(TSchemeShard* SS, const TString &opWorkingDir)
8181

8282
} // anonymous namespace
8383

84-
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID, const TString& sanitizedToken) {
84+
void AuditLogModifySchemeOperation(const NKikimrSchemeOp::TModifyScheme& operation,
85+
NKikimrScheme::EStatus status, const TString& reason, TSchemeShard* SS,
86+
const TString& peerName, const TString& userSID, const TString& sanitizedToken,
87+
ui64 txId, const TParts& additionalParts) {
88+
auto logEntry = MakeAuditLogFragment(operation);
89+
90+
TPath databasePath = DatabasePathFromWorkingDir(SS, operation.GetWorkingDir());
91+
auto [cloud_id, folder_id, database_id] = GetDatabaseCloudIds(databasePath);
92+
auto address = NKikimr::NAddressClassifier::ExtractAddress(peerName);
93+
94+
AUDIT_LOG(
95+
AUDIT_PART("component", SchemeshardComponentName)
96+
AUDIT_PART("tx_id", std::to_string(txId))
97+
AUDIT_PART("remote_address", (!address.empty() ? address : EmptyValue))
98+
AUDIT_PART("subject", (!userSID.empty() ? userSID : EmptyValue))
99+
AUDIT_PART("sanitized_token", (!sanitizedToken.empty() ? sanitizedToken : EmptyValue))
100+
AUDIT_PART("database", (!databasePath.IsEmpty() ? databasePath.GetDomainPathString() : EmptyValue))
101+
AUDIT_PART("operation", logEntry.Operation)
102+
AUDIT_PART("paths", RenderList(logEntry.Paths), !logEntry.Paths.empty())
103+
AUDIT_PART("status", GeneralStatus(status))
104+
AUDIT_PART("detailed_status", NKikimrScheme::EStatus_Name(status))
105+
AUDIT_PART("reason", reason, !reason.empty())
106+
107+
for (const auto& [name, value] : additionalParts) {
108+
AUDIT_PART(name, (!value.empty() ? value : EmptyValue))
109+
}
110+
111+
AUDIT_PART("cloud_id", cloud_id, !cloud_id.empty());
112+
AUDIT_PART("folder_id", folder_id, !folder_id.empty());
113+
AUDIT_PART("resource_id", database_id, !database_id.empty());
114+
115+
// Additionally:
116+
117+
// ModifyACL.
118+
// Technically, non-empty ModifyACL field could come with any ModifyScheme operation.
119+
// In practice, ModifyACL will get processed only by:
120+
// 1. explicit operation ESchemeOpModifyACL -- to modify ACL on a path
121+
// 2. ESchemeOpMkDir or ESchemeOpCreate* operations -- to set rights to newly created paths/entities
122+
// 3. ESchemeOpCopyTable -- to be checked against acl size limit, not to be applied in any way
123+
AUDIT_PART("new_owner", logEntry.NewOwner, !logEntry.NewOwner.empty());
124+
AUDIT_PART("acl_add", RenderList(logEntry.ACLAdd), !logEntry.ACLAdd.empty());
125+
AUDIT_PART("acl_remove", RenderList(logEntry.ACLRemove), !logEntry.ACLRemove.empty());
126+
127+
// AlterUserAttributes.
128+
// 1. explicit operation ESchemeOpAlterUserAttributes -- to modify user attributes on a path
129+
// 2. ESchemeOpMkDir or some ESchemeOpCreate* operations -- to set user attributes for newly created paths/entities
130+
AUDIT_PART("user_attrs_add", RenderList(logEntry.UserAttrsAdd), !logEntry.UserAttrsAdd.empty());
131+
AUDIT_PART("user_attrs_remove", RenderList(logEntry.UserAttrsRemove), !logEntry.UserAttrsRemove.empty());
132+
133+
// AlterLogin.
134+
// explicit operation ESchemeOpAlterLogin -- to modify user and groups
135+
AUDIT_PART("login_user", logEntry.LoginUser);
136+
AUDIT_PART("login_group", logEntry.LoginGroup);
137+
AUDIT_PART("login_member", logEntry.LoginMember);
138+
);
139+
}
140+
141+
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request,
142+
const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS,
143+
const TString& peerName, const TString& userSID, const TString& sanitizedToken) {
85144
// Each TEvModifySchemeTransaction.Transaction is a self sufficient operation and should be logged independently
86145
// (even if it was packed into a single TxProxy transaction with some other operations).
146+
const auto txId = request.GetTxId();
147+
const auto status = response.GetStatus();
148+
const auto reason = response.HasReason() ? response.GetReason() : TString();
87149
for (const auto& operation : request.GetTransaction()) {
88-
auto logEntry = MakeAuditLogFragment(operation);
89-
90-
TPath databasePath = DatabasePathFromWorkingDir(SS, operation.GetWorkingDir());
91-
auto [cloud_id, folder_id, database_id] = GetDatabaseCloudIds(databasePath);
92-
auto peerName = NKikimr::NAddressClassifier::ExtractAddress(request.GetPeerName());
93-
94-
AUDIT_LOG(
95-
AUDIT_PART("component", SchemeshardComponentName)
96-
AUDIT_PART("tx_id", std::to_string(request.GetTxId()))
97-
AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EmptyValue))
98-
AUDIT_PART("subject", (!userSID.empty() ? userSID : EmptyValue))
99-
AUDIT_PART("sanitized_token", (!sanitizedToken.empty() ? sanitizedToken : EmptyValue))
100-
AUDIT_PART("database", (!databasePath.IsEmpty() ? databasePath.GetDomainPathString() : EmptyValue))
101-
AUDIT_PART("operation", logEntry.Operation)
102-
AUDIT_PART("paths", RenderList(logEntry.Paths), !logEntry.Paths.empty())
103-
AUDIT_PART("status", GeneralStatus(response.GetStatus()))
104-
AUDIT_PART("detailed_status", NKikimrScheme::EStatus_Name(response.GetStatus()))
105-
AUDIT_PART("reason", response.GetReason(), response.HasReason())
106-
107-
AUDIT_PART("cloud_id", cloud_id, !cloud_id.empty());
108-
AUDIT_PART("folder_id", folder_id, !folder_id.empty());
109-
AUDIT_PART("resource_id", database_id, !database_id.empty());
110-
111-
// Additionally:
112-
113-
// ModifyACL.
114-
// Technically, non-empty ModifyACL field could come with any ModifyScheme operation.
115-
// In practice, ModifyACL will get processed only by:
116-
// 1. explicit operation ESchemeOpModifyACL -- to modify ACL on a path
117-
// 2. ESchemeOpMkDir or ESchemeOpCreate* operations -- to set rights to newly created paths/entities
118-
// 3. ESchemeOpCopyTable -- to be checked against acl size limit, not to be applied in any way
119-
AUDIT_PART("new_owner", logEntry.NewOwner, !logEntry.NewOwner.empty());
120-
AUDIT_PART("acl_add", RenderList(logEntry.ACLAdd), !logEntry.ACLAdd.empty());
121-
AUDIT_PART("acl_remove", RenderList(logEntry.ACLRemove), !logEntry.ACLRemove.empty());
122-
123-
// AlterUserAttributes.
124-
// 1. explicit operation ESchemeOpAlterUserAttributes -- to modify user attributes on a path
125-
// 2. ESchemeOpMkDir or some ESchemeOpCreate* operations -- to set user attributes for newly created paths/entities
126-
AUDIT_PART("user_attrs_add", RenderList(logEntry.UserAttrsAdd), !logEntry.UserAttrsAdd.empty());
127-
AUDIT_PART("user_attrs_remove", RenderList(logEntry.UserAttrsRemove), !logEntry.UserAttrsRemove.empty());
128-
129-
// AlterLogin.
130-
// explicit operation ESchemeOpAlterLogin -- to modify user and groups
131-
AUDIT_PART("login_user", logEntry.LoginUser);
132-
AUDIT_PART("login_group", logEntry.LoginGroup);
133-
AUDIT_PART("login_member", logEntry.LoginMember);
134-
);
150+
const auto type = operation.GetOperationType();
151+
if (NKikimrSchemeOp::EOperationType::ESchemeOpAlterLogin == type) {
152+
continue;
153+
}
154+
AuditLogModifySchemeOperation(operation, status, reason, SS, peerName, userSID, sanitizedToken, txId, TParts());
135155
}
136156
}
137157

@@ -194,7 +214,7 @@ struct TXxportRecord {
194214
TString Status;
195215
Ydb::StatusIds::StatusCode DetailedStatus;
196216
TString Reason;
197-
TVector<std::pair<TString, TString>> AdditionalParts;
217+
TParts AdditionalParts;
198218
TString StartTime;
199219
TString EndTime;
200220
TString CloudId;

ydb/core/tx/schemeshard/schemeshard_audit_log.h

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
#include <util/generic/string.h>
44

55
namespace NKikimrScheme {
6+
enum EStatus : int;
7+
68
class TEvModifySchemeTransaction;
79
class TEvModifySchemeTransactionResult;
810

@@ -24,13 +26,26 @@ namespace NHttp {
2426
class THttpIncomingRequest;
2527
}
2628

29+
namespace NKikimrSchemeOp {
30+
class TModifyScheme;
31+
}
32+
2733
namespace NKikimr::NSchemeShard {
2834

2935
class TSchemeShard;
3036
struct TExportInfo;
3137
struct TImportInfo;
3238

33-
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID, const TString& sanitizedToken);
39+
using TParts = TVector<std::pair<TString, TString>>;
40+
41+
void AuditLogModifySchemeOperation(const NKikimrSchemeOp::TModifyScheme& operation,
42+
NKikimrScheme::EStatus status, const TString& reason, TSchemeShard* SS,
43+
const TString& peerName, const TString& userSID, const TString& sanitizedToken,
44+
ui64 txId, const TParts& additionalParts);
45+
46+
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request,
47+
const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS,
48+
const TString& peerName, const TString& userSID, const TString& sanitizedToken);
3449
void AuditLogModifySchemeTransactionDeprecated(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID);
3550

3651
void AuditLogExportStart(const NKikimrExport::TEvCreateExportRequest& request, const NKikimrExport::TEvCreateExportResponse& response, TSchemeShard* SS);

0 commit comments

Comments
 (0)