Files
vpd-permission-poc/docs/design/617-dds-mcp-end-user-context

설계서: DDS MCP END USER Context 및 런타임 권한 판정 (#617)

상태: Context 경로 Implemented · 런타임 권한 함수 설계 Approved, 이행 Pending 최종수정: 2026-07-03 추적성 — Redmine: #617 · 관련 ADR: ADR-0001, ADR-0002 · 현재 구현: DdsMcpBearerAuthenticator, DdsMcpEndUserResolver, DdsMcpContextExecutor, DdsMcpSseController · 기존 PoC 게시: sql/adb/44_dds_mcp_local_end_user_setup.sql · 검증: sql/adb/45_dds_mcp_local_end_user_test.sql, SSE tools/call

1. 결정과 목적

MCP Tool의 보호 SQL은 요청 Bearer가 지정한 업무 사용자의 local DDS END USER Context에서만 실행한다. OCI IAM Confidential Application은 이 Context attach를 승인하는 서비스 신원이며, 사람 사용자 계정을 IAM에 만들 필요는 없다.

업무 사용자·그룹·역할·퍼미션(CB_APP_USER, CB_USER_ROLE, CB_USER_GROUP, CB_GROUP_ROLE, CB_PERMISSION, 규칙·컬럼 테이블)은 권한의 단일 원천이다. DDS는 이 원천을 복제한 사용자별 Grant를 매 변경마다 재생성하는 대신, 보호 Object/행/컬럼 그룹별로 미리 만든 DATA GRANT가 DB 소유 권한 판정 함수를 호출하도록 한다.

따라서 역할·퍼미션 변경은 다음 SQL부터 반영되고 DDS DDL 재게시가 필요 없다. 새 보호 Object·새 컬럼·새 CRUD 동작을 등록하는 경우에만 안전하게 검토한 DDL을 한 번 provisioning한다.

2. 토큰과 사용자 — 반드시 구분할 것

역할 사람 사용자인가
MCP 요청 Bearer 해시·만료·회수 검증 후 CB_APP_USER를 결정 예. 업무 사용자 매핑 기준
local DDS END USER DDS_U_<CB_APP_USER.user_id> 예. DDS가 집행하는 보안 주체
OCI IAM database-access token Confidential Application의 client-credentials로 Context attach를 승인 아니오. 서비스 애플리케이션 신원
OCI IAM end-user token 향후 authorization-code/OBO 확장 현재 MCP 인증 입력으로 사용하지 않음

client-credentials token의 sub/client_id는 서비스 애플리케이션이다. 이를 업무 사용자로 쓰면 모든 요청이 같은 사람 권한으로 해석된다. MCP Bearer 검증 결과와 local END USER 매핑이 업무 사용자를 결정한다.

3. 목표 아키텍처

권한 원천 (같은 Oracle DB의 별도 관리 테이블)
  CB_APP_USER + 직접/그룹 역할 + CB_PERMISSION + RULE + COLUMN
                 ↑
                 │ 함수가 매 보호 SQL에서 읽음
  ADMIN.DDS_MCP_AUTHZ_PKG.can_access(...)
                 ↑
고정 DDS DATA GRANT ── Object/CRUD/컬럼 그룹별 1회 provisioning
  ON ADMIN.CUSTOMER
  AS SELECT (CUSTOMER_ID, CUSTOMER_NAME)
  WHERE can_access(ORA_END_USER_CONTEXT.username,
                   'ADMIN.CUSTOMER', 'SELECT', 'BASIC', ...) = 1
  TO DDS_MCP_RUNTIME
                 ↑
local END USER DDS_U_<id> ── GRANT DATA ROLE DDS_MCP_RUNTIME
                 ↑
MCP Bearer → CB_APP_USER → EndUserSecurityContext attach

object, action, column group은 DATA GRANT 정의에 넣는 검증된 상수다. DDS WHERE에 현재 조회 중인 Object 명을 자동 전달하는 내장 인자는 없다. 함수의 업무 사용자 인자는 클라이언트가 보낸 ID가 아니라 ORA_END_USER_CONTEXT.username이며, 함수가 CB_DDS_END_USER_MAP을 통해 실제 user_id로 해석한다.

4. 권한 모델: 동적 범위와 정적 범위

구분 결정 위치 권한 변경 시 DDL
업무 사용자 활성·역할·그룹·퍼미션 CB_* 권한 테이블, can_access/EXISTS 불필요 — 다음 쿼리부터 반영
행 범위: 본인·테넌트·부서·상태·태그 DATA GRANT WHERE와 Context/함수 인자 불필요
컬럼 노출/수정 Object·CRUD·컬럼 그룹별 DATA GRANT 퍼미션 값 변경은 불필요. 새 컬럼/새 그룹만 provisioning
Object·View·CRUD 등록 안전 검토한 DATA GRANT DDL 필요
local END USER 등록 CREATE END USER, 공통 DDS_MCP_RUNTIME Role 부여 신규 사용자 최초 1회

예를 들어 PHONE, EMAIL의 열 그룹에는 다음처럼 한 번만 Grant를 만든다.

CREATE DATA GRANT admin.customer_contact_read
  AS SELECT (phone, email)
  ON admin.customer
  WHERE admin.dds_mcp_authz_pkg.can_access(
          ORA_END_USER_CONTEXT.username,
          'ADMIN.CUSTOMER', 'SELECT', 'CONTACT', tenant_id, customer_type
        ) = 1
  TO dds_mcp_runtime;

권한 테이블에서 해당 사용자의 CONTACT 읽기 권한을 제거하면 함수가 0을 반환하므로 즉시 PHONEEMAIL은 노출되지 않는다. 같은 Object의 기본 컬럼은 별도 Grant로 정의한다. DDS Grant는 합산(additive)되므로 AS SELECT 같은 광범위 Grant를 공통 Role에 만들면 안 되며, 컬럼 그룹은 겹치지 않게 관리한다.

WHERE는 행별 true/false를 판단한다. 따라서 함수가 컬럼 목록을 반환하거나 클라이언트의 Object/컬럼 문자열로 동적 SQL을 만드는 구조는 사용하지 않는다. 함수는 고정 Object·컬럼 그룹의 권한과 행 조건만 판정한다.

5. 함수와 데이터 경계

DDS_MCP_AUTHZ_PKG.can_accessAUTHID DEFINER 패키지로 구현하고 다음을 지킨다.

  1. ORA_END_USER_CONTEXT.usernameCB_DDS_END_USER_MAP → 활성 CB_APP_USER를 해석한다. MCP 파라미터의 사용자 ID·테넌트 ID를 신뢰하지 않는다.
  2. 직접 역할과 활성 그룹 역할, ALLOW/DENY, Object·동작·컬럼 그룹·행 규칙을 같은 DB의 CB_* 테이블에서 평가한다.
  3. 미매핑, 비활성, 권한 없음, 예상 밖의 입력, 예외는 모두 0으로 처리한다. 예외를 허용으로 바꾸지 않는다.
  4. Function의 SQL은 bind 기반 정적 SQL만 사용한다. Object·동작·컬럼 그룹은 DATA GRANT에 적은 whitelist 상수로만 전달하며 동적 SQL을 만들지 않는다.
  5. 권한 테이블은 보호 대상 업무 Object와 분리한다. predicate가 대상 Object를 다시 조회하면 DDS 순환 predicate 오류가 발생한다.
  6. Grant owner에는 함수와 권한 테이블에 대한 필요한 직접 권한만 부여한다. MCP END USER에는 권한 테이블의 직접 조회·수정 권한을 주지 않는다.
  7. 즉시 반영이 목적이므로 함수에 DETERMINISTIC 또는 임의의 결과 캐시를 붙이지 않는다. 권한 테이블에는 사용자·역할·Object·동작 기준 인덱스를 둔다.

권한 테이블이 다른 DB나 DB link에만 존재하면 이 모델은 사용할 수 없다. DDS predicate는 원격 Object 참조를 지원하지 않는다. 이 경우에는 현행 게시형 동기화나, 동일 DB의 읽기 전용 권한 projection 테이블이 필요하다.

6. 전환 계획과 현재 구현의 위치

현재 PoC는 사용자별 DDS_U_<id>_ROLE 및 사용자별 벡터 DATA GRANT를 생성하고, 권한 변경 시 전체 재게시한다. Context attach·OCI IAM token·Bearer→사용자 매핑·fail-closed clear는 실증 완료된 기반이며 그대로 유지한다.

다음 이행에서 바꿀 부분은 권한 집행 계층이다.

  1. DDS_MCP_RUNTIME 공통 DATA ROLE과 CB_DDS_END_USER_MAP 기반 local END USER provisioning을 만든다.
  2. DDS_MCP_AUTHZ_PKG와 권한 테이블 인덱스를 만든다.
  3. 보호 Object/CRUD/컬럼 그룹별 장기 DATA GRANT를 생성한다. Object/컬럼 메타데이터 변경 때만 이 DDL을 변경한다.
  4. DdsMcpAuthorizationChangeListener의 사용자별 Grant bulk 재발행을 제거한다. 관리 변경은 권한 테이블 transaction commit만으로 즉시 반영한다.
  5. 기존 사용자별 Grant를 회수하고 dictionary·MCP regression으로 default deny, 컬럼 NULL, 행 필터, 권한 변경 직후 반영을 검증한다.

수동 publish는 권한 변경 반영 수단이 아니라 최초 provisioning, 스키마 변경, drift 복구와 검증 용도로 남긴다.

7. OCI IAM / ADB 전제조건

  1. ADB external authentication에 OCI_IAM 및 domain/application ID를 설정한다.
  2. OCI_IAM_DOMAIN_DB_CRED$에 Confidential Application 자격증명을 보관한다.
  3. pool account에 CREATE SESSION, CREATE END USER SECURITY CONTEXT를 부여하고 TLS wallet 연결을 사용한다.
  4. application identity를 IAM_OAUTH_CLIENT_ID=<client id>로 등록한다.
  5. application은 database resource scope의 client-credentials token을 얻는다.

DB는 resource_app_id, tenant_iss, audience와 scope를 OCI IAM/DB 설정과 비교한다. client ID, secret, raw token, lookup key는 문서·Git·Redmine·로그에 기록하지 않는다.

8. 검증 상태와 인수 기준

완료된 기반 검증 (2026-07-02)

  • SQLcl로 ADB 접속 및 local END USER, DATA ROLE, DATA GRANT catalog 생성 검증.
  • OCI IAM database-access token, database credential, application identity mapping 검증.
  • /dds/mcp/sse, tools/list, 실제 dds_vector_search의 Bearer → Context attach → 보호 query → clear HTTP 200 검증.

런타임 권한 함수 이행 인수 기준

  • CB_PERMISSION의 ALLOW/DENY 또는 사용자·그룹 역할을 commit하면 다음 MCP 쿼리 결과가 DDL 없이 바뀐다.
  • Object 권한 제거 시 결과는 default deny(행 없음 또는 접근 불가)다.
  • 컬럼 그룹 권한 제거 시 해당 컬럼만 NULL이고, 다른 승인 컬럼·행 범위는 유지된다.
  • 다른 사용자/테넌트/부서의 행은 함수 predicate를 통과하지 않는다.
  • 함수 예외, 매핑 누락, 비활성 사용자는 fail-closed다.
  • A→B→A 및 동시 pooled connection에서 Context와 권한이 섞이지 않는다.

9. 보안 불변식

  1. 유효한 MCP Bearer 하나는 정확히 하나의 활성 CB_APP_USER와 local DDS END USER로만 해석된다.
  2. Context attach 이전·clear 이후에는 보호 SQL을 실행하지 않는다.
  3. 서비스 token은 Context attach 승인용일 뿐 사용자 권한을 결정하지 않는다.
  4. 업무 권한 원천은 CB_* 테이블 하나이며 함수와 관리 UI 모두 이를 사용한다.
  5. 예외·미매핑·권한 없음은 항상 거부다.
  6. secret, raw bearer, database-access token, lookup key는 응답·로그·Git·Redmine에 기록하지 않는다.

10. 참고