Skip to content

Commit ca794da

Browse files
committed
Apply PR feedback
Signed-off-by: Simeon Widdis <sawiddis@amazon.com>
1 parent e875afd commit ca794da

1 file changed

Lines changed: 89 additions & 113 deletions

File tree

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

Lines changed: 89 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
import org.jetbrains.annotations.NotNull;
1515
import org.json.JSONObject;
1616
import org.junit.jupiter.api.BeforeAll;
17-
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.api.BeforeEach;
1818
import org.junit.jupiter.api.TestInstance;
1919
import org.junit.jupiter.api.TestInstance.Lifecycle;
20+
import org.junit.jupiter.params.ParameterizedTest;
21+
import org.junit.jupiter.params.provider.ValueSource;
2022
import org.opensearch.client.Request;
2123
import org.opensearch.client.RequestOptions;
2224
import org.opensearch.client.Response;
@@ -51,7 +53,7 @@ public class FGACIndexScanningIT extends SecurityTestBase {
5153
private static final String SECURE_LOGS = "secure_logs_fgac";
5254
private static final String EMPLOYEE_RECORDS = "employee_records_fgac";
5355

54-
private static final int LARGE_DATASET_SIZE = 2000;
56+
private static final int LARGE_DATASET_SIZE = 100;
5557

5658
@SneakyThrows
5759
@BeforeAll
@@ -64,18 +66,30 @@ public void initialize() {
6466
@Override
6567
protected void init() throws Exception {
6668
super.init();
67-
enableCalcite();
6869
allowCalciteFallback();
6970
}
7071

72+
/**
73+
* Configures the query engine for the test.
74+
*
75+
* @param useCalcite true to use V3 (Calcite) engine, false to use V2 (legacy) engine
76+
*/
77+
private void configureEngine(boolean useCalcite) throws IOException {
78+
if (useCalcite) {
79+
enableCalcite();
80+
} else {
81+
disableCalcite();
82+
}
83+
}
84+
7185
private void setupTestIndices() throws IOException {
7286
createPublicLogsIndex();
7387
createSensitiveLogsIndex();
7488
createEmployeeRecordsIndex();
7589
createSecureLogsIndex();
7690
}
7791

78-
/** Creates public_logs index with 2000+ documents. */
92+
/** Creates public_logs index with test documents. */
7993
private void createPublicLogsIndex() throws IOException {
8094
Request request = new Request("PUT", "/" + PUBLIC_LOGS);
8195
request.setJsonEntity(
@@ -99,7 +113,7 @@ private void createPublicLogsIndex() throws IOException {
99113
bulkInsertDocs(PUBLIC_LOGS, "public");
100114
}
101115

102-
/** Creates sensitive_logs index with 2000+ documents. */
116+
/** Creates sensitive_logs index with test documents. */
103117
private void createSensitiveLogsIndex() throws IOException {
104118
Request request = new Request("PUT", "/" + SENSITIVE_LOGS);
105119
request.setJsonEntity(
@@ -177,8 +191,6 @@ private void createSecureLogsIndex() throws IOException {
177191
""");
178192
client().performRequest(request);
179193

180-
// Insert documents with mixed security levels
181-
// 1000 public, 500 internal, 500 confidential
182194
bulkInsertDocsWithSecurityLevel();
183195
}
184196

@@ -255,8 +267,12 @@ private static String getBulkEmployeeIndexRequest() {
255267
private void bulkInsertDocsWithSecurityLevel() throws IOException {
256268
StringBuilder bulk = new StringBuilder();
257269

258-
// 1000 public documents
259-
for (int i = 0; i < 1000; i++) {
270+
int publicCount = LARGE_DATASET_SIZE / 2;
271+
int internalStart = publicCount;
272+
int internalCount = LARGE_DATASET_SIZE / 4;
273+
int confidentialStart = internalStart + internalCount;
274+
275+
for (int i = 0; i < publicCount; i++) {
260276
bulk.append(
261277
String.format(
262278
Locale.ROOT,
@@ -268,8 +284,7 @@ private void bulkInsertDocsWithSecurityLevel() throws IOException {
268284
i));
269285
}
270286

271-
// 500 internal documents
272-
for (int i = 1000; i < 1500; i++) {
287+
for (int i = internalStart; i < confidentialStart; i++) {
273288
bulk.append(
274289
String.format(
275290
Locale.ROOT,
@@ -281,8 +296,7 @@ private void bulkInsertDocsWithSecurityLevel() throws IOException {
281296
i));
282297
}
283298

284-
// 500 confidential documents
285-
for (int i = 1500; i < 2000; i++) {
299+
for (int i = confidentialStart; i < LARGE_DATASET_SIZE; i++) {
286300
bulk.append(
287301
String.format(
288302
Locale.ROOT,
@@ -345,17 +359,21 @@ private void createRoleWithFieldLevelSecurity() throws IOException {
345359
createRoleWithFLS(MANAGER_ROLE, EMPLOYEE_RECORDS, RECORDS_INDEX_COLUMNS);
346360
}
347361

348-
@Test
349-
public void testPublicUserCanAccessPublicLogs() throws IOException {
350-
// public_user can access public_logs (large dataset triggers background scanning)
362+
@ParameterizedTest
363+
@ValueSource(booleans = {false, true})
364+
public void testPublicUserCanAccessPublicLogs(boolean useCalcite) throws IOException {
365+
configureEngine(useCalcite);
366+
// public_user can access public_logs
351367
JSONObject result =
352368
executeQueryAsUser(
353369
String.format("search source=%s | fields message | head 10", PUBLIC_LOGS), PUBLIC_USER);
354370
verifyColumn(result, columnName("message"));
355371
}
356372

357-
@Test
358-
public void testPublicUserCannotAccessSensitiveLogs() throws IOException {
373+
@ParameterizedTest
374+
@ValueSource(booleans = {false, true})
375+
public void testPublicUserCannotAccessSensitiveLogs(boolean useCalcite) throws IOException {
376+
configureEngine(useCalcite);
359377
// public_user cannot access sensitive_logs (should fail at planning stage)
360378
try {
361379
executeQueryAsUser(
@@ -370,8 +388,10 @@ public void testPublicUserCannotAccessSensitiveLogs() throws IOException {
370388
}
371389
}
372390

373-
@Test
374-
public void testSensitiveUserCanAccessSensitiveLogs() throws IOException {
391+
@ParameterizedTest
392+
@ValueSource(booleans = {false, true})
393+
public void testSensitiveUserCanAccessSensitiveLogs(boolean useCalcite) throws IOException {
394+
configureEngine(useCalcite);
375395
// sensitive_user can access sensitive_logs
376396
JSONObject result =
377397
executeQueryAsUser(
@@ -380,8 +400,10 @@ public void testSensitiveUserCanAccessSensitiveLogs() throws IOException {
380400
verifyColumn(result, columnName("message"));
381401
}
382402

383-
@Test
384-
public void testSensitiveUserCannotAccessPublicLogs() throws IOException {
403+
@ParameterizedTest
404+
@ValueSource(booleans = {false, true})
405+
public void testSensitiveUserCannotAccessPublicLogs(boolean useCalcite) throws IOException {
406+
configureEngine(useCalcite);
385407
// sensitive_user cannot access public_logs
386408
try {
387409
executeQueryAsUser(
@@ -396,8 +418,11 @@ public void testSensitiveUserCannotAccessPublicLogs() throws IOException {
396418
}
397419
}
398420

399-
@Test
400-
public void testHrUserCanSeeAllFieldsIncludingSensitiveData() throws IOException {
421+
@ParameterizedTest
422+
@ValueSource(booleans = {false, true})
423+
public void testHrUserCanSeeAllFieldsIncludingSensitiveData(boolean useCalcite)
424+
throws IOException {
425+
configureEngine(useCalcite);
401426
// hr_user can see ALL fields including sensitive ssn
402427
String queryAllFields =
403428
String.format(
@@ -421,8 +446,10 @@ public void testHrUserCanSeeAllFieldsIncludingSensitiveData() throws IOException
421446
assertTrue("hr_user should see 'department' field", hrHasDepartment);
422447
}
423448

424-
@Test
425-
public void testManagerUserCannotSeeSensitiveFields() throws IOException {
449+
@ParameterizedTest
450+
@ValueSource(booleans = {false, true})
451+
public void testManagerUserCannotSeeSensitiveFields(boolean useCalcite) throws IOException {
452+
configureEngine(useCalcite);
426453
// manager_user can see most fields but NOT ssn
427454
String queryAllowedFields =
428455
String.format(
@@ -452,8 +479,10 @@ public void testManagerUserCannotSeeSensitiveFields() throws IOException {
452479
managerHasSSN);
453480
}
454481

455-
@Test
456-
public void testManagerUserCannotQueryRestrictedField() throws IOException {
482+
@ParameterizedTest
483+
@ValueSource(booleans = {false, true})
484+
public void testManagerUserCannotQueryRestrictedField(boolean useCalcite) throws IOException {
485+
configureEngine(useCalcite);
457486
// Verify manager_user cannot even reference ssn in query (field is invisible)
458487
try {
459488
String queryWithSSN =
@@ -471,8 +500,11 @@ public void testManagerUserCannotQueryRestrictedField() throws IOException {
471500
}
472501
}
473502

474-
@Test
475-
public void testFieldLevelSecurityEnforcedWithLargeDataset() throws IOException {
503+
@ParameterizedTest
504+
@ValueSource(booleans = {false, true})
505+
public void testFieldLevelSecurityEnforcedWithLargeDataset(boolean useCalcite)
506+
throws IOException {
507+
configureEngine(useCalcite);
476508
// Verify with large result set that FLS is still enforced
477509
String queryLargeDataset =
478510
String.format(
@@ -490,15 +522,16 @@ public void testFieldLevelSecurityEnforcedWithLargeDataset() throws IOException
490522
}
491523

492524
assertFalse(
493-
"SECURITY VIOLATION: manager_user should NOT see 'ssn' even with large dataset (2000+"
494-
+ " rows). Field-level security must be enforced.",
525+
"SECURITY VIOLATION: manager_user should NOT see 'ssn' even with large dataset. "
526+
+ "Field-level security must be enforced.",
495527
hasSSNInLarge);
496528
}
497529

498-
@Test
499-
public void testRowLevelSecurityV2() throws IOException {
500-
// Test V2 (legacy) engine explicitly
501-
disableCalcite();
530+
@ParameterizedTest
531+
@ValueSource(booleans = {false, true})
532+
public void testRowLevelSecurity(boolean useCalcite) throws IOException {
533+
configureEngine(useCalcite);
534+
String engineLabel = useCalcite ? "V3" : "V2";
502535

503536
// limited_user should only see "public" documents
504537

@@ -534,93 +567,36 @@ public void testRowLevelSecurityV2() throws IOException {
534567
}
535568

536569
assertFalse(
537-
"[V2] SECURITY VIOLATION: limited_user should NOT see 'confidential' documents. "
538-
+ "This indicates ThreadContext is not being copied to async worker threads in V2, "
539-
+ "causing queries to run with admin permissions and bypass row-level security.",
540-
sawConfidential);
541-
542-
assertFalse(
543-
"[V2] SECURITY VIOLATION: limited_user should NOT see 'internal' documents. "
544-
+ "This indicates ThreadContext is not being copied to async worker threads in V2, "
545-
+ "causing queries to run with admin permissions and bypass row-level security.",
546-
sawInternal);
547-
548-
assertEquals(
549-
"[V2] limited_user should ONLY see 'public' documents (~1000). "
550-
+ "Seeing more indicates row-level security is being bypassed in V2.",
551-
1000,
552-
publicDocs);
553-
554-
assertEquals(
555-
"[V2] Total visible documents should be ~1000 (only public). "
556-
+ "Seeing 2000 documents indicates row-level security is completely bypassed in V2.",
557-
1000,
558-
totalDocs);
559-
}
560-
561-
@Test
562-
public void testRowLevelSecurity() throws IOException {
563-
// Test V3 (Calcite) engine - Calcite is enabled in init()
564-
// limited_user should only see "public" documents
565-
566-
// Execute query as limited_user
567-
String query =
568570
String.format(
569-
"search source=%s | fields security_level, message | stats count() by security_level",
570-
SECURE_LOGS);
571-
JSONObject result = executeQueryAsUser(query, LIMITED_USER);
572-
573-
// Extract the datarows for validation
574-
var datarows = result.getJSONArray("datarows");
575-
576-
// limited_user should ONLY see "public" documents
577-
// Note: Without DLS configured in Security Plugin, all documents are visible
578-
// Once DLS is configured with a rule like: { "match": { "security_level": "public" } }
579-
// Then with the ThreadContext fix, this test should pass
580-
581-
// Count total documents visible
582-
int totalDocs = 0;
583-
boolean sawConfidential = false;
584-
boolean sawInternal = false;
585-
int publicDocs = 0;
586-
587-
for (int i = 0; i < datarows.length(); i++) {
588-
var row = datarows.getJSONArray(i);
589-
int count = row.getInt(0);
590-
String securityLevel = row.getString(1);
591-
totalDocs += count;
592-
593-
if ("confidential".equals(securityLevel)) {
594-
sawConfidential = true;
595-
} else if ("internal".equals(securityLevel)) {
596-
sawInternal = true;
597-
} else if ("public".equals(securityLevel)) {
598-
publicDocs = count;
599-
}
600-
}
601-
602-
assertFalse(
603-
"[V3] SECURITY VIOLATION: limited_user should NOT see 'confidential' documents. "
604-
+ "This indicates ThreadContext is not being properly copied to search threads in V3, "
605-
+ "causing queries to run with admin permissions and bypass row-level security.",
571+
"[%s] SECURITY VIOLATION: limited_user should NOT see 'confidential' documents. "
572+
+ "This indicates ThreadContext is not being copied to async worker threads, "
573+
+ "causing queries to run with admin permissions and bypass row-level security.",
574+
engineLabel),
606575
sawConfidential);
607576

608577
assertFalse(
609-
"[V3] SECURITY VIOLATION: limited_user should NOT see 'internal' documents. "
610-
+ "This indicates ThreadContext is not being properly copied to search threads in V3, "
611-
+ "causing queries to run with admin permissions and bypass row-level security.",
578+
String.format(
579+
"[%s] SECURITY VIOLATION: limited_user should NOT see 'internal' documents. "
580+
+ "This indicates ThreadContext is not being copied to async worker threads, "
581+
+ "causing queries to run with admin permissions and bypass row-level security.",
582+
engineLabel),
612583
sawInternal);
613584

585+
int expectedPublicDocs = LARGE_DATASET_SIZE / 2;
614586
assertEquals(
615-
"[V3] limited_user should ONLY see 'public' documents (~1000). "
616-
+ "Seeing more indicates row-level security is being bypassed in V3.",
617-
1000,
587+
String.format(
588+
"[%s] limited_user should ONLY see 'public' documents (half of dataset). "
589+
+ "Seeing more indicates row-level security is being bypassed.",
590+
engineLabel),
591+
expectedPublicDocs,
618592
publicDocs);
619593

620594
assertEquals(
621-
"[V3] Total visible documents should be ~1000 (only public). "
622-
+ "Seeing 2000 documents indicates row-level security is completely bypassed in V3.",
623-
1000,
595+
String.format(
596+
"[%s] Total visible documents should match public documents only. "
597+
+ "Seeing all documents indicates row-level security is completely bypassed.",
598+
engineLabel),
599+
expectedPublicDocs,
624600
totalDocs);
625601
}
626602
}

0 commit comments

Comments
 (0)