fix #557: guide permission-driven VPD flow

This commit is contained in:
devmrko
2026-06-29 12:11:25 +09:00
parent fbe3d4682b
commit 908ac9a386
57 changed files with 1952 additions and 312 deletions

View File

@@ -2,7 +2,7 @@
> **상태**: Approved
> **작성**: [AI] Architect · **최종수정**: 2026-06-29
> **추적성** — Redmine: #557 · 관련 ADR: 없음
> **추적성** — Redmine: #557 · 후속 UX: #558~#565 · 관련 ADR: 없음
> · 구현 파일: `ProbeController`, `ProbeResult`, `VpdPolicyService`, 대시보드/VPD/토큰/검증 템플릿
> · 테스트: `ProbeResultTest`, `VpdPolicyServiceTest`, `GuidedFlowTemplateTest`
@@ -12,7 +12,7 @@
## 2. 범위 (Scope)
- **포함**: Macro→Micro 설명 구조, 내비게이션 재분류, 동적 권한 VPD 기본 적용, custom filter 고급 분리, 토큰 검증 단순화, 상태별 일반 문장과 다음 행동, 발급→검증 연결.
- **포함**: Macro→Micro 설명 구조, 내비게이션 재분류, 동적 권한 VPD 기본 적용, custom filter 고급 분리, 토큰 검증 단순화, 원문/임시 토큰 검증, 상태별 일반 문장과 다음 행동, 발급→검증 연결.
- **제외**: 권한 데이터 모델 변경, VPD 함수 SQL 알고리즘 변경, 토큰 원문 저장, 운영 DB DDL 자동 실행, ORDS handler 생성 방식 변경.
## 3. 인수조건 (Acceptance Criteria)
@@ -21,7 +21,7 @@
- [x] 기본 VPD 적용은 객체만 선택하고 권한체계 동적 함수와 SELECT 정책을 서버가 결정한다.
- [x] `CB_AGENT_DOC_VPD_FILTER`는 일반 UI에서 수정할 수 없고 직접 POST도 거부된다.
- [x] 별도 filter/predicate와 policy 교체는 사용 기준·안전 규칙이 있는 고급 영역에만 노출된다.
- [x] 검증 화면은 등록 토큰 선택 없이 Bearer 원문과 대상 입력한다.
- [x] 검증 화면은 등록 토큰 선택 없이 Bearer 원문 또는 테스트 중에만 존재하는 임시 토큰과 대상 입력한다.
- [x] 검증 결과가 상태, 적용 주체, 행 결과, 다음 행동 순으로 설명되고 HTTP 원문은 접혀 있다.
- [x] 존재하지 않는 테스트 토큰은 “DB에 등록된 토큰이 아님”과 재발급 절차를 안내한다.
- [x] 발급 직후 토큰 복사와 검증 화면 이동이 명확하다.
@@ -45,7 +45,7 @@
[Evidence: 실제로 지켜졌는가]
1회 표시 토큰 → ORDS 호출 → 사용자/역할 + 보이는 행 + 다음 행동
1회 표시/임시 토큰 → ORDS 호출 → 사용자/역할 + 보이는 행 + 다음 행동
```
- I/O 경계: controller/service는 DB catalog와 ORDS를 호출한다.
@@ -57,7 +57,7 @@
- 입력: `objectKey`, `bearerToken`, `objectId`, `limit`.
- 기본 VPD 명령: policy `CB_PERMISSION_SELECT_POLICY`, function `*.CB_AGENT_DOC_VPD_FILTER`, statements `SELECT`, enabled `true`, update check `false`.
- 검증 표현: 기존 `ProbeResult`에 상태별 `title`, `plainSummary`, `nextAction`, `successLike` 계산 메서드를 둔다.
- 검증 컨텍스트: hash로 찾은 `TokenContextView``ProtectedObject`를 controller model에 추가한다. 원문은 model/result/log에 저장하지 않는다.
- 검증 컨텍스트: hash로 찾은 `TokenContextView``ProtectedObject`를 controller model에 추가한다. 임시 토큰은 실행 후 회수한다. 원문은 model/result/log에 저장하지 않는다.
## 7. 함수 명세 (Function Specs)

View File

@@ -0,0 +1,172 @@
# 설계서: 기술 태그 기반 벡터 지식자료 검색과 VPD 연결
> **상태**: 구현 초안
> **추적성**: Redmine #565 · 기준 구현: `28_agent_ords_vector_tag_vpd_setup.sql`, `29_agent_ords_vector_search_ords.sql`
## 1. 한 문장으로 이해하기
문서를 잘게 나누고(청킹) 숫자 벡터로 바꾼 뒤(벡터화) 각 조각에 `SPRING_BOOT`, `ORACLE_VPD` 같은 기술 태그를 붙인다. 사용자의 권한에는 “이 태그를 볼 수 있음”을 등록하고, ORDS 검색은 권한에 맞는 조각만 검색 결과에 남긴다.
이 기능에서 태그는 문서의 분류표이고, VPD는 그 분류표를 이용해 DB가 실제로 보여 줄 행을 제한하는 장치다. 화면에서 검색 조건을 넣는 것만으로는 우회할 수 없도록 DB 안에서 마지막 필터를 적용한다.
## 2. 큰 흐름 (일반 사용자가 보는 순서)
```text
지식자료 등록
→ 문서를 문단 단위 청크로 나눔
→ 각 청크를 임베딩 모델로 벡터화
→ 청크마다 TECH_TAG를 하나 이상 부여
→ ADMIN.CB_VECTOR_SEARCH_DOCUMENTS 뷰에 노출
권한 설계
→ 역할에 SELECT 권한 부여
→ 행 규칙에서 특정 기술 태그(TAG)를 여러 개 등록
→ 같은 ALLOW 권한의 태그는 OR(하나라도 일치하면 허용)
→ DENY 태그는 허용 후보에서 다시 제외
검색
→ 호출자가 Bearer 토큰을 보냄
→ ORDS가 토큰으로 사용자 컨텍스트를 설정
→ VPD가 TECH_TAG 권한에 맞지 않는 행을 먼저 제거
→ 남은 청크만 벡터 거리순으로 정렬해 반환
```
## 3. 마이크로 동작 (DB에서 실제로 일어나는 일)
### 3.1 저장 구조
| 객체 | 역할 |
|---|---|
| `CB_VECTOR_DOCUMENT_CHUNK` | 문서 ID, 청크 번호, 제목, 본문, 원본 주소, 임베딩 벡터 저장 |
| `CB_VECTOR_DOCUMENT_TAG` | 청크와 기술 태그의 다대다 연결. 한 청크에 태그 여러 개 가능 |
| `CB_VECTOR_SEARCH_DOCUMENTS` | 청크 한 개당 한 행으로 태그를 쉼표로 합친 검색용 뷰. VPD를 이 뷰에 부착 |
| `CB_PERMISSION_RULE` | 기존 권한 체계에 `rule_type = TAG`, `rule_value = 태그`로 저장 |
`TECH_TAG`는 VPD가 읽는 행 접근 기준이다. `EMBEDDING`의 민감도나 `CHUNK_TEXT`의 표시 방식은 별도의 컬럼 표시 보호 정책이며, 태그 권한과 섞지 않는다.
### 3.2 TAG 규칙
- 컬럼을 비워 두면 기본 컬럼 `TECH_TAG`를 사용한다.
- 태그 값은 저장할 때 대문자로 정규화한다. 예: `spring_boot``SPRING_BOOT`.
- 같은 `ALLOW` 권한에 태그를 여러 개 넣으면 OR이다.
```text
ALLOW TAG SPRING_BOOT
ALLOW TAG ORACLE_VPD
결과: TECH_TAG 목록에 SPRING_BOOT가 포함됨
OR TECH_TAG 목록에 ORACLE_VPD가 포함됨
```
- `DENY` 태그도 내부적으로 OR로 묶은 뒤 허용 결과에서 뺀다.
```text
(허용 태그 A OR 허용 태그 B)
AND NOT (거부 태그 C OR 거부 태그 D)
```
- 허용 규칙이 없거나 태그 컬럼이 실제 객체에 없으면 `1 = 0`으로 닫는다(fail-closed).
- 사용자가 여러 역할을 가지고 있어도 허용 태그는 합쳐져 OR가 된다. 어떤 역할의 DENY 태그와 일치하면 최종 결과에서 제외된다.
## 4. 예시 시나리오
샘플 데이터에는 다음과 같은 청크가 있다.
| 청크 | 태그 |
|---|---|
| Spring Boot VPD 시작하기 | `SPRING_BOOT`, `ORDS` |
| Oracle VPD 정책 연결 | `ORACLE_VPD`, `ORDS` |
| MCP 도구 권한 설계 | `MCP` |
역할을 다음처럼 만든다.
1. `KNOWLEDGE_BACKEND`: `ALLOW TAG SPRING_BOOT`, `ALLOW TAG ORACLE_VPD`
2. `KNOWLEDGE_MCP`: `ALLOW TAG MCP`
3. `KNOWLEDGE_NO_ORDS`: `ALLOW TAG SPRING_BOOT`, `ALLOW TAG ORACLE_VPD`, `DENY TAG ORDS`
그러면:
- `KNOWLEDGE_BACKEND` 사용자는 첫 번째와 두 번째 청크를 본다.
- `KNOWLEDGE_MCP` 사용자는 세 번째 청크만 본다.
- `KNOWLEDGE_NO_ORDS` 사용자는 `ORDS` 태그가 붙은 첫 번째·두 번째 청크가 거부되어 결과가 없다.
- 태그 권한이 전혀 없는 사용자는 결과가 없다.
## 5. ORDS API 계약
### 요청
```http
POST /ords/cb-ords/cb-agent-vector/search?limit=10
Authorization: Bearer < >
Content-Type: application/json
{"embedding":[0.10,0.20,0.30,0.40]}
```
`embedding`은 검색어를 외부 임베딩 모델로 변환한 벡터다. DB는 임베딩 모델을 호출하지 않는다. 따라서 모델 선택·차원·재임베딩 일정은 지식자료 파이프라인에서 관리한다.
### 응답
```json
{
"items": [
{
"chunk_id": 28001,
"document_id": "knowledge-001",
"chunk_no": 1,
"title": "Spring Boot VPD 시작하기",
"chunk_text": "...",
"source_uri": "kb://security/spring-boot-vpd",
"tech_tag": "ORDS,SPRING_BOOT",
"score": 0.0123
}
]
}
```
`CB_VECTOR_SEARCH_DOCUMENTS`는 청크 한 개당 한 행을 만들고 `TECH_TAG`에 태그 목록을 담는다. 그래서 VPD가 허용/거부를 청크 전체에 적용할 수 있다. ORDS 핸들러는 VPD를 통과한 청크를 벡터 거리순으로 정렬한다. `EMBEDDING` 자체는 응답에 포함하지 않는다.
## 6. 설치·등록 순서
운영 DB에 자동 실행하지 않고 다음 순서로 승인된 환경에서 실행한다.
1. `25_agent_ords_security_backoffice_support.sql` — 기존 backoffice 권한 메타데이터
2. `31_agent_ords_backoffice_ux_metadata.sql` — 기존 설치에 조회 대상 설명/정책 설명 메타데이터 추가
3. `26_agent_ords_security_dynamic_vpd_filter.sql``TAG` 분기를 포함한 동적 VPD 함수
4. `28_agent_ords_vector_tag_vpd_setup.sql` — VECTOR 테이블·태그 테이블·뷰·VPD·보호 객체 등록
5. `29_agent_ords_vector_search_ords.sql``cb-agent-vector/search` ORDS 모듈/핸들러 (CB_ORDS로 실행)
6. backoffice `/permissions`에서 역할별 `특정 기술 태그` 규칙을 저장
7. `30_agent_ords_vector_tag_vpd_test.sql` — OR와 DENY 우선순위 확인
backoffice의 `/objects` 화면은 이 객체에 일반 Handler를 생성하지 않도록 막는다. 일반 Handler를 사용하면 벡터 컬럼을 그대로 반환할 수 있기 때문에, 임베딩을 응답에서 제외하고 벡터 거리 검색을 하는 전용 Handler만 허용한다.
실제 지식자료를 넣을 때는 샘플 청크 대신 ingestion 작업이 다음을 반복한다.
```text
원문 → 청크 분할 → 임베딩 생성 → CB_VECTOR_DOCUMENT_CHUNK 저장
→ CB_VECTOR_DOCUMENT_TAG에 태그 저장
```
## 7. 운영 가이드라인
- 태그는 자유 문장보다 대문자·언더스코어 형태의 안정적인 ID로 관리한다. 예: `SPRING_BOOT`, `ORACLE_VPD`, `INTERNAL_ONLY`.
- 현재 예시의 태그 ID 허용 문자는 영문·숫자·`_`·`-`다. 이 범위를 벗어난 값은 UI와 VPD 함수에서 모두 무시해 권한이 넓어지지 않게 한다.
- 동의어를 무분별하게 만들지 말고 태그 사전을 먼저 정한다. `SPRING`, `SPRINGBOOT`, `SPRING_BOOT`을 모두 별개로 만들면 권한 누락이 생긴다.
- 문서 청크에 최소 한 개의 기술 태그를 부여한다. 태그가 없는 청크는 이 뷰에 노출되지 않는다.
- 태그를 수정하면 권한 결과가 즉시 바뀐다. 임베딩을 다시 만들 필요가 없는 태그 변경과, 내용이 바뀌어 재임베딩해야 하는 변경을 구분한다.
- `TECH_TAG`는 행 접근용이고, 컬럼 민감도/마스킹은 별도 정책이다. “컬럼을 PUBLIC으로 표시했다”는 사실이 행 조회 권한을 부여하지 않는다.
- 벡터 차원은 ingestion 모델과 테이블 데이터가 일치해야 한다. 차원이 다르면 ORDS가 4xx/5xx 오류를 반환하므로 모델 버전을 함께 기록한다.
## 8. 검증 기준
- 같은 ALLOW 권한의 태그 두 개가 각각 일치하는 청크를 반환한다.
- ALLOW 태그와 DENY 태그가 함께 있으면 DENY 일치 청크는 반환되지 않는다.
- 태그 권한이 없는 사용자, 잘못된 토큰, 존재하지 않는 태그는 결과가 0건 또는 차단으로 끝난다.
- ORDS 응답에는 허용되지 않은 태그의 청크가 섞이지 않는다.
- 검색 결과에 임베딩 원문이 포함되지 않는다.
- `/permissions` 화면에서 `TAG` 규칙이 기본 `TECH_TAG`와 OR 의미를 일반 문장으로 설명한다.
## 9. 범위 밖
- 임베딩 모델 호출·문서 업로드 UI·태그 사전 승인 워크플로는 이번 시나리오에서 구현하지 않는다.
- 운영 DB에 대한 DDL, 기존 정책 교체, 방화벽/NSG 변경은 별도 승인을 받아 실행한다.