aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorijon <ijon@ydb.tech>2025-02-12 22:23:23 +0300
committerGitHub <noreply@github.com>2025-02-12 22:23:23 +0300
commit4045ae8943df276c31c030751c64701b5e4d8a5b (patch)
tree2674bc4c869bbadc1360f63d8406d2252397beb1
parent74671b6a5da5f7ebe749cabd5d80a3eccf97bd64 (diff)
downloadydb-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.cpp77
-rw-r--r--ydb/core/base/auth.h11
-rw-r--r--ydb/core/base/auth_ut.cpp135
-rw-r--r--ydb/core/base/ut_auth/ya.make18
-rw-r--r--ydb/core/base/ya.make1
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
)