Skip to content
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

Prepare login lockout info in order to use it in system view #13123

Merged
Merged
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
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 @@ -7403,11 +7391,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 @@ -1468,15 +1468,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
Loading