Skip to content

Extract password verification to a separate actor #19687

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ydb/core/protos/counters_schemeshard.proto
Original file line number Diff line number Diff line change
Expand Up @@ -645,4 +645,6 @@ enum ETxTypes {

TXTYPE_ADD_SHARDS_DATA_ERASURE = 98 [(TxTypeOpts) = {Name: "TxAddShardsDataErasure"}];
TXTYPE_CANCEL_SHARDS_DATA_ERASURE = 99 [(TxTypeOpts) = {Name: "TxCancelShardsDataErasure"}];

TXTYPE_LOGIN_FINALIZE = 100 [(TxTypeOpts) = {Name: "TxLoginFinalize"}];
}
175 changes: 78 additions & 97 deletions ydb/core/tx/schemeshard/schemeshard__login.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#include "schemeshard_impl.h"
#include <ydb/library/login/login.h>
#include <ydb/library/security/util.h>
#include <ydb/core/protos/auth.pb.h>
#include <ydb/core/base/auth.h>
#include <ydb/core/base/local_user_token.h>

#include "schemeshard_impl.h"

namespace NKikimr {
namespace NSchemeShard {

using namespace NTabletFlatExecutor;

struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
TEvSchemeShard::TEvLogin::TPtr Request;
NLogin::TLoginProvider::TLoginUserResponse Response;
TPathId SubDomainPathId;
bool NeedPublishOnComplete = false;
THolder<TEvSchemeShard::TEvLoginResult> Result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
bool SendFinalizeEvent = false;
TString ErrMessage;

TTxLogin(TSelf *self, TEvSchemeShard::TEvLogin::TPtr &ev)
: TRwTxBase(self)
Expand Down Expand Up @@ -43,134 +43,115 @@ struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
<< " at schemeshard: " << Self->TabletID());
NIceDb::TNiceDb db(txc.DB);
if (Self->LoginProvider.IsItTimeToRotateKeys()) {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, "TTxLogin RotateKeys at schemeshard: " << Self->TabletID());
std::vector<ui64> keysExpired;
std::vector<ui64> keysAdded;
Self->LoginProvider.RotateKeys(keysExpired, keysAdded);
SubDomainPathId = Self->GetCurrentSubDomainPathId();
TSubDomainInfo::TPtr domainPtr = Self->ResolveDomainInfo(SubDomainPathId);

// TODO(xenoxeno): optimize security state changes
domainPtr->UpdateSecurityState(Self->LoginProvider.GetSecurityState());
domainPtr->IncSecurityStateVersion();


Self->PersistSubDomainSecurityStateVersion(db, SubDomainPathId, *domainPtr);
RotateKeys(ctx, db);
NeedPublishOnComplete = true;
}

for (ui64 keyId : keysExpired) {
db.Table<Schema::LoginKeys>().Key(keyId).Delete();
}
for (ui64 keyId : keysAdded) {
const auto* key = Self->LoginProvider.FindKey(keyId);
if (key) {
db.Table<Schema::LoginKeys>().Key(keyId).Update<Schema::LoginKeys::KeyDataPEM, Schema::LoginKeys::ExpiresAt>(
key->PublicKey, ToInstant(key->ExpiresAt).MilliSeconds());
}
const auto& loginRequest = GetLoginRequest();
if (!loginRequest.ExternalAuth) {
if (!AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) {
ErrMessage = "Login authentication is disabled";
} else {
CheckLockOutUserAndSetErrorIfAny(loginRequest.User, db);
}
}

NeedPublishOnComplete = true;
if (ErrMessage) {
SendError();
return;
}

LoginAttempt(db, ctx);
TString passwordHash;
if (Self->LoginProvider.NeedVerifyHash(loginRequest, &Response, &passwordHash)) {
ctx.Send(
Self->LoginHelper,
MakeHolder<TEvPrivate::TEvVerifyPassword>(loginRequest, Response, Request->Sender, passwordHash),
0,
Request->Cookie
);
} else {
SendFinalizeEvent = true;
}
}

void DoComplete(const TActorContext &ctx) override {
if (NeedPublishOnComplete) {
Self->PublishToSchemeBoard(TTxId(), {SubDomainPathId}, ctx);
}

if (SendFinalizeEvent) {
auto event = MakeHolder<TEvPrivate::TEvLoginFinalize>(
GetLoginRequest(), Response, Request->Sender, "", /*needUpdateCache*/ false
);
TEvPrivate::TEvLoginFinalize::TPtr eventPtr = (TEventHandle<TEvPrivate::TEvLoginFinalize>*) new IEventHandle(
Self->SelfId(), Self->SelfId(), event.Release()
);
Self->Execute(Self->CreateTxLoginFinalize(eventPtr), ctx);
}

LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
"TTxLogin Complete"
<< ", result: " << Result->Record.ShortDebugString()
<< ", with " << (ErrMessage ? "error: " + ErrMessage : "no errors")
<< ", at schemeshard: " << Self->TabletID());

