Skip to content

Commit 9b2bdb0

Browse files
authored
Sanitized token for login audit logs (#12031)
1 parent 4da9280 commit 9b2bdb0

File tree

10 files changed

+40
-7
lines changed

10 files changed

+40
-7
lines changed

ydb/core/grpc_services/audit_logins.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
namespace NKikimr {
1212
namespace NGRpcService {
1313

14-
void AuditLogLogin(IAuditCtx* ctx, const TString& database, const Ydb::Auth::LoginRequest& request, const Ydb::Auth::LoginResponse& response, const TString& errorDetails)
14+
void AuditLogLogin(IAuditCtx* ctx, const TString& database, const Ydb::Auth::LoginRequest& request, const Ydb::Auth::LoginResponse& response, const TString& errorDetails, const TString& sanitizedToken)
1515
{
1616
static const TString GrpcLoginComponentName = "grpc-login";
1717
static const TString LoginOperationName = "LOGIN";
@@ -49,11 +49,11 @@ void AuditLogLogin(IAuditCtx* ctx, const TString& database, const Ydb::Auth::Log
4949

5050
// Login
5151
AUDIT_PART("login_user", (!request.user().empty() ? request.user() : EmptyValue))
52+
AUDIT_PART("sanitized_token", (!sanitizedToken.empty() ? sanitizedToken : EmptyValue))
5253

5354
//TODO: (?) it is possible to show masked version of the resulting token here
5455
);
5556
}
5657

5758
}
5859
}
59-

ydb/core/grpc_services/audit_logins.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,6 @@ namespace NKikimr::NGRpcService {
1313
class IAuditCtx;
1414

1515
// logins log
16-
void AuditLogLogin(IAuditCtx* ctx, const TString& database, const Ydb::Auth::LoginRequest& request, const Ydb::Auth::LoginResponse& response, const TString& errorDetails);
16+
void AuditLogLogin(IAuditCtx* ctx, const TString& database, const Ydb::Auth::LoginRequest& request, const Ydb::Auth::LoginResponse& response, const TString& errorDetails, const TString& sanitizedToken);
1717

1818
} // namespace NKikimr::NGRpcService

ydb/core/grpc_services/rpc_login.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> {
8888
ReplyErrorAndPassAway(Ydb::StatusIds::INTERNAL_ERROR, "Failed to produce a token");
8989
} else {
9090
// success = token + no errors
91-
ReplyAndPassAway(loginResult.token());
91+
ReplyAndPassAway(loginResult.GetToken(), loginResult.GetSanitizedToken());
9292
}
9393
}
9494

@@ -113,7 +113,7 @@ class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> {
113113
}
114114
}
115115

116-
void ReplyAndPassAway(const TString& resultToken) {
116+
void ReplyAndPassAway(const TString& resultToken, const TString& sanitizedToken) {
117117
TResponse response;
118118

119119
Ydb::Operations::Operation& operation = *response.mutable_operation();
@@ -126,7 +126,7 @@ class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> {
126126
operation.mutable_result()->PackFrom(result);
127127
}
128128

129-
AuditLogLogin(Request.Get(), PathToDatabase, *GetProtoRequest(), response, /* errorDetails */ TString());
129+
AuditLogLogin(Request.Get(), PathToDatabase, *GetProtoRequest(), response, /* errorDetails */ TString(), sanitizedToken);
130130

131131
return CleanupAndReply(response);
132132
}
@@ -143,7 +143,7 @@ class TLoginRPC : public TRpcRequestActor<TLoginRPC, TEvLoginRequest, true> {
143143
issue->set_message(error);
144144
}
145145

146-
AuditLogLogin(Request.Get(), PathToDatabase, *GetProtoRequest(), response, reason);
146+
AuditLogLogin(Request.Get(), PathToDatabase, *GetProtoRequest(), response, reason, {});
147147

148148
return CleanupAndReply(response);
149149
}

ydb/core/protos/flat_tx_scheme.proto

+1
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ message TEvLogin {
153153
message TEvLoginResult {
154154
optional string Error = 1;
155155
optional string Token = 2; // signed jwt token
156+
optional string SanitizedToken = 3; // sanitized token without signature
156157
}
157158

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

ydb/core/security/ticket_parser_impl.h

+3
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,9 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
272272
if (TokenType == TDerived::ETokenType::NebiusAccessService) {
273273
return SanitizeNebiusTicket(Ticket);
274274
}
275+
if (TokenType == TDerived::ETokenType::Login) {
276+
return NLogin::TLoginProvider::SanitizeJwtToken(Ticket);
277+
}
275278
return MaskTicket(Ticket);
276279
}
277280
};

ydb/core/tx/schemeshard/schemeshard__login.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
8383
}
8484
if (loginResponse.Token) {
8585
result->Record.SetToken(loginResponse.Token);
86+
result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
8687
}
8788

8889
} else {

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

+10
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
157157
UNIT_ASSERT_STRING_CONTAINS(last, "status=SUCCESS");
158158
UNIT_ASSERT(!last.contains("reason"));
159159
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1");
160+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token=");
161+
UNIT_ASSERT(last.find("sanitized_token={none}") == std::string::npos);
160162
}
161163

162164
Y_UNIT_TEST(AuditLogLoginBadPassword) {
@@ -198,6 +200,7 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
198200
UNIT_ASSERT_STRING_CONTAINS(last, "status=ERROR");
199201
UNIT_ASSERT_STRING_CONTAINS(last, "reason=Invalid password");
200202
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1");
203+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token={none}");
201204
}
202205

