[Developer] #567 clarify SQL evidence privilege status
This commit is contained in:
@@ -150,4 +150,4 @@ Oracle은 VPD의 내부 rewrite 결과를 별도 최종 SQL 문자열로 `V$SQL`
|
|||||||
|
|
||||||
최근 SQL 매칭은 대상 객체와 최근 2분 실행 시각으로 고르므로, 같은 객체를 동시에 호출하는 운영 환경에서는 다른 요청 cursor가 선택될 가능성이 있다. 현재 UI는 이를 “최근 실행 증적”으로 명시한다. 요청별 완전 상관이 필요해지면 ORDS Handler에 검증 요청 ID를 주입해 고유 SQL comment/module-action으로 cursor를 추적하는 후속 작업으로 확장한다.
|
최근 SQL 매칭은 대상 객체와 최근 2분 실행 시각으로 고르므로, 같은 객체를 동시에 호출하는 운영 환경에서는 다른 요청 cursor가 선택될 가능성이 있다. 현재 UI는 이를 “최근 실행 증적”으로 명시한다. 요청별 완전 상관이 필요해지면 ORDS Handler에 검증 요청 ID를 주입해 고유 SQL comment/module-action으로 cursor를 추적하는 후속 작업으로 확장한다.
|
||||||
|
|
||||||
실행 증적 연결에는 최소한 `V$SQL`과 `DBMS_XPLAN.DISPLAY_CURSOR`를 조회할 수 있는 catalog 권한이 필요하다. 해당 권한은 ORDS parsing schema 연결에서 먼저 조회하고, 사용할 수 없으면 백오피스 연결로 한 번 더 시도한다. Autonomous의 일반 `ADMIN` 계정은 `SYS.V_$SQL` 권한을 다른 계정에 위임하지 못할 수 있으므로, 이 경우에는 DBA가 [34_agent_ords_execution_evidence_grant.sql](../../sql/adb/34_agent_ords_execution_evidence_grant.sql)을 실행해야 한다. 권한이나 shared pool 보존 시간이 부족하면 권한 검증 결과를 실패시키지 않고 증적 미수집 안내만 표시한다.
|
실행 증적 연결에는 최소한 `V$SQL`과 `DBMS_XPLAN.DISPLAY_CURSOR`를 조회할 수 있는 catalog 권한이 필요하다. 해당 권한은 ORDS parsing schema 연결에서 먼저 조회하고, 사용할 수 없으면 백오피스 연결로 한 번 더 시도한다. Autonomous의 일반 `ADMIN` 계정은 `SYS.V_$SQL` 권한을 다른 계정에 위임하지 못할 수 있으므로, 이 경우에는 DBA가 [34_agent_ords_execution_evidence_grant.sql](../../sql/adb/34_agent_ords_execution_evidence_grant.sql)을 실행해야 한다. cursor 미발견과 catalog 권한 미보유는 UI에서 서로 다른 안내로 표시하며, 어느 경우도 권한 검증 결과를 실패시키지 않는다.
|
||||||
|
|||||||
@@ -46,6 +46,12 @@ public class OrdsProbeService {
|
|||||||
};
|
};
|
||||||
private static final Logger log = LoggerFactory.getLogger(OrdsProbeService.class);
|
private static final Logger log = LoggerFactory.getLogger(OrdsProbeService.class);
|
||||||
|
|
||||||
|
private record ExecutionEvidenceLookup(
|
||||||
|
SqlExecutionEvidence evidence,
|
||||||
|
boolean sqlCatalogAccessUnavailable
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
private final BearerTokenService tokenService;
|
private final BearerTokenService tokenService;
|
||||||
private final ProtectedObjectService protectedObjectService;
|
private final ProtectedObjectService protectedObjectService;
|
||||||
private final AuditService auditService;
|
private final AuditService auditService;
|
||||||
@@ -248,11 +254,17 @@ public class OrdsProbeService {
|
|||||||
*/
|
*/
|
||||||
private ProbeResult attachRecentExecutionEvidence(ProbeResult result, ProtectedObject object) {
|
private ProbeResult attachRecentExecutionEvidence(ProbeResult result, ProtectedObject object) {
|
||||||
try {
|
try {
|
||||||
SqlExecutionEvidence evidence = findRecentExecutionEvidence(object);
|
ExecutionEvidenceLookup lookup = findRecentExecutionEvidence(object);
|
||||||
|
SqlExecutionEvidence evidence = lookup.evidence();
|
||||||
if (evidence == null) {
|
if (evidence == null) {
|
||||||
|
if (lookup.sqlCatalogAccessUnavailable()) {
|
||||||
|
return result.withExecutionEvidence(null,
|
||||||
|
"SQL_ID 실행 증적을 수집할 권한이 없습니다. 현재 권한 검증 결과는 정상입니다. "
|
||||||
|
+ "DBA에게 ORDS 실행 계정의 V$SQL 및 DBMS_XPLAN 조회 권한을 요청하세요.");
|
||||||
|
}
|
||||||
return result.withExecutionEvidence(null,
|
return result.withExecutionEvidence(null,
|
||||||
"최근 2분 내 이 보호 대상의 SQL_ID를 shared pool에서 찾지 못했습니다. "
|
"최근 2분 내 이 보호 대상의 실행 cursor를 찾지 못했습니다. "
|
||||||
+ "ORDS 실행 계정의 V$SQL 보존 시간과 대상 SQL을 확인하세요.");
|
+ "해당 ORDS 요청이 DB까지 실행됐는지 확인하세요.");
|
||||||
}
|
}
|
||||||
String message = evidence.hasPredicatePlan() ? null
|
String message = evidence.hasPredicatePlan() ? null
|
||||||
: "SQL_ID는 찾았지만 DBMS_XPLAN Predicate Information을 읽지 못했습니다. "
|
: "SQL_ID는 찾았지만 DBMS_XPLAN Predicate Information을 읽지 못했습니다. "
|
||||||
@@ -266,17 +278,37 @@ public class OrdsProbeService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlExecutionEvidence findRecentExecutionEvidence(ProtectedObject object) {
|
private ExecutionEvidenceLookup findRecentExecutionEvidence(ProtectedObject object) {
|
||||||
JdbcTemplate evidenceJdbcTemplate = ordsMetadataService.executionEvidenceJdbcTemplate();
|
JdbcTemplate evidenceJdbcTemplate = ordsMetadataService.executionEvidenceJdbcTemplate();
|
||||||
|
boolean sqlCatalogAccessUnavailable = false;
|
||||||
try {
|
try {
|
||||||
SqlExecutionEvidence evidence = findRecentExecutionEvidence(evidenceJdbcTemplate, object);
|
SqlExecutionEvidence evidence = findRecentExecutionEvidence(evidenceJdbcTemplate, object);
|
||||||
if (evidence != null || evidenceJdbcTemplate == jdbcTemplate) {
|
if (evidence != null || evidenceJdbcTemplate == jdbcTemplate) {
|
||||||
return evidence;
|
return new ExecutionEvidenceLookup(evidence, false);
|
||||||
}
|
}
|
||||||
} catch (RuntimeException exception) {
|
} catch (RuntimeException exception) {
|
||||||
|
sqlCatalogAccessUnavailable = isSqlCatalogAccessUnavailable(exception);
|
||||||
log.debug("ORDS parsing-schema cursor evidence is unavailable: {}", exception.getMessage());
|
log.debug("ORDS parsing-schema cursor evidence is unavailable: {}", exception.getMessage());
|
||||||
}
|
}
|
||||||
return findRecentExecutionEvidence(jdbcTemplate, object);
|
try {
|
||||||
|
return new ExecutionEvidenceLookup(
|
||||||
|
findRecentExecutionEvidence(jdbcTemplate, object), sqlCatalogAccessUnavailable);
|
||||||
|
} catch (RuntimeException exception) {
|
||||||
|
log.debug("Backoffice cursor evidence is unavailable: {}", exception.getMessage());
|
||||||
|
return new ExecutionEvidenceLookup(null,
|
||||||
|
sqlCatalogAccessUnavailable || isSqlCatalogAccessUnavailable(exception));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSqlCatalogAccessUnavailable(RuntimeException exception) {
|
||||||
|
String message = exception.getMessage();
|
||||||
|
if (message == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String normalized = message.toUpperCase(Locale.ROOT);
|
||||||
|
return normalized.contains("ORA-00942")
|
||||||
|
|| normalized.contains("V_$SQL")
|
||||||
|
|| normalized.contains("V$SQL");
|
||||||
}
|
}
|
||||||
|
|
||||||
private SqlExecutionEvidence findRecentExecutionEvidence(
|
private SqlExecutionEvidence findRecentExecutionEvidence(
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ class ProbeResultTemplateRenderTest {
|
|||||||
"{}",
|
"{}",
|
||||||
"{}",
|
"{}",
|
||||||
"{}"
|
"{}"
|
||||||
).withExecutionEvidence(null, "최근 SQL_ID를 찾지 못했습니다.");
|
).withExecutionEvidence(null,
|
||||||
|
"SQL_ID 실행 증적을 수집할 권한이 없습니다. 현재 권한 검증 결과는 정상입니다.");
|
||||||
var context = new Context(Locale.KOREAN);
|
var context = new Context(Locale.KOREAN);
|
||||||
context.setVariable("result", result);
|
context.setVariable("result", result);
|
||||||
context.setVariable("vectorSearch", false);
|
context.setVariable("vectorSearch", false);
|
||||||
@@ -46,6 +47,6 @@ class ProbeResultTemplateRenderTest {
|
|||||||
|
|
||||||
String rendered = engine.process("fragments/probe-result", context);
|
String rendered = engine.process("fragments/probe-result", context);
|
||||||
|
|
||||||
assertThat(rendered).contains("최근 SQL_ID를 찾지 못했습니다.");
|
assertThat(rendered).contains("SQL_ID 실행 증적을 수집할 권한이 없습니다.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user