ctx.Send(Request->Sender, std::move(Result), 0, Request->Cookie);
}
}

private:
bool IsAdmin() const {
const auto& user = Request->Get()->Record.GetUser();
const auto userToken = NKikimr::BuildLocalUserToken(Self->LoginProvider, user);
return IsAdministrator(AppData(), &userToken);
}

void LoginAttempt(NIceDb::TNiceDb& db, const TActorContext& ctx) {
const auto& loginRequest = GetLoginRequest();
if (!loginRequest.ExternalAuth && !AppData(ctx)->AuthConfig.GetEnableLoginAuthentication()) {
Result->Record.SetError("Login authentication is disabled");
return;
}
if (loginRequest.ExternalAuth) {
HandleExternalAuth(loginRequest);
} else {
HandleLoginAuth(loginRequest, db);
}
}

void HandleExternalAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest) {
const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
switch (loginResponse.Status) {
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
Result->Record.SetToken(loginResponse.Token);
Result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
Result->Record.SetIsAdmin(IsAdmin());
break;
}
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
Result->Record.SetError(loginResponse.Error);
break;
void RotateKeys(const TActorContext& ctx, NIceDb::TNiceDb& db) {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD, "TTxLogin RotateKeys at schemeshard: " << Self->TabletID());
std::vector<ui64> keysExpired;
std::vector<ui64> keysAdded;
Self->LoginProvider.RotateKeys(keysExpired, keysAdded);
SubDomainPathId = Self->GetCurrentSubDomainPathId();
TSubDomainInfo::TPtr domainPtr = Self->ResolveDomainInfo(SubDomainPathId);

// TODO(xenoxeno): optimize security state changes
domainPtr->UpdateSecurityState(Self->LoginProvider.GetSecurityState());
domainPtr->IncSecurityStateVersion();

Self->PersistSubDomainSecurityStateVersion(db, SubDomainPathId, *domainPtr);

for (ui64 keyId : keysExpired) {
db.Table<Schema::LoginKeys>().Key(keyId).Delete();
}
for (ui64 keyId : keysAdded) {
const auto* key = Self->LoginProvider.FindKey(keyId);
if (key) {
db.Table<Schema::LoginKeys>().Key(keyId).Update<Schema::LoginKeys::KeyDataPEM, Schema::LoginKeys::ExpiresAt>(
key->PublicKey, ToInstant(key->ExpiresAt).MilliSeconds());
}
}
}

void HandleLoginAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
void CheckLockOutUserAndSetErrorIfAny(const TString& user, NIceDb::TNiceDb& db) {
using namespace NLogin;
const TLoginProvider::TCheckLockOutResponse checkLockOutResponse = Self->LoginProvider.CheckLockOutUser({.User = loginRequest.User});
const TLoginProvider::TCheckLockOutResponse checkLockOutResponse = Self->LoginProvider.CheckLockOutUser({.User = user});
switch (checkLockOutResponse.Status) {
case TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS:
case TLoginProvider::TCheckLockOutResponse::EStatus::INVALID_USER: {
Result->Record.SetError(checkLockOutResponse.Error);
ErrMessage = checkLockOutResponse.Error;
return;
}
case TLoginProvider::TCheckLockOutResponse::EStatus::RESET: {
const auto& sid = Self->LoginProvider.Sids[loginRequest.User];
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::FailedAttemptCount>(sid.FailedLoginAttemptCount);
const auto& sid = Self->LoginProvider.Sids[user];
db.Table<Schema::LoginSids>().Key(user).Update<Schema::LoginSids::FailedAttemptCount>(sid.FailedLoginAttemptCount);
break;
}
case TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED:
case TLoginProvider::TCheckLockOutResponse::EStatus::UNSPECIFIED: {
break;
}
}
}

const TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
switch (loginResponse.Status) {
case TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
const auto& sid = Self->LoginProvider.Sids[loginRequest.User];
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastSuccessfulAttempt,
Schema::LoginSids::FailedAttemptCount>(ToMicroSeconds(sid.LastSuccessfulLogin), sid.FailedLoginAttemptCount);
Result->Record.SetToken(loginResponse.Token);
Result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
Result->Record.SetIsAdmin(IsAdmin());
break;
}
case TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: {
const auto& sid = Self->LoginProvider.Sids[loginRequest.User];
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastFailedAttempt,
Schema::LoginSids::FailedAttemptCount>(ToMicroSeconds(sid.LastFailedLogin), sid.FailedLoginAttemptCount);
Result->Record.SetError(loginResponse.Error);
break;
}
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
Result->Record.SetError(loginResponse.Error);
break;
}
}
void SendError() {
THolder<TEvSchemeShard::TEvLoginResult> result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
result->Record.SetError(ErrMessage);
Self->Send(
Request->Sender,
std::move(result),
0,
Request->Cookie
);
}
};