203206
Y_UNIT_TEST(AuditLogLdapLoginSuccess) {
@@ -292,6 +295,8 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
292295
UNIT_ASSERT(!last.contains("detailed_status"));
293296
UNIT_ASSERT(!last.contains("reason"));
294297
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1@ldap");
298+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token=");
299+
UNIT_ASSERT(last.find("sanitized_token={none}") == std::string::npos);
295300
}
296301

297302
Y_UNIT_TEST(AuditLogLdapLoginBadPassword) {
@@ -386,6 +391,7 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
386391
UNIT_ASSERT_STRING_CONTAINS(last, "detailed_status=UNAUTHORIZED");
387392
UNIT_ASSERT_STRING_CONTAINS(last, "reason=Could not login via LDAP: LDAP login failed for user uid=user1,dc=search,dc=yandex,dc=net on server ldap://localhost:");
388393
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1@ldap");
394+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token={none}");
389395
}
390396

391397
Y_UNIT_TEST(AuditLogLdapLoginBadUser) {
@@ -480,6 +486,7 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
480486
UNIT_ASSERT_STRING_CONTAINS(last, "detailed_status=UNAUTHORIZED");
481487
UNIT_ASSERT_STRING_CONTAINS(last, "reason=Could not login via LDAP: LDAP user bad_user does not exist. LDAP search for filter uid=bad_user on server ldap://localhost:");
482488
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=bad_user@ldap");
489+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token={none}");
483490
}
484491

485492
// LDAP responses to bad BindDn or bad BindPassword are the same, so this test covers the both cases.
@@ -575,6 +582,7 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
575582
UNIT_ASSERT_STRING_CONTAINS(last, "detailed_status=UNAUTHORIZED");
576583
UNIT_ASSERT_STRING_CONTAINS(last, "reason=Could not login via LDAP: Could not perform initial LDAP bind for dn cn=robouser,dc=search,dc=yandex,dc=net on server ldap://localhost:");
577584
UNIT_ASSERT_STRING_CONTAINS(last, "login_user=user1@ldap");
585+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token={none}");
578586
}
579587

580588
Y_UNIT_TEST(AuditLogLogout) {
@@ -677,6 +685,8 @@ Y_UNIT_TEST_SUITE(TWebLoginService) {
677685
UNIT_ASSERT_STRING_CONTAINS(last, "subject=user1");
678686
UNIT_ASSERT_STRING_CONTAINS(last, "operation=LOGOUT");
679687
UNIT_ASSERT_STRING_CONTAINS(last, "status=SUCCESS");
688+
UNIT_ASSERT_STRING_CONTAINS(last, "sanitized_token=");
689+
UNIT_ASSERT(last.find("sanitized_token={none}") == std::string::npos);
680690
}
681691
}
682692
}

ydb/library/login/login.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@ TLoginProvider::TLoginUserResponse TLoginProvider::LoginUser(const TLoginUserReq
352352
auto encoded_token = token.sign(algorithm);
353353

354354
response.Token = TString(encoded_token);
355+
response.SanitizedToken = SanitizeJwtToken(response.Token);
355356

356357
return response;
357358
}
@@ -650,4 +651,12 @@ void TLoginProvider::UpdateSecurityState(const NLoginProto::TSecurityState& stat
650651
}
651652
}
652653

654+
TString TLoginProvider::SanitizeJwtToken(const TString& token) {
655+
const size_t signaturePos = token.find_last_of('.');
656+
if (signaturePos == TString::npos || signaturePos == token.size() - 1) {
657+
return {};
658+
}
659+
return TStringBuilder() << TStringBuf(token).SubString(0, signaturePos) << ".**"; // <token>.**
660+
}
661+
653662
}

ydb/library/login/login.h

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class TLoginProvider {
4848

4949
struct TLoginUserResponse : TBasicResponse {
5050
TString Token;
51+
TString SanitizedToken; // Token for audit logs
5152
};
5253

5354
struct TValidateTokenRequest : TBasicRequest {
@@ -178,6 +179,7 @@ class TLoginProvider {
178179
std::vector<TString> GetGroupsMembership(const TString& member);
179180
static TString GetTokenAudience(const TString& token);
180181
static std::chrono::system_clock::time_point GetTokenExpiresAt(const TString& token);
182+
static TString SanitizeJwtToken(const TString& token);
181183

182184
private:
183185
std::deque<TKeyRecord>::iterator FindKeyIterator(ui64 keyId);

ydb/library/login/login_ut.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -283,4 +283,11 @@ Y_UNIT_TEST_SUITE(Login) {
283283
UNIT_ASSERT(response3.ExternalAuth.empty());
284284
}
285285
}
286+
287+
Y_UNIT_TEST(SanitizeJwtToken) {
288+
UNIT_ASSERT_VALUES_EQUAL(TLoginProvider::SanitizeJwtToken("123.456"), "123.**");
289+
UNIT_ASSERT_VALUES_EQUAL(TLoginProvider::SanitizeJwtToken("123.456.789"), "123.456.**");
290+
UNIT_ASSERT_VALUES_EQUAL(TLoginProvider::SanitizeJwtToken("token_without_dot"), "");
291+
UNIT_ASSERT_VALUES_EQUAL(TLoginProvider::SanitizeJwtToken("token_without_signature."), "");
292+
}
286293
}

0 commit comments

Comments
 (0)