Skip to content

Commit f972e3a

Browse files
committed
auditlog: add logins
Add audit logging for login operation. KIKIMR-21774
1 parent 81ca5b1 commit f972e3a

File tree

10 files changed

+190
-25
lines changed

10 files changed

+190
-25
lines changed

ydb/core/grpc_services/rpc_login.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> {
6363
PipeClient = RegisterWithSameMailbox(pipe);
6464
THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>();
6565
request.Get()->Record = CreateLoginRequest(Credentials, AppData()->AuthConfig);
66+
request.Get()->Record.SetPeerName(Request->GetPeerName());
6667
NTabletPipe::SendData(SelfId(), PipeClient, request.Release());
6768
return;
6869
}

ydb/core/protos/flat_tx_scheme.proto

+3-2
Original file line numberDiff line numberDiff line change
@@ -144,14 +144,15 @@ message TEvUpdateConfigResult {
144144

145145
message TEvLogin {
146146
optional string User = 1;
147-
optional string Password = 2;
147+
optional string Password = 2 [(Ydb.sensitive) = true];
148148
optional string ExternalAuth = 3;
149149
optional uint64 ExpiresAfterMs = 4;
150+
optional string PeerName = 5; // IP address actually, same as TEvModifySchemeTransaction.PeerName
150151
}
151152

152153
message TEvLoginResult {
153154
optional string Error = 1;
154-
optional string Token = 2;
155+
optional string Token = 2; // signed jwt token
155156
}
156157

157158
// Sending actor registers itself to be notified when tx completes

ydb/core/security/login_page.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ class TLoginRequest : public NActors::TActorBootstrapped<TLoginRequest> {
113113
PipeClient = RegisterWithSameMailbox(pipe);
114114
THolder<TEvSchemeShard::TEvLogin> request = MakeHolder<TEvSchemeShard::TEvLogin>();
115115
request.Get()->Record = CreateLoginRequest(AuthCredentials, AppData()->AuthConfig);
116+
request.Get()->Record.SetPeerName(Request->Address->ToString());
116117
NTabletPipe::SendData(SelfId(), PipeClient, request.Release());
117118
}
118119

ydb/core/testlib/basics/runtime.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace NActors {
1717
void TTestBasicRuntime::Initialize(TEgg egg)
1818
{
1919
AddICStuff();
20+
AddAuditLogStuff();
2021

2122
TTestActorRuntime::Initialize(std::move(egg));
2223
}
@@ -76,4 +77,21 @@ namespace NActors {
7677
}
7778
}
7879
}
80+
81+
void TTestBasicRuntime::AddAuditLogStuff()
82+
{
83+
if (AuditLogBackends) {
84+
for (ui32 nodeIndex = 0; nodeIndex < GetNodeCount(); ++nodeIndex) {
85+
AddLocalService(
86+
NKikimr::NAudit::MakeAuditServiceID(),
87+
TActorSetupCmd(
88+
NKikimr::NAudit::CreateAuditWriter(std::move(AuditLogBackends)).Release(),
89+
TMailboxType::HTSwap,
90+
0
91+
),
92+
nodeIndex
93+
);
94+
}
95+
}
96+
}
7997
}

ydb/core/testlib/basics/runtime.h

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <ydb/core/audit/audit_log_service.h>
34
#include <ydb/core/testlib/actors/test_runtime.h>
45
#include <ydb/library/actors/interconnect/interconnect.h>
56

@@ -12,9 +13,13 @@ namespace NActors {
1213
using TNodeLocationCallback = std::function<TNodeLocation(ui32)>;
1314
TNodeLocationCallback LocationCallback;
1415

16+
NKikimr::NAudit::TAuditLogBackends AuditLogBackends;
17+
1518
~TTestBasicRuntime();
1619

1720
void Initialize(TEgg) override;
21+
1822
void AddICStuff();
23+
void AddAuditLogStuff();
1924
};
2025
}

