# 설계서: ORDS/VPD 실제 실행 감사 증적 (#567) > **상태**: Implemented > **작성**: [AI] Architect · **최종수정**: 2026-07-01 > **추적성** — Redmine: #567 · 관련 이슈: #561 > · 구현: `ProbeResult`, `FgaExecutionEvidence`, `OrdsProbeService`, `OrdsMetadataService`, `probe-result.html`, `22_*`, `29_*`, `42_*`, `43_*` > · 검증: `ProbeResultTest`, `ProbeResultTemplateRenderTest`, `mvn test` ## 목적 권한 결과 확인에서 보여 주는 SQL을 두 종류로 분리한다. | 구분 | 출처 | 용도 | |---|---|---| | DB 감사 실행 증적 | Oracle FGA 감사 행 | **실제로 실행된 SQL**과 DB가 기록한 VPD `RLS_INFO` 확인 | | 참고용 SQL 재현 | Handler 응답/동일 정책 함수 | 사람이 권한 규칙과 SELECT 형태를 이해하는 보조 설명 | 재현 SQL이나 정책 함수를 다시 호출해 얻은 predicate를 실제 실행 증적으로 주장하지 않는다. `V$SQL`의 최신 cursor 후보를 찾아 추정하는 방식도 요청별 상관관계가 없어 증적 화면의 기준으로 사용하지 않는다. ## 해결 방식 ```text [권한 결과 확인] │ UUID 생성 ▼ X-VPD-Probe-Id HTTP header │ ▼ [ORDS Handler] set_vpd_context(auth, probe_id) │ └─ DBMS_SESSION.SET_IDENTIFIER(probe_id) ▼ [VPD 보호 SELECT] │ ├─ DBMS_RLS가 정책 predicate 적용 ▼ [DBMS_FGA SELECT 감사] SQL_TEXT + RLS_INFO + CLIENT_ID 저장 │ ▼ [Backoffice] UNIFIED_AUDIT_TRAIL(ADB)에서 CLIENT_IDENTIFIER=probe_id로 한 행 조회 DBA_FGA_AUDIT_TRAIL은 전통 FGA DB fallback │ ▼ DB 감사 실행 증적 화면 ``` - `CLIENT_ID`는 백오피스가 매 probe마다 생성한 UUID다. 토큰, 사용자명, 권한 값은 넣지 않는다. - ORDS connection pool 재사용 시 남는 식별자가 없도록 `set_vpd_context` 시작과 `clear_vpd_context`에서 `DBMS_SESSION.CLEAR_IDENTIFIER`를 호출한다. - FGA는 `SQL_TEXT`와 VPD 정책명·predicate가 담긴 `RLS_INFO`를 DB 감사 trail에 저장한다. 이 Autonomous DB에서는 `UNIFIED_AUDIT_TRAIL`을 사용하며, 전통 FGA 환경은 `DBA_FGA_AUDIT_TRAIL`을 fallback으로 조회한다. - FGA 감사 행이 없으면 기존의 권한 결과 성공/0행 상태를 실패로 바꾸지 않는다. 다만 “실제 실행 증적 없음”으로 명확히 표시한다. ## 데이터 계약 ### ORDS 요청 ```http X-VPD-Probe-Id: 8db9fc64-c71b-4b9e-9514-2fcaa355d9f3 Authorization: Bearer ``` `X-VPD-Probe-Id`는 일반 object handler, 기본 문서 handler, vector handler 모두 `:probe_id`로 bind한다. ### 화면 모델 `FgaExecutionEvidence`는 다음 값을 보관한다. - 감사 시각 `EVENT_TIMESTAMP_UTC` (`EXTENDED_TIMESTAMP` fallback) - DB 실행 사용자 `DBUSERNAME` (`DB_USER` fallback) - 요청 식별자 `CLIENT_IDENTIFIER` (`CLIENT_ID` fallback) - 문장 유형 `ACTION_NAME` (`STATEMENT_TYPE` fallback) - 실제 SQL `SQL_TEXT` - 실제 VPD 정보 `RLS_INFO` `ProbeResult`는 이 값을 별도 필드로 보관한다. 기존 `vpdPredicate`/`effectiveSql`은 “참고용 SQL 재현”에만 사용한다. ## 설치와 운영 절차 1. `ADMIN`으로 [42_agent_ords_fga_execution_audit.sql](../../../sql/adb/42_agent_ords_fga_execution_audit.sql)을 실행해 활성 보호 객체마다 FGA `SELECT` 정책을 만든다. 2. `CB_ORDS`로 [22_agent_ords_security_ords_handler_setup.sql](../../../sql/adb/22_agent_ords_security_ords_handler_setup.sql), [29_agent_ords_vector_search_ords.sql](../../../sql/adb/29_agent_ords_vector_search_ords.sql), [43_agent_ords_probe_id_handler_patch.sql](../../../sql/adb/43_agent_ords_probe_id_handler_patch.sql)을 순서대로 실행한다. 3. 새 권한 결과 확인을 실행한다. 이전 요청에는 `CLIENT_ID`가 없으므로 소급해 매칭하지 않는다. 4. 백오피스 실행 계정이 Autonomous의 `UNIFIED_AUDIT_TRAIL`을 조회할 수 있어야 한다. 전통 FGA 환경은 `DBA_FGA_AUDIT_TRAIL`을 fallback으로 사용한다. 일반 계정이면 감사 조회 권한을 가진 전용 observer 계정을 사용한다. ## 인수조건 - [ ] probe마다 새로운 request ID가 ORDS header에 전달된다. - [ ] Handler가 request ID를 `CLIENT_IDENTIFIER`로 설정하고 종료 시 제거한다. - [ ] FGA가 활성 보호 객체의 `SELECT`를 감사한다. - [ ] 화면은 같은 request ID의 `SQL_TEXT`와 `RLS_INFO`만 실제 실행 증적으로 보여 준다. - [ ] FGA 행이 없거나 읽기 권한이 없을 때 다른 cursor나 재현 predicate를 실제 값처럼 대신 보여 주지 않는다. - [ ] 재현 SQL은 참고용으로만 표시되며 실제 증적과 시각적으로 구분된다. - [ ] Bearer token 원문은 header, 감사 correlation ID, 화면 어느 곳에도 기록하지 않는다. ## 실패 처리 | 상황 | 화면 동작 | |---|---| | FGA 정책 미적용 | 권한 결과는 표시하고, FGA 적용 안내를 표시 | | ORDS Handler가 구버전 | request ID가 DB에 설정되지 않아 해당 요청의 감사 행을 표시하지 않음 | | 감사 조회 권한 없음 | 권한 결과는 유지하고, audit trail 권한 안내 표시 | | `RLS_INFO` 비어 있음 | `SQL_TEXT`는 표시하고 audit 설정 확인 안내 표시 | | 0행 결과 | VPD 차단 결과와 FGA `SELECT` 감사 행을 함께 표시 | ## 리스크와 선택 근거 - VPD policy function 안에서 별도 DML 로그를 남기지 않는다. 정책 함수에는 DB 상태 변경 제약이 있어, 감사 책임을 FGA에 둔다. - `V$SQL`은 shared pool cursor cache이며 보존·권한·동시성에 따라 해당 요청을 신뢰성 있게 찾을 수 없다. 운영 진단 보조로는 가능하지만 요청 증적의 기준으로는 부적합하다. - FGA audit trail은 보존 정책을 운영에서 정해야 한다. 장기 감사가 필요하면 Unified Audit 보존·전송 정책을 별도로 설정한다. ## 테스트 계획 - 단위: `ProbeResult`가 FGA 증적과 참고용 재현 SQL을 서로 다른 필드로 유지한다. - 템플릿: FGA `SQL_TEXT`, `RLS_INFO`, `CLIENT_ID`가 표시되고 V$SQL 후보 목록은 표시되지 않는다. - 통합: ADMIN/CB_ORDS 설치 후 각 보호 객체에 probe 실행 → `UNIFIED_AUDIT_TRAIL.CLIENT_IDENTIFIER`가 화면 요청 ID와 같은지, `RLS_INFO`에 해당 VPD 정책이 있는지 확인한다.