refs #740: enforce carrier VPD by MCP user

This commit is contained in:
devmrko
2026-08-10 17:26:01 +09:00
parent 2f5fc2bfbd
commit 6394bbf078
11 changed files with 521 additions and 122 deletions

View File

@@ -1,123 +1,52 @@
# #740 HMM MCP VPD 실행 경계 복구
# #740 HMM MCP VPD 실행 경계
## 배경
## 목적
HMM MCP의 `search_hr_data`는 현재 `ADMIN` JDBC 세션에서
`DBMS_CLOUD_AI_AGENT.RUN_TOOL`을 실행한다. 토큰으로
`HMM_ACCESS_CTX`를 설정해도 `ADMIN`에는 `EXEMPT ACCESS POLICY`가 있으므로
`ADMIN.HMM_LEAVE_BALANCES``ADMIN.HMM_LEAVE_REQUESTS`의 VPD 정책이
실제 조회에 적용되지 않는다.
HMM MCP의 인증 사용자를 Oracle DB 세션 사용자 문맥으로 연결하고, HR 및 선사 배정 데이터를
Oracle VPD로 제한한다. 팀원은 본인 행, 팀장은 본인과 직속 팀원 행을 조회한다.
컨텍스트 값이 올바른 것과 VPD가 적용되는 것은 별개의 조건이다. 보호 테이블을
읽는 최종 SQL은 반드시 `EXEMPT ACCESS POLICY`가 없는 계정의 동일 DB 세션에서
컨텍스트 설정과 함께 실행해야 한다.
## 범위와 결정사항
## 목표
- 포털의 사용자 preset은 각각 다른 Bearer token 환경변수를 사용한다.
- 백오피스는 token hash로 직원을 식별하고 실제 SELECT와 같은 `CB_ORDS` 연결에
`SET_VPD_CONTEXT`를 호출한다.
- `ADMIN``EXEMPT ACCESS POLICY`가 있으므로 Select AI `SHOWSQL` 생성만 담당한다.
- `HMM_CARRIER_ASSIGNMENTS_V.EMPLOYEE_ID``SELF`, `MANAGED_TEAM`, `ALL` 규칙을 적용한다.
- 기존 MCP 이름 `search_carrier_performance`, 자연어 Select AI, 선사 `CARRIER_CODE` 조인과 HTML
renderer는 유지한다. 고정 SQL이나 대체 조회 패키지를 만들지 않는다.
- `ADMIN`은 Select AI `SHOWSQL` 생성만 담당한다.
- 비면제 런타임 스키마 `CB_ORDS`가 생성 SQL을 읽기 전용으로 실행한다.
- 실행 직전 같은 `CB_ORDS` 세션에서
`CB_ORDS_HANDLER_PKG.SET_VPD_CONTEXT`를 호출한다.
- 실행 종료 시 성공·실패와 관계없이 `CLEAR_VPD_CONTEXT`를 호출하고 롤백한다.
- 런타임 계정이 `ADMIN`이거나 `EXEMPT ACCESS POLICY`를 가진 경우 fail-closed 한다.
- MCP `search_hr_data`만 새 경계로 전환하며 용어·정책 검색 도구는 기존 경로를 유지한다.
## 구조
## 전체 구성
```text
MCP tools/call
└─ bearer token 해시 인증
├─ ADMIN / DBMS_CLOUD_AI.GENERATE(..., 'showsql')
│ └─ 허용된 HMM HR 객체만 포함한 SELECT/WITH 생성
└─ CB_ORDS JDBC session
├─ CB_ORDS_HANDLER_PKG.SET_VPD_CONTEXT('Bearer ...')
│ └─ ADMIN.HMM_ACCESS_CTX_PKG.SET_USER_BY_BEARER(token)
├─ runtime principal/컨텍스트 일치 검증
├─ SET TRANSACTION READ ONLY
├─ 생성 SQL 실행 → HMM_LEAVE_SCOPE_POLICY 적용
├─ ROLLBACK
└─ CB_ORDS_HANDLER_PKG.CLEAR_VPD_CONTEXT
포털 사용자 preset
→ 사용자별 Bearer token
→ token hash로 EMPLOYEE_ID 확인
→ ADMIN Select AI SHOWSQL 생성
CB_ORDS 연결에서 SET_VPD_CONTEXT
→ 생성 SELECT 실행
→ HMM_CARRIER_ASSIGNMENTS_V VPD
├─ E1001 팀장: 직속 팀원 배정 8건
└─ E1002 팀원: 본인 배정 C001·C002 2건
→ 허용된 CARRIER_CODE만 원격 KPI와 조인
→ HTML renderer
```
## DB 설계
핵심은 token 소유자, DB context의 직원, VPD predicate가 동일한 요청 안에서 이어지는 것이다.
`SET_VPD_CONTEXT`를 호출해도 `EXEMPT ACCESS POLICY`를 가진 연결에서 SELECT하면 정책이 우회된다.
### 런타임 스키마
## 현재 상태
`CB_ORDS`에는 다음 최소 권한만 부여한다.
2026-08-10 운영 적용 및 MCP 호출 검증을 완료했다.
- `CREATE SESSION`
- `ADMIN.CB_ORDS_HANDLER_PKG` 실행
- Select AI 프로필의 승인 객체 6개에 대한 `SELECT`
- `HMM_ORG_TEAMS`
- `HMM_HR_EMPLOYEES`
- `HMM_LEAVE_BALANCES`
- `HMM_LEAVE_REQUESTS`
- `HMM_ATTENDANCE_DAILY`
- `HMM_HR_TERMS`
- 사용자별 portal token 5개가 서로 다른 hash로 직원에게 연결됨
- `HMM_CARRIER_SCOPE_POLICY` 활성화
- MCP E1001: `vpdEnforced=true`, 8건
- MCP E1002: `vpdEnforced=true`, 2건, 직원 범위 E1002만 포함
- E1002 renderer: 입력 2건, HTML에 C001·C002 포함, E1003 미포함
`EXEMPT ACCESS POLICY`, `SELECT ANY TABLE`, 객체 생성 `ANY` 권한은 부여하지 않는다.
생성 SQL의 스키마 한정 여부에 영향을 받지 않도록 승인 객체에 한해
`CB_ORDS` private synonym을 만든다.
## 문서 지도
### 컨텍스트 패키지
`ADMIN.CB_ORDS_HANDLER_PKG`는 HTTP Authorization 값에서 Bearer 토큰을
추출한 후 `ADMIN.HMM_ACCESS_CTX_PKG.SET_USER_BY_BEARER`를 호출한다.
토큰 원문은 테이블이나 로그에 저장하지 않는다.
`CB_ORDS`에는 동일 이름의 private synonym만 제공하여 런타임 SQL에서는
`CB_ORDS_HANDLER_PKG.SET_VPD_CONTEXT`로 호출한다. 컨텍스트 패키지 자체의
직접 실행 권한은 런타임 계정에 노출하지 않는다.
`CLEAR_VPD_CONTEXT`는 HMM 컨텍스트와 client identifier를 모두 정리한다.
유효하지 않은 인증 헤더는 컨텍스트를 먼저 지운 뒤 오류로 종료한다.
## 애플리케이션 설계
`backoffice.select-ai` 설정을 두 연결로 분리한다.
- 생성 연결: 기존 `db-url`, `db-username`, `db-password`, `profile`
- 실행 연결: `runtime-db-url`, `runtime-db-username`, `runtime-db-password`
생성 연결은 기존 `BACKOFFICE_DB_*`를 기본값으로 사용할 수 있다. 실행 연결은
명시적으로 설정해야 하며 생성 연결로 자동 폴백하지 않는다.
실행 전 다음을 검증한다.
1. `USER``ADMIN`이 아니다.
2. `SESSION_PRIVS``EXEMPT ACCESS POLICY`가 없다.
3. 패키지 호출 후 `HMM_ACCESS_CTX.EMPLOYEE_CODE`가 토큰 인증 결과와 일치한다.
하나라도 실패하면 SQL을 실행하지 않는다.
## 운영 설정
`search_hr_data``executionType``AGENT_TOOL`에서 `SELECT_AI`로 변경한다.
다른 두 도구는 그대로 유지한다.
필수 환경값:
```properties
BACKOFFICE_SELECT_AI_PROFILE=HMM_HR_DATA_GPT54_PROFILE
BACKOFFICE_SELECT_AI_RUNTIME_DB_URL=<ADB JDBC URL>
BACKOFFICE_SELECT_AI_RUNTIME_DB_USERNAME=CB_ORDS
BACKOFFICE_SELECT_AI_RUNTIME_DB_PASSWORD=<secret>
```
비밀번호는 Git·Redmine·로그에 기록하지 않고 서버 환경 파일에서만 관리한다.
## 검증 기준
1. `CB_ORDS``EXEMPT ACCESS POLICY`가 없음을 확인한다.
2. E1006 토큰으로 `HMM_LEAVE_BALANCES` 조회 결과의 직원은 E1006 한 명뿐이다.
3. E1006 토큰으로 `HMM_LEAVE_REQUESTS` 조회 결과에 E1002가 없다.
4. E1006이 E1002 휴가를 직접 요청하면 0건을 반환한다.
5. 관리자 E1001은 권한 규칙에 따라 자기 자신과 직속 팀원 범위를 조회한다.
6. 무효 토큰, 컨텍스트 불일치, 런타임 권한 오설정은 fail-closed 한다.
## 롤백
- 애플리케이션 환경의 `search_hr_data`를 이전 `AGENT_TOOL` 정의로 되돌리고
이전 JAR를 재기동한다.
- `CB_ORDS` 스키마는 즉시 삭제하지 않고 계정을 잠가 조사 가능 상태로 보존한다.
- VPD 정책 자체와 기존 `HMM_ACCESS_CTX_PKG`는 변경하지 않는다.
- [아키텍처와 신뢰 경계](architecture.md)
- [적용 및 검증 절차](cookbook.md)
- [문제 해결](troubleshooting.md)
- [HTML 리포트 후속 처리](../hmm-html-report-mcp/README.md)

