[Developer] #567 record actual VPD execution with FGA

This commit is contained in:
devmrko
2026-07-01 16:02:03 +09:00
parent 57d77d2df2
commit ebcce66bf7
17 changed files with 532 additions and 220 deletions

View File

@@ -180,4 +180,35 @@ class ProbeResultTest {
assertThat(result.hasExecutionCandidates()).isTrue();
assertThat(result.executionCandidates()).hasSize(1);
}
@Test
void keepsAuditedExecutionEvidenceSeparateFromTheReconstructedSql() {
FgaExecutionEvidence evidence = new FgaExecutionEvidence(
"2026-07-01T10:00:00Z",
"CB_ORDS",
"probe-123",
"SELECT",
"SELECT d.doc_id FROM ADMIN.CB_V_SEARCH_DOCUMENTS d",
"CB_AGENT_DOC_POLICY: (DEPT_CODE = 'HR')"
);
ProbeResult result = new ProbeResult(
ProbeStatus.SUCCESS,
List.of("DOC_ID"),
List.of(Map.of("DOC_ID", 1)),
1,
List.of(),
null,
null,
null,
null,
null,
null
).withSqlTrace("DEPT_CODE = SYS_CONTEXT(...)", "SELECT ... WHERE (...)")
.withFgaExecutionEvidence(evidence, null);
assertThat(result.hasFgaExecutionEvidence()).isTrue();
assertThat(result.fgaExecutionEvidence().clientId()).isEqualTo("probe-123");
assertThat(result.fgaExecutionEvidence().hasRlsInfo()).isTrue();
assertThat(result.effectiveSql()).contains("WHERE");
}
}

View File

@@ -45,7 +45,7 @@ class OrdsMetadataServiceTest {
String source = service.objectQueryHandlerSource(7L);
assertThat(source)
.contains("cb_ords_handler_pkg.set_vpd_context(:auth_header)")
.contains("cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id)")
.contains("admin.cb_agent_doc_vpd_filter('ADMIN', 'CB_V_SEARCH_DOCUMENTS')")
.contains("v_vpd_predicate")
.contains("APEX_JSON.WRITE('effective_sql', v_effective_sql)")

View File

@@ -109,14 +109,14 @@ class GuidedFlowTemplateTest {
.doesNotContain("name=\"tokenKeyId\"");
assertThat(result)
.contains("적용된 사용자와 권한")
.contains("DB 실행 증적")
.contains("최근 DB cursor 10건")
.contains("실행 요청 SQL")
.contains("실제 DB cursor SQL")
.contains("DB 감사 실행 증적")
.contains("DB가 감사한 실행 SQL")
.contains("DB가 감사한 VPD predicate")
.contains("참고용 SQL 재현")
.contains("set_vpd_context 사용자 컨텍스트")
.contains("CB_AGENT_CTX.USER_ID")
.contains("벡터 유사도 검색 기준")
.contains("역할 기반 권한 필터 (VPD)")
.contains("역할 기반 권한 필터 (재현)")
.contains("VECTOR_DISTANCE")
.contains("벡터 검색 Top-K")
.contains("벡터 거리 (SCORE)")
@@ -265,18 +265,18 @@ class GuidedFlowTemplateTest {
String vectorSql = Files.readString(Path.of("sql/adb/29_agent_ords_vector_search_ords.sql"));
assertThat(result)
.contains("DB 실행 증적")
.contains("DB가 기록한 원문 SQL")
.contains("DBMS_XPLAN Predicate Information")
.contains("SQL_ID")
.contains("실행 요청 SQL")
.contains("실제 DB cursor SQL")
.contains("실행 요청 SQL (재현)")
.contains("VPD가 추가한 WHERE 조건")
.contains("DB 감사 실행 증적")
.contains("DB가 감사한 실행 SQL")
.contains("DB가 감사한 VPD predicate")
.contains("RLS_INFO")
.contains("참고용 SQL 재현")
.contains("SQL (재현)")
.contains("VPD 조건 (재현)")
.contains("ALL: 추가 행 필터 없음")
.contains("기본 whitelist 역할");
assertThat(vectorSql)
.contains("VECTOR_DISTANCE(d.embedding, TO_VECTOR(:embedding), COSINE)")
.contains("cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id)")
.contains("IF TRIM(v_vpd_predicate) <> '1 = 1' THEN")
.contains("' AND (' || v_vpd_predicate || ')'");
}

View File

@@ -2,9 +2,9 @@ package com.cloudhandson.vpdbackoffice.web;
import static org.assertj.core.api.Assertions.assertThat;
import com.cloudhandson.vpdbackoffice.domain.probe.FgaExecutionEvidence;
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeResult;
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
import com.cloudhandson.vpdbackoffice.domain.probe.SqlExecutionCandidate;
import java.nio.file.Path;
import java.util.List;
import java.util.Locale;
@@ -17,7 +17,7 @@ import org.thymeleaf.templateresolver.FileTemplateResolver;
class ProbeResultTemplateRenderTest {
@Test
void rendersRecentSqlEvidenceUnavailableMessageAsTextNotAsABoolean() {
void rendersFgaEvidenceAndUnavailableMessageAsTextNotAsABoolean() {
var resolver = new FileTemplateResolver();
resolver.setPrefix(Path.of("src/main/resources/templates").toAbsolutePath() + "/");
resolver.setSuffix(".html");
@@ -38,11 +38,14 @@ class ProbeResultTemplateRenderTest {
"{}",
"{}",
"{}"
).withExecutionEvidence(null,
"최근 cursor를 찾지 못했습니다.",
List.of(new SqlExecutionCandidate(
"4f2jz7n2k0s3p", 0, "CB_ORDS",
"SELECT * FROM ADMIN.CB_V_SEARCH_DOCUMENTS", "2026-07-01T15:00", true)));
).withFgaExecutionEvidence(new FgaExecutionEvidence(
"2026-07-01T15:00:00Z",
"CB_ORDS",
"8db9fc64-c71b-4b9e-9514-2fcaa355d9f3",
"SELECT",
"SELECT * FROM ADMIN.CB_V_SEARCH_DOCUMENTS",
"CB_AGENT_DOC_POLICY: (DEPT_CODE = 'HR')"
), null);
var context = new Context(Locale.KOREAN);
context.setVariable("result", result);
context.setVariable("vectorSearch", false);
@@ -52,9 +55,9 @@ class ProbeResultTemplateRenderTest {
String rendered = engine.process("fragments/probe-result", context);
assertThat(rendered)
.contains("최근 cursor를 찾지 못했습니다.")
.contains("최근 DB cursor 10건")
.contains("4f2jz7n2k0s3p")
.contains("보호 대상 일치");
.contains("DB 감사 실행 증적")
.contains("8db9fc64-c71b-4b9e-9514-2fcaa355d9f3")
.contains("SELECT * FROM ADMIN.CB_V_SEARCH_DOCUMENTS")
.contains("CB_AGENT_DOC_POLICY: (DEPT_CODE = &#39;HR&#39;)");
}
}