ydb/core/tx/schemeshard/schemeshard__login.cpp

+11-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
#include "schemeshard_impl.h"
21
#include <ydb/library/security/util.h>
32
#include <ydb/core/protos/auth.pb.h>
43

4+
#include "schemeshard_audit_log.h"
5+
#include "schemeshard_impl.h"
6+
57
namespace NKikimr {
68
namespace NSchemeShard {
79

@@ -76,13 +78,16 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
7678
THolder<TEvSchemeShard::TEvLoginResult> result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
7779
const auto& loginRequest = GetLoginRequest();
7880
if (loginRequest.ExternalAuth || AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) {
79-
NLogin::TLoginProvider::TLoginUserResponse LoginResponse = Self->LoginProvider.LoginUser(loginRequest);
80-
if (LoginResponse.Error) {
81-
result->Record.SetError(LoginResponse.Error);
81+
NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
82+
if (loginResponse.Error) {
83+
result->Record.SetError(loginResponse.Error);
8284
}
83-
if (LoginResponse.Token) {
84-
result->Record.SetToken(LoginResponse.Token);
85+
if (loginResponse.Token) {
86+
result->Record.SetToken(loginResponse.Token);
8587
}
88+
89+
AuditLogLogin(Request->Get()->Record, result->Record, Self);
90+
8691
} else {
8792
result->Record.SetError("Login authentication is disabled");
8893
}

ydb/core/tx/schemeshard/schemeshard_audit_log.cpp

+28-6
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@
99

1010
namespace NKikimr::NSchemeShard {
1111

12+
namespace {
13+
const TString SchemeshardComponentName = "schemeshard";
14+
15+
//NOTE: EmptyValue couldn't be an empty string as AUDIT_PART() skips parts with an empty values
16+
const TString EmptyValue = "{none}";
17+
}
18+
1219
TString GeneralStatus(NKikimrScheme::EStatus actualStatus) {
1320
switch(actualStatus) {
1421
case NKikimrScheme::EStatus::StatusAlreadyExists:
@@ -62,11 +69,6 @@ TPath DatabasePathFromWorkingDir(TSchemeShard* SS, const TString &opWorkingDir)
6269
}
6370

6471
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID) {
65-
static const TString SchemeshardComponentName = "schemeshard";
66-
67-
//NOTE: EmptyValue couldn't be an empty string as AUDIT_PART() skips parts with an empty values
68-
static const TString EmptyValue = "{none}";
69-
7072
// Each TEvModifySchemeTransaction.Transaction is a self sufficient operation and should be logged independently
7173
// (even if it was packed into a single TxProxy transaction with some other operations).
7274
for (const auto& operation : request.GetTransaction()) {
@@ -79,7 +81,7 @@ void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransac
7981
AUDIT_LOG(
8082
AUDIT_PART("component", SchemeshardComponentName)
8183
AUDIT_PART("tx_id", std::to_string(request.GetTxId()))
82-
AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EmptyValue) )
84+
AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EmptyValue))
8385
AUDIT_PART("subject", (!userSID.empty() ? userSID : EmptyValue))
8486
AUDIT_PART("database", (!databasePath.IsEmpty() ? databasePath.GetDomainPathString() : EmptyValue))
8587
AUDIT_PART("operation", logEntry.Operation)
@@ -165,4 +167,24 @@ void AuditLogModifySchemeTransactionDeprecated(const NKikimrScheme::TEvModifySch
165167
}
166168
}
167169

170+
void AuditLogLogin(const NKikimrScheme::TEvLogin& request, const NKikimrScheme::TEvLoginResult& response, TSchemeShard* SS) {
171+
static const TString LoginOperationName = "LOGIN";
172+
173+
TPath databasePath = TPath::Root(SS);
174+
auto peerName = NKikimr::NAddressClassifier::ExtractAddress(request.GetPeerName());
175+
176+
AUDIT_LOG(
177+
AUDIT_PART("component", SchemeshardComponentName)
178+
AUDIT_PART("remote_address", (!peerName.empty() ? peerName : EmptyValue))
179+
AUDIT_PART("database", (!databasePath.PathString().empty() ? databasePath.PathString() : EmptyValue))
180+
AUDIT_PART("operation", LoginOperationName)
181+
AUDIT_PART("status", TString(response.GetError().empty() ? "SUCCESS" : "ERROR"))
182+
AUDIT_PART("reason", response.GetError(), response.HasError())
183+
184+
// Login
185+
AUDIT_PART("login_user", (request.HasUser() ? request.GetUser() : EmptyValue))
186+
AUDIT_PART("login_auth_domain", (!request.GetExternalAuth().empty() ? request.GetExternalAuth() : EmptyValue))
187+
);
188+
}
189+
168190
}

ydb/core/tx/schemeshard/schemeshard_audit_log.h

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
namespace NKikimrScheme {
66
class TEvModifySchemeTransaction;
77
class TEvModifySchemeTransactionResult;
8+
9+
class TEvLogin;
10+
class TEvLoginResult;
811
}
912

1013
namespace NKikimr::NSchemeShard {
@@ -14,4 +17,5 @@ class TSchemeShard;
1417
void AuditLogModifySchemeTransaction(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID);
1518
void AuditLogModifySchemeTransactionDeprecated(const NKikimrScheme::TEvModifySchemeTransaction& request, const NKikimrScheme::TEvModifySchemeTransactionResult& response, TSchemeShard* SS, const TString& userSID);
1619

20+
void AuditLogLogin(const NKikimrScheme::TEvLogin& request, const NKikimrScheme::TEvLoginResult& response, TSchemeShard* SS);
1721
}

ydb/core/tx/schemeshard/ut_login/ut_login.cpp

+113-8
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include <util/string/join.h>
2+
13
#include <ydb/core/tx/schemeshard/ut_helpers/helpers.h>
24
#include <ydb/library/login/login.h>
35
#include <ydb/core/protos/auth.pb.h>
@@ -6,15 +8,47 @@ using namespace NKikimr;
68
using namespace NSchemeShard;
79
using namespace NSchemeShardUT_Private;
810

11+
namespace NSchemeShardUT_Private {
12+
13+
// convert into generic test helper?
14+
void TestCreateAlterLoginCreateUser(TTestActorRuntime& runtime, ui64 txId, const TString& database, const TString& user, const TString& password, const TVector<TExpectedResult>& expectedResults) {
15+
std::unique_ptr<TEvSchemeShard::TEvModifySchemeTransaction> modifyTx(CreateAlterLoginCreateUser(txId, user, password));
16+
//TODO: move setting of TModifyScheme.WorkingDir into CreateAlterLoginCreateUser()
17+
//NOTE: TModifyScheme.Name isn't set, intentionally
18+
modifyTx->Record.MutableTransaction(0)->SetWorkingDir(database);
19+
AsyncSend(runtime, TTestTxConfig::SchemeShard, modifyTx.release());
20+
// AlterLoginCreateUser is synchronous in nature, result is returned immediately
21+
TestModificationResults(runtime, txId, expectedResults);
22+
}
23+
24+
} // namespace NSchemeShardUT_Private
25+
26+
namespace {
27+
28+
class TMemoryLogBackend: public TLogBackend {
29+
public:
30+
std::vector<std::string>& Buffer;
31+
32+
TMemoryLogBackend(std::vector<std::string>& buffer)
33+
: Buffer(buffer)
34+
{}
35+
36+
virtual void WriteData(const TLogRecord& rec) override {
37+
Buffer.emplace_back(rec.Data, rec.Len);
38+
}
39+
40+
virtual void ReopenLog() override {
41+
}
42+
};
43+
44+
} // anonymous namespace
45+
946
Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
1047
Y_UNIT_TEST(BasicLogin) {
1148
TTestBasicRuntime runtime;
1249
TTestEnv env(runtime);
1350
ui64 txId = 100;
14-
TActorId sender = runtime.AllocateEdgeActor();
15-
std::unique_ptr<TEvSchemeShard::TEvModifySchemeTransaction> transaction(CreateAlterLoginCreateUser(++txId, "user1", "password1"));
16-
transaction->Record.MutableTransaction(0)->SetWorkingDir("/MyRoot");
17-
ForwardToTablet(runtime, TTestTxConfig::SchemeShard, sender, transaction.release());
51+
TestCreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusSuccess}});
1852
auto resultLogin = Login(runtime, "user1", "password1");
1953
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "");
2054
auto describe = DescribePath(runtime, TTestTxConfig::SchemeShard, "/MyRoot");
@@ -35,10 +69,7 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
3569
TTestEnv env(runtime);
3670
runtime.GetAppData().AuthConfig.SetEnableLoginAuthentication(false);
3771
ui64 txId = 100;
38-
TActorId sender = runtime.AllocateEdgeActor();
39-
std::unique_ptr<TEvSchemeShard::TEvModifySchemeTransaction> transaction(CreateAlterLoginCreateUser(++txId, "user1", "password1"));
40-
transaction->Record.MutableTransaction(0)->SetWorkingDir("/MyRoot");
41-
ForwardToTablet(runtime, TTestTxConfig::SchemeShard, sender, transaction.release());
72+
TestCreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusPreconditionFailed}});
4273
auto resultLogin = Login(runtime, "user1", "password1");
4374
UNIT_ASSERT_VALUES_EQUAL(resultLogin.error(), "Login authentication is disabled");
4475
UNIT_ASSERT_VALUES_EQUAL(resultLogin.token(), "");
@@ -48,4 +79,78 @@ Y_UNIT_TEST_SUITE(TSchemeShardLoginTest) {
4879
UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().HasSecurityState());
4980
UNIT_ASSERT(describe.GetPathDescription().GetDomainDescription().GetSecurityState().PublicKeysSize() > 0);
5081
}
82+
83+
NAudit::TAuditLogBackends CreateTestAuditLogBackends(std::vector<std::string>& buffer) {
84+
NAudit::TAuditLogBackends logBackends;
85+
logBackends[NKikimrConfig::TAuditConfig::TXT].emplace_back(new TMemoryLogBackend(buffer));
86+
return logBackends;
87+
}
88+
89+
Y_UNIT_TEST(AuditLogLoginSuccess) {
90+
TTestBasicRuntime runtime;
91+
std::vector<std::string> lines;
92+
runtime.AuditLogBackends = std::move(CreateTestAuditLogBackends(lines));
93+
TTestEnv env(runtime);
94+
95+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 1); // alter root subdomain
96+
97+
ui64 txId = 100;
98+
99+
TestCreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusSuccess}});
100+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 2); // +user creation
101+
102+
// test body
103+
{
104+
auto resultLogin = Login(runtime, "user1", "password1");
105+
UNIT_ASSERT_C(resultLogin.error().empty(), resultLogin);
106+
}
107+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 3); // +user login
108+
109+
Cerr << "auditlog lines:\n" << JoinSeq('\n', lines) << Endl;
110+
auto last = lines[lines.size() - 1];
111+
Cerr << "auditlog last line:\n" << last << Endl;
112+
113+
UNIT_ASSERT_STRING_CONTAINS(last, "component=schemeshard");
114+
UNIT_ASSERT_STRING_CONTAINS(last, "remote_address="); // can't check the value
115+
UNIT_ASSERT_STRING_CONTAINS(last, "database=/MyRoot");
116+
UNIT_ASSERT_STRING_CONTAINS(last, "operation=LOGIN");
117+
UNIT_ASSERT_STRING_CONTAINS(last, "status=SUCCESS");
118+
UNIT_ASSERT(!last.contains("reason"));
119+
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1");
120+
UNIT_ASSERT_STRING_CONTAINS(last, "login_auth_domain={none}");
121+
}
122+
123+
Y_UNIT_TEST(AuditLogLoginFailure) {
124+
TTestBasicRuntime runtime;
125+
std::vector<std::string> lines;
126+
runtime.AuditLogBackends = std::move(CreateTestAuditLogBackends(lines));
127+
TTestEnv env(runtime);
128+
129+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 1); // alter root subdomain
130+
131+
ui64 txId = 100;
132+
133+
TestCreateAlterLoginCreateUser(runtime, ++txId, "/MyRoot", "user1", "password1", {{NKikimrScheme::StatusSuccess}});
134+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 2); // +user creation
135+
136+
// test body
137+
{
138+
auto resultLogin = Login(runtime, "user1", "bad_password");
139+
UNIT_ASSERT_C(!resultLogin.error().empty(), resultLogin);
140+
}
141+
UNIT_ASSERT_VALUES_EQUAL(lines.size(), 3); // +user login
142+
143+
Cerr << "auditlog lines:\n" << JoinSeq('\n', lines) << Endl;
144+
auto last = lines[lines.size() - 1];
145+
Cerr << "auditlog last line:\n" << last << Endl;
146+
147+
UNIT_ASSERT_STRING_CONTAINS(last, "component=schemeshard");
148+
UNIT_ASSERT_STRING_CONTAINS(last, "remote_address="); // can't check the value
149+
UNIT_ASSERT_STRING_CONTAINS(last, "database=/MyRoot");
150+
UNIT_ASSERT_STRING_CONTAINS(last, "operation=LOGIN");
151+
UNIT_ASSERT_STRING_CONTAINS(last, "status=ERROR");
152+
UNIT_ASSERT_STRING_CONTAINS(last, "reason=Invalid password");
153+
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1");
154+
UNIT_ASSERT_STRING_CONTAINS(last, "login_auth_domain={none}");
155+
}
51156
}