View File

@@ -0,0 +1,41 @@
# HMM MCP VPD 아키텍처
[개요](README.md) · [적용 절차](cookbook.md) · [문제 해결](troubleshooting.md)
## 권한 흐름
```text
포털 사용자 preset
→ 사용자별 Bearer token
→ token hash로 EMPLOYEE_ID 식별
→ HMM_ACCESS_CTX.EMPLOYEE_ID 설정
→ Select AI가 만든 SELECT를 CB_ORDS에서 실행
→ HMM_CARRIER_ASSIGNMENTS_V VPD
├─ VIEWER: 본인 EMPLOYEE_ID
└─ MANAGER: 본인과 직속 팀원 EMPLOYEE_ID
→ 허용된 CARRIER_CODE만 원격 선사 실적과 조인
→ HTML renderer
```
선사 조회의 자연어 처리와 `CARRIER_CODE` 조인은 기존 Select AI 프로필을 유지한다. 권한 조건을
질문이나 고정 SQL에 넣지 않고 Oracle VPD가 세션 사용자 ID로 자동 적용한다.
## 신뢰 경계
- 화면에 표시된 사용자 코드는 권한 근거가 아니다. 선택 preset의 전용 Bearer token이 근거다.
- 토큰 원문은 서버 환경에만 저장하고 DB에는 SHA-256 hash만 저장한다.
- `ADMIN``EXEMPT ACCESS POLICY`가 있으므로 SQL 생성만 수행한다.
- 실제 SELECT는 비면제 계정 `CB_ORDS`가 같은 요청의 VPD context를 설정한 뒤 실행한다.
- renderer는 이미 필터링된 행만 표현하며 사용자나 권한을 다시 판단하지 않는다.
## 선사 VPD 규칙
보호 객체는 `ADMIN.HMM_CARRIER_ASSIGNMENTS_V`, 기준 컬럼은 `EMPLOYEE_ID`다.
| 역할 | 규칙 | 적용 범위 |
|---|---|---|
| `HMM_HR_VIEWER` | `SELF` | 현재 인증 사용자 |
| `HMM_HR_MANAGER` | `MANAGED_TEAM` | 현재 사용자와 직속 팀원 |
| `HMM_HR_ADMIN` | `ALL` | 명시적 관리자 전체 |
컨텍스트, 활성 역할 또는 허용 규칙이 없으면 `1=0`으로 차단한다.

