158 lines
14 KiB
Markdown
158 lines
14 KiB
Markdown
# 설계서: ORDS/VPD 권한 적용 SQL trace (#567)
|
|
|
|
> **상태**: Approved
|
|
> **작성**: [AI] Architect · **최종수정**: 2026-06-30
|
|
> **추적성** — Redmine: #567 · 관련 이슈: #561 · 관련 ADR: 없음
|
|
> · 구현 파일: `ProbeResult`, `OrdsProbeService`, `OrdsMetadataService`, `probe-result.html`, `ords-handlers.html`, `sql/adb/17_*`, `22_*`, `26_*`, `29_*`, `33_*`
|
|
> · 테스트: `ProbeResultTest`, `OrdsMetadataServiceTest`, `GuidedFlowTemplateTest`, `mvn test`
|
|
|
|
## 1. 목적 (Why)
|
|
|
|
권한 결과 확인에서 `set_vpd_context`가 토큰 사용자를 DB 컨텍스트로 설정한 뒤, VPD 정책 함수가 반환한 행 조건이 기본 SELECT에 어떻게 결합되는지 운영자가 직접 확인할 수 있게 한다. 이 기능은 #561의 Handler 흐름 설명을 실행 증거까지 확장한다.
|
|
|
|
## 2. 범위 (Scope)
|
|
|
|
- **포함**:
|
|
- ORDS Handler 성공 응답의 `vpd_predicate`, `effective_sql` trace 필드.
|
|
- 기존 trace 미적용 Handler를 위한 동일 DB fallback predicate 조회.
|
|
- `/probe` 결과의 “토큰 적용 후 SQL” 영역과 trace 미지원 안내.
|
|
- 신규/갱신 Handler SQL 및 기존 설치용 `GRANT` 패치 스크립트.
|
|
- 토큰 원문 마스킹과 trace 관련 단위/템플릿/소스 테스트.
|
|
- **제외 (out of scope)**:
|
|
- Oracle optimizer 실행계획, `V$SQL` 조회, bind 값 치환 결과의 수집.
|
|
- custom VPD Filter의 임의 SQL 해석 또는 자동 수정.
|
|
- 권한 모델/토큰 저장 구조 변경.
|
|
- 원문 Bearer Token의 응답·로그·DB 저장.
|
|
|
|
## 3. 인수조건 (Acceptance Criteria)
|
|
|
|
- [ ] 유효 토큰으로 권한 결과를 조회하면 VPD predicate와 기본 SELECT에 결합한 effective SQL을 확인할 수 있다.
|
|
- [ ] 0행 결과도 성공적인 VPD 차단 결과로 trace 표시 대상이 된다.
|
|
- [ ] 신규/갱신 object Handler와 기본 VPD/벡터 Handler는 진단 필드를 응답에 포함한다.
|
|
- [ ] 기존 Handler가 진단 필드를 반환하지 않아도 같은 DB의 `set_user_by_bearer` 컨텍스트에서 기본 VPD predicate를 재조회한다.
|
|
- [ ] trace를 얻을 수 없는 DB 계정·custom Filter·구버전 Handler는 조회 결과를 실패로 바꾸지 않고 적용 방법을 안내한다.
|
|
- [ ] trace에는 predicate 표현식과 SQL 구조만 포함하며 Bearer 원문은 포함하지 않는다.
|
|
- [ ] 초기화 및 기존 설치용 SQL에 `CB_AGENT_DOC_VPD_FILTER` 실행 권한과 갱신 절차가 명시된다.
|
|
- [ ] 관련 단위/템플릿/Handler source 테스트와 전체 Maven 테스트가 통과한다.
|
|
|
|
## 4. 컨텍스트 & 제약
|
|
|
|
- ORDS는 공통 `CB_ORDS` 계정으로 접속하고 `cb_ords_handler_pkg.set_vpd_context`가 Bearer key를 `CB_AGENT_CTX`에 매핑한다.
|
|
- 기본 VPD 함수 `ADMIN.CB_AGENT_DOC_VPD_FILTER`가 현재 컨텍스트의 권한 규칙으로 predicate를 만든다. 정책 함수의 반환값을 직접 표시하는 것이 행 결과를 다시 계산하는 별도 Java 권한 로직보다 정합성이 높다.
|
|
- 기존 Handler는 응답 계약을 바꾸지 않을 수 있으므로, 백오피스 DB 연결에서 같은 Bearer key를 별도 세션 컨텍스트에 설정해 fallback을 수행한다. fallback은 세션 종료 전에 반드시 `clear_user`를 실행한다.
|
|
- ORDS 응답 trace가 없거나 fallback 권한이 없으면 권한 결과 자체는 그대로 반환한다. trace는 관측성 보조 기능이며 접근 통제 경로가 아니다.
|
|
- `ADMIN`은 현재 PoC의 VPD 함수/컨텍스트 소유자다. 다른 소유자 구조는 이번 범위에서 자동 추론하지 않는다.
|
|
|
|
## 5. 아키텍처 개요
|
|
|
|
```text
|
|
[Probe form: bearerToken + object]
|
|
│
|
|
▼
|
|
[ORDS Handler: set_vpd_context]
|
|
│
|
|
┌──────┴────────┐
|
|
│ │
|
|
▼ ▼
|
|
[DBMS_RLS SELECT] [same-context trace]
|
|
│ │
|
|
└──────┬────────┘
|
|
▼
|
|
[ORDS JSON: items + vpd_predicate + effective_sql]
|
|
│
|
|
▼ (trace absent only)
|
|
[Backoffice DB fallback: set_user_by_bearer → filter → clear_user]
|
|
│
|
|
▼
|
|
[ProbeResult → “토큰 적용 후 SQL”]
|
|
```
|
|
|
|
- I/O 경계: `OrdsProbeService.findVpdPredicate`와 ORDS HTTP 호출은 외부 I/O다. `ProbeResult.hasSqlTrace/withSqlTrace`와 SQL 문자열 조합은 표현/변환 책임으로 둔다.
|
|
- 안전 경계: predicate 조회 실패는 조회 실패가 아니며 trace만 생략한다. Bearer header는 기존처럼 마스킹된 request detail만 노출한다.
|
|
|
|
## 6. 데이터 모델
|
|
|
|
- `ProbeResult.vpdPredicate`: VPD 정책 함수가 현재 토큰 컨텍스트에서 반환한 predicate 문자열(nullable).
|
|
- `ProbeResult.effectiveSql`: Handler의 기본 SELECT와 predicate/row limit을 결합한 확인용 SQL(nullable).
|
|
- ORDS JSON 성공 응답에 두 문자열을 optional root field로 추가한다. `items`/`rows` 기존 배열 계약은 유지한다.
|
|
- trace SQL은 `:row_limit` bind 표기를 유지하며 실제 Bearer 값이나 context 값은 문자열에 치환하지 않는다.
|
|
|
|
## 7. 함수 명세 (Function Specs)
|
|
|
|
| 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? |
|
|
|------|-----------|----------------|------|------|-----------|-------|
|
|
| `ProbeResult.hasSqlTrace` | effective SQL 존재 여부를 판단 | `boolean hasSqlTrace()` | result fields | boolean | blank/null이면 false | 단순 |
|
|
| `ProbeResult.withSqlTrace` | 기존 결과에 trace 두 필드를 불변으로 부착 | `ProbeResult withSqlTrace(String, String)` | predicate, SQL | 새 `ProbeResult` | 없음 | 단순 |
|
|
| `OrdsProbeService.runProbe` | ORDS 결과를 파싱하고 필요한 경우 trace fallback을 연결 | `ProbeResult runProbe(ProbeCommand)` | token/object/limit | ProbeResult | 기존 상태 분류 유지 | **복잡** |
|
|
| `parseSuccess` | ORDS JSON 배열과 optional trace 필드 파싱 | `ProbeResult parseSuccess(...)` | body, objectId, HTTP evidence | ProbeResult | 배열 누락 시 예외 | 단순 |
|
|
| `traceValue` | JSON scalar trace 필드 정규화 | `String traceValue(JsonNode, String)` | root/field | nullable String | 비 scalar/blank이면 null | 단순 |
|
|
| `addLocalSqlTrace` | predicate와 보호 객체 컬럼으로 effective SQL 생성 | `ProbeResult addLocalSqlTrace(...)` | result/token/object | trace 부착 결과 | DB/컬럼 조회 실패 시 원 결과 | **복잡** |
|
|
| `findVpdPredicate` | 같은 DB 연결에서 컨텍스트 설정→predicate 조회→컨텍스트 정리 | `String findVpdPredicate(String, ProtectedObject)` | bearer/object | nullable predicate | 권한/DB 오류는 null | **복잡** |
|
|
| `executeContextSetter` | DB 세션에 Bearer 기반 사용자 컨텍스트 설정 | `void executeContextSetter(Connection, String)` | connection/token | 없음 | SQLException | 단순 |
|
|
| `clearContext` | fallback 세션의 사용자 컨텍스트 제거 | `void clearContext(Connection)` | connection | 없음 | cleanup 오류 무시 | 단순 |
|
|
| `objectQuerySource` | 생성 Handler에 trace metadata 출력 블록을 포함 | `String objectQuerySource(ProtectedObject,List<String>)` | object/columns | PL/SQL source | 식별자/컬럼 검증은 호출부 | **복잡** |
|
|
|
|
복잡 함수 상세는 `fn-addLocalSqlTrace.md`, `fn-findVpdPredicate.md`에 둔다. `runProbe`의 기본 상태 분류 계약은 기존 #557 설계를 계승한다.
|
|
|
|
## 8. 흐름 / 알고리즘
|
|
|
|
1. ORDS base URL, token, 보호 객체를 기존 검증 절차로 확인한다.
|
|
2. ORDS POST를 호출한다. 갱신된 Handler는 `set_vpd_context` 직후 동일 VPD 함수를 호출해 trace를 만들고, `items`와 함께 JSON으로 반환한다.
|
|
3. 백오피스는 `items`/`rows`를 기존 방식으로 파싱한다.
|
|
4. trace가 없고 일반 object Handler이면 백오피스 DB의 한 연결에서 `set_user_by_bearer`를 호출한다.
|
|
5. 같은 연결에서 `SELECT admin.cb_agent_doc_vpd_filter(?, ?) FROM dual`을 수행한다.
|
|
6. `finally` 경로에서 `clear_user`를 호출하고 연결을 pool에 반환한다.
|
|
7. 보호 객체의 등록 컬럼과 predicate를 이용해 `SELECT ... FROM OWNER.OBJECT o WHERE (...) AND ROWNUM ...` 형태를 만든다.
|
|
8. 화면은 결과 행·사용자 권한 뒤에 predicate와 effective SQL을 표시한다. trace가 없으면 ORDS/DB 적용 절차를 안내한다.
|
|
|
|
## 9. 엣지케이스 & 에러 처리
|
|
|
|
- token/ORDS 오류: 기존 `ProbeStatus`와 HTTP evidence 처리만 수행하며 fallback을 실행하지 않는다.
|
|
- 0행: `VPD_DENY_EMPTY_RESULT`를 유지하고 trace를 표시한다.
|
|
- ORDS 신규 trace가 있고 local DB trace가 불가능한 경우: ORDS 응답 trace를 그대로 사용한다.
|
|
- 기존 Handler·DB 계정에 trace 권한이 없는 경우: 결과 성공/0행을 유지하고 trace 미지원 안내를 표시한다.
|
|
- fallback context setter가 실패한 경우: 반드시 cleanup을 시도하고 trace는 null로 둔다.
|
|
- vector object: 전용 Handler의 복합 vector SQL을 일반 `SELECT`로 오표시하지 않도록 local generic fallback을 생략한다. 갱신된 vector Handler가 자체 trace를 반환하면 표시한다.
|
|
- custom VPD Filter: 기본 함수 반환값을 custom Filter의 실제 predicate로 주장하지 않는다. trace가 없으면 별도 Filter 확인을 안내한다.
|
|
- predicate가 길어도 DB 함수 반환 한도(`VARCHAR2(32767)`)와 화면 `<pre>`로 처리한다.
|
|
|
|
## 10. 테스트 계획
|
|
|
|
- `ProbeResultTest`: trace 부착 후 기존 rows/status 보존, blank trace 미표시.
|
|
- `OrdsMetadataServiceTest`: 생성 object Handler에 `set_vpd_context`, default VPD function, JSON trace 필드와 컬럼 SQL이 포함되는지 확인.
|
|
- `GuidedFlowTemplateTest`: 결과 화면/Handler 안내/초기화 SQL/기존 설치 grant 및 vector trace source 확인.
|
|
- `mvn test`: 전체 단위·템플릿 테스트.
|
|
- 운영 smoke: 신규 토큰으로 `/probe`에서 HR/SELF/ALL 결과를 각각 실행하고 predicate의 `DEPT_CODE`, `OWNER_EMP_NO`, `1 = 1` 또는 `1 = 0` 차이를 확인한다. 토큰 원문이 response body/log에 없는지 확인한다.
|
|
|
|
## 11. 리스크 & 대안 검토
|
|
|
|
- `V$SQL`/`DBMS_XPLAN` 조회는 connection pool의 다른 세션·cursor·권한 문제 때문에 결과가 불안정하다. 정책 함수 반환값을 같은 context에서 표시하는 방식을 선택한다.
|
|
- Java에서 permission table을 다시 계산하는 방식은 그룹 상속·DENY 우선순위·custom policy와 어긋날 수 있다. DB의 정책 함수 자체를 trace source로 사용한다.
|
|
- Handler 응답 계약을 강제로 변경하면 기존 Handler가 실패할 수 있다. optional response fields와 non-failing fallback을 사용한다.
|
|
- fallback에서 context cleanup을 놓치면 pool connection 간 사용자 혼선이 발생한다. `finally` cleanup을 고정하고 cleanup 오류는 결과를 덮지 않는다.
|
|
|
|
## 12. 미해결 질문 (Open Questions)
|
|
|
|
- custom VPD Filter까지 정책 함수별로 자동 trace할지는 별도 이슈로 남긴다.
|
|
- 운영 DB에서 `ADMIN` 이외의 VPD 소유자 구조를 지원하려면 함수 owner 설정을 명시적으로 추가해야 한다.
|
|
|
|
## 13. SQL_ID 실행 증적 확장 — 2026-07-01
|
|
|
|
재현 SQL만으로는 “이 조건이 실제 DB cursor에 적용됐는가”를 충분히 설명하지 못한다. `/probe` 성공·0행 결과 뒤에 백오피스는 `V$SQL`에서 시간 조건 없이 마지막 활성 cursor 10건을 먼저 읽고, Java에서 `SQL_FULLTEXT`에 보호 객체명이 있는 cursor만 고른다. DB에서 SQL 텍스트를 `LIKE`로 필터링하지 않아 ORDS Handler의 PL/SQL·공백·긴 SQL 표현 때문에 놓치는 문제를 줄인다. 이어 `DBMS_XPLAN.DISPLAY_CURSOR(SQL_ID, CHILD_NUMBER, 'ALLSTATS LAST +PREDICATE +ALIAS')`의 Predicate Information을 함께 표시한다.
|
|
|
|
| 화면 증적 | DB 출처 | 의미 |
|
|
|---|---|---|
|
|
| DB가 기록한 원문 SQL | `V$SQL.SQL_FULLTEXT` | ORDS가 DB에 제출한 VPD 주입 전 SQL과 SQL_ID |
|
|
| DBMS_XPLAN Predicate Information | 해당 SQL_ID/child cursor 실행계획 | DB cursor에 적용된 Access/Filter predicate |
|
|
| 권한 조건 재현 SQL | Handler trace 또는 동일 context 정책 함수 | 사람이 WHERE 결합 형태를 읽기 위한 설명용 표현 |
|
|
|
|
Oracle은 VPD의 내부 rewrite 결과를 별도 최종 SQL 문자열로 `V$SQL`에 보관하지 않는다. 따라서 원문 SQL과 실행계획 predicate를 함께 제시하는 것이 실제 실행에 대한 DB 증적이다. `executions`, `rows_processed`, `elapsed_time`, `buffer_gets`는 cursor 누적값이며 단일 HTTP 요청만의 계측값은 아니다.
|
|
|
|
화면에서는 설명용 표현을 **실행 요청 SQL (재현)**, cursor에서 읽은 원문을 **실제 DB cursor SQL**로 구분한다. `V$SQL` 권한이 없는 환경에서도 재현 SQL은 표시하고, 실제 cursor 증적이 없다는 이유를 별도로 표시한다.
|
|
|
|
최근 SQL 매칭은 최신 10건과 보호 객체명으로 고르므로, 같은 객체를 동시에 호출하는 운영 환경에서는 다른 요청 cursor가 선택될 가능성이 있다. 현재 UI는 이를 “최근 실행 증적”으로 명시한다. 요청별 완전 상관이 필요해지면 ORDS Handler에 검증 요청 ID를 주입해 고유 SQL comment/module-action으로 cursor를 추적하는 후속 작업으로 확장한다.
|
|
|
|
매칭에 실패해도 후보를 버리지 않는다. 화면은 최신 10건의 `SQL_ID`, child cursor, parsing schema, 마지막 실행 시각, 원문 SQL을 그대로 보이며, Java가 보호 객체명과 일치시킨 후보에는 별도 배지를 표시한다. 운영자는 이 목록에서 실제 Handler SQL의 형태를 직접 확인할 수 있다.
|
|
|
|
실행 증적 연결에는 최소한 `V$SQL`과 `DBMS_XPLAN.DISPLAY_CURSOR`를 조회할 수 있는 catalog 권한이 필요하다. 증적은 ORDS parsing schema가 아니라 화면을 표시하는 `BACKOFFICE_DB_USERNAME` 연결에서 조회한다. 실제 SQL 실행 계정과 진단 조회 계정은 달라도 된다. 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에서 서로 다른 안내로 표시하며, 어느 경우도 권한 검증 결과를 실패시키지 않는다.
|