refs #740: enforce HMM MCP VPD runtime boundary

This commit is contained in:
devmrko
2026-07-31 13:57:04 +09:00
parent af6add5a44
commit 2e44ed0b97
8 changed files with 468 additions and 26 deletions

View File

@@ -0,0 +1,123 @@
# #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 정책이
실제 조회에 적용되지 않는다.
컨텍스트 값이 올바른 것과 VPD가 적용되는 것은 별개의 조건이다. 보호 테이블을
읽는 최종 SQL은 반드시 `EXEMPT ACCESS POLICY`가 없는 계정의 동일 DB 세션에서
컨텍스트 설정과 함께 실행해야 한다.
## 목표
- `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
```
## DB 설계
### 런타임 스키마
`CB_ORDS`에는 다음 최소 권한만 부여한다.
- `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`
`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`는 변경하지 않는다.

View File

@@ -125,16 +125,35 @@ MCP의 표준 `Authorization` header는 하나이므로 “공통 gateway token
1. 백오피스에서 E1001, E1002 등 직원별 opaque token을 각각 발급한다.
2. MCP 서버는 그 직원 token 자체를 Bearer Token으로 검증한다.
3. 검증된 같은 token으로 DB 연결에서
`ADMIN.HMM_ACCESS_CTX_PKG.SET_USER_BY_BEARER(:token)`을 호출한다.
4. 같은 DB 세션에서 `HMM_LEAVE_BALANCES`, `HMM_LEAVE_REQUESTS`를 조회한다.
5. `finally`에서 context를 초기화하고 connection pool에 반환한다.
6. 서버 관리용 공통 토큰과 직원별 토큰을 동시에 요구해야 한다면 OAuth/API Gateway에서
3. `ADMIN` 연결은 Select AI `SHOWSQL` 생성까지만 수행한다.
4. `EXEMPT ACCESS POLICY`가 없는 `CB_ORDS` 연결에서
`CB_ORDS_HANDLER_PKG.SET_VPD_CONTEXT('Bearer ' || :token)`을 호출한다.
5. 같은 `CB_ORDS` DB 세션에서 생성된 읽기 전용 SQL로
`HMM_LEAVE_BALANCES`, `HMM_LEAVE_REQUESTS`를 조회한다.
6. 실행 전 런타임 사용자가 `ADMIN`이 아니고 `EXEMPT ACCESS POLICY`가 없으며,
`HMM_ACCESS_CTX.EMPLOYEE_CODE`가 토큰 인증 결과와 일치하는지 확인한다.
7. 하나라도 다르면 조회하지 않고 fail-closed 한다.
8. `finally`에서 `CB_ORDS_HANDLER_PKG.CLEAR_VPD_CONTEXT`와 rollback을 수행한다.
9. 서버 관리용 공통 토큰과 직원별 토큰을 동시에 요구해야 한다면 OAuth/API Gateway에서
application identity와 user subject를 하나의 검증 가능한 access token으로 합친다.
이 흐름은 2026-07-23 백오피스 MCP에 적용됐다. 모든 MCP method가 토큰 해시·만료·회수·재직
상태를 확인하며, Tool 호출은 같은 JDBC connection에서 `HMM_ACCESS_CTX_PKG.SET_USER_BY_BEARER`
후 실행하고 `finally`에서 context를 지운다. 무토큰·무효·회수 토큰은 HTTP 401이다.
이 흐름은 2026-07-31 백오피스 MCP `search_hr_data`에 적용됐다. 모든 MCP method가 토큰
해시·만료·회수·재직 상태를 확인한다. `search_hr_data`
`HMM_HR_DATA_GPT54_PROFILE`로 SQL만 생성하고, 실제 SQL은 위 `CB_ORDS` 세션에서 실행한다.
무토큰·무효·회수 토큰은 HTTP 401이다.
운영 환경에는 다음 값이 필요하다.
```dotenv
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>
```
DB 구성 스크립트는 `sql/adb/73_hmm_mcp_vpd_runtime.sql`이다. 이 스크립트는 승인된 HMM
HR 객체에 대한 개별 `SELECT`와 handler package 실행 권한만 부여하며
`EXEMPT ACCESS POLICY`는 부여하지 않는다.
포털의 `HMM_MCP_BEARER_TOKEN` 공용 preset은 별도 호환 gateway를 사용하는 기존 UI 라우팅이다.
Agent Factory의 사용자별 권한 검증에는 반드시 백오피스 MCP 주소와 직원 토큰을 사용한다.