View File

@@ -0,0 +1,25 @@
# HMM MCP VPD 적용 절차
[개요](README.md) · [아키텍처](architecture.md) · [문제 해결](troubleshooting.md)
1. `ADMIN`이 VPD 우회 권한을 가지고 `CB_ORDS`는 가지지 않는지 확인한다.
2. `database/adb/84_hmm_carrier_team_vpd.sql`을 ADMIN으로 실행한다.
3. 포털 preset마다 `HMM_MCP_BEARER_TOKEN_<EMPLOYEE_CODE>` 전용 토큰을 발급한다.
4. 선사 조회 MCP는 기존 `HMM_RDS_FEDERATION_PROFILE``SHOWSQL`을 생성하고 `CB_ORDS`에서 실행한다.
운영 도구 정의는 이름과 입력 계약을 유지하고 `executionType``SELECT_AI`로 설정한다.
5. 서비스를 재시작하고 동일 질문을 E1001과 E1002로 각각 호출한다.
성공 기준:
| 사용자 | 허용된 선사 배정 | 예상 건수 |
|---|---|---:|
| E1001 팀장 | 직속 팀원 C001~C008 | 8 |
| E1002 팀원 | 본인 C001, C002 | 2 |
첫 조회 결과, renderer 입력, HTML 표의 행 수와 직원 범위가 모두 같아야 한다. 토큰 원문과 DB
비밀번호는 명령·로그·문서에 출력하지 않는다.
MCP 응답에서 `vpdEnforced=true`, `scopeEmployeeCode`가 선택 사용자와 같은지도 확인한다.
롤백할 때는 VPD 정책을 삭제하지 말고 disable하여 조사 가능 상태로 보존한다. 사용자별 token
환경변수도 공용 token으로 되돌리지 않고 사용자 선택 기능을 일시 비활성화한다.

