Skip to content

Commit 3479e07

Browse files
authored
Merge fdbc79b into 2764fa1
2 parents 2764fa1 + fdbc79b commit 3479e07

File tree

7 files changed

+175
-12
lines changed

7 files changed

+175
-12
lines changed

ydb/core/tx/scheme_board/cache.cpp

+4-5
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,8 @@ namespace {
190190
return entry.KeyDescription->SecurityObject;
191191
}
192192

193-
static ui32 GetAccess(const TNavigate::TEntry&) {
194-
return NACLib::EAccessRights::DescribeSchema;
193+
static ui32 GetAccess(const TNavigate::TEntry& entry) {
194+
return entry.Access;
195195
}
196196

197197
static ui32 GetAccess(const TResolve::TEntry& entry) {
@@ -296,12 +296,11 @@ namespace {
296296
continue;
297297
}
298298

299-
const ui32 access = GetAccess(entry);
300-
if (!securityObject->CheckAccess(access, *Context->Request->UserToken)) {
299+
if (!securityObject->CheckAccess(entry.Access, *Context->Request->UserToken)) {
301300
SBC_LOG_W("Access denied"
302301
<< ": self# " << this->SelfId()
303302
<< ", for# " << Context->Request->UserToken->GetUserSID()
304-
<< ", access# " << NACLib::AccessRightsToString(access));
303+
<< ", access# " << NACLib::AccessRightsToString(entry.Access));
305304

306305
SetErrorAndClear(
307306
Context.Get(),

ydb/core/tx/scheme_cache/scheme_cache.h

+1
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ struct TSchemeCacheNavigate {
258258

259259
// in
260260
TVector<TString> Path;
261+
ui32 Access = NACLib::DescribeSchema;
261262
TTableId TableId;
262263
ERequestType RequestType = ERequestType::ByPath;
263264
EOp Operation = OpUnknown;

ydb/public/sdk/cpp/client/ydb_topic/ut/describe_topic_ut.cpp

+76-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
#include "impl/trace_lazy.h"
12
#include "ut_utils/topic_sdk_test_setup.h"
23

4+
#include <format>
35
#include <ydb/library/persqueue/topic_parser_public/topic_parser.h>
6+
#include <ydb/public/api/protos/persqueue_error_codes_v1.pb.h>
47
#include <ydb/public/sdk/cpp/client/ydb_topic/topic.h>
58
#include <ydb/public/sdk/cpp/client/ydb_persqueue_public/persqueue.h>
69
#include <ydb/public/sdk/cpp/client/ydb_topic/impl/common.h>
@@ -10,8 +13,6 @@
1013
#include <library/cpp/threading/future/future.h>
1114
#include <library/cpp/threading/future/async.h>
1215

13-
#include <future>
14-
1516
namespace NYdb::NTopic::NTests {
1617

1718
Y_UNIT_TEST_SUITE(Describe) {
@@ -344,5 +345,78 @@ namespace NYdb::NTopic::NTests {
344345
DescribeConsumer(setup, client, false, false, true, true);
345346
DescribePartition(setup, client, false, false, true, true);
346347
}
348+
349+
TDescribePartitionResult RunPermissionTest(TTopicSdkTestSetup& setup, int userId, bool existingTopic, bool allowUpdateRow, bool allowDescribeSchema) {
350+
TString authToken = TStringBuilder() << "x-user-" << userId << "@builtin";
351+
Cerr << std::format("=== existingTopic={} allowUpdateRow={} allowDescribeSchema={} authToken={}\n",
352+
existingTopic, allowUpdateRow, allowDescribeSchema, std::string(authToken));
353+
354+
auto driverConfig = setup.MakeDriverConfig().SetAuthToken(authToken);
355+
auto client = TTopicClient(TDriver(driverConfig));
356+
auto settings = TDescribePartitionSettings().IncludeLocation(true);
357+
i64 testPartitionId = 0;
358+
359+
NACLib::TDiffACL acl;
360+
if (allowDescribeSchema) {
361+
acl.AddAccess(NACLib::EAccessType::Allow, NACLib::DescribeSchema, authToken);
362+
}
363+
if (allowUpdateRow) {
364+
acl.AddAccess(NACLib::EAccessType::Allow, NACLib::UpdateRow, authToken);
365+
}
366+
setup.GetServer().AnnoyingClient->ModifyACL("/Root", TEST_TOPIC, acl.SerializeAsString());
367+
368+
return client.DescribePartition(existingTopic ? TEST_TOPIC : "bad-topic", testPartitionId, settings).GetValueSync();
369+
}
370+
371+
Y_UNIT_TEST(DescribePartitionPermissions) {
372+
TTopicSdkTestSetup setup(TEST_CASE_NAME);
373+
setup.GetServer().EnableLogs({NKikimrServices::TX_PROXY_SCHEME_CACHE, NKikimrServices::SCHEME_BOARD_SUBSCRIBER}, NActors::NLog::PRI_TRACE);
374+
375+
int userId = 0;
376+
377+
struct Expectation {
378+
bool existingTopic;
379+
bool allowUpdateRow;
380+
bool allowDescribeSchema;
381+
EStatus status;
382+
NYql::TIssueCode issueCode;
383+
};
384+
385+
std::vector<Expectation> expectations{
386+
{0, 0, 0, EStatus::SCHEME_ERROR, Ydb::PersQueue::ErrorCode::ACCESS_DENIED},
387+
{0, 0, 1, EStatus::SCHEME_ERROR, Ydb::PersQueue::ErrorCode::ACCESS_DENIED},
388+
{0, 1, 0, EStatus::SCHEME_ERROR, Ydb::PersQueue::ErrorCode::ACCESS_DENIED},
389+
{0, 1, 1, EStatus::SCHEME_ERROR, Ydb::PersQueue::ErrorCode::ACCESS_DENIED},
390+
{1, 0, 0, EStatus::SCHEME_ERROR, Ydb::PersQueue::ErrorCode::ACCESS_DENIED},
391+
{1, 0, 1, EStatus::SUCCESS, 0},
392+
{1, 1, 0, EStatus::SUCCESS, 0},
393+
{1, 1, 1, EStatus::SUCCESS, 0},
394+
};
395+
396+
for (auto [existing, update, describe, status, issue] : expectations) {
397+
auto result = RunPermissionTest(setup, userId++, existing, update, describe);
398+
auto resultStatus = result.GetStatus();
399+
auto line = TStringBuilder() << "status=" << resultStatus;
400+
NYql::TIssueCode resultIssue = 0;
401+
if (!result.GetIssues().Empty()) {
402+
resultIssue = result.GetIssues().begin()->GetCode();
403+
line << " issueCode=" << resultIssue;
404+
}
405+
line << " issues=" << result.GetIssues().ToOneLineString() << Endl;
406+
Cerr << line;
407+
408+
if (resultStatus == EStatus::SUCCESS) {
409+
auto& p = result.GetPartitionDescription().GetPartition();
410+
TRACE_LAZY(setup.GetLog(), "PartitionDescription",
411+
TRACE_KV("partition_id", p.GetPartitionId()),
412+
TRACE_KV("is_active", p.GetActive()),
413+
TRACE_IF(p.GetPartitionLocation().Defined(),
414+
TRACE_KV("node_id", p.GetPartitionLocation()->GetNodeId()),
415+
TRACE_KV("generation", p.GetPartitionLocation()->GetGeneration())));
416+
}
417+
UNIT_ASSERT_EQUAL(resultStatus, status);
418+
UNIT_ASSERT_EQUAL(resultIssue, issue);
419+
}
420+
}
347421
}
348422
}

ydb/public/sdk/cpp/client/ydb_topic/ut/local_partition_ut.cpp

+49
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,55 @@ namespace NYdb::NTopic::NTests {
610610
UNIT_ASSERT(expected.Matches(events));
611611
}
612612

613+
Y_UNIT_TEST(DirectWriteWithoutDescribeResourcesPermission) {
614+
// It should be possible to use DirectWrite option with UpdateRow permission only.
615+
// In this test we don't grant DescribeSchema permission and check that direct write still works.
616+
617+
auto existingTopic = GetTestParam("existing", "yes") == "yes";
618+
auto allowUpdateRow = GetTestParam("update", "allow") == "allow";
619+
auto allowDescribe = GetTestParam("describe") == "allow";
620+
auto authToken = GetTestParam("token", "x-user-x@builtin");
621+
622+
auto setup = CreateSetup(TEST_CASE_NAME);
623+
setup->GetServer().EnableLogs({NKikimrServices::TX_PROXY_SCHEME_CACHE}, NActors::NLog::PRI_TRACE);
624+
625+
{
626+
// Add UpdateRow permission.
627+
NACLib::TDiffACL acl;
628+
if (allowUpdateRow) {
629+
acl.AddAccess(NACLib::EAccessType::Allow, NACLib::UpdateRow, authToken);
630+
}
631+
// DescribePartitionRequest should work without DescribeSchema permission.
632+
if (allowDescribe) {
633+
acl.AddAccess(NACLib::EAccessType::Allow, NACLib::DescribeSchema, authToken);
634+
}
635+
setup->GetServer().AnnoyingClient->ModifyACL("/Root", TEST_TOPIC, acl.SerializeAsString());
636+
}
637+
638+
TMockDiscoveryService discovery;
639+
discovery.SetGoodEndpoints(*setup);
640+
auto* tracingBackend = new TTracingBackend();
641+
auto driverConfig = CreateConfig(*setup, discovery.GetDiscoveryAddr())
642+
.SetLog(CreateCompositeLogBackend({new TStreamLogBackend(&Cerr), tracingBackend}))
643+
.SetAuthToken(authToken);
644+
TDriver driver(driverConfig);
645+
646+
auto clientSettings = TTopicClientSettings()
647+
.Database("/Root");
648+
649+
TTopicClient client(driver, clientSettings);
650+
651+
auto sessionSettings = TWriteSessionSettings()
652+
.Path(existingTopic ? TEST_TOPIC : "non-existent")
653+
.ProducerId(TEST_MESSAGE_GROUP_ID)
654+
.MessageGroupId(TEST_MESSAGE_GROUP_ID)
655+
.DirectWriteToPartition(true);
656+
657+
auto writeSession = client.CreateSimpleBlockingWriteSession(sessionSettings);
658+
UNIT_ASSERT(writeSession->Write("message"));
659+
writeSession->Close();
660+
}
661+
613662
Y_UNIT_TEST(WithoutPartitionWithSplit) {
614663
auto setup = CreateSetupForSplitMerge(TEST_CASE_NAME);
615664
setup.CreateTopic(TEST_TOPIC, TEST_CONSUMER, 1, 100);

ydb/services/lib/actors/pq_schema_actor.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ namespace NKikimr::NGRpcProxy::V1 {
134134
navigateRequest->DatabaseName = CanonizePath(Database);
135135

136136
NSchemeCache::TSchemeCacheNavigate::TEntry entry;
137+
if (CheckAccessWithUpdateRowPermission) {
138+
entry.Access = NACLib::EAccessRights::UpdateRow;
139+
}
137140
entry.Path = NKikimr::SplitPath(GetTopicPath());
138141
entry.SyncVersion = true;
139142
entry.ShowPrivatePath = showPrivate;
@@ -199,6 +202,8 @@ namespace NKikimr::NGRpcProxy::V1 {
199202
return static_cast<TDerived*>(this)->HandleCacheNavigateResponse(ev);
200203
}
201204
break;
205+
case NSchemeCache::TSchemeCacheNavigate::EStatus::AccessDenied:
206+
[[fallthrough]];
202207
case NSchemeCache::TSchemeCacheNavigate::EStatus::PathErrorUnknown: {
203208
AddIssue(
204209
FillIssue(
@@ -245,7 +250,7 @@ namespace NKikimr::NGRpcProxy::V1 {
245250
}
246251
}
247252

248-
253+
249254
void StateWork(TAutoPtr<IEventHandle>& ev) {
250255
switch (ev->GetTypeRewrite()) {
251256
hFunc(TEvTxProxySchemeCache::TEvNavigateKeySetResult, Handle);
@@ -256,6 +261,7 @@ namespace NKikimr::NGRpcProxy::V1 {
256261

257262
protected:
258263
bool IsDead = false;
264+
bool CheckAccessWithUpdateRowPermission = false;
259265
const TString TopicPath;
260266
const TString Database;
261267
};

ydb/services/persqueue_v1/actors/schema_actors.cpp

+32
Original file line numberDiff line numberDiff line change
@@ -1311,20 +1311,52 @@ TDescribePartitionActor::TDescribePartitionActor(NKikimr::NGRpcService::IRequest
13111311

13121312
void TDescribePartitionActor::Bootstrap(const NActors::TActorContext& ctx)
13131313
{
1314+
LOG_DEBUG_S(ctx, NKikimrServices::PQ_READ_PROXY, "TDescribePartitionActor" << ctx.SelfID.ToString() << ": Bootstrap");
1315+
CheckAccessWithUpdateRowPermission = true;
13141316
TBase::Bootstrap(ctx);
13151317
SendDescribeProposeRequest(ctx);
13161318
Become(&TDescribePartitionActor::StateWork);
13171319
}
13181320

13191321
void TDescribePartitionActor::StateWork(TAutoPtr<IEventHandle>& ev) {
13201322
switch (ev->GetTypeRewrite()) {
1323+
case TEvTxProxySchemeCache::TEvNavigateKeySetResult::EventType:
1324+
if (MaybeRequestWithDescribeSchema(ev)) {
1325+
break;
1326+
}
1327+
[[fallthrough]];
13211328
default:
13221329
if (!TDescribeTopicActorImpl::StateWork(ev, ActorContext())) {
13231330
TBase::StateWork(ev);
13241331
};
13251332
}
13261333
}
13271334

1335+
// Return true if the second request to SchemeCache has been sent,
1336+
// i.e. we had already sent the first request checking the UpdateRow permission,
1337+
// but the result was negative, so we try again, this time using the DescribeSchema permission.
1338+
bool TDescribePartitionActor::MaybeRequestWithDescribeSchema(TAutoPtr<IEventHandle>& ev) {
1339+
if (!std::exchange(CheckAccessWithUpdateRowPermission, false)) {
1340+
// We've got a response to our second request.
1341+
return false;
1342+
}
1343+
1344+
auto evNav = *reinterpret_cast<typename TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr*>(&ev);
1345+
auto const& entries = evNav->Get()->Request.Get()->ResultSet;
1346+
Y_ABORT_UNLESS(entries.size() == 1);
1347+
1348+
if (entries.front().Status == NSchemeCache::TSchemeCacheNavigate::EStatus::Ok) {
1349+
// We do have access to the requested entity.
1350+
// Transfer ownership to the ev pointer, and let the base classes' StateWork methods handle the response.
1351+
ev = *reinterpret_cast<TAutoPtr<IEventHandle>*>(&evNav);
1352+
return false;
1353+
}
1354+
1355+
// We do not have the UpdateRow permission. Check if we're allowed to DescribeSchema.
1356+
SendDescribeProposeRequest(ActorContext());
1357+
return true;
1358+
}
1359+
13281360
void TDescribePartitionActor::HandleCacheNavigateResponse(
13291361
TEvTxProxySchemeCache::TEvNavigateKeySetResult::TPtr& ev
13301362
) {

ydb/services/persqueue_v1/actors/schema_actors.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ struct TDescribeTopicActorSettings {
7171
TVector<ui32> Partitions;
7272
bool RequireStats = false;
7373
bool RequireLocation = false;
74-
74+
7575
TDescribeTopicActorSettings()
7676
: Mode(EMode::DescribeTopic)
7777
{}
@@ -85,7 +85,7 @@ struct TDescribeTopicActorSettings {
8585
, RequireStats(requireStats)
8686
, RequireLocation(requireLocation)
8787
{}
88-
88+
8989
static TDescribeTopicActorSettings DescribeTopic(bool requireStats, bool requireLocation, const TVector<ui32>& partitions = {}) {
9090
TDescribeTopicActorSettings res{EMode::DescribeTopic, requireStats, requireLocation};
9191
res.Partitions = partitions;
@@ -104,7 +104,7 @@ struct TDescribeTopicActorSettings {
104104
res.Partitions = partitions;
105105
return res;
106106
}
107-
107+
108108
static TDescribeTopicActorSettings DescribePartitionSettings(ui32 partition, bool stats, bool location) {
109109
TDescribeTopicActorSettings res{EMode::DescribePartitions, stats, location};
110110
res.Partitions = {partition};
@@ -261,9 +261,11 @@ using TTabletInfo = TDescribeTopicActorImpl::TTabletInfo;
261261
void ApplyResponse(TTabletInfo& tabletInfo, NKikimr::TEvPersQueue::TEvStatusResponse::TPtr& ev, const TActorContext& ctx) override;
262262
void ApplyResponse(TTabletInfo& tabletInfo, NKikimr::TEvPersQueue::TEvReadSessionsInfoResponse::TPtr& ev, const TActorContext& ctx) override;
263263
bool ApplyResponse(TEvPersQueue::TEvGetPartitionsLocationResponse::TPtr& ev, const TActorContext& ctx) override;
264-
264+
265265
virtual void Reply(const TActorContext& ctx) override;
266266

267+
bool MaybeRequestWithDescribeSchema(TAutoPtr<IEventHandle>& ev);
268+
267269
private:
268270
TIntrusiveConstPtr<NSchemeCache::TSchemeCacheNavigate::TPQGroupInfo> PQGroupInfo;
269271
Ydb::Topic::DescribePartitionResult Result;

0 commit comments

Comments
 (0)