Skip to content

Commit ff29028

Browse files
authored
Update IAM API. Use attributes for proper access to IAM permissions (#3099)
1 parent 858a7e9 commit ff29028

File tree

6 files changed

+103
-29
lines changed

6 files changed

+103
-29
lines changed

ydb/core/grpc_services/grpc_request_check_actor.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class TGrpcRequestCheckActor
7373
}
7474

7575
void ProcessCommonAttributes(const TSchemeBoardEvents::TDescribeSchemeResult& schemeData) {
76-
static std::vector<TString> allowedAttributes = {"folder_id", "service_account_id", "database_id"};
76+
static std::vector<TString> allowedAttributes = {"folder_id", "service_account_id", "database_id", "container_id"};
7777
TVector<std::pair<TString, TString>> attributes;
7878
attributes.reserve(schemeData.GetPathDescription().UserAttributesSize());
7979
for (const auto& attr : schemeData.GetPathDescription().GetUserAttributes()) {

ydb/core/security/ticket_parser_impl.h

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -387,12 +387,22 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
387387
}
388388

389389
template <>
390-
static void AddResourcePath<nebius::iam::v1::ResourcePath*>(nebius::iam::v1::ResourcePath* pathsContainer, const TString& id, const TString& type) {
391-
auto resourcePath = pathsContainer->add_path();
390+
static void AddResourcePath<nebius::iam::v1::AuthorizeCheck*>(nebius::iam::v1::AuthorizeCheck* pathsContainer, const TString& id, const TString& type) {
391+
auto resourcePath = pathsContainer->mutable_resource_path()->add_path();
392392
resourcePath->set_id(id);
393393
Y_UNUSED(type);
394394
}
395395

396+
template <typename TPathsContainerPtr>
397+
static void AddContainerId(TPathsContainerPtr pathsContainer, const TString& id) {
398+
Y_UNUSED(pathsContainer, id);
399+
}
400+
401+
template <>
402+
static void AddContainerId<nebius::iam::v1::AuthorizeCheck*>(nebius::iam::v1::AuthorizeCheck* pathsContainer, const TString& id) {
403+
pathsContainer->set_container_id(id);
404+
}
405+
396406
template <typename TTokenRecord, typename TPathsContainerPtr>
397407
void addResourcePaths(const TTokenRecord& record, const TString& permission, TPathsContainerPtr pathsContainer) const {
398408
if (const auto databaseId = record.GetAttributeValue(permission, "database_id"); databaseId) {
@@ -412,6 +422,10 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
412422
if (const TString gizmoId = record.GetAttributeValue(permission, "gizmo_id"); gizmoId) {
413423
AddResourcePath(pathsContainer, gizmoId, "iam.gizmo");
414424
}
425+
426+
if (const TString containerId = record.GetAttributeValue(permission, "container_id"); containerId) {
427+
AddContainerId(pathsContainer, containerId);
428+
}
415429
}
416430

417431
template <typename TTokenRecord>
@@ -452,7 +466,7 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
452466
auto& check = (*request->Request.mutable_checks())[i];
453467
check.set_iam_token(record.Ticket);
454468
check.mutable_permission()->set_name(permissionName);
455-
addResourcePaths(record, permissionName, check.mutable_resource_path());
469+
addResourcePaths(record, permissionName, &check);
456470
requestForPermissions << " " << permissionName;
457471
++i;
458472
}
@@ -555,6 +569,10 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
555569
}
556570

557571
bool ApplySubjectName(const nebius::iam::v1::AuthenticateResponse& response, TString& subject, TString& error) {
572+
if (response.resultcode() != nebius::iam::v1::AuthenticateResponse::OK) {
573+
error = nebius::iam::v1::AuthenticateResponse::ResultCode_Name(response.resultcode());
574+
return false;
575+
}
558576
return ApplySubjectName(response.account(), subject, error);
559577
}
560578

@@ -1033,7 +1051,7 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
10331051
}
10341052
const auto& check = checkIt->second;
10351053