ydb/services/auth/grpc_service.cpp

+6-3
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,21 @@ void TGRpcAuthService::SetupIncomingRequests(NYdbGrpc::TLoggerPtr logger) {
3232
#error ADD_REQUEST macro already defined
3333
#endif
3434

35-
#define ADD_REQUEST_LIMIT(NAME, CB, LIMIT_TYPE) \
35+
#define ADD_REQUEST_LIMIT(NAME, CB, RATE_LIMITER_MODE, AUDIT_MODE) \
3636
MakeIntrusive<TGRpcRequest<Ydb::Auth::NAME##Request, Ydb::Auth::NAME##Response, TGRpcAuthService>> \
3737
(this, this->GetService(), CQ_, \
3838
[this](NYdbGrpc::IRequestContextBase *ctx) { \
3939
NGRpcService::ReportGrpcReqToMon(*ActorSystem_, ctx->GetPeer(), GetSdkBuildInfo(ctx)); \
4040
ActorSystem_->Send(GRpcRequestProxyId_, \
4141
new TGrpcRequestOperationCall<Ydb::Auth::NAME##Request, Ydb::Auth::NAME##Response> \
42-
(ctx, &CB, TRequestAuxSettings{TRateLimiterMode::LIMIT_TYPE, nullptr})); \
42+
(ctx, &CB, TRequestAuxSettings{ \
43+
.RlMode = RATE_LIMITER_MODE, \
44+
.AuditMode = AUDIT_MODE, \
45+
})); \
4346
}, &Ydb::Auth::V1::AuthService::AsyncService::Request ## NAME, \
4447
#NAME, logger, getCounterBlock("login", #NAME))->Run();
4548

46-
ADD_REQUEST_LIMIT(Login, DoLoginRequest, Off)
49+
ADD_REQUEST_LIMIT(Login, DoLoginRequest, TRateLimiterMode::Off, TAuditMode::Auditable)
4750

4851
#undef ADD_REQUEST_LIMIT
4952

0 commit comments

Comments
 (0)