[Developer] #617 apply DDS MCP end-user authorization

This commit is contained in:
devmrko
2026-07-02 09:34:58 +09:00
parent fd09622c82
commit ef1331be4b
53 changed files with 2040 additions and 337 deletions

View File

@@ -1,32 +1,21 @@
# ADR-0001: DDS MCP Context에는 개인 IAM 사용자 대신 서비스 애플리케이션을 사용한다
# ADR-0001: DDS MCP Context에는 개인 IAM 사용자 대신 Confidential Application을 사용한다
> **상태**: Proposed
> **날짜**: 2026-07-01 · **결정자**: [AI] Architect · **관련 이슈**: #617
> **상태**: Accepted · **날짜**: 2026-07-02 · **관련 이슈**: #617
## 맥락 (Context)
## 결정
MCP SSE 서비스는 자체 Bearer로 애플리케이션 사용자를 식별하고, local DDS END USER Context를 부착해 Tool SQL을 실행해야 한다. application-mediated DDS Context에는 database-access token이 필요하다.
OCI IAM Confidential Application의 client-credentials token을 database-access token으로 사용한다. MCP 요청자는 기존 opaque Bearer → `CB_APP_USER` 매핑으로 판정하고, 그 사용자를 local DDS `END USER`로 attach한다.
개인 IAM 사용자 `joungmin.ko@oracle.com`의 장기 token을 서비스 설정에 넣으면 개인 계정 수명주기, 감사, 회전, 퇴사, 권한 범위가 MCP 서비스의 가용성과 보안 경계에 직접 결합된다. 이 IAM 사용자는 OCI CLI를 통한 서비스 애플리케이션 등록의 관리자 역할로만 사용한다.
## 근거
## 결정 (Decision)
client-credentials token의 `client_id`/`sub`는 서비스 애플리케이션이다. 이를 업무 사용자로 사용하면 모든 MCP 요청이 같은 사람 권한으로 해석되는 오류가 생긴다. 서비스 승인과 업무 사용자 신원을 분리하면 기존 사용자·그룹·권한 관리 모델을 유지하면서 DDS가 사용자별 `DATA ROLE`/`DATA GRANT`를 집행한다.
`mcp-dds-service`라는 OCI IAM 서비스 애플리케이션을 별도로 등록하고, client-credentials flow로 short-lived database-access token을 취득한다. MCP 사용자는 IAM 사용자가 아니라 기존 `CB_APP_USER` 및 로컬 DDS END USER로 관리한다. client ID·client secret은 승인된 배포 환경의 secret store에만 저장하며 채팅·Redmine·Git에 남기지 않는다.
## 검증
이 결정은 P0 스파이크에서 실제 OCI tenancy/ADB 설정으로 Context attach가 증명되기 전까지 Proposed 상태다.
ADB OCI IAM 설정, database credential, application identity, TLS 연결을 구성하고 실제 `EndUserSecurityContext` attach/query/clear와 SSE MCP `tools/call` HTTP 200을 확인했다.
## 근거 (Rationale)
## 결과
서비스 신뢰와 업무 사용자 신원을 분리한다. database-access token은 서비스의 Context 부착 권한이고, DDS END USER는 DB가 DATA ROLE/DATA GRANT를 집행할 실제 권한 주체다.
## 결과 (Consequences)
- **긍정**: 개인 계정과 서비스 실행 권한을 분리하고, 사용자별 IAM 계정 없이 DDS Context를 적용할 수 있다.
- **부정 / 비용**: OCI IAM 애플리케이션 등록, token cache/rotation, TLS/DB identity 구성, secret 관리가 필요하다.
- **후속 작업**: Free Identity Domain entitlement, client-credentials database token, ADB local END USER Context attach를 스파이크로 검증한다.
## 검토한 대안 (Alternatives Considered)
- **개인 IAM 사용자 token 고정** — 개인 수명주기·감사·권한 회전에 서비스가 종속되어 기각.
- **MCP 사용자마다 IAM 사용자 생성** — ERP/기존 사용자 관리 모델을 IAM으로 중복 이전해야 하므로 기각.
- **IAM 없이 자체 Bearer만 사용** — application-mediated DDS Context attach에 필요한 database-access token을 제공하지 못하므로 기각.
- 개인 IAM 계정·장기 token은 런타임에 사용하지 않는다.
- client ID/secret, raw token, lookup key는 secret store/환경 변수 외에 저장하지 않는다.
- IAM 사용자 token을 직접 받아 DDS에 전달하는 OBO/authorization-code 모델은 별도 ADR과 endpoint 검증으로만 도입한다.

View File

