diff options
author | ijon <ijon@ydb.tech> | 2025-02-12 22:23:23 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-02-12 22:23:23 +0300 |
commit | 4045ae8943df276c31c030751c64701b5e4d8a5b (patch) | |
tree | 2674bc4c869bbadc1360f63d8406d2252397beb1 | |
parent | 74671b6a5da5f7ebe749cabd5d80a3eccf97bd64 (diff) | |
download | ydb-4045ae8943df276c31c030751c64701b5e4d8a5b.tar.gz |
security: unify and extend admin checks (#14489)
- add `IsTokenAllowed()` to universally check token membership in any `*_allowed_sids` lists
- add `IsDatabaseAdministrator()`
-rw-r--r-- | ydb/core/base/auth.cpp | 77 | ||||
-rw-r--r-- | ydb/core/base/auth.h | 11 | ||||
-rw-r--r-- | ydb/core/base/auth_ut.cpp | 135 | ||||
-rw-r--r-- | ydb/core/base/ut_auth/ya.make | 18 | ||||
-rw-r--r-- | ydb/core/base/ya.make | 1 |
5 files changed, 220 insertions, 22 deletions
diff --git a/ydb/core/base/auth.cpp b/ydb/core/base/auth.cpp index 4994e96fbb..59027978c3 100644 --- a/ydb/core/base/auth.cpp +++ b/ydb/core/base/auth.cpp @@ -1,12 +1,26 @@ +#include <ydb/library/aclib/aclib.h> + #include "auth.h" namespace NKikimr { namespace { -bool HasToken(const TAppData* appData, const NACLib::TUserToken& userToken) { - for (const auto& sid : appData->AdministrationAllowedSIDs) { - if (userToken.IsExist(sid)) { +template <class Iterable> +bool IsTokenAllowedImpl(const NACLib::TUserToken* userToken, const Iterable& allowedSIDs) { + // empty set contains any element + if (allowedSIDs.empty()) { + return true; + } + + // empty element does not belong to any non-empty set + if (!userToken || userToken->GetUserSID().empty()) { + return false; + } + + // its enough to a single sid (user or group) from the token to be in the set + for (const auto& sid : allowedSIDs) { + if (userToken->IsExist(sid)) { return true; } } @@ -14,35 +28,56 @@ bool HasToken(const TAppData* appData, const NACLib::TUserToken& userToken) { return false; } +NACLib::TUserToken ParseUserToken(const TString& userTokenSerialized) { + NACLibProto::TUserToken tokenPb; + if (!tokenPb.ParseFromString(userTokenSerialized)) { + // we want to treat invalid token as empty, + // so then result object must be recreated (or cleared) + // because failed parsing makes result object dirty + tokenPb = NACLibProto::TUserToken(); + } + return NACLib::TUserToken(tokenPb); } -bool IsAdministrator(const TAppData* appData, const TString& userToken) { - if (appData->AdministrationAllowedSIDs.empty()) { - return true; - } +} - if (!userToken) { - return false; - } +bool IsTokenAllowed(const NACLib::TUserToken* userToken, const TVector<TString>& allowedSIDs) { + return IsTokenAllowedImpl(userToken, allowedSIDs); +} - NACLibProto::TUserToken tokenPb; - if (!tokenPb.ParseFromString(userToken)) { - return false; - } +bool IsTokenAllowed(const NACLib::TUserToken* userToken, const NProtoBuf::RepeatedPtrField<TString>& allowedSIDs) { + return IsTokenAllowedImpl(userToken, allowedSIDs); +} + +bool IsTokenAllowed(const TString& userTokenSerialized, const TVector<TString>& allowedSIDs) { + NACLib::TUserToken userToken = ParseUserToken(userTokenSerialized); + return IsTokenAllowed(&userToken, allowedSIDs); +} + +bool IsTokenAllowed(const TString& userTokenSerialized, const NProtoBuf::RepeatedPtrField<TString>& allowedSIDs) { + NACLib::TUserToken userToken = ParseUserToken(userTokenSerialized); + return IsTokenAllowed(&userToken, allowedSIDs); +} - return HasToken(appData, NACLib::TUserToken(std::move(tokenPb))); +bool IsAdministrator(const TAppData* appData, const TString& userTokenSerialized) { + return IsTokenAllowed(userTokenSerialized, appData->AdministrationAllowedSIDs); } bool IsAdministrator(const TAppData* appData, const NACLib::TUserToken* userToken) { - if (appData->AdministrationAllowedSIDs.empty()) { - return true; - } + return IsTokenAllowed(userToken, appData->AdministrationAllowedSIDs); +} + - if (!userToken || userToken->GetSerializedToken().empty()) { +bool IsDatabaseAdministrator(const NACLib::TUserToken* userToken, const NACLib::TSID& databaseOwner) { + // no database, no access + if (databaseOwner.empty()) { return false; } - - return HasToken(appData, *userToken); + // empty token can't have raised access level + if (!userToken || userToken->GetUserSID().empty()) { + return false; + } + return userToken->IsExist(databaseOwner); } } diff --git a/ydb/core/base/auth.h b/ydb/core/base/auth.h index 83de9307ed..3709f45030 100644 --- a/ydb/core/base/auth.h +++ b/ydb/core/base/auth.h @@ -5,8 +5,17 @@ namespace NKikimr { -bool IsAdministrator(const TAppData* appData, const TString& userToken); +// Check token against given list of allowed sids +bool IsTokenAllowed(const NACLib::TUserToken* userToken, const TVector<TString>& allowedSIDs); +bool IsTokenAllowed(const NACLib::TUserToken* userToken, const NProtoBuf::RepeatedPtrField<TString>& allowedSIDs); +bool IsTokenAllowed(const TString& userTokenSerialized, const TVector<TString>& allowedSIDs); +bool IsTokenAllowed(const TString& userTokenSerialized, const NProtoBuf::RepeatedPtrField<TString>& allowedSIDs); +// Check token against AdministrationAllowedSIDs +bool IsAdministrator(const TAppData* appData, const TString& userTokenSerialized); bool IsAdministrator(const TAppData* appData, const NACLib::TUserToken* userToken); +// Check token against database owner +bool IsDatabaseAdministrator(const NACLib::TUserToken* userToken, const NACLib::TSID& databaseOwner); + } diff --git a/ydb/core/base/auth_ut.cpp b/ydb/core/base/auth_ut.cpp new file mode 100644 index 0000000000..c05cf9cd54 --- /dev/null +++ b/ydb/core/base/auth_ut.cpp @@ -0,0 +1,135 @@ +#include <library/cpp/testing/unittest/registar.h> + +#include "auth.h" + +using namespace NKikimr; + +Y_UNIT_TEST_SUITE(AuthTokenAllowed) { + + const TVector<TString> EmptyList; + + // Empty list allows empty token (regardless of its kind) + Y_UNIT_TEST(PassOnEmptyListAndEmptyToken) { + NACLib::TUserToken token(NACLib::TUserToken::TUserTokenInitFields{}); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, EmptyList), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), EmptyList), true); + } + Y_UNIT_TEST(PassOnEmptyListAndTokenWithEmptyUserSid) { + NACLib::TUserToken token({ .UserSID = "" }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, EmptyList), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), EmptyList), true); + } + Y_UNIT_TEST(PassOnEmptyListAndTokenWithEmptyUserSidAndGroups) { + NACLib::TUserToken token({ .UserSID = "", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, EmptyList), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), EmptyList), true); + } + Y_UNIT_TEST(PassOnEmptyListAndNoToken) { + UNIT_ASSERT_EQUAL(IsTokenAllowed(nullptr, EmptyList), true); + } + Y_UNIT_TEST(PassOnEmptyListAndToken) { + NACLib::TUserToken token({ .UserSID = "user1" }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, EmptyList), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), EmptyList), true); + } + Y_UNIT_TEST(PassOnEmptyListAndInvalidTokenSerialized) { + UNIT_ASSERT_EQUAL(IsTokenAllowed("invalid-serialized-protobuf", EmptyList), true); + } + + // Non empty list forbids empty token (regardless of its kind) + Y_UNIT_TEST(FailOnListAndEmptyToken) { + NACLib::TUserToken token(NACLib::TUserToken::TUserTokenInitFields{}); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"entry"}), false); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"entry"}), false); + } + Y_UNIT_TEST(FailOnListAndTokenWithEmptyUserSid) { + NACLib::TUserToken token({ .UserSID = "" }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"entry"}), false); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"entry"}), false); + } + Y_UNIT_TEST(FailOnListAndTokenWithEmptyUserSidAndGroups) { + NACLib::TUserToken token({ .UserSID = "", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"entry"}), false); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"entry"}), false); + } + Y_UNIT_TEST(FailOnListAndNoToken) { + UNIT_ASSERT_EQUAL(IsTokenAllowed(nullptr, {"entry"}), false); + } + + // List matches token + Y_UNIT_TEST(PassOnListMatchUserSid) { + NACLib::TUserToken token({ .UserSID = "user1" }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"group1", "group2", "user1", "user2"}), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"group1", "group2", "user1", "user2"}), true); + } + Y_UNIT_TEST(PassOnListMatchUserSidWithGroup) { + NACLib::TUserToken token({ .UserSID = "user1", .GroupSIDs = {"no-match-group"} }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"group1", "group2", "user1", "user2"}), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"group1", "group2", "user1", "user2"}), true); + } + Y_UNIT_TEST(PassOnListMatchGroupSid) { + NACLib::TUserToken token({ .UserSID = "no-match-user", .GroupSIDs = {"group2"} }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"group1", "group2", "user1", "user2"}), true); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"group1", "group2", "user1", "user2"}), true); + } + + // List does not matchs token + Y_UNIT_TEST(FailOnListMatchGroupSid) { + NACLib::TUserToken token({ .UserSID = "no-match-user", .GroupSIDs = {"no-match-group"} }); + UNIT_ASSERT_EQUAL(IsTokenAllowed(&token, {"group1", "group2", "user1", "user2"}), false); + UNIT_ASSERT_EQUAL(IsTokenAllowed(token.SerializeAsString(), {"group1", "group2", "user1", "user2"}), false); + } + +} + +Y_UNIT_TEST_SUITE(AuthDatabaseAdmin) { + + // Empty owner forbids empty token (regardless of its kind) + Y_UNIT_TEST(FailOnEmptyOwnerAndEmptyToken) { + NACLib::TUserToken token(NACLib::TUserToken::TUserTokenInitFields{}); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, ""), false); + } + Y_UNIT_TEST(FailOnEmptyOwnerAndTokenWithEmptyUserSid) { + NACLib::TUserToken token({ .UserSID = "" }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, ""), false); + } + Y_UNIT_TEST(FailOnEmptyOwnerAndTokenWithEmptyUserSidAndGroups) { + NACLib::TUserToken token({ .UserSID = "", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, ""), false); + } + Y_UNIT_TEST(FailOnEmptyOwnerAndNoToken) { + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(nullptr, ""), false); + } + + // Non empty owner forbids empty token (regardless of its kind) + Y_UNIT_TEST(FailOnOwnerAndEmptyToken) { + NACLib::TUserToken token(NACLib::TUserToken::TUserTokenInitFields{}); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "owner"), false); + } + Y_UNIT_TEST(FailOnOwnerAndTokenWithEmptyUserSid) { + NACLib::TUserToken token({ .UserSID = "" }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "owner"), false); + } + Y_UNIT_TEST(FailOnOwnerAndTokenWithEmptyUserSidAndGroups) { + NACLib::TUserToken token({ .UserSID = "", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "owner"), false); + } + Y_UNIT_TEST(FailOnOwnerAndNoToken) { + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(nullptr, "owner"), false); + } + + // Owner matches token + Y_UNIT_TEST(PassOnOwnerMatchUserSid) { + NACLib::TUserToken token({ .UserSID = "user1" }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "user1"), true); + } + Y_UNIT_TEST(PassOnOwnerMatchUserSidWithGroup) { + NACLib::TUserToken token({ .UserSID = "user1", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "user1"), true); + } + Y_UNIT_TEST(PassOnOwnerMatchGroupSid) { + NACLib::TUserToken token({ .UserSID = "user1", .GroupSIDs = {"group1"} }); + UNIT_ASSERT_EQUAL(IsDatabaseAdministrator(&token, "group1"), true); + } + +} diff --git a/ydb/core/base/ut_auth/ya.make b/ydb/core/base/ut_auth/ya.make new file mode 100644 index 0000000000..456ed858dd --- /dev/null +++ b/ydb/core/base/ut_auth/ya.make @@ -0,0 +1,18 @@ +UNITTEST_FOR(ydb/core/base) + +FORK_SUBTESTS() + +SIZE(MEDIUM) + +PEERDIR( + library/cpp/testing/unittest + ydb/library/aclib +) + +YQL_LAST_ABI_VERSION() + +SRCS( + auth_ut.cpp +) + +END() diff --git a/ydb/core/base/ya.make b/ydb/core/base/ya.make index f1364782c9..7487120139 100644 --- a/ydb/core/base/ya.make +++ b/ydb/core/base/ya.make @@ -125,5 +125,6 @@ RECURSE( RECURSE_FOR_TESTS( ut + ut_auth ut_board_subscriber ) |