Files
vpd-permission-poc/docs/design/744-windows-ad-dds-end-user-mapping

설계서: Windows AD 기반 DDS END USER 매핑 테스트 (#744)

상태: OIDC JWT → MCP → OCI IAM → DDS context 및 Alice/Bob 권한 차이 검증 완료 최종수정: 2026-08-04
추적성: Redmine #744 · 선행 설계: DDS MCP END USER Context

1. 목적과 범위

Microsoft Entra ID 테넌트가 아직 준비되지 않은 상황에서, OCI 격리 네트워크의 Windows Server AD DS를 이용해 업무 사용자 디렉터리와 DDS END USER 매핑을 먼저 검증한다.

이 환경은 Microsoft Entra access token을 Oracle DB가 직접 검증하는 환경이 아니다. AD DS는 사용자·그룹·UPN을 제공하고, 이후 Keycloak 또는 별도 검증 서비스가 AD LDAP을 기반으로 발급·검증한 토큰의 안정적인 subject를 MCP의 업무 사용자로 해석한다. Entra tenant와 app registration이 준비되면 OIDC issuer/audience/JWKS 검증 경로로 교체 또는 병행한다.

2. 격리 인프라

항목 구성
네트워크 handson-vcn의 전용 10.0.2.0/28 public subnet
Windows VM hmm-ad-dss-test-isolated, Windows Server 2022, 2 OCPU / 16 GB
관리 접근 RDP TCP/3389은 작업자 공인 IP 한 곳에만 NSG로 허용
OIDC HTTPS https://ad.cloud-handson.com (Caddy TLS reverse proxy → Keycloak)
서브넷 보안 목록 인바운드 전부 차단, Windows Update 및 테스트용 아웃바운드만 허용
도메인 dds.test (AD DS forest/domain)

이 VM은 테스트 전용이다. 기존 공유 public subnet의 NAT 경로를 변경하지 않으며, 비용 발생 리소스이므로 검증 종료 뒤 중지 또는 삭제를 결정한다.

3. 식별자와 권한 흐름

AD 사용자 (UPN: alice@dds.test, objectGUID)
          │ LDAP / OIDC bridge
          ▼
검증된 Bearer claims
  iss, aud, exp, sub = 불변 외부 subject
          │ MCP bearer authenticator
          ▼
CB_EXTERNAL_IDENTITY_BINDING
  issuer + subject -> CB_APP_USER.user_id
          ▼
CB_DDS_END_USER_MAP
  user_id -> DDS_U_<user_id> local END USER
          ▼
ORA_END_USER_CONTEXT + DDS DATA GRANT / 권한 함수

sub는 Keycloak이 발급하는 안정적인 federated-user 식별자다. AD objectGUID는 원천 디렉터리의 감사 식별자로 유지하되, 실제 권한 키는 **검증된 JWT의 iss + sub**로 고정한다. UPN은 로그인 화면·감사 표시용으로 보관할 수 있지만 권한 키로 신뢰하지 않는다. iss + sub 조합이 유일하지 않거나, 매핑이 없거나, 사용자가 비활성이면 요청은 fail-closed로 거부한다.

사용자 인증과 DB 접속 신뢰의 분리

이 PoC에는 서로 다른 두 신뢰 체인이 있다. 둘을 같은 OAuth token으로 혼동하지 않는다.

[업무 사용자 인증]
AD user -> Keycloak (AD LDAP 인증) -> Keycloak JWT
        -> MCP validates iss + sub -> DDS_U_1 / DDS_U_2 선택

[DB 접속 신뢰]
MCP service -> OCI IAM DDS_MCP_SERVICE_TEST (client credentials)
            -> database-access token -> Oracle DB
            -> 선택된 local DDS END USER context attach
역할 현재 담당 하는 일
AD 사용자 인증 Keycloak AD LDAP의 계정·비밀번호를 확인하고 Keycloak JWT를 발급
업무 사용자 식별 MCP 검증된 Keycloak iss + sub를 HMM application user와 local DDS END USER에 매핑
DB 접속 애플리케이션 신뢰 OCI IAM credential app MCP service가 database-access token을 받아 Oracle DB에 신뢰된 application임을 증명
최종 데이터 권한 Oracle Deep Sec attach된 local DDS END USER의 DATA ROLE과 DATA GRANT로 행·열을 제한

따라서 OCI IAM credential app은 현재 AD 사용자를 직접 로그인시키지 않는다. OCI IAM은 MCP service의 database-access token 발급자이며, Oracle DB가 해당 service client를 application identity로 신뢰하게 한다. AD 사용자가 어떤 DDS END USER가 되는지는 MCP가 결정한다.

향후 OCI IAM을 사용자 인증의 중심으로 바꾸려면 Keycloak을 OCI IAM의 외부 IdP(OIDC 또는 SAML)로 federation하고, AIPF/MCP가 OCI IAM user token을 받도록 변경한다. 그 경우 Oracle DB도 OCI IAM user token의 issuer·group claim을 직접 검증할 수 있다. 현재 PoC의 local DDS END USER 매핑과는 별도의 확장 경로다.

4. 구현 단계

  1. Windows Server에 AD DS를 설치하고 dds.test forest를 생성한다.
  2. 테스트 사용자 dds-alice, dds-bob와 권한 그룹을 만들고, LDAP 조회와 인증을 확인한다.
  3. AD LDAP과 연동한 OIDC bridge(예: Keycloak)를 별도 서비스로 구성한다. bridge가 내는 JWT의 iss, aud, exp, JWKS 서명을 MCP가 검증한다.
  4. issuer + sub에서 CB_APP_USERCB_DDS_END_USER_MAP을 해석하도록 백오피스/DB 매핑을 추가한다.
  5. alice/bob의 서로 다른 DDS_U_* context에서 동일 MCP tool 호출 결과가 권한에 따라 달라지는지 검증한다.
  6. 매핑 누락, 만료 토큰, 다른 issuer/audience, 비활성 계정은 모두 거부되는 regression을 추가한다.

4.1 현재 테스트 bridge

database/adb/47_dds_ad_identity_test_setup.sql은 HMM DB의 기존 업무 사용자 원천을 변경하지 않는다. 스크립트가 만든 CB_EXTERNAL_IDENTITY_BINDING에는 최종 OIDC issuer와 Keycloak이 실제로 발급한 subject만 보관한다.

AD 사용자 OIDC issuer JWT sub HMM application user DDS END USER
dds-alice https://ad.cloud-handson.com/realms/dds-test 11cf09c9-4713-4145-85cd-e3397d4a2405 1 DDS_U_1
dds-bob https://ad.cloud-handson.com/realms/dds-test d9610e18-a327-4dda-9496-69e5a2067460 2 DDS_U_2

dds-mcp client는 access token audience에 dds-mcp를 포함한다. MCP는 Keycloak JWKS에서 JWT 서명과 iss, aud, 만료 시간을 검증한 뒤에만 위 매핑을 조회한다. 실제 /dds/mcp/messages 초기화 요청에 Alice JWT를 넣어 성공 응답을 확인했다.

초기 게시 단계에서는 END USER와 DATA ROLE만 만들고 default deny로 시작한다. 현재 테스트에서는 Alice에만 vector view SELECT data grant를 추가했고 Bob은 grant 없이 유지한다.

4.2 AIPF MCP 등록 값

AIPF의 Edit MCP server 화면에서 다음 값으로 등록한다. AIPF는 confidential OAuth client를 요구하므로, 일반 테스트용 public client dds-mcp가 아니라 AIPF 전용 client aipf-dds-mcp를 사용한다.

AIPF 항목 입력값
Server name alice-dds (Bob 검증 때는 bob-dds처럼 별도 등록)
Server URL https://hmm-backoffice.cloud-handson.com/mcp/dds/sse
Authentication mode OAuth
Redirect callback URL AIPF 표시값 https://aipf.cloud-handson.com/agentFactory/v1/tools/mcp/callback — 입력하지 않는 읽기 전용 값
OAuth client ID aipf-dds-mcp
OAuth client secret 운영자 보관 secret. 저장소·문서·대화에 기록하지 않는다. 현재 작업 환경에서는 .runtime/aipf-dds-mcp-client.envOAUTH_CLIENT_SECRET 값을 사용한다.
Authorization URL https://ad.cloud-handson.com/realms/dds-test/protocol/openid-connect/auth
Token URL https://ad.cloud-handson.com/realms/dds-test/protocol/openid-connect/token
Refresh URL https://ad.cloud-handson.com/realms/dds-test/protocol/openid-connect/token
Scopes openid profile email

OAuth client secret을 얻는 방법

여기서 필요한 secret은 AD 사용자 비밀번호가 아니라 Keycloak의 aipf-dds-mcp OAuth client secret이다. secret은 저장소에 커밋하거나 문서·대화에 적지 않는다. 현재 작업 환경에서는 권한 제한 파일에 보관하며, 다음 명령으로 값을 화면에 표시하지 않고 macOS 클립보드에 복사한다.

sed -n 's/^OAUTH_CLIENT_SECRET=//p' .runtime/aipf-dds-mcp-client.env | pbcopy

명령 출력이 없는 것이 정상이다. 이후 AIPF의 OAuth client secret 입력칸에 ⌘V로 붙여넣고 저장한다. 복사 명령은 pbcopy이며 pbcop가 아니다. 파일의 secret 값은 Keycloak에서 OAuth client를 재생성하거나 rotation한 경우에만 다시 동기화한다.

AIPF callback 호환 보정

현재 AIPF는 화면에 표시하는 callback과 달리 OAuth authorization request에는 /agentFactory가 빠진 https://aipf.cloud-handson.com/v1/tools/mcp/callback을 보낸다. AIPF Nginx는 이 경로를 같은 origin의 실제 callback으로 302 전환하도록 구성했다.

/v1/tools/mcp/callback
  -> /agentFactory/v1/tools/mcp/callback

이 전환은 OAuth codestate를 보존하고 브라우저가 /agentFactory 범위의 AIPF 세션 쿠키를 다시 전송하게 한다. AIPF의 OAuth callback 생성 로직이 수정되면 이 Nginx 보정은 제거할 수 있다.

4.3 테스트 시나리오

A. Alice 인증과 MCP 연결

  1. AIPF에서 위 값으로 alice-dds를 저장하고 연결한다.

  2. Keycloak 로그인 화면에서 AD 사용자 dds-alice로 로그인한다. 도메인 표기가 필요하면 DDS\\dds-alice를 사용한다.

  3. AIPF가 callback으로 돌아오면 alice-dds MCP 연결이 완료된다.

  4. AIPF Agent 대화에서 다음 요청을 실행한다.

    dds_vector_search 도구를 사용해서 "휴가"를 검색해줘.
    embeddingMode는 DEMO, limit은 10으로 해줘.
    
  5. MCP는 Keycloak access token의 검증된 iss + sub를 Alice의 application user와 DDS_U_1에 매핑한 뒤 dds_vector_search를 호출한다.

B. Bob 권한 비교

  1. AIPF OAuth 연결을 끊거나 새 MCP 등록 bob-dds를 만든다.
  2. 같은 OAuth 설정으로 연결한 뒤 AD 사용자 dds-bob으로 로그인한다.
  3. Alice와 동일한 검색 요청을 실행한다.
  4. DDS data grant 구성 후 기대 결과는 다음과 같다.
사용자 JWT subject 매핑 기대 결과
dds-alice application user 1DDS_U_1 허용된 지식 행만 반환
dds-bob application user 2DDS_U_2 빈 결과 또는 DDS 권한 거부

C. 판정 기준과 현 상태

확인 항목 판정 방법 현재 상태
OAuth 로그인 AIPF가 Keycloak 로그인 후 MCP 등록 화면으로 복귀 확인 완료
JWT 검증/매핑 tools/list 또는 dds_vector_search가 Bearer 토큰 검증을 통과 확인 완료
Alice/Bob 데이터 차이 같은 dds_vector_search 호출에서 반환 행이 다름 대기 — OCI IAM DB service credential 및 DDS DATA GRANT 필요
미매핑 토큰 거부 매핑 없는 iss + sub의 MCP 호출이 AUTHORIZATION_DENIED 구현 완료, 회귀 검증 대기

dds_vector_search는 현재 유일한 DDS MCP 도구이며 입력값은 query(필수), limit(1~100), embeddingMode(DEMO 또는 AI)다. service identity와 Alice 허용/Bob 거부 DDS grant를 반영해 권한 차이까지 검증했다.

4.4 OCI IAM database-access token 구성

Keycloak access token은 MCP의 업무 사용자 식별과 iss + sub 매핑에만 사용한다. Oracle Deep Sec context를 열 때는 별도의 OCI IAM database-access token이 필요하다. 이 토큰은 DDS_MCP_SERVICE_TEST confidential client가 client credentials flow로 받고, DB는 해당 client ID를 application identity로 신뢰한다.

AD user -> Keycloak token -> MCP user mapping -> local DDS END USER
                                           \
OCI IAM DDS_MCP_SERVICE_TEST -- database-access token --> Oracle Deep Sec context

구성 순서는 OCI IAM database resource(DDS_ORACLE_DB_TEST)와 scope(DB_ACCESS_SCOPE) 생성, OCI IAM service client 생성, Autonomous DB의 OCI IAM identity provider/credential 등록, 그리고 CREATE APPLICATION IDENTITY ... MAPPED TO 'IAM_OAUTH_CLIENT_ID=...' 순서다. 이 단계는 Autonomous DB의 외부 인증 구성을 변경할 수 있으므로 기존 설정을 먼저 조회하고 Redmine에 기록한다.

적용 결과와 검증

구성 요소 적용값 상태
OCI IAM database resource DDS_ORACLE_DB_TEST / DB_ACCESS_SCOPE 적용 완료
OCI IAM service client DDS_MCP_SERVICE_TEST, client credentials만 허용 적용 완료
Autonomous DB identity provider OCI_IAM, database resource app ID와 OCI IAM domain URL 등록 적용 완료
DB signing-key credential OCI_IAM_DOMAIN_DB_CRED$ 적용 완료
DB application identity DDS_MCP_SERVICE_TEST → OCI IAM service client ID 적용 완료
HMM DDS MCP runtime client ID·secret·scope를 권한 제한 환경 파일로 로드 적용 완료
Alice data grant DDS_U_1_ROLECB_VECTOR_SEARCH_DOCUMENTS SELECT 적용 완료
Bob data grant 없음 default deny

database-access token은 resource_app_id, tenant_iss, scope가 DB identity provider 등록과 모두 일치해야 한다. OCI IAM domain URL은 token의 issuer와 같은 정규형(:443 포함)을 사용했다. 포트가 빠진 URL로 등록하면 Oracle이 ORA-52602(invalid database access token)으로 context 사용을 거부한다.

2026-08-04 검증 결과는 다음과 같다. 동일한 dds_vector_search 호출에서 Alice는 DDS context attach 후 query가 성공했고, Bob은 data grant가 없어 보호 객체 조회가 거부됐다. 현재 HMM knowledge chunk 데이터가 없으므로 Alice의 성공 응답 행 수는 0이다. 이는 권한 허용과 데이터 존재 여부를 구분한 결과다.

이후 권한 차이를 눈으로 확인할 수 있도록 DDS_AD_ALICE_LEAVE_001 테스트 문서와 DDS_AD_TEST 태그를 HMM knowledge 원천에 추가했다. 같은 휴가 검색에서 Alice는 이 문서 1건을 반환하고 Bob은 ORA-00942 기반 default deny로 거부되는 것을 확인했다.

5. Entra ID로 전환할 때

Entra tenant가 확보되면 AD DS/bridge 테스트에서 확인한 issuer + immutable subject -> CB_APP_USER -> DDS END USER 계약은 유지한다. 바뀌는 부분은 token issuer와 JWKS 검증 설정뿐이다. Entra의 claim 이름(oid, sub, preferred_username 등)은 실제 발급 토큰을 확인한 뒤 결정하며, sub 단독이 아니라 tenant/issuer 경계를 반드시 포함한다.

6. 완료 기준

  • dds.test AD forest와 두 테스트 사용자가 생성되었다.
  • Keycloak LDAP bridge가 HTTPS OIDC JWT를 발급하고 각 사용자가 서로 다른 안정 subject를 가진다.
  • MCP가 JWT signature/issuer/audience를 검증하고 issuer + sub 매핑을 해석한다.
  • subject 매핑을 통해 각 요청에 대응하는 DDS END USER context만 attach된다.
  • 서로 다른 권한의 동일 MCP 호출에서 데이터 행/열 결과가 달라진다.
  • 미매핑·만료·issuer/audience 불일치 요청은 데이터 접근 전에 거부된다.
  • 종료 시 테스트 VM과 전용 네트워크 리소스의 정리 여부 및 비용 상태를 Redmine에 기록한다.