@@ -1,231 +1,110 @@
# 설계서: DDS MCP 사용자별 END USER Context 및 권한 게시 (#617)
> **상태**: Draft — P0 스파이크 승인 전 구현 금지
> **작성**: [AI] Architect · **최종수정**: 2026-07-01
> **상태**: Implemented — OCI IAM·ADB·SSE 실증 완료
> **최종수정**: 2026-07-02
> **추적성** — Redmine: #617 · 관련 ADR: [ADR-0001](../../adr/0001-dds-mcp-service-identity.md)
> · 구현 파일: TBD · 테스트: TBD
> · 구현: `DdsMcpBearerAuthenticator`, `DdsMcpEndUserResolver`, `DdsMcpContextExecutor`, `DdsMcpSseController`
> · DB 게시: `sql/adb/44_dds_mcp_local_end_user_setup.sql`
> · 검증: `sql/adb/45_dds_mcp_local_end_user_test.sql`, SSE `tools/call`
## 1. 목적 (Why)
## 1. 결정과 목적
MCP SSE 서비스가 요청자의 권한을 대신 판단하거나 SQL에 조건을 덧붙이지 않고, 각 Tool의 DB 작업을 해당 요청자에 대응하는 Oracle Deep Data Security(DDS) END USER Context 실행한다.
MCP Tool의 보호 SQL은 요청 Bearer가 지정한 업무 사용자의 local DDS `END USER` Context에서만 실행한다. DDS가 `DATA ROLE``DATA GRANT`로 행·컬럼 접근을 집행하며, Tool은 권한 predicate를 직접 만들지 않는다.
VPD 트랙은 기존 구현과 데이터 모델을 변경하지 않는다. DDS 트랙은 별도의 보호 객체, 별도의 권한 게시 모델, 별도의 런타임 Context 경로를 갖는다.
VPD 경로는 변경하지 않는다. 기존 DDS 관리의 권한 게시 경계는 MCP용 END USER/역할/grant도 함께 갱신한다.
## 2. 범위 (Scope)
## 2. 토큰과 사용자 — 반드시 구분할 것
- **포함**
- MCP Bearer → `CB_APP_USER` → 로컬 DDS END USER의 명시적 매핑.
- MCP Tool 호출마다 DDS END USER Context를 부착·해제하는 실행 경계.
- 보호 데이터 객체, 접근 역할, 테이블 세부 권한, 사용자/그룹 역할 할당을 관리하는 DDS 전용 백오피스 모델.
- 역할·그룹의 유효 멤버십을 DDS `END USER` / `DATA ROLE` / `DATA GRANT` DDL로 게시·회수·검증하는 Publisher.
- OCI IAM 서비스 애플리케이션 database-access token과 local END USER lookup key의 기술 스파이크.
- **제외 (out of scope)**
- VPD 정책, VPD 백오피스, 기존 `CB_AGENT_CTX` 경로의 변경.
- MCP 사용자마다 OCI IAM 사용자를 생성·관리하는 기능.
- 사용자 요청 시점의 `CREATE END USER`, `GRANT DATA ROLE`, `CREATE DATA GRANT` 수행.
- 임의 SQL predicate 입력 및 기존 VPD `DENY 우선` 규칙의 자동 변환.
- OCI IAM 도메인/애플리케이션 등록의 실제 운영 배포. 스파이크 성공 후 별도 작업으로 분리한다.
| 값 | 현재 구현에서의 역할 | 사람 사용자인가 |
|---|---|---|
| MCP 요청 Bearer | `CB_AGENT_BEARER_KEY` 해시·만료·회수 검증 후 `CB_APP_USER`를 결정 | **예.** DB의 업무 사용자 매핑 기준 |
| local DDS END USER | `DDS_U_<CB_APP_USER.user_id>` | **예.** DDS가 집행하는 보안 주체 |
| OCI IAM database-access token | Confidential application의 client-credentials로 매 Tool Context attach를 승인 | 아니오. 서비스 애플리케이션 신원 |
| OCI IAM end-user token | 향후 authorization-code/OBO 전용 확장 | 현재 MCP 인증 입력으로 사용하지 않음 |
## 3. 인수조건 (Acceptance Criteria)
따라서 현재 MCP Bearer가 `CB_APP_USER`를 지정할 수 있으면 그 사용자별 DDS 권한은 적용된다. 반면 client-credentials 토큰의 `sub`/`client_id`는 서비스 애플리케이션이므로 사람 사용자를 판정하는 데 사용하면 안 된다.
- [ ] 활성 MCP Bearer는 정확히 하나의 활성 `CB_APP_USER`로 해석되며, DDS END USER 매핑이 없으면 Tool 실행 전에 권한 없음으로 종료된다.
- [ ] 동일한 공용 연결 풀에서 서로 다른 두 요청자가 연속·동시 Tool을 호출해도 각 요청은 자신의 DDS END USER Context와 DATA GRANT 결과만 받는다.
- [ ] 예외·취소·타임아웃을 포함한 모든 실행 경로에서 Context가 해제되어 다음 요청에 이전 권한이 남지 않는다.
- [ ] 백오피스 게시기는 사용자/그룹 역할 할당과 테이블 세부 권한을 idempotent DDS DDL로 반영하며, 게시 diff·사유·결과·드리프트를 보관한다.
- [ ] 그룹 멤버 추가/제거 후 게시하면 필요한 DDS END USER/DATA ROLE은 부여되고 불필요한 역할은 회수된다.
- [ ] MCP Tool SQL은 권한 `WHERE`를 직접 만들지 않으며, DDS DATA GRANT가 반환 행·컬럼을 제한한다.
- [ ] 미매핑·비활성·회수된 Bearer, 미게시 사용자, DATA GRANT 없는 사용자, Context attach 실패는 fail-closed로 처리한다.
- [ ] VPD 회귀 테스트가 통과하며 VPD 스키마·정책·UI에 변경이 없다.
OCI IAM JWT의 사람 사용자 claim을 그대로 DDS에 전달하려면 별도의 authorization-code 또는 OBO flow, 해당 end-user token 검증, IAM role↔DDS DATA ROLE 매핑으로 확장해야 한다. 이 경로는 현재 local END USER 모델과 혼용하지 않는다.
## 4. 컨텍스트 & 제약
- 기존 `CB_BEARER_TOKEN`/`CB_APP_USER`는 애플리케이션 논리 사용자 식별 수단이다. DDS 내장 인증 객체가 아니다.
- 로컬 DDS END USER는 `CREATE END USER`로 별도 생성한다. ERP/MCP 사용자는 IAM 사용자가 아니어도 되지만, DDS END USER는 사용자별로 존재한다.
- MCP SSE 서비스는 공용 DB 연결을 사용한다. DDS END USER Context는 SSE 연결 전체가 아니라 **매 Tool DB 실행 경계**마다 부착·해제한다.
- application-mediated local END USER Context에는 서비스 애플리케이션의 database-access token이 필요하다. 개인 IAM 사용자 토큰을 장기 설정값으로 저장하지 않는다.
- `joungmin.ko@oracle.com` IAM 사용자는 OCI CLI로 서비스 애플리케이션을 등록·관리하는 관리자일 뿐, MCP 실행 경로의 신원이나 token 발급 주체가 아니다.
- 런타임 신원은 `mcp-dds-service` IAM 애플리케이션이다. 이 앱의 client credentials로 short-lived database-access token을 발급받는다.
- IAM app client secret, database-access token, local END USER lookup key는 채팅·Redmine·Git에 기록하지 않는다. 배포 환경의 secret store 또는 환경 변수 참조만 허용한다.
- 서비스 애플리케이션의 OCI IAM 등록, token 발급, TLS/DB 설정, lookup key 발급은 P0 스파이크로 검증되기 전에는 구현 사실로 간주하지 않는다.
- DDS DATA GRANT는 additive다. 여러 DATA ROLE의 권한은 합집합이므로 VPD의 `DENY 우선` 의미를 자동 보존할 수 없다.
- END USER 이름은 ASCII 안정 식별자 `DDS_U_<CB_APP_USER.user_id>`를 사용한다. 표시 이름·한글 로그인명과 분리한다.
## 5. 아키텍처 개요
### 5.1 두 평면
## 3. 실제 아키텍처
```text
권한 게시 평면
백오피스
→ 보호 데이터 객체 / 접근 역할 / 세부 권한 / 사용자·그룹 할당
→ 유효 멤버십 계산
DDS Publisher
END USER · DATA ROLE · DATA GRANT DDL
→ 게시 이력 · 검증 · 드리프트 상태
권한 관리 / DDS 게시
CB_APP_USER + 직접/그룹 역할 + CB_PERMISSION 규칙
→ DDS Publisher
→ CB_DDS_END_USER_MAP
CREATE END USER DDS_U_<id>
CREATE/GRANT DATA ROLE DDS_U_<id>_ROLE
→ CREATE DATA GRANT (보호 벡터 객체)
요청 실행 평면
MCP Client Bearer
MCP SSE 인
CB_BEARER_TOKEN / CB_APP_USER 확인
DDS END USER 매핑 확인
서비스 IAM database-access token + lookup key로 Context 부착
MCP Tool DB 작업
DDS DATA GRANT 집행
Context 해제
MCP SSE Tool 호출
Authorization: Bearer <MCP bearer>
매 메시지마다 bearer 검
→ CB_APP_USER 확인
게시된 DDS_U_<id> map 확인
OCI IAM database-access token 취득/캐시
EndUserSecurityContext(name, lookup key) attach
보호 SQL 실행 (DATA GRANT 집행)
finally: context clear, 연결 반환
```
### 5.2 신원 분리
SSE 연결 자체는 인증을 보조할 뿐이다. `/dds/mcp/messages`**매 요청마다** Bearer와 사용자를 재검증하므로, 연결을 오래 유지해도 이전 사용자 권한을 신뢰하지 않는다.
| 항목 | 의미 | 관리 주체 |
## 4. 게시 모델과 운영 규칙
- 활성 `CB_APP_USER`마다 `CB_DDS_END_USER_MAP``DDS_U_<id>`, `DDS_U_<id>_ROLE`, grant 이름과 게시 상태를 기록한다.
- local END USER Context의 lookup key는 서비스 secret과 사용자 ID로 HMAC 파생한다. 원문/파생값은 DB·Git·Redmine·로그에 저장하지 않는다.
- 사용자의 직접 역할과 활성 그룹 역할에서 `CB_PERMISSION` / `CB_PERMISSION_RULE` / 허용 컬럼을 계산해 벡터 보호 객체의 `DATA GRANT`를 재생성한다.
- ALLOW가 없으면 grant를 제거하여 DDS 기본 거부 상태를 유지한다. 비활성 사용자는 data role과 grant를 회수한다.
- 사용자 활성화·직접 역할·그룹 멤버/역할·permission rule/허용 컬럼 변경은 같은 요청 안에서 활성 사용자 전체를 bulk 재발행한다. 수동 **DDS 관리의 게시 작업**은 전체 복구·재검증용으로도 제공한다.
- `withDataRoles(...)`는 local username+lookup-key Context에 사용하지 않는다. 역할은 게시된 local END USER grant에서만 활성화된다.
## 5. OCI IAM / ADB 전제조건
1. ADB external authentication을 `OCI_IAM`으로 등록하고 application ID와 domain URL을 설정한다.
2. `OCI_IAM_DOMAIN_DB_CRED$` credential에 Confidential application의 client ID/secret을 보관한다.
3. pool account에 `CREATE SESSION`, `CREATE END USER SECURITY CONTEXT`를 부여하고 TLS wallet 연결을 사용한다.
4. application identity를 `IAM_OAUTH_CLIENT_ID=<client id>`로 등록한다. 서비스 역할을 application identity에 부여할 경우 해당 역할만 활성화된다.
5. application은 database resource scope로 client-credentials token을 얻는다.
DB가 확인하는 token claim은 `resource_app_id`, `tenant_iss`, audience와 scope다. 이 값은 DB OCI IAM 설정 및 database resource registration과 일치해야 한다.
## 6. 구현 경계
| 컴포넌트 | 책임 | 실패 처리 |
|---|---|---|
| MCP Bearer | MCP 요청자가 어느 애플리케이션 사용자 인지 식별 | 기존 `CB_BEARER_TOKEN` |
| 애플리케이션 사용자 | 업무 사용자·그룹·역할 할당의 기준 | `CB_APP_USER` 및 DDS 전용 할당 모델 |
| DDS END USER | DB가 DATA ROLE/DATA GRANT를 집행하는 보안 주체 | DDS Publisher |
| Database-access token | MCP SSE 서비스가 Context를 부착할 수 있다는 애플리케이션 신뢰 | OCI IAM 서비스 애플리케이션 |
| `DdsMcpBearerAuthenticator` | MCP Bearer → 활성 `CB_APP_USER` | 인증 실패, SQL 미실행 |
| `DdsMcpEndUserResolver` | 사용자 → 게시된 DDS principal | 미매핑/미게시면 거부 |
| `DdsMcpDatabaseAccessTokenProvider` | OCI IAM service token 발급·만료 전 갱신 | Context 실행 차단 |
| `DdsMcpContextExecutor` | attach → 제한된 SQL → clear | clear 실패 시 연결 폐기 |
| `DdsMcpVectorSearchService` | Context 내부 보호 벡터 SQL만 실행 | DDS 오류를 안전한 MCP 오류로 변환 |
| `DdsMcpAuthorizationChangeListener` | 권한 변경 event → 전체 local END USER grant 재발행 | 요청을 실패로 알리고 게시 상태를 확인하게 함 |
| DDS Publisher | 기존 권한 모델 → local END USER/DATA ROLE/DATA GRANT | 부분 실패를 게시 실패로 기록 |
### 5.2.1 IAM 책임 경계
Tool/Repository는 raw Bearer, client secret, lookup key 또는 `setEndUserSecurityContext`를 직접 다루지 않는다.
```text
Provisioning (관리자 작업)
joungmin.ko@oracle.com IAM 사용자
→ OCI CLI
→ mcp-dds-service IAM 애플리케이션 등록
→ client ID / client secret을 승인된 secret store에 등록
## 7. 검증 결과 (2026-07-02)
Runtime (MCP SSE 서비스)
mcp-dds-service client credentials
→ short-lived database-access token
→ local DDS END USER Context 부착
```
- [x] SQLcl로 ADB 접속 및 local `END USER`, `DATA ROLE`, `DATA GRANT` catalog 생성 검증.
- [x] OCI IAM database-access token 발급 성공. `resource_app_id`, `tenant_iss`, audience `DDSDB`, scope `DB_ACCESS_SCOPE`가 DB 설정과 일치.
- [x] DB credential `OCI_IAM_DOMAIN_DB_CRED$`, OCI IAM application identity mapping 확인.
- [x] `/dds/mcp/sse` endpoint event 및 `tools/list` HTTP 200 확인.
- [x] `dds_vector_search` tool call: Bearer → application user 101 → local DDS Context attach → 보호 query 3행 반환 → context clear, HTTP 200.
- [ ] 사용자 A→B→A 및 동시 요청의 full isolation regression을 자동화한다.
- [ ] DDS 관리 UI publish가 MCP END USER map/grant를 같은 트랜잭션 경계에서 갱신하도록 통합한다.
MCP/ERP 업무 사용자는 IAM 사용자로 등록하지 않는다. 기존 Bearer가 식별한 `CB_APP_USER`를 local DDS END USER로 매핑한다.
SSE stream은 연결을 유지하므로 client read timeout이 날 수 있다. 이는 실패 판정 기준이 아니며, Tool RPC의 HTTP 결과와 attach/query/clear 로그로 성공을 판정한다.
### 5.3 실행 경계
## 8. 보안 불변식
`McpDdsContextExecutor`만 보호 객체용 연결을 획득한다. 이 경계는 다음 순서를 강제한다.
1. 유효한 MCP Bearer 하나는 정확히 하나의 활성 `CB_APP_USER`와 하나의 게시된 DDS END USER로만 해석된다.
2. Context attach 이전·clear 이후에는 보호 SQL을 실행하지 않는다.
3. attach/query/clear 어느 단계가 실패해도 fail-closed한다.
4. client-credentials token은 서비스 승인용이며 업무 사용자 권한 확대에 사용하지 않는다.
5. secret, raw bearer, database-access token, lookup key는 응답·로그·Git·Redmine에 기록하지 않는다.
1. Bearer를 재검증하고 활성 애플리케이션 사용자를 찾는다.
2. 게시 완료된 DDS END USER 매핑과 lookup key 참조를 찾는다.
3. 서비스 database-access token을 획득/갱신한다.
4. 연결에 DDS END USER Context를 부착한다.
5. Tool의 제한된 DB 작업을 실행한다.
6. `finally`에서 Context를 해제한 뒤 연결을 반환한다.
## 9. 참고
Tool·Repository는 Context 설정 API, DATA GRANT DDL, 원시 Bearer를 직접 다루지 않는다.
## 6. 데이터 모델
기존 VPD 공통 권한 테이블은 읽거나 변경하지 않는다. DDS 게시용 모델은 별도 관리한다.
| 개념 | 잠정 저장 구조 | 핵심 값 |
|---|---|---|
| 보호 데이터 객체 | `CB_DDS_PROTECTED_OBJECT` | object owner/name, Tool 노출명, 활성 상태 |
| 접근 역할 | `CB_DDS_ACCESS_ROLE` | 표시명, `DATA ROLE` 이름, 상태 |
| 테이블 세부 권한 | `CB_DDS_OBJECT_GRANT` | 역할, 객체, action, 허용/제외 컬럼, 행 조건 DSL |
| 역할 할당 | `CB_DDS_ROLE_ASSIGNMENT` | 역할, 대상 유형(USER/GROUP), 대상 ID |
| DDS END USER 매핑 | `CB_DDS_END_USER_MAP` | `CB_APP_USER.user_id`, DDS END USER 이름, 게시 상태, lookup key 참조 |
| 게시 실행 | `CB_DDS_PUBLISH_RUN` / 항목 | diff, 사유, 실행 결과, DDL fingerprint, 검증 결과 |
행 조건은 자유 SQL 문자열이 아니다. 객체별 허용 컬럼, 비교 연산자, 리터럴 타입, `ORA_END_USER_CONTEXT` 허용 경로만으로 구성된 DSL을 Publisher가 검증된 SQL predicate로 컴파일한다.
## 7. 함수 명세 (Function Specs)
| 함수 | 책임(1줄) | 시그니처(잠정) | 입력 | 출력 | 에러/실패 | 복잡? |
|---|---|---|---|---|---|---|
| `authenticateMcpBearer` | Bearer를 활성 업무 사용자로 해석 | `Bearer → AppUser` | Bearer | `AppUser` | 미존재·만료·회수 → deny | **복잡** |
| `resolveDdsEndUser` | 업무 사용자의 게시된 DDS END USER를 찾음 | `AppUser → DdsPrincipal` | 사용자 ID | END USER/lookup key 참조 | 미매핑·미게시 → deny | 단순 |
| `getDatabaseAccessToken` | 서비스용 short-lived DB access token을 제공 | `() → Token` | 서비스 IAM 설정 | token | 발급 실패 → fail closed | **복잡** |
| `withDdsEndUserContext` | Context를 부착·DB 작업·해제를 원자적으로 수행 | `(DdsPrincipal, SqlWork) → T` | DDS principal, 작업 | 결과 | attach/query/clear 실패 | **복잡** |
| `compileDdsPublishPlan` | 백오피스 권한 모델을 DDL diff로 변환 | `Draft → PublishPlan` | 객체/역할/할당 | DDL plan | DSL/충돌/deny 규칙 오류 | **복잡** |
| `publishDdsPlan` | 승인된 plan을 멱등 게시하고 검증 | `PublishPlan → PublishRun` | plan, 사유 | 결과/증적 | 부분 실패·drift | **복잡** |
| `verifyDdsIsolation` | 대표 사용자별 Context·객체·행·컬럼 결과를 확인 | `PublishRun → Verification` | 사용자 matrix | 검증 결과 | context 누수·권한 불일치 | **복잡** |
복잡 함수의 세부 명세는 다음 문서로 분리한다.
- [Bearer 인증](fn-authenticate-mcp-bearer.md)
- [서비스 database-access token](fn-get-database-access-token.md)
- [DDS Context 실행 경계](fn-with-dds-end-user-context.md)
- [DDS 게시 계획 컴파일](fn-compile-dds-publish-plan.md)
- [DDS 게시·검증](fn-publish-and-verify-dds-plan.md)
## 8. 흐름 / 알고리즘
### 8.1 권한 게시
1. 운영자가 보호 객체, 접근 역할, 세부 권한, 사용자/그룹 할당을 수정한다.
2. Publisher는 그룹을 포함한 역할별 유효 사용자를 계산한다.
3. 누락된 사용자에 대해 비밀번호 없는 DDS END USER 생성 계획을 만든다.
4. 역할별 DATA ROLE과 객체별 DATA GRANT의 생성·교체·회수 diff를 만든다.
5. 승인된 plan만 실행한다. 요청 실행 경로에서는 DDL을 실행하지 않는다.
6. 게시 후 END USER/ROLE/GRANT dictionary와 대표 사용자 matrix를 확인한다.
### 8.2 MCP Tool 실행
1. MCP 요청의 Bearer를 검증한다.
2. 사용자·DDS END USER 매핑·게시 상태를 확인한다.
3. Context 부착 전에 미매핑/비활성/회수 상태면 권한 없음으로 중단한다.
4. Tool의 DB 작업을 DDS Context 안에서 실행한다.
5. 성공·실패와 상관없이 Context를 제거한다.
6. MCP 응답에는 Tool이 반환한 DDS 제한 결과만 포함한다. END USER 이름, lookup key, database-access token은 노출하지 않는다.
## 9. 엣지케이스 & 에러 처리
| 상황 | 처리 |
|---|---|
| Bearer가 없거나 회수됨 | 인증 실패. DDS END USER 조회/부착을 시도하지 않는다. |
| 사용자 매핑 없음 | 권한 없음. 요청 중 END USER를 생성하지 않는다. |
| 사용자 비활성/게시 실패/drift | 권한 없음. 마지막 성공 게시 결과를 사용하지 않는다. |
| Context attach 실패 | Tool SQL을 실행하지 않는다. correlation ID만 기록한다. |
| Tool SQL 예외/취소/시간초과 | `finally`에서 Context 해제 후 안전한 오류 응답. |
| 연결 풀 재사용 | 매 DB 작업에 새 Context를 부착하고 `finally`에서 해제한다. |
| 그룹에서 사용자 제거 | 다음 게시에서 DATA ROLE 회수, 이후 요청은 새 게시 결과만 사용한다. |
| 비ASCII 업무 사용자명 | `DDS_U_<id>`를 사용하며 표시명과 END USER 식별자를 분리한다. |
| VPD DENY 규칙 | DDS 1차 모델에서 미지원. 게시 전 오류로 차단한다. |
## 10. 테스트 계획
| 인수조건 | 검증 |
|---|---|
| Context 사용자 격리 | 같은 공용 풀에서 A → B → A 순서와 동시 실행. `ORA_END_USER_CONTEXT.username` 및 결과 행/컬럼이 각 사용자와 일치. |
| Context 해제 | Tool 예외·취소·타임아웃 뒤 다음 요청이 이전 사용자의 Context를 갖지 않음. |
| 토큰 fail-closed | 미존재·만료·회수 Bearer와 미매핑 사용자 모두 SQL 실행 전 거부. |
| 역할/그룹 게시 | 직접 사용자·그룹 추가/제거 후 예상 DATA ROLE grant/revoke와 결과 확인. |
| 세부 권한 | 역할별 객체 차단, 행 조건, 컬럼 제외, 다중 역할 합집합을 검증. |
| DDL 멱등성 | 동일 plan 재게시가 변경 없음으로 완료. |
| 드리프트 | dictionary의 END USER/DATA ROLE/DATA GRANT를 의도적으로 변경한 뒤 탐지. |
| VPD 회귀 | 기존 VPD Maven/SQL/HTTP 회귀 테스트 전체 통과. |
| P0 스파이크 | OCI IAM 서비스 token + local END USER name/lookup key로 Context attach 성공 및 두 사용자 격리 증명. |
## 11. 리스크 & 대안 검토
### 선택: DDS 선언 게시 + MCP Context 부착
권한을 백오피스에서 검토·승인·게시하고, MCP Tool은 게시된 DDS 권한을 집행한다. Agent가 SQL을 만들더라도 DB가 결과를 제한한다.
### 대안: 기존 VPD 공통 권한 테이블을 요청마다 동적 해석
기존 VPD 모델과 즉시 반영 특성은 유지하지만, DDS DATA ROLE/DATA GRANT를 권한의 단일 출처로 쓰려는 목표와 맞지 않는다. VPD 트랙에 남긴다.
### 대안: 사용자별 IAM 계정
IAM token claim에서 DATA ROLE을 직접 활성화할 수 있으나 ERP/MCP 사용자 관리 주체를 IAM으로 옮겨야 한다. 이번 범위에서는 제외한다.
### 주요 리스크
- local END USER application-mediated Context에는 service IAM database-access token이 필요하다.
- lookup key 생성/보관 방식이 미확정이다.
- DDS 권한은 additive이므로 VPD DENY 우선 모델을 그대로 이식할 수 없다.
- Publisher의 partial failure는 사용자별 권한 불일치를 만들 수 있으므로 실행 plan, compensation, drift 탐지가 필요하다.
OCI IAM 서비스 애플리케이션의 도메인·token flow·비밀 보관 방식은 되돌리기 어려우므로 ADR로 분리한다.
## 12. 미해결 질문 (Open Questions)
1. local END USER security-context lookup key의 정확한 생성 API/명령, 회전·보관 기준은 무엇인가?
2. 현재 OCI tenancy의 Free Identity Domain에서 DDS용 application registration 및 client-credentials database-access token을 발급할 수 있는가?
3. ADB의 TLS, identity provider, application identity, pool account에 필요한 정확한 설정·권한은 무엇인가?
4. Java 26ai JDBC artifact/API와 현재 `dds-backoffice`의 OJDBC 버전·Spring pool이 Context attach를 지원하는가?
5. VPD의 DENY 규칙을 DDS 1차 모델에서 명시적으로 제외할지, 제한된 조합 규칙으로 컴파일할지?
6. Data Grant의 다중 역할 컬럼/행 합집합 의미를 실제 ADB에서 어떤 matrix로 검증할지?
이 여섯 항목은 P0 스파이크가 승인되기 전까지 임의 구현으로 채우지 않는다.
- [Oracle: Configure the Database for IAM Integration](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/configure-database-iam-integration.html)
- [Oracle: Prerequisites for Establishing a Local Security Context](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/prerequisites-establishing-local-security-context.html)
- [Oracle: End-User Security Context Issues](https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/end-user-security-context-issues.html)