Expand Down
132 changes: 132 additions & 0 deletions ydb/core/tx/schemeshard/schemeshard__login_finalize.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
#include "schemeshard_impl.h"
#include <ydb/library/security/util.h>
#include <ydb/core/protos/auth.pb.h>
#include <ydb/core/base/auth.h>
#include <ydb/core/base/local_user_token.h>

namespace NKikimr {
namespace NSchemeShard {

struct TSchemeShard::TTxLoginFinalize : TSchemeShard::TRwTxBase {
private:
TEvPrivate::TEvLoginFinalize::TPtr LoginFinalizeEventPtr;
TString ErrMessage;

public:
TTxLoginFinalize(TSelf *self, TEvPrivate::TEvLoginFinalize::TPtr &ev)
: TRwTxBase(self)
, LoginFinalizeEventPtr(std::move(ev))
{}

TTxType GetTxType() const override {
return TXTYPE_LOGIN_FINALIZE;
}

void DoExecute(TTransactionContext& txc, const TActorContext& ctx) override {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
"TTxLoginFinalize Execute"
<< " at schemeshard: " << Self->TabletID());

const auto& event = *LoginFinalizeEventPtr->Get();
if (event.NeedUpdateCache) {
const auto isSuccessVerifying =
event.CheckResult.Status == NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS;
Self->LoginProvider.UpdateCache(
event.Request,
event.PasswordHash,
isSuccessVerifying
);
}
const auto response = Self->LoginProvider.LoginUser(event.Request, event.CheckResult);

if (!LoginFinalizeEventPtr->Get()->Request.ExternalAuth) {
UpdateLoginSidsStats(response, txc);
}
if (!response.Error.empty()) {
ErrMessage = response.Error;
SendError(response.Error);
return;
}
FillResult(response);
}

void DoComplete(const TActorContext &ctx) override {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
"TTxLoginFinalize Completed"
<< ", with " << (ErrMessage ? "error: " + ErrMessage : "no errors")
<< " at schemeshard: " << Self->TabletID());
}

private:
bool IsAdmin(const TString& user) const {
const auto userToken = NKikimr::BuildLocalUserToken(Self->LoginProvider, user);
return IsAdministrator(AppData(), &userToken);
}

void FillResult(const NLogin::TLoginProvider::TLoginUserResponse& response) {
THolder<TEvSchemeShard::TEvLoginResult> result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
switch (response.Status) {
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
result->Record.SetToken(response.Token);
result->Record.SetSanitizedToken(response.SanitizedToken);
result->Record.SetIsAdmin(IsAdmin(LoginFinalizeEventPtr->Get()->Request.User));
break;
}
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
result->Record.SetError(response.Error);
break;
}
}
Self->Send(
LoginFinalizeEventPtr->Get()->Source,
std::move(result),
0,
LoginFinalizeEventPtr->Cookie
);
}

void SendError(const TString& error) {
auto result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
result->Record.SetError(error);
Self->Send(
LoginFinalizeEventPtr->Get()->Source,
std::move(result),
0,
LoginFinalizeEventPtr->Cookie
);
}

void UpdateLoginSidsStats(const NLogin::TLoginProvider::TLoginUserResponse& response, TTransactionContext& txc) {
NIceDb::TNiceDb db(txc.DB);
switch (response.Status) {
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
const auto& sid = Self->LoginProvider.Sids[LoginFinalizeEventPtr->Get()->Request.User];
db.Table<Schema::LoginSids>()
.Key(LoginFinalizeEventPtr->Get()->Request.User)
.Update<Schema::LoginSids::LastSuccessfulAttempt, Schema::LoginSids::FailedAttemptCount>(
ToMicroSeconds(sid.LastSuccessfulLogin), sid.FailedLoginAttemptCount
);
break;
}
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: {
const auto& sid = Self->LoginProvider.Sids[LoginFinalizeEventPtr->Get()->Request.User];
db.Table<Schema::LoginSids>()
.Key(LoginFinalizeEventPtr->Get()->Request.User)
.Update<Schema::LoginSids::LastFailedAttempt, Schema::LoginSids::FailedAttemptCount>(
ToMicroSeconds(sid.LastFailedLogin), sid.FailedLoginAttemptCount
);
}
default:
break;
}
}
};

NTabletFlatExecutor::ITransaction* TSchemeShard::CreateTxLoginFinalize(TEvPrivate::TEvLoginFinalize::TPtr &ev) {
return new TTxLoginFinalize(this, ev);
}

}}
Loading
Loading