1036-
if (!subjectIsResolved && result.authorized()) {
1054+
if (!subjectIsResolved && result.resultcode() == nebius::iam::v1::AuthorizeResult::OK) {
10371055
const auto& account = result.account();
10381056
TString errorMessage;
10391057
if (!ApplySubjectName(account, record.Subject, errorMessage)) {
@@ -1056,7 +1074,7 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
10561074
if (permissionIt != examinedPermissions.end()) {
10571075
processedPermissions.insert(permissionIt->first);
10581076
auto& permissionRecord = permissionIt->second;
1059-
if (!result.authorized()) {
1077+
if (result.resultcode() != nebius::iam::v1::AuthorizeResult::OK) {
10601078
permissionDeniedCount++;
10611079
permissionRecord.Subject.clear();
10621080
BLOG_TRACE("Ticket " << record.GetMaskedTicket() << " permission " << permissionName << " access denied for subject \"" << (record.Subject ? record.Subject : "<not resolved>") << "\"");
@@ -1070,7 +1088,7 @@ class TTicketParserImpl : public TActorBootstrapped<TDerived> {
10701088
errorMessage << " - ";
10711089
requiredPermissions.push_back(permissionIt);
10721090
}
1073-
errorMessage << "Access Denied";
1091+
errorMessage << nebius::iam::v1::AuthorizeResult::ResultCode_Name(result.resultcode());
10741092
permissionRecord.Error = {.Message = errorMessage, .Retryable = false};
10751093
}
10761094
} else {

ydb/core/security/ticket_parser_ut.cpp

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -253,23 +253,23 @@ void SetUseAccessService<NKikimr::TNebiusAccessServiceMock>(NKikimrProto::TAuthC
253253
}
254254

255255
template <class TAccessServiceMock>
256-
bool IsApiKeySupported() {
257-
return true;
256+
constexpr bool IsNebiusAccessService() {
257+
return false;
258258
}
259259

260260
template <>
261-
bool IsApiKeySupported<NKikimr::TNebiusAccessServiceMock>() {
262-
return false;
261+
constexpr bool IsNebiusAccessService<NKikimr::TNebiusAccessServiceMock>() {
262+
return true;
263263
}
264264

265265
template <class TAccessServiceMock>
266-
bool IsSignatureSupported() {
267-
return true;
266+
constexpr bool IsApiKeySupported() {
267+
return !IsNebiusAccessService<TAccessServiceMock>();
268268
}
269269

270-
template <>
271-
bool IsSignatureSupported<NKikimr::TNebiusAccessServiceMock>() {
272-
return false;
270+
template <class TAccessServiceMock>
271+
constexpr bool IsSignatureSupported() {
272+
return !IsNebiusAccessService<TAccessServiceMock>();
273273
}
274274

275275
} // namespace
@@ -1666,10 +1666,16 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
16661666
TActorId sender = runtime->AllocateEdgeActor();
16671667
TAutoPtr<IEventHandle> handle;
16681668

1669+
TVector<std::pair<TString, TString>> attrs = {{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}};
1670+
if constexpr (IsNebiusAccessService<TAccessServiceMock>()) {
1671+
accessServiceMock.ContainerId = "my_container";
1672+
attrs.emplace_back("container_id", "my_container");
1673+
}
1674+
16691675
// Authorization successful.
16701676
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
16711677
userToken,
1672-
{{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
1678+
attrs,
16731679
{"something.read"})), 0);
16741680
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
16751681
UNIT_ASSERT_C(result->Error.empty(), result->Error);
@@ -1679,7 +1685,7 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
16791685
accessServiceMock.AllowedUserPermissions.insert("user1-something.connect");
16801686
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
16811687
userToken,
1682-
{{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
1688+
attrs,
16831689
{"something.read", "something.connect", "something.list", "something.update"})), 0);
16841690
result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
16851691
UNIT_ASSERT_C(result->Error.empty(), result->Error);
@@ -1689,17 +1695,31 @@ Y_UNIT_TEST_SUITE(TTicketParserTest) {
16891695
UNIT_ASSERT_C(!result->Token->IsExist("something.update-bbbb4554@as"), result->Token->ShortDebugString());
16901696

16911697
// Authorization ApiKey successful.
1692-
if (IsApiKeySupported<TAccessServiceMock>()) {
1698+
if constexpr (IsApiKeySupported<TAccessServiceMock>()) {
16931699
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
16941700
"ApiKey ApiKey-value-valid",
1695-
{{"folder_id", "aaaa1234"}, {"database_id", "bbbb4554"}},
1701+
attrs,
16961702
{"something.read"})), 0);
16971703
result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
16981704
UNIT_ASSERT_C(result->Error.empty(), result->Error);
16991705
UNIT_ASSERT_C(result->Token->IsExist("something.read-bbbb4554@as"), result->Token->ShortDebugString());
17001706
UNIT_ASSERT_C(!result->Token->IsExist("something.write-bbbb4554@as"), result->Token->ShortDebugString());
17011707
}
17021708

1709+
if constexpr (IsNebiusAccessService<TAccessServiceMock>()) {
1710+
// check wrong container
1711+
accessServiceMock.ContainerId = "other_container";
1712+
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
1713+
userToken,
1714+
attrs,
1715+
{"something.read", "read.something", "something.connect", "something.list", "something.update"})), 0);
1716+
TEvTicketParser::TEvAuthorizeTicketResult* result = runtime->GrabEdgeEvent<TEvTicketParser::TEvAuthorizeTicketResult>(handle);
1717+
UNIT_ASSERT_C(!result->Error.empty(), result->Token->ShortDebugString());
1718+
1719+
// switch off this check
1720+
accessServiceMock.ContainerId = "";
1721+
}
1722+
17031723
// Authorization failure with not enough permissions.
17041724
runtime->Send(new IEventHandle(MakeTicketParserID(), sender, new TEvTicketParser::TEvAuthorizeTicket(
17051725
userToken,

ydb/library/ncloud/impl/access_service_ut.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ Y_UNIT_TEST_SUITE_F(TNebiusAccessServiceTest, TTestSetup) {
108108
auto& resp = AccessServiceMock.AuthorizeData["token-perm-path_id"];
109109
{
110110
auto& result = (*resp.Response.mutable_results())[0];
111-
result.set_authorized(true);
111+
result.set_resultcode(nebius::iam::v1::AuthorizeResult::OK);
112112
result.mutable_account()->mutable_user_account()->set_id("user_id");
113113
}
114114

ydb/library/testlib/service_mocks/nebius_access_service_mock.h

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class TTicketParserNebiusAccessServiceMock : public nebius::iam::v1::AccessServi
154154
THashMap<TString, TString> AllowedServicePermissions = {{"service1-something.write", "root1/folder1"}};
155155
THashSet<TString> AllowedResourceIds;
156156
THashSet<TString> UnavailableUserPermissions;
157+
TString ContainerId;
157158

158159
grpc::Status Authorize(
159160
grpc::ServerContext*,
@@ -193,15 +194,21 @@ class TTicketParserNebiusAccessServiceMock : public nebius::iam::v1::AccessServi
193194
"Authorize");
194195
}
195196

196-
auto& result = (*response->mutable_results())[checkId];
197-
198197
if (IsIn(UnavailableUserPermissions, mockPermToFind)) {
199198
return LogResponse(
200199
grpc::Status(grpc::StatusCode::UNAVAILABLE, "Service Unavailable"),
201200
response,
202201
"Authorize");
203202
}
204203

204+
auto& result = (*response->mutable_results())[checkId];
205+
result.set_resultcode(nebius::iam::v1::AuthorizeResult::PERMISSION_DENIED);
206+
207+
if (ContainerId && check.container_id() != ContainerId) {
208+
result.set_resultcode(nebius::iam::v1::AuthorizeResult::PERMISSION_DENIED);
209+
continue;
210+
}
211+
205212
bool allowedResource = true;
206213
if (!AllowedResourceIds.empty()) {
207214
allowedResource = false;
@@ -216,14 +223,14 @@ class TTicketParserNebiusAccessServiceMock : public nebius::iam::v1::AccessServi
216223
if (IsIn(AllowedUserTokens, token)) {
217224
if (IsIn(AllowedUserPermissions, mockPermToFind)) {
218225
result.mutable_account()->mutable_user_account()->set_id(token);
219-
result.set_authorized(true);
226+
result.set_resultcode(nebius::iam::v1::AuthorizeResult::OK);
220227
}
221228
}
222229

223230
if (IsIn(AllowedServiceTokens, token)) {
224231
if (IsIn(AllowedServicePermissions, mockPermToFind)) {
225232
result.mutable_account()->mutable_service_account()->set_id(token);
226-
result.set_authorized(true);
233+
result.set_resultcode(nebius::iam::v1::AuthorizeResult::OK);
227234
}
228235
}
229236
}

ydb/public/api/client/nc_private/accessservice/access_service.proto

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,20 @@ service AccessService {
2323
message AuthenticateRequest {
2424
oneof credentials {
2525
string iam_token = 1;
26+
AwsCompatibleSignature aws_compatible_signature = 2;
2627
}
2728
}
2829

2930
message AuthenticateResponse {
30-
Account account = 1;
31-
google.protobuf.Timestamp session_expires_at = 2;
31+
ResultCode resultCode = 1;
32+
Account account = 2;
33+
google.protobuf.Timestamp session_expires_at = 3;
34+
35+
enum ResultCode {
36+
OK = 0; // authentication was successful.
37+
UNKNOWN_SUBJECT = 1; // if the subject: doesn't exist, deleted, not found or account doesn't exist in the tenant owning the resource.
38+
INVALID_TOKEN = 2; // The iam_token is not valid. It has an invalid length, invalid signature, etc.
39+
}
3240
}
3341

3442
message AuthorizeRequest {
@@ -43,13 +51,34 @@ message AuthorizeCheck {
4351
Permission permission = 1;
4452
string container_id = 2;
4553
ResourcePath resource_path = 3;
46-
oneof credentials {
54+
oneof identifier {
4755
string iam_token = 4;
4856
Account account = 5;
57+
AwsCompatibleSignature aws_compatible_signature = 6;
4958
}
5059
}
5160

5261
message AuthorizeResult {
53-
bool authorized = 1;
62+
ResultCode resultCode = 1;
5463
Account account = 2;
64+
65+
enum ResultCode {
66+
OK = 0; // Access granted.
67+
PERMISSION_DENIED = 1; // Other cases of access denied.
68+
UNKNOWN_SUBJECT = 2; // if the subject: doesn't exist, deleted, not found or account doesn't exist in the tenant owning the resource.
69+
INVALID_TOKEN = 3; // The iam_token is not valid. It has an invalid length, invalid signature, etc.
70+
}
71+
}
72+
73+
message AwsCompatibleSignature {
74+
string aws_access_key_id = 1;
75+
string string_to_sign = 2;
76+
string signature = 3;
77+
AmzSignatureV4KeyParams sign_key_params = 4;
78+
}
79+
80+
message AmzSignatureV4KeyParams {
81+
google.protobuf.Timestamp amz_date = 1;
82+
string amz_region = 2;
83+
string amz_service = 3;
5584
}

0 commit comments

Comments
 (0)