View File

@@ -1,28 +1,23 @@
# 함수 명세: `authenticateMcpBearer` (#617)
> **상태**: Draft · **분류**: 복잡 — 인증 저장소 I/O 및 fail-closed 경계
> **상태**: Implemented · 구현: `DdsMcpBearerAuthenticator`
## 책임
MCP Tool 호출의 Bearer를 기존 애플리케이션 사용자로 해석한다. DDS 역할·DDL을 결정하지 않는다.
## 시그니처
`BearerToken -> AuthenticatedAppUser`
MCP Tool 요청의 opaque Bearer를 활성 `CB_APP_USER`로 해석하고 해당 사용자의 게시된 local DDS principal을 반환한다. OCI IAM client-credentials token을 업무 사용자 token으로 해석하지 않는다.
## 입력과 출력
- 입력: `Authorization: Bearer <opaque-token>`에서 추출한 원문 token.
- 출력: 활성 `CB_APP_USER``userId`, 표시명, token 식별자, 검증 시각.
- 입력: `Authorization: Bearer <opaque-token>`
- 출력: `applicationUserId`, 표시 사용자명, `DDS_U_<id>`, data role 이름, lookup-key 참조
## 규칙
1. token 형식·길이를 먼저 검증한다.
2. 해시 비교, 만료, 회수, 활성 사용자 여부를 한 트랜잭션에서 검증한다.
3. token이 없거나 하나의 활성 사용자로 해석되지 않으면 동일한 권한 없음 결과를 반환한다.
4. 원문 token, 해시, 사용자 상세를 로그·MCP 응답에 지 않는다.
5. 각 Tool 호출마다 재검증한다. SSE 연결 생성 시점의 결과를 재사용하지 않는다.
1. `CB_AGENT_BEARER_KEY`의 해시 일치, 만료·회수 여부와 `CB_APP_USER.active`를 매 Tool 요청마다 확인한다.
2. `CB_DDS_END_USER_MAP`에서 `PUBLISHED` 상태의 사용자 매핑을 반드시 찾는다.
3. 하나라도 없으면 `AUTHORIZATION_DENIED`로 끝내며 context attach 또는 보호 SQL을 호출하지 않는다.
4. raw bearer와 hash는 로그·MCP 응답에 노출하지 않는다.
## 실패
## 주의
`AUTHORIZATION_DENIED`로 fail-closed한다. DDS Context 부착과 DB Tool SQL을 호출하지 않는다.
현재 opaque Bearer의 사용자 매핑이 DDS 집행 주체를 결정한다. OCI IAM JWT의 사람 claim을 직접 사용하는 기능은 별도 OBO/authorization-code 확장이다.

