Adding additional blob granule authz tests (#9443)

* added granule location authz tests

* added authz test for blob worker endpoint

* addressing comments

* fixing ide build
This commit is contained in:
Josh Slocum 2023-02-24 09:32:05 -06:00 committed by GitHub
parent 29819b0645
commit 910965a5a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 192 additions and 14 deletions

View File

@ -40,6 +40,32 @@
FDB_BOOLEAN_PARAM(PositiveTestcase);
bool checkGranuleLocations(ErrorOr<GetBlobGranuleLocationsReply> rep, TenantInfo tenant) {
if (rep.isError()) {
if (rep.getError().code() == error_code_permission_denied) {
TraceEvent(SevError, "AuthzSecurityError")
.detail("Case", "CrossTenantGranuleLocationCheckDisallowed")
.log();
}
return false;
} else {
ASSERT(!rep.get().results.empty());
for (auto const& [range, bwIface] : rep.get().results) {
if (!range.begin.startsWith(tenant.prefix.get())) {
TraceEvent(SevError, "AuthzSecurityBlobGranuleRangeLeak")
.detail("TenantId", tenant.tenantId)
.detail("LeakingRangeBegin", range.begin.printable());
}
if (!range.end.startsWith(tenant.prefix.get())) {
TraceEvent(SevError, "AuthzSecurityBlobGranuleRangeLeak")
.detail("TenantId", tenant.tenantId)
.detail("LeakingRangeEnd", range.end.printable());
}
}
return true;
}
}
struct AuthzSecurityWorkload : TestWorkload {
static constexpr auto NAME = "AuthzSecurity";
int actorCount;
@ -55,8 +81,9 @@ struct AuthzSecurityWorkload : TestWorkload {
WipedString signedTokenAnotherTenant;
Standalone<StringRef> tLogConfigKey;
PerfIntCounter crossTenantGetPositive, crossTenantGetNegative, crossTenantCommitPositive, crossTenantCommitNegative,
publicNonTenantRequestPositive, tLogReadNegative, keyLocationLeakNegative, crossTenantBGReadPositive,
crossTenantBGReadNegative;
publicNonTenantRequestPositive, tLogReadNegative, keyLocationLeakNegative, bgLocationLeakNegative,
crossTenantBGLocPositive, crossTenantBGLocNegative, crossTenantBGReqPositive, crossTenantBGReqNegative,
crossTenantBGReadPositive, crossTenantBGReadNegative;
std::vector<std::function<Future<Void>(Database cx)>> testFunctions;
bool checkBlobGranules;
@ -65,8 +92,10 @@ struct AuthzSecurityWorkload : TestWorkload {
crossTenantGetNegative("CrossTenantGetNegative"), crossTenantCommitPositive("CrossTenantCommitPositive"),
crossTenantCommitNegative("CrossTenantCommitNegative"),
publicNonTenantRequestPositive("PublicNonTenantRequestPositive"), tLogReadNegative("TLogReadNegative"),
keyLocationLeakNegative("KeyLocationLeakNegative"), crossTenantBGReadPositive("CrossTenantBGReadPositive"),
crossTenantBGReadNegative("CrossTenantBGReadNegative") {
keyLocationLeakNegative("KeyLocationLeakNegative"), bgLocationLeakNegative("BGLocationLeakNegative"),
crossTenantBGLocPositive("CrossTenantBGLocPositive"), crossTenantBGLocNegative("CrossTenantBGLocNegative"),
crossTenantBGReqPositive("CrossTenantBGReqPositive"), crossTenantBGReqNegative("CrossTenantBGReqNegative"),
crossTenantBGReadPositive("CrossTenantBGReadPositive"), crossTenantBGReadNegative("CrossTenantBGReadNegative") {
testDuration = getOption(options, "testDuration"_sr, 10.0);
transactionsPerSecond = getOption(options, "transactionsPerSecond"_sr, 500.0) / clientCount;
actorCount = getOption(options, "actorsPerClient"_sr, transactionsPerSecond / 5);
@ -91,6 +120,15 @@ struct AuthzSecurityWorkload : TestWorkload {
testFunctions.push_back([this](Database cx) { return testKeyLocationLeakDisallowed(this, cx); });
if (checkBlobGranules) {
testFunctions.push_back([this](Database cx) { return testBlobGranuleLocationLeakDisallowed(this, cx); });
testFunctions.push_back(
[this](Database cx) { return testCrossTenantBGLocDisallowed(this, cx, PositiveTestcase::True); });
testFunctions.push_back(
[this](Database cx) { return testCrossTenantBGLocDisallowed(this, cx, PositiveTestcase::False); });
testFunctions.push_back(
[this](Database cx) { return testCrossTenantBGRequestDisallowed(this, cx, PositiveTestcase::True); });
testFunctions.push_back(
[this](Database cx) { return testCrossTenantBGRequestDisallowed(this, cx, PositiveTestcase::False); });
testFunctions.push_back(
[this](Database cx) { return testCrossTenantBGReadDisallowed(this, cx, PositiveTestcase::True); });
testFunctions.push_back(
@ -121,10 +159,17 @@ struct AuthzSecurityWorkload : TestWorkload {
if (errors)
TraceEvent(SevError, "TestFailure").detail("Reason", "There were client errors.");
clients.clear();
return errors == 0 && crossTenantGetPositive.getValue() > 0 && crossTenantGetNegative.getValue() > 0 &&
crossTenantCommitPositive.getValue() > 0 && crossTenantCommitNegative.getValue() > 0 &&
publicNonTenantRequestPositive.getValue() > 0 && tLogReadNegative.getValue() > 0 &&
keyLocationLeakNegative.getValue() > 0;
bool success = errors == 0 && crossTenantGetPositive.getValue() > 0 && crossTenantGetNegative.getValue() > 0 &&
crossTenantCommitPositive.getValue() > 0 && crossTenantCommitNegative.getValue() > 0 &&
publicNonTenantRequestPositive.getValue() > 0 && tLogReadNegative.getValue() > 0 &&
keyLocationLeakNegative.getValue() > 0;
if (checkBlobGranules) {
success &= bgLocationLeakNegative.getValue() > 0 && crossTenantBGLocPositive.getValue() > 0 &&
crossTenantBGLocNegative.getValue() > 0 && crossTenantBGReqPositive.getValue() > 0 &&
crossTenantBGReqNegative.getValue() > 0 && crossTenantBGReadPositive.getValue() > 0 &&
crossTenantBGReadNegative.getValue() > 0;
}
return success;
}
void getMetrics(std::vector<PerfMetric>& m) override {
@ -135,8 +180,15 @@ struct AuthzSecurityWorkload : TestWorkload {
m.push_back(publicNonTenantRequestPositive.getMetric());
m.push_back(tLogReadNegative.getMetric());
m.push_back(keyLocationLeakNegative.getMetric());
m.push_back(crossTenantBGReadPositive.getMetric());
m.push_back(crossTenantBGReadNegative.getMetric());
if (checkBlobGranules) {
m.push_back(bgLocationLeakNegative.getMetric());
m.push_back(crossTenantBGLocPositive.getMetric());
m.push_back(crossTenantBGLocNegative.getMetric());
m.push_back(crossTenantBGReqPositive.getMetric());
m.push_back(crossTenantBGReqNegative.getMetric());
m.push_back(crossTenantBGReadPositive.getMetric());
m.push_back(crossTenantBGReadNegative.getMetric());
}
}
void setAuthToken(Transaction& tr, StringRef token) {
@ -482,6 +534,99 @@ struct AuthzSecurityWorkload : TestWorkload {
return Void();
}
ACTOR static Future<ErrorOr<GetBlobGranuleLocationsReply>> getGranuleLocations(AuthzSecurityWorkload* self,
Database cx,
TenantInfo tenant,
Version v) {
try {
GetBlobGranuleLocationsReply reply =
wait(basicLoadBalance(cx->getCommitProxies(UseProvisionalProxies::False),
&CommitProxyInterface::getBlobGranuleLocations,
GetBlobGranuleLocationsRequest(
SpanContext(), tenant, ""_sr, Optional<KeyRef>(), 100, false, v, Arena())));
return reply;
} catch (Error& e) {
if (e.code() == error_code_operation_cancelled) {
throw e;
}
CODE_PROBE(e.code() == error_code_permission_denied,
"Cross tenant blob granule locations meets permission_denied");
return e;
}
}
ACTOR static Future<Void> testBlobGranuleLocationLeakDisallowed(AuthzSecurityWorkload* self, Database cx) {
state Key key = self->randomString();
state Value value = self->randomString();
state Version v1 =
wait(setAndCommitKeyValueAndGetVersion(self, cx, self->tenant, self->signedToken, key, value));
state Version v2 = wait(setAndCommitKeyValueAndGetVersion(
self, cx, self->anotherTenant, self->signedTokenAnotherTenant, key, value));
state bool success = true;
state TenantInfo tenantInfo;
{
tenantInfo = TenantInfo(self->tenant->id(), self->signedToken);
ErrorOr<GetBlobGranuleLocationsReply> rep = wait(getGranuleLocations(self, cx, tenantInfo, v2));
bool checkSuccess = checkGranuleLocations(rep, tenantInfo);
success &= checkSuccess;
}
{
tenantInfo = TenantInfo(self->anotherTenant->id(), self->signedTokenAnotherTenant);
ErrorOr<GetBlobGranuleLocationsReply> rep = wait(getGranuleLocations(self, cx, tenantInfo, v2));
bool checkSuccess = checkGranuleLocations(rep, tenantInfo);
success &= checkSuccess;
}
if (success) {
++self->bgLocationLeakNegative;
}
return Void();
}
ACTOR static Future<Optional<Error>> tryBlobGranuleRequest(AuthzSecurityWorkload* self,
Database cx,
Reference<Tenant> tenant,
WipedString locToken,
WipedString reqToken,
Version committedVersion) {
try {
ErrorOr<GetBlobGranuleLocationsReply> rep =
wait(getGranuleLocations(self, cx, TenantInfo(tenant->id(), locToken), committedVersion));
if (rep.isError()) {
if (rep.getError().code() == error_code_permission_denied) {
TraceEvent(SevError, "AuthzSecurityError")
.detail("Case", "GranuleLocBeforeRequestDisallowed")
.log();
}
throw rep.getError();
}
int locIdx = deterministicRandom()->randomInt(0, rep.get().results.size());
ASSERT(!rep.get().results.empty());
BlobGranuleFileRequest req;
req.arena.dependsOn(rep.get().arena);
req.keyRange = rep.get().results[locIdx].first;
req.tenantInfo = TenantInfo(tenant->id(), reqToken);
req.readVersion = committedVersion;
auto& bwInterf = rep.get().results[locIdx].second;
ErrorOr<BlobGranuleFileReply> fileRep = wait(bwInterf.blobGranuleFileRequest.tryGetReply(req));
if (fileRep.isError()) {
throw fileRep.getError();
}
ASSERT(!fileRep.get().chunks.empty());
return Optional<Error>();
} catch (Error& e) {
CODE_PROBE(e.code() == error_code_permission_denied,
"Cross tenant blob granule read meets permission_denied");
return e;
}
}
ACTOR static Future<Optional<Error>> tryBlobGranuleRead(AuthzSecurityWorkload* self,
Database cx,
Reference<Tenant> tenant,
@ -501,8 +646,6 @@ struct AuthzSecurityWorkload : TestWorkload {
}
}
// TODO: add separate tests to separately test blob granule locations call and blob worker interface call
static void checkCrossTenantOutcome(std::string testcase,
PerfIntCounter& positiveCounter,
PerfIntCounter& negativeCounter,
@ -533,6 +676,40 @@ struct AuthzSecurityWorkload : TestWorkload {
}
}
ACTOR static Future<Void> testCrossTenantBGLocDisallowed(AuthzSecurityWorkload* self,
Database cx,
PositiveTestcase positive) {
state Key key = self->randomString();
state Value value = self->randomString();
state Version committedVersion =
wait(setAndCommitKeyValueAndGetVersion(self, cx, self->tenant, self->signedToken, key, value));
TenantInfo tenantInfo(self->tenant->id(), positive ? self->signedToken : self->signedTokenAnotherTenant);
ErrorOr<GetBlobGranuleLocationsReply> rep = wait(getGranuleLocations(self, cx, tenantInfo, committedVersion));
Optional<Error> outcome = rep.isError() ? rep.getError() : Optional<Error>();
checkCrossTenantOutcome(
"BGLoc", self->crossTenantBGLocPositive, self->crossTenantBGLocNegative, outcome, positive);
return Void();
}
ACTOR static Future<Void> testCrossTenantBGRequestDisallowed(AuthzSecurityWorkload* self,
Database cx,
PositiveTestcase positive) {
state Key key = self->randomString();
state Value value = self->randomString();
state Version committedVersion =
wait(setAndCommitKeyValueAndGetVersion(self, cx, self->tenant, self->signedToken, key, value));
Optional<Error> outcome =
wait(tryBlobGranuleRequest(self,
cx,
self->tenant,
self->signedToken,
positive ? self->signedToken : self->signedTokenAnotherTenant,
committedVersion));
checkCrossTenantOutcome(
"BGRequest", self->crossTenantBGReqPositive, self->crossTenantBGReqNegative, outcome, positive);
return Void();
}
ACTOR static Future<Void> testCrossTenantBGReadDisallowed(AuthzSecurityWorkload* self,
Database cx,
PositiveTestcase positive) {

View File

@ -21,6 +21,7 @@ clearAfterTest = false
[[test.workload]]
testName = 'CreateTenant'
name = 'AnotherAuthzSecurityTenant'
blobbify = true
[[test]]
testTitle = 'AuthzSecurityCheck'
@ -30,12 +31,12 @@ clearAfterTest = false
testName = 'LeakTLogInterface'
tenant = 'AuthzSecurityTenant'
key = 'TLogInterface'
testDuration = 10.0
testDuration = 20.0
[[test.workload]]
testName = 'AuthzSecurity'
tenantA = 'AuthzSecurityTenant'
tenantB = 'AnotherAuthzSecurityTenant'
tLogConfigKey = 'TLogInterface'
testDuration = 10.0
testDuration = 20.0
checkBlobGranules = true