Skip to content

Commit

Permalink
Prepare login lockout info in order to use it in system view (#13123)
Browse files Browse the repository at this point in the history
  • Loading branch information
molotkov-and authored Jan 17, 2025
1 parent 2f884a8 commit 2f9c083
Show file tree
Hide file tree
Showing 15 changed files with 484 additions and 163 deletions.
4 changes: 3 additions & 1 deletion ydb/core/tx/schemeshard/schemeshard__init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3884,7 +3884,9 @@ struct TSchemeShard::TTxInit : public TTransactionBase<TSchemeShard> {
sid.SetType(rowset.GetValue<Schema::LoginSids::SidType>());
sid.SetHash(rowset.GetValue<Schema::LoginSids::SidHash>());
sid.SetCreatedAt(rowset.GetValueOrDefault<Schema::LoginSids::CreatedAt>());
sid.SetLastSuccessfulLogin(rowset.GetValue<Schema::LoginSids::LastSuccessfulAttempt>());
sid.SetFailedLoginAttemptCount(rowset.GetValueOrDefault<Schema::LoginSids::FailedAttemptCount>());
sid.SetLastFailedLogin(rowset.GetValueOrDefault<Schema::LoginSids::LastFailedAttempt>());
sid.SetLastSuccessfulLogin(rowset.GetValueOrDefault<Schema::LoginSids::LastSuccessfulAttempt>());
sidIndex[sid.name()] = securityState.SidsSize() - 1;
if (!rowset.Next()) {
return false;
Expand Down
112 changes: 41 additions & 71 deletions ydb/core/tx/schemeshard/schemeshard__login.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ namespace NSchemeShard {

using namespace NTabletFlatExecutor;

struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
struct TSchemeShard::TTxLogin : TSchemeShard::TRwTxBase {
TEvSchemeShard::TEvLogin::TPtr Request;
TPathId SubDomainPathId;
bool NeedPublishOnComplete = false;
THolder<TEvSchemeShard::TEvLoginResult> Result = MakeHolder<TEvSchemeShard::TEvLoginResult>();
size_t CurrentFailedAttemptCount = 0;

TTxLogin(TSelf *self, TEvSchemeShard::TEvLogin::TPtr &ev)
: TTransactionBase<TSchemeShard>(self)
: TRwTxBase(self)
, Request(std::move(ev))
{}

Expand All @@ -36,7 +36,7 @@ struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
};
}

bool Execute(TTransactionContext& txc, const TActorContext& ctx) override {
void DoExecute(TTransactionContext& txc, const TActorContext& ctx) override {
LOG_DEBUG_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
"TTxLogin Execute"
<< " at schemeshard: " << Self->TabletID());
Expand Down Expand Up @@ -70,10 +70,10 @@ struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
NeedPublishOnComplete = true;
}

return LoginAttempt(db, ctx);
LoginAttempt(db, ctx);
}

void Complete(const TActorContext &ctx) override {
void DoComplete(const TActorContext &ctx) override {
if (NeedPublishOnComplete) {
Self->PublishToSchemeBoard(TTxId(), {SubDomainPathId}, ctx);
}
Expand Down Expand Up @@ -103,19 +103,20 @@ struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
return std::find_if(adminSids.begin(), adminSids.end(), hasSid) != adminSids.end();
}

bool LoginAttempt(NIceDb::TNiceDb& db, const TActorContext& ctx) {
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 true;
return;
}
if (loginRequest.ExternalAuth) {
return HandleExternalAuth(loginRequest);
HandleExternalAuth(loginRequest);
} else {
HandleLoginAuth(loginRequest, db);
}
return HandleLoginAuth(loginRequest, db, ctx);
}

bool HandleExternalAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest) {
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: {
Expand All @@ -132,84 +133,53 @@ struct TSchemeShard::TTxLogin : TTransactionBase<TSchemeShard> {
break;
}
}
return true;
}

bool HandleLoginAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db, const TActorContext& /* ctx */) {
auto row = db.Table<Schema::LoginSids>().Key(loginRequest.User).Select();
if (!row.IsReady()) {
return false;
}
if (!row.IsValid()) {
Result->Record.SetError(TStringBuilder() << "Cannot find user: " << loginRequest.User);
return true;
}
CurrentFailedAttemptCount = row.GetValueOrDefault<Schema::LoginSids::FailedAttemptCount>();
TInstant lastFailedAttempt = TInstant::FromValue(row.GetValue<Schema::LoginSids::LastFailedAttempt>());
if (CheckAccountLockout()) {
if (ShouldUnlockAccount(lastFailedAttempt)) {
UnlockAccount(loginRequest, db);
} else {
Result->Record.SetError(TStringBuilder() << "User " << loginRequest.User << " is locked out");
return true;
void HandleLoginAuth(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
using namespace NLogin;
const TLoginProvider::TCheckLockOutResponse checkLockOutResponse = Self->LoginProvider.CheckLockOutUser({.User = loginRequest.User});
switch (checkLockOutResponse.Status) {
case TLoginProvider::TCheckLockOutResponse::EStatus::SUCCESS:
case TLoginProvider::TCheckLockOutResponse::EStatus::INVALID_USER: {
Result->Record.SetError(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);
break;
}
case TLoginProvider::TCheckLockOutResponse::EStatus::UNLOCKED:
case TLoginProvider::TCheckLockOutResponse::EStatus::UNSPECIFIED: {
break;
}
} else if (ShouldResetFailedAttemptCount(lastFailedAttempt)) {
ResetFailedAttemptCount(loginRequest, db);
}
const NLogin::TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);

const TLoginProvider::TLoginUserResponse loginResponse = Self->LoginProvider.LoginUser(loginRequest);
switch (loginResponse.Status) {
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::SUCCESS: {
HandleLoginAuthSuccess(loginRequest, loginResponse, db);
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>(ToInstant(sid.LastSuccessfulLogin).MilliSeconds(), sid.FailedLoginAttemptCount);
Result->Record.SetToken(loginResponse.Token);
Result->Record.SetSanitizedToken(loginResponse.SanitizedToken);
Result->Record.SetIsAdmin(IsAdmin());
break;
}
case NLogin::TLoginProvider::TLoginUserResponse::EStatus::INVALID_PASSWORD: {
HandleLoginAuthInvalidPassword(loginRequest, loginResponse, db);
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>(ToInstant(sid.LastFailedLogin).MilliSeconds(), 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: {
case TLoginProvider::TLoginUserResponse::EStatus::INVALID_USER:
case TLoginProvider::TLoginUserResponse::EStatus::UNAVAILABLE_KEY:
case TLoginProvider::TLoginUserResponse::EStatus::UNSPECIFIED: {
Result->Record.SetError(loginResponse.Error);
break;
}
}
return true;
}

bool CheckAccountLockout() const {
return (Self->AccountLockout.AttemptThreshold != 0 && CurrentFailedAttemptCount >= Self->AccountLockout.AttemptThreshold);
}

bool ShouldResetFailedAttemptCount(const TInstant& lastFailedAttempt) {
if (Self->AccountLockout.AttemptResetDuration == TDuration::Zero()) {
return false;
}
return lastFailedAttempt + Self->AccountLockout.AttemptResetDuration < TAppData::TimeProvider->Now();
}

bool ShouldUnlockAccount(const TInstant& lastFailedAttempt) {
return ShouldResetFailedAttemptCount(lastFailedAttempt);
}

void ResetFailedAttemptCount(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::FailedAttemptCount>(Schema::LoginSids::FailedAttemptCount::Default);
CurrentFailedAttemptCount = Schema::LoginSids::FailedAttemptCount::Default;
}

void UnlockAccount(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, NIceDb::TNiceDb& db) {
ResetFailedAttemptCount(loginRequest, db);
}

void HandleLoginAuthSuccess(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& loginResponse, NIceDb::TNiceDb& db) {
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastSuccessfulAttempt, Schema::LoginSids::FailedAttemptCount>(loginResponse.LoginAttemptTime, Schema::LoginSids::FailedAttemptCount::Default);
}

void HandleLoginAuthInvalidPassword(const NLogin::TLoginProvider::TLoginUserRequest& loginRequest, const NLogin::TLoginProvider::TLoginUserResponse& /* loginResponse */, NIceDb::TNiceDb& db) {
db.Table<Schema::LoginSids>().Key(loginRequest.User).Update<Schema::LoginSids::LastFailedAttempt, Schema::LoginSids::FailedAttemptCount>(TAppData::TimeProvider->Now().MicroSeconds(), CurrentFailedAttemptCount + 1);
}
};

Expand Down
31 changes: 12 additions & 19 deletions ydb/core/tx/schemeshard/schemeshard_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <ydb/core/tx/columnshard/bg_tasks/events/events.h>
#include <ydb/core/tx/scheme_board/events_schemeshard.h>
#include <ydb/library/login/password_checker/password_checker.h>
#include <ydb/library/login/account_lockout/account_lockout.h>
#include <yql/essentials/minikql/mkql_type_ops.h>
#include <yql/essentials/providers/common/proto/gateways_config.pb.h>
#include <util/random/random.h>
Expand Down Expand Up @@ -4438,20 +4439,6 @@ TActorId TSchemeShard::TPipeClientFactory::CreateClient(const TActorContext& ctx
return clientId;
}

TSchemeShard::TAccountLockout::TAccountLockout(const ::NKikimrProto::TAccountLockout& accountLockout)
: AttemptThreshold(accountLockout.GetAttemptThreshold())
{
AttemptResetDuration = TDuration::Zero();
if (accountLockout.GetAttemptResetDuration().empty()) {
return;
}
if (TDuration::TryParse(accountLockout.GetAttemptResetDuration(), AttemptResetDuration)) {
if (AttemptResetDuration.Seconds() == 0) {
AttemptResetDuration = TDuration::Zero();
}
}
}

TSchemeShard::TSchemeShard(const TActorId &tablet, TTabletStorageInfo *info)
: TActor(&TThis::StateInit)
, TTabletExecutedFlat(info, tablet, new NMiniKQL::TMiniKQLFactory)
Expand Down Expand Up @@ -4489,8 +4476,9 @@ TSchemeShard::TSchemeShard(const TActorId &tablet, TTabletStorageInfo *info)
.MinSpecialCharsCount = AppData()->AuthConfig.GetPasswordComplexity().GetMinSpecialCharsCount(),
.SpecialChars = AppData()->AuthConfig.GetPasswordComplexity().GetSpecialChars(),
.CanContainUsername = AppData()->AuthConfig.GetPasswordComplexity().GetCanContainUsername()
}))
, AccountLockout(AppData()->AuthConfig.GetAccountLockout())
}), {.AttemptThreshold = AppData()->AuthConfig.GetAccountLockout().GetAttemptThreshold(),
.AttemptResetDuration = AppData()->AuthConfig.GetAccountLockout().GetAttemptResetDuration()
})
{
TabletCountersPtr.Reset(new TProtobufTabletCounters<
ESimpleCounters_descriptor,
Expand Down Expand Up @@ -7418,11 +7406,16 @@ void TSchemeShard::ConfigureAccountLockout(
const ::NKikimrProto::TAuthConfig& config,
const TActorContext &ctx)
{
AccountLockout = TAccountLockout(config.GetAccountLockout());
NLogin::TAccountLockout::TInitializer accountLockoutInitializer {
.AttemptThreshold = config.GetAccountLockout().GetAttemptThreshold(),
.AttemptResetDuration = config.GetAccountLockout().GetAttemptResetDuration()
};

LoginProvider.UpdateAccountLockout(accountLockoutInitializer);

LOG_NOTICE_S(ctx, NKikimrServices::FLAT_TX_SCHEMESHARD,
"AccountLockout configured: AttemptThreshold# " << AccountLockout.AttemptThreshold
<< ", AttemptResetDuration# " << AccountLockout.AttemptResetDuration.ToString());
"AccountLockout configured: AttemptThreshold# " << accountLockoutInitializer.AttemptThreshold
<< ", AttemptResetDuration# " << accountLockoutInitializer.AttemptResetDuration);
}

void TSchemeShard::StartStopCompactionQueues() {
Expand Down
9 changes: 0 additions & 9 deletions ydb/core/tx/schemeshard/schemeshard_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -1470,15 +1470,6 @@ class TSchemeShard

NLogin::TLoginProvider LoginProvider;

struct TAccountLockout {
size_t AttemptThreshold = 4;
TDuration AttemptResetDuration = TDuration::Hours(1);

TAccountLockout(const ::NKikimrProto::TAccountLockout& accountLockout);
};

TAccountLockout AccountLockout;

private:
void OnDetach(const TActorContext &ctx) override;
void OnTabletDead(TEvTablet::TEvTabletDead::TPtr &ev, const TActorContext &ctx) override;
Expand Down
Loading

0 comments on commit 2f9c083

Please sign in to comment.