View File

@@ -1,28 +1,18 @@
# 함수 명세: `getDatabaseAccessToken` (#617)
> **상태**: Draft · **분류**: 복잡 — OCI IAM 외부 I/O·비밀·만료 관리
> **상태**: Implemented · 구현: `DdsMcpDatabaseAccessTokenProvider`
## 책임
MCP SSE 서비스 애플리케이션의 short-lived database-access token을 제공한다. MCP 사용자 Bearer와 혼동하거나 대체하지 않는다.
## 시그니처
`() -> DatabaseAccessToken`
OCI IAM Confidential Application의 client-credentials token을 database-access token으로 발급·캐시한다. 이 token은 local DDS Context attach의 서비스 승인용이며 MCP 업무 사용자를 식별하지 않는다.
## 규칙
1. 개인 IAM 사용자(`joungmin.ko@oracle.com`)의 장기 token을 사용하지 않는다.
2. `mcp-dds-service` 전용 OCI IAM 애플리케이션의 client-credentials flow를 사용한다.
3. access token은 만료 전 안전 여유를 두고 갱신하며, 원문을 로그·DB·Redmine에 저장하지 않는다.
4. 토큰 발급 실패 시 이전 만료 token을 재사용하지 않고 Tool DB 작업을 차단한다.
5. secret은 배포 환경의 secret store만 사용한다.
1. database resource scope로 `/oauth2/v1/token`을 호출한다.
2. 만료 전 refresh skew를 두고 갱신한다.
3. token 발급 실패 또는 응답 불완전 시 기존 만료 token을 사용하지 않고 `DDS_CONTEXT_UNAVAILABLE`로 fail-closed한다.
4. token, client secret, Authorization header는 로그·DB·Git·Redmine에 기록하지 않는다.
## 스파이크 확인
## 운영 검증
- OCI Identity Domain의 무료/현재 entitlement에서 database-access token 발급이 가능한지 확인한다.
- ADB의 identity provider, TLS, pool account, application identity 조건을 실제 연결로 확인한다.
## 실패
`DDS_CONTEXT_UNAVAILABLE`로 fail-closed한다.
발급 token의 `resource_app_id`/`tenant_iss`/audience/scope가 ADB OCI IAM 설정 및 database resource와 일치해야 한다. ADB의 `OCI_IAM_DOMAIN_DB_CRED$`도 필수다.