View File

@@ -0,0 +1,27 @@
# HMM MCP VPD 문제 해결
[개요](README.md) · [아키텍처](architecture.md) · [적용 절차](cookbook.md)
## 사용자를 바꿔도 같은 결과
- 원인: preset들이 같은 token 환경변수를 참조하거나 선사 View에 VPD가 없다.
- 확인: token 원문을 출력하지 않고 preset 환경변수 이름과 token hash의 employee mapping을 확인한다.
- 해결: 사용자별 token을 발급하고 `HMM_CARRIER_SCOPE_POLICY`를 활성화한다.
## 컨텍스트는 다른데 전체 행이 보임
- 원인: 최종 SELECT가 `EXEMPT ACCESS POLICY`를 가진 ADMIN에서 실행됐다.
- 확인: 실행 계정의 `SESSION_PRIVS`와 MCP 응답의 `scopeEmployeeCode`를 확인한다.
- 해결: ADMIN은 Select AI `SHOWSQL`만 수행하고 실제 SELECT는 `CB_ORDS`에서 실행한다.
## 팀원 결과가 0건
- 원인: token→직원, 직원→역할, 직원→선사 access group 중 하나가 누락됐다.
- 확인: 위 순서로 원장 연결을 확인한다.
- 해결: 누락된 원장만 보강한다. 조회 함수에 직원 코드나 결과 행을 하드코딩하지 않는다.
## 원격 선사 KPI가 과다 노출됨
- 원인: 생성 SQL이 로컬 `HMM_CARRIER_ASSIGNMENTS_V` 없이 원격 KPI View만 읽었다.
- 해결: Select AI profile metadata에서 직원·팀 질의는 배정 View와 KPI View를 `CARRIER_CODE`
조인하도록 유지한다. SQL 자체는 고정하지 않는다.