Skip to content

Commit bc2577c

Browse files
Add multi-index FGAC bypass regression tests
Regression tests for authorization bypass where PPL multi-index queries (source = idx1, idx2) could leak data from unauthorized indices when cluster.pluggable.dataformat=composite routes queries through the analytics engine. Tests verify that multi-index queries are denied (403 or 400) when the user lacks permission on any listed index: - Authorized first, unauthorized second - Backtick-quoted variant - Unauthorized first (control) - Both authorized (positive case) Also fixes: - Plugin install order (composite-engine before analytics-backend-lucene) - Adds cluster.pluggable.dataformat=composite to test cluster config Signed-off-by: Finnegan Carroll <carrofin@amazon.com> Signed-off-by: Finn Carroll <carrofin@amazon.com>
1 parent 1dc92d6 commit bc2577c

2 files changed

Lines changed: 66 additions & 2 deletions

File tree

integ-test/build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,8 @@ ext {
151151
'plugins.security.enable_snapshot_restore_privilege' : 'true',
152152
'plugins.security.check_snapshot_restore_write_privileges' : 'true',
153153
'plugins.security.restapi.roles_enabled' : '["all_access", "security_rest_api_access"]',
154-
'plugins.security.system_indices.enabled' : 'true'
154+
'plugins.security.system_indices.enabled' : 'true',
155+
'cluster.pluggable.dataformat' : 'composite'
155156
].forEach { name, value ->
156157
cluster.setting name, value
157158
}
@@ -502,11 +503,11 @@ testClusters.analyticsEngineSecurityIT {
502503
plugin(getJobSchedulerPlugin())
503504
plugin(getArrowBasePlugin())
504505
plugin(getArrowFlightRpcPlugin())
506+
plugin(getCompositeEnginePlugin())
505507
plugin(getAnalyticsEnginePlugin())
506508
plugin(getAnalyticsBackendLucenePlugin())
507509
plugin(getAnalyticsBackendDatafusionPlugin())
508510
plugin(getParquetDataFormatPlugin())
509-
plugin(getCompositeEnginePlugin())
510511
plugin ":opensearch-sql-plugin"
511512
// Arrow Flight / streaming transport requirements
512513
jvmArgs '--add-opens=java.base/java.nio=ALL-UNNAMED'

integ-test/src/test/java/org/opensearch/sql/security/AnalyticsEngineSecurityIT.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,69 @@ public void testPPLQueryWithWildcardIndexPartialAccessDenied() throws IOExceptio
393393
assertEquals(403, e.getResponse().getStatusLine().getStatusCode());
394394
}
395395

396+
// --- Multi-index comma-separated source tests (FGAC bypass regression) ---
397+
398+
@Test
399+
public void testPPLMultiIndexDeniedWhenSecondIndexUnauthorized() throws IOException {
400+
// ALLOWED_USER has access to TEST_INDEX but NOT FORBIDDEN_INDEX.
401+
// A comma-separated source listing an authorized index first followed by an unauthorized
402+
// index must be denied — security must evaluate ALL indices, not just the first.
403+
ResponseException e =
404+
assertThrows(
405+
ResponseException.class,
406+
() ->
407+
executePPLAsUser(
408+
"source = " + TEST_INDEX + ", " + FORBIDDEN_INDEX + " | fields name, age",
409+
ALLOWED_USER));
410+
int status = e.getResponse().getStatusLine().getStatusCode();
411+
assertTrue("Expected 403 or 400 (denied), got " + status, status == 403 || status == 400);
412+
}
413+
414+
@Test
415+
public void testPPLMultiIndexDeniedWithBackticksAuthorizedFirst() throws IOException {
416+
// Same bypass vector using backtick-quoted index names.
417+
ResponseException e =
418+
assertThrows(
419+
ResponseException.class,
420+
() ->
421+
executePPLAsUser(
422+
"source = `" + TEST_INDEX + "`, `" + FORBIDDEN_INDEX + "` | fields name, age",
423+
ALLOWED_USER));
424+
int status = e.getResponse().getStatusLine().getStatusCode();
425+
assertTrue("Expected 403 or 400 (denied), got " + status, status == 403 || status == 400);
426+
}
427+
428+
@Test
429+
public void testPPLMultiIndexDeniedWithUnauthorizedFirst() throws IOException {
430+
// Unauthorized index listed first — should also be denied.
431+
ResponseException e =
432+
assertThrows(
433+
ResponseException.class,
434+
() ->
435+
executePPLAsUser(
436+
"source = " + FORBIDDEN_INDEX + ", " + TEST_INDEX + " | fields name, age",
437+
ALLOWED_USER));
438+
int status = e.getResponse().getStatusLine().getStatusCode();
439+
assertTrue("Expected 403 or 400 (denied), got " + status, status == 403 || status == 400);
440+
}
441+
442+
@Test
443+
public void testPPLMultiIndexAllowedWhenAllAuthorized() throws IOException {
444+
// WILDCARD_USER has "analytics_security*" covering both TEST_INDEX and TEST_INDEX_2.
445+
try {
446+
JSONObject result =
447+
executePPLAsUser(
448+
"source = " + TEST_INDEX + ", " + TEST_INDEX_2 + " | fields name, age",
449+
WILDCARD_USER);
450+
assertTrue("Expected datarows in response", result.has("datarows"));
451+
} catch (ResponseException e) {
452+
assertNotEquals(
453+
"Expected auth to pass (not 403) when all indices are authorized",
454+
403,
455+
e.getResponse().getStatusLine().getStatusCode());
456+
}
457+
}
458+
396459
@Test
397460
public void testSQLQueryAllowedForAuthorizedUser() throws IOException {
398461
try {

0 commit comments

Comments
 (0)