View File

@@ -1,36 +1,25 @@
# 함수 명세: `withDdsEndUserContext` (#617)
> **상태**: Draft · **분류**: 복잡 — 연결 풀 보안 경계
> **상태**: Implemented · 구현: `DdsMcpContextExecutor`
## 책임
한 DB 작업을 정확히 하나의 local DDS END USER Context 안에서 실행하고, 어떤 종료 경로에서도 Context를 해제한다.
## 시그니처
`(DdsPrincipal, SqlWork<T>) -> T`
## 입력
- `DdsPrincipal`: 게시된 DDS END USER 이름, lookup key 참조, 사용자 ID, 게시 version.
- `SqlWork<T>`: 보호 객체를 읽거나 쓰는 제한된 Tool DB 작업.
Tool DB 작업을 정확히 하나의 local DDS END USER Context 안에서 실행하고 항상 해제한다.
## 알고리즘
1. `DdsPrincipal`이 활성·게시 완료 상태인지 확인한다.
2. 공용 연결을 획득하고 서비스 database-access token을 얻다.
3. local END USER 이름과 lookup key로 DDS Context를 부착한다.
4. `SqlWork`를 실행한다. Repository는 Context 설정/해제를 직접 호출할 수 없다.
5. 성공·실패·취소·시간초과와 무관하게 `finally`에서 Context를 해제한다.
6. 해제 실패는 연결을 풀에 반환하지 않고 폐기한다.
1. 게시된 DDS principal을 확인한다.
2. OCI IAM database-access token을 얻고 공용 풀에서 연결을 획득한다.
3. `EndUserSecurityContext.createWithName(databaseAccessToken, endUserName, lookupKey)`를 만든다.
4. `setEndUserSecurityContext` 후 보호 SQL을 실행한다.
5. `finally`에서 `clearEndUserSecurityContext` 후 연결을 반환한다. clear 실패 연결은 폐기한다.
## 보안 불변식
## 불변식
- 하나의 Tool DB 작업에는 하나의 DDS END USER만 존재한다.
- 이전 Tool의 Context가 다음 Tool에 남아서는 안 된다.
- Context 부착 이전과 해제 이후에는 보호 SQL을 실행하지 않는다.
- local END USER의 DATA ROLE은 payload로 임의 추가하지 않고, 게시된 DDS DDL에서만 얻는다.
- local username+lookup-key Context에는 `withDataRoles(...)`를 전달하지 않는다.
- local END USER에 미리 `GRANT DATA ROLE`된 역할과 `DATA GRANT`만 적용한다.
- 연결 풀 재사용 시에도 이전 사용자 Context가 남지 않아야 한다.
## 스파이크 확인
## 실증
동일 풀 연결에서 사용자 A → B → A와 동시 요청을 실행해 `ORA_END_USER_CONTEXT.username`, 행, 컬럼이 각각 맞는지 확인한다.
실제 ADB에서 local END USER attach, 보호 벡터 query 3행, context clear까지 완료했다. 동시 사용자 isolation regression은 후속 자동화 대상이다.