[Developer] #567 record actual VPD execution with FGA
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
package com.cloudhandson.vpdbackoffice.domain.probe;
|
||||
|
||||
/**
|
||||
* Durable, database-generated FGA evidence for a protected-object SELECT.
|
||||
* SQL text and RLS information come from Oracle's audit trail, not from a
|
||||
* re-evaluation of the VPD policy function.
|
||||
*/
|
||||
public record FgaExecutionEvidence(
|
||||
String eventAt,
|
||||
String dbUser,
|
||||
String clientId,
|
||||
String statementType,
|
||||
String sqlText,
|
||||
String rlsInfo
|
||||
) {
|
||||
|
||||
public boolean hasRlsInfo() {
|
||||
return rlsInfo != null && !rlsInfo.isBlank();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,9 @@ public record ProbeResult(
|
||||
String effectiveSql,
|
||||
SqlExecutionEvidence executionEvidence,
|
||||
String executionEvidenceMessage,
|
||||
List<SqlExecutionCandidate> executionCandidates
|
||||
List<SqlExecutionCandidate> executionCandidates,
|
||||
FgaExecutionEvidence fgaExecutionEvidence,
|
||||
String fgaExecutionEvidenceMessage
|
||||
) {
|
||||
|
||||
public ProbeResult(
|
||||
@@ -51,7 +53,9 @@ public record ProbeResult(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
List.of()
|
||||
List.of(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,7 +90,9 @@ public record ProbeResult(
|
||||
effectiveSql,
|
||||
null,
|
||||
null,
|
||||
List.of()
|
||||
List.of(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
@@ -143,7 +149,9 @@ public record ProbeResult(
|
||||
sql,
|
||||
executionEvidence,
|
||||
executionEvidenceMessage,
|
||||
executionCandidates
|
||||
executionCandidates,
|
||||
fgaExecutionEvidence,
|
||||
fgaExecutionEvidenceMessage
|
||||
);
|
||||
}
|
||||
|
||||
@@ -180,7 +188,9 @@ public record ProbeResult(
|
||||
effectiveSql,
|
||||
evidence,
|
||||
unavailableMessage,
|
||||
candidates == null ? List.of() : List.copyOf(candidates)
|
||||
candidates == null ? List.of() : List.copyOf(candidates),
|
||||
fgaExecutionEvidence,
|
||||
fgaExecutionEvidenceMessage
|
||||
);
|
||||
}
|
||||
|
||||
@@ -188,6 +198,37 @@ public record ProbeResult(
|
||||
return executionCandidates != null && !executionCandidates.isEmpty();
|
||||
}
|
||||
|
||||
public boolean hasFgaExecutionEvidence() {
|
||||
return fgaExecutionEvidence != null && fgaExecutionEvidence.sqlText() != null
|
||||
&& !fgaExecutionEvidence.sqlText().isBlank();
|
||||
}
|
||||
|
||||
public ProbeResult withFgaExecutionEvidence(
|
||||
FgaExecutionEvidence evidence,
|
||||
String unavailableMessage
|
||||
) {
|
||||
return new ProbeResult(
|
||||
status,
|
||||
columns,
|
||||
rows,
|
||||
rowCount,
|
||||
maskedColumns,
|
||||
errorCode,
|
||||
errorMessage,
|
||||
requestHeaders,
|
||||
requestPayload,
|
||||
responseHeaders,
|
||||
responseBody,
|
||||
vpdPredicate,
|
||||
effectiveSql,
|
||||
executionEvidence,
|
||||
executionEvidenceMessage,
|
||||
executionCandidates,
|
||||
evidence,
|
||||
unavailableMessage
|
||||
);
|
||||
}
|
||||
|
||||
public String title() {
|
||||
return switch (status) {
|
||||
case SUCCESS -> "권한에 따라 데이터를 볼 수 있습니다.";
|
||||
|
||||
@@ -221,6 +221,16 @@ public class OrdsMetadataService {
|
||||
p_param_type => 'STRING',
|
||||
p_access_method => 'IN'
|
||||
);
|
||||
ORDS.DEFINE_PARAMETER(
|
||||
p_module_name => ?,
|
||||
p_pattern => ?,
|
||||
p_method => 'POST',
|
||||
p_name => 'X-VPD-Probe-Id',
|
||||
p_bind_variable_name => 'probe_id',
|
||||
p_source_type => 'HEADER',
|
||||
p_param_type => 'STRING',
|
||||
p_access_method => 'IN'
|
||||
);
|
||||
ORDS.DEFINE_PARAMETER(
|
||||
p_module_name => ?,
|
||||
p_pattern => ?,
|
||||
@@ -239,6 +249,7 @@ public class OrdsMetadataService {
|
||||
moduleName, template,
|
||||
moduleName, template, source,
|
||||
moduleName, template,
|
||||
moduleName, template,
|
||||
moduleName, template);
|
||||
protectedObjectService.updateOrdsPath(object.objectId(), ordsPath);
|
||||
return new OrdsObjectHandlerResult(object.objectId(), ordsPath, moduleName, template);
|
||||
@@ -259,7 +270,7 @@ public class OrdsMetadataService {
|
||||
v_vpd_predicate VARCHAR2(32767);
|
||||
v_effective_sql VARCHAR2(32767);
|
||||
BEGIN
|
||||
cb_ords_handler_pkg.set_vpd_context(:auth_header);
|
||||
cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id);
|
||||
|
||||
-- This is the same predicate function invoked by DBMS_RLS for the
|
||||
-- SELECT below. It is returned only as diagnostic metadata.
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.cloudhandson.vpdbackoffice.domain.audit.AuditEvent;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeCommand;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeResult;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.ProbeStatus;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.FgaExecutionEvidence;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.SqlExecutionCandidate;
|
||||
import com.cloudhandson.vpdbackoffice.domain.probe.SqlExecutionEvidence;
|
||||
import com.cloudhandson.vpdbackoffice.domain.protectedobject.ProtectedColumn;
|
||||
@@ -25,6 +26,7 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.springframework.jdbc.core.ConnectionCallback;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -149,6 +151,8 @@ public class OrdsProbeService {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setBearerAuth(command.bearerToken());
|
||||
headers.setContentType(MediaType.APPLICATION_JSON);
|
||||
String probeRequestId = UUID.randomUUID().toString();
|
||||
headers.set("X-VPD-Probe-Id", probeRequestId);
|
||||
String requestBody = command.requestBody() == null || command.requestBody().isBlank()
|
||||
? "{}"
|
||||
: command.requestBody().trim();
|
||||
@@ -164,7 +168,7 @@ public class OrdsProbeService {
|
||||
prettyHeaders(response.getHeaders()),
|
||||
prettyJson(response.getBody())
|
||||
);
|
||||
result = attachRecentExecutionEvidence(result, object);
|
||||
result = attachFgaExecutionEvidence(result, object, probeRequestId);
|
||||
if (isVectorSearchObject(object)) {
|
||||
result = addVectorSqlTrace(result, command.bearerToken(), object);
|
||||
} else if (!result.hasSqlTrace()) {
|
||||
@@ -259,6 +263,114 @@ public class OrdsProbeService {
|
||||
return text == null || text.isBlank() ? null : text;
|
||||
}
|
||||
|
||||
/**
|
||||
* FGA records are durable database audit evidence. Unlike the optional
|
||||
* V$SQL diagnostic view, SQL_TEXT and RLS_INFO originate from the SELECT
|
||||
* that Oracle actually audited.
|
||||
*/
|
||||
private ProbeResult attachFgaExecutionEvidence(
|
||||
ProbeResult result,
|
||||
ProtectedObject object,
|
||||
String probeRequestId
|
||||
) {
|
||||
String owner = object.owner().toUpperCase(Locale.ROOT);
|
||||
String name = object.objectName().toUpperCase(Locale.ROOT);
|
||||
FgaExecutionEvidence evidence = findUnifiedFgaEvidence(owner, name, probeRequestId);
|
||||
if (evidence == null) {
|
||||
evidence = findTraditionalFgaEvidence(owner, name, probeRequestId);
|
||||
}
|
||||
if (evidence == null) {
|
||||
return result.withFgaExecutionEvidence(null,
|
||||
"이 요청의 DB 감사 행을 찾지 못했습니다. FGA 실행 감사 정책과 ORDS Handler의 X-VPD-Probe-Id 연동을 적용한 뒤 다시 검증하세요.");
|
||||
}
|
||||
String message = evidence.hasRlsInfo() ? null
|
||||
: "FGA 감사 SQL은 기록됐지만 RLS_INFO가 비어 있습니다. 감사 정책과 DB audit trail 설정을 확인하세요.";
|
||||
return result.withFgaExecutionEvidence(evidence, message);
|
||||
}
|
||||
|
||||
private FgaExecutionEvidence findUnifiedFgaEvidence(
|
||||
String owner,
|
||||
String name,
|
||||
String probeRequestId
|
||||
) {
|
||||
return queryFgaEvidence("""
|
||||
SELECT event_timestamp_utc AS event_at,
|
||||
dbusername AS db_user,
|
||||
client_identifier AS client_id,
|
||||
action_name AS statement_type,
|
||||
sql_text,
|
||||
rls_info
|
||||
FROM (
|
||||
SELECT event_timestamp_utc,
|
||||
dbusername,
|
||||
client_identifier,
|
||||
action_name,
|
||||
sql_text,
|
||||
rls_info
|
||||
FROM unified_audit_trail
|
||||
WHERE object_schema = ?
|
||||
AND object_name = ?
|
||||
AND action_name = 'SELECT'
|
||||
AND client_identifier = ?
|
||||
AND fga_policy_name IS NOT NULL
|
||||
ORDER BY event_timestamp_utc DESC
|
||||
)
|
||||
WHERE ROWNUM = 1
|
||||
""", owner, name, probeRequestId);
|
||||
}
|
||||
|
||||
private FgaExecutionEvidence findTraditionalFgaEvidence(
|
||||
String owner,
|
||||
String name,
|
||||
String probeRequestId
|
||||
) {
|
||||
return queryFgaEvidence("""
|
||||
SELECT extended_timestamp AS event_at,
|
||||
db_user,
|
||||
client_id,
|
||||
statement_type,
|
||||
sql_text,
|
||||
rls_info
|
||||
FROM (
|
||||
SELECT extended_timestamp,
|
||||
db_user,
|
||||
client_id,
|
||||
statement_type,
|
||||
sql_text,
|
||||
rls_info
|
||||
FROM dba_fga_audit_trail
|
||||
WHERE object_schema = ?
|
||||
AND object_name = ?
|
||||
AND statement_type = 'SELECT'
|
||||
AND client_id = ?
|
||||
ORDER BY extended_timestamp DESC
|
||||
)
|
||||
WHERE ROWNUM = 1
|
||||
""", owner, name, probeRequestId);
|
||||
}
|
||||
|
||||
private FgaExecutionEvidence queryFgaEvidence(String sql, Object... arguments) {
|
||||
try {
|
||||
return jdbcTemplate.query(sql, resultSet -> {
|
||||
if (!resultSet.next()) {
|
||||
return null;
|
||||
}
|
||||
return new FgaExecutionEvidence(
|
||||
resultSet.getTimestamp("event_at") == null
|
||||
? null : resultSet.getTimestamp("event_at").toInstant().toString(),
|
||||
resultSet.getString("db_user"),
|
||||
resultSet.getString("client_id"),
|
||||
resultSet.getString("statement_type"),
|
||||
resultSet.getString("sql_text"),
|
||||
resultSet.getString("rls_info")
|
||||
);
|
||||
}, arguments);
|
||||
} catch (RuntimeException exception) {
|
||||
log.debug("FGA audit trail query is unavailable: {}", exception.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* V$SQL keeps the statement submitted by ORDS, not Oracle's internally
|
||||
* rewritten VPD text. DBMS_XPLAN is therefore the authoritative place to
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
|
||||
<section class="product-help-section product-help-sql">
|
||||
<h2>실행 SQL은 어디에서 확인하나요?</h2>
|
||||
<p><strong>실행 요청 SQL</strong>은 현재 사용자 컨텍스트와 VPD 조건을 결합한 읽기용 재현 SQL입니다. 실제 DB cursor가 기록한 원문 SQL과 Predicate Information은 <code>V$SQL</code>·<code>DBMS_XPLAN</code> 권한이 있을 때 접근 검증 결과에서 함께 표시됩니다. Oracle은 VPD가 내부적으로 붙인 최종 rewrite 문자열 자체를 별도 SQL 텍스트로 보관하지 않으므로, 실제 적용 근거는 실행계획의 Predicate Information으로 확인합니다.</p>
|
||||
<p><strong>DB 감사 실행 증적</strong>은 FGA가 남긴 실제 실행 SQL과 VPD RLS 정보를 보여줍니다. 화면의 SQL 재현은 설정을 이해하기 위한 참고용이며, 실행 증적과 구분합니다.</p>
|
||||
</section>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
@@ -108,73 +108,48 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="result-section" th:if="${result.hasExecutionEvidence()}">
|
||||
<section class="result-section" th:if="${result.hasFgaExecutionEvidence()}">
|
||||
<div class="section-heading compact-heading">
|
||||
<div>
|
||||
<h3>DB 실행 증적</h3>
|
||||
<p class="section-subtitle">DB가 보관한 최신 cursor 10건에서 같은 보호 대상을 찾아 SQL_ID로 확인한 결과입니다.</p>
|
||||
<h3>DB 감사 실행 증적</h3>
|
||||
<p class="section-subtitle">이번 요청 ID와 일치하는 DB 감사 행입니다. 화면에서 재계산한 값이 아닙니다.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-success" th:text="${'SQL_ID ' + result.executionEvidence().sqlId()}">SQL_ID</span>
|
||||
<span class="badge text-bg-success">FGA</span>
|
||||
</div>
|
||||
<div class="effective-preview">
|
||||
<dl>
|
||||
<div><dt>Child cursor</dt><dd th:text="${result.executionEvidence().childNumber()}">0</dd></div>
|
||||
<div><dt>최근 실행</dt><dd th:text="${result.executionEvidence().lastActiveAt()} ?: '-'">2026-07-01T10:00</dd></div>
|
||||
<div><dt>Cursor 실행 횟수</dt><dd th:text="${result.executionEvidence().executions()}">1</dd></div>
|
||||
<div><dt>Cursor 처리 행</dt><dd th:text="${result.executionEvidence().rowsProcessed()}">10</dd></div>
|
||||
<div><dt>누적 경과 시간</dt><dd th:text="${result.executionEvidence().elapsedMillis() + ' ms'}">10 ms</dd></div>
|
||||
<div><dt>Buffer gets</dt><dd th:text="${result.executionEvidence().bufferGets()}">0</dd></div>
|
||||
<div><dt>감사 시각</dt><dd th:text="${result.fgaExecutionEvidence().eventAt()} ?: '-'">2026-07-01T10:00:00Z</dd></div>
|
||||
<div><dt>DB 실행 사용자</dt><dd th:text="${result.fgaExecutionEvidence().dbUser()} ?: '-'">CB_ORDS</dd></div>
|
||||
<div><dt>요청 식별자</dt><dd><code th:text="${result.fgaExecutionEvidence().clientId()} ?: '-'">request-id</code></dd></div>
|
||||
<div><dt>문장 종류</dt><dd th:text="${result.fgaExecutionEvidence().statementType()} ?: '-'">SELECT</dd></div>
|
||||
</dl>
|
||||
</div>
|
||||
<div class="probe-exchange-grid mt-3">
|
||||
<section class="probe-exchange" data-sql-trace-field="executed_sql">
|
||||
<h3>DB가 기록한 원문 SQL</h3>
|
||||
<p class="form-hint">V$SQL의 원문입니다. Oracle VPD가 WHERE 조건을 내부적으로 주입하기 전 SQL 형태로 저장됩니다.</p>
|
||||
<pre th:text="${result.executionEvidence().originalSql()}">SELECT ...</pre>
|
||||
<h3>DB가 감사한 실행 SQL</h3>
|
||||
<p class="form-hint">FGA의 SQL_TEXT입니다.</p>
|
||||
<pre th:text="${result.fgaExecutionEvidence().sqlText()}">SELECT ...</pre>
|
||||
</section>
|
||||
<section class="probe-exchange" data-sql-trace-field="execution_plan" th:if="${result.executionEvidence().hasPredicatePlan()}">
|
||||
<h3>DBMS_XPLAN Predicate Information</h3>
|
||||
<p class="form-hint">이 cursor의 실행계획에 기록된 Access/Filter predicate입니다. VPD 적용 근거는 여기서 확인합니다.</p>
|
||||
<pre th:text="${result.executionEvidence().predicatePlan()}">Predicate Information</pre>
|
||||
<section class="probe-exchange" data-sql-trace-field="fga_rls_info" th:if="${result.fgaExecutionEvidence().hasRlsInfo()}">
|
||||
<h3>DB가 감사한 VPD predicate</h3>
|
||||
<p class="form-hint">FGA의 RLS_INFO입니다. 적용된 VPD 정책명과 predicate가 기록됩니다.</p>
|
||||
<pre th:text="${result.fgaExecutionEvidence().rlsInfo()}">RLS_INFO</pre>
|
||||
</section>
|
||||
</div>
|
||||
<div class="alert alert-light mt-3 mb-0" th:if="${result.executionEvidenceMessage() != null}"
|
||||
th:text="${result.executionEvidenceMessage()}"></div>
|
||||
</section>
|
||||
|
||||
<section class="result-section" th:if="${result.hasExecutionCandidates()}">
|
||||
<div class="section-heading compact-heading">
|
||||
<div>
|
||||
<h3>최근 DB cursor 10건</h3>
|
||||
<p class="section-subtitle">백오피스 DB 연결이 읽은 최신 cursor입니다. 보호 대상과 일치한 SQL은 초록 배지로 표시합니다.</p>
|
||||
</div>
|
||||
<span class="badge text-bg-light" th:text="${result.executionCandidates().size() + '건'}">10건</span>
|
||||
</div>
|
||||
<article class="probe-exchange mt-3" th:each="candidate : ${result.executionCandidates()}">
|
||||
<div class="section-heading compact-heading">
|
||||
<div>
|
||||
<h3 th:text="${'SQL_ID ' + candidate.sqlId()}">SQL_ID</h3>
|
||||
<p class="form-hint mb-0"
|
||||
th:text="${'최근 실행: ' + (candidate.lastActiveAt() ?: '-') + ' · Parsing schema: ' + (candidate.parsingSchema() ?: '-') + ' · Child cursor: ' + candidate.childNumber()}">최근 실행</p>
|
||||
</div>
|
||||
<span class="badge"
|
||||
th:classappend="${candidate.matchesProtectedObject()} ? ' text-bg-success' : ' text-bg-light'"
|
||||
th:text="${candidate.matchesProtectedObject()} ? '보호 대상 일치' : '다른 SQL'">다른 SQL</span>
|
||||
</div>
|
||||
<pre class="mt-2 mb-0" th:text="${candidate.originalSql()} ?: '-'">SELECT ...</pre>
|
||||
</article>
|
||||
<div class="alert alert-light mt-3 mb-0" th:if="${result.fgaExecutionEvidenceMessage() != null}"
|
||||
th:text="${result.fgaExecutionEvidenceMessage()}"></div>
|
||||
</section>
|
||||
|
||||
<section class="result-section sql-trace-section" th:if="${result.hasSqlTrace()}">
|
||||
<div class="section-heading compact-heading">
|
||||
<div>
|
||||
<h3>실행 요청 SQL</h3>
|
||||
<h3>참고용 SQL 재현</h3>
|
||||
<p class="section-subtitle"
|
||||
th:text="${vectorSearch ? 'VECTOR_DISTANCE 검색과 현재 사용자의 역할 기반 VPD 조건을 결합한 읽기용 SQL입니다.' : '현재 사용자 컨텍스트에서 계산한 VPD 조건을 ORDS 조회문에 결합한 읽기용 SQL입니다.'}">
|
||||
현재 사용자 컨텍스트에서 계산한 VPD 조건을 ORDS 조회문에 결합한 읽기용 SQL입니다.
|
||||
th:text="${vectorSearch ? 'VECTOR_DISTANCE 검색과 권한 규칙을 조합한 참고용 표현입니다.' : '현재 권한 규칙을 조합한 참고용 표현입니다.'}">
|
||||
현재 권한 규칙을 조합한 참고용 표현입니다.
|
||||
</p>
|
||||
</div>
|
||||
<span class="badge text-bg-light">DBMS_RLS predicate</span>
|
||||
<span class="badge text-bg-light">참고용</span>
|
||||
</div>
|
||||
<div class="probe-exchange-grid mt-3">
|
||||
<section class="probe-exchange" data-sql-trace-field="vpd_context" th:if="${tokenContext}">
|
||||
@@ -205,24 +180,24 @@ ROWNUM <= :row_limit</pre>
|
||||
<p class="form-hint mb-0">이 부분이 검색어 벡터와 저장 벡터의 거리 계산입니다. 아래에는 실제로 등록된 역할 기반 행 접근 조건만 표시됩니다.</p>
|
||||
</section>
|
||||
<section class="probe-exchange" data-sql-trace-field="vpd_predicate">
|
||||
<h3 th:text="${vectorSearch ? '역할 기반 권한 필터 (VPD)' : 'VPD가 추가한 WHERE 조건'}">VPD가 추가한 WHERE 조건</h3>
|
||||
<h3 th:text="${vectorSearch ? '역할 기반 권한 필터 (재현)' : 'VPD 조건 (재현)'}">VPD 조건 (재현)</h3>
|
||||
<pre th:if="${result.vpdPredicate() == '1 = 1'}">1 = 1 (ALL: 추가 행 필터 없음)</pre>
|
||||
<pre th:unless="${result.vpdPredicate() == '1 = 1'}" th:text="${result.vpdPredicate()}">(DEPT_CODE = SYS_CONTEXT('CB_AGENT_CTX', 'DEPT_CODE'))</pre>
|
||||
<p class="form-hint mb-0" th:if="${vectorSearch}">선택한 사용자의 직접 역할·그룹 상속 역할에 연결된 permission rule에서 계산됩니다. 기본 whitelist 역할의 ALL은 추가 행 필터 없이 조회를 허용하고, 실제로 TAG·부서 조건을 등록한 역할만 그 조건이 SQL에 들어갑니다. 권한이 없으면 <code>1 = 0</code>입니다.</p>
|
||||
</section>
|
||||
<section class="probe-exchange" data-sql-trace-field="effective_sql">
|
||||
<h3>실행 요청 SQL (재현)</h3>
|
||||
<h3>SQL (재현)</h3>
|
||||
<pre th:text="${result.effectiveSql()}">SELECT ... WHERE (...) AND ROWNUM <= ...</pre>
|
||||
</section>
|
||||
</div>
|
||||
<p class="form-hint mt-2 mb-0">
|
||||
이 SQL은 현재 요청의 권한 조건을 읽기 쉽게 재현한 표현입니다. 실제 DB cursor 원문과 적용 predicate는 SQL_ID·DBMS_XPLAN 증적에서 확인합니다. 컬럼 마스킹은 별도 Redaction 정책입니다.
|
||||
이 영역은 권한 규칙을 읽기 쉽게 재현한 표현일 뿐, 실행 증적이 아닙니다. 실제 SQL과 VPD predicate는 위의 DB 감사 실행 증적(FGA)으로 확인합니다. 컬럼 마스킹은 별도 Redaction 정책입니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<div class="alert alert-light mb-0" th:if="${result.successLike() and !result.hasExecutionEvidence() and result.executionEvidenceMessage() != null}">
|
||||
<strong>실제 DB cursor SQL</strong>
|
||||
<span th:text="${result.executionEvidenceMessage()}">최근 SQL_ID를 찾지 못했습니다.</span>
|
||||
<div class="alert alert-light mb-0" th:if="${result.successLike() and !result.hasFgaExecutionEvidence() and result.fgaExecutionEvidenceMessage() != null}">
|
||||
<strong>DB 감사 실행 증적</strong>
|
||||
<span th:text="${result.fgaExecutionEvidenceMessage()}">감사 행을 찾지 못했습니다.</span>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-light mb-0" th:if="${result.successLike() and !result.hasSqlTrace()}">
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
</form>
|
||||
<details class="explanation-details explanation-warning mt-3">
|
||||
<summary>기본 Handler 구성과 수정 가이드 보기</summary>
|
||||
<p><code>cb_ords_handler_pkg.set_vpd_context(:auth_header)</code>로 토큰의 사용자·역할 컨텍스트를 넣고, 선택한 한 테이블에 <code>SELECT ... FROM OWNER.TABLE</code>을 실행한 뒤 JSON으로 반환합니다. 이것은 유일한 사용 방식이 아니라 시작점이며, Handler 소스와 ORDS 메타데이터에서 수정할 수 있습니다. 컬럼 민감도·마스킹은 이 화면에서 다루지 않고 <a href="/permissions">권한 관리의 원문 표시 허용 컬럼</a>에서 별도로 설정합니다.</p>
|
||||
<p><code>cb_ords_handler_pkg.set_vpd_context(:auth_header, :probe_id)</code>로 토큰의 사용자·역할 컨텍스트와 검증 요청 식별자를 넣고, 선택한 한 테이블에 <code>SELECT ... FROM OWNER.TABLE</code>을 실행한 뒤 JSON으로 반환합니다. 이것은 유일한 사용 방식이 아니라 시작점이며, Handler 소스와 ORDS 메타데이터에서 수정할 수 있습니다. 컬럼 민감도·마스킹은 이 화면에서 다루지 않고 <a href="/permissions">권한 관리의 원문 표시 허용 컬럼</a>에서 별도로 설정합니다.</p>
|
||||
</details>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<h1>접근 검증</h1>
|
||||
<details class="explanation-details">
|
||||
<summary>도움말</summary>
|
||||
<p>한 사용자의 토큰으로 실제 데이터를 요청해, 설계한 권한이 DB에서 그대로 적용되는지 확인합니다. 결과의 실행 요청 SQL은 현재 컨텍스트를 결합한 읽기용 표현이며, DB cursor가 기록한 원문 SQL은 <code>V$SQL</code>·<code>DBMS_XPLAN</code> 진단 권한이 있을 때만 함께 표시됩니다.</p>
|
||||
<p>한 사용자의 토큰으로 실제 데이터를 요청해, 설계한 권한이 DB에서 그대로 적용되는지 확인합니다. 결과는 DB 감사 로그에 남은 실행 SQL과 VPD 정보를 보여주며, SQL 재현은 설정을 읽기 위한 참고용으로 구분합니다.</p>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user