204 lines
14 KiB
Markdown
204 lines
14 KiB
Markdown
# 설계서: 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](../617-dds-mcp-end-user-context/README.md)
|
|
|
|
## 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. 식별자와 권한 흐름
|
|
|
|
```text
|
|
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으로 혼동하지 않는다.
|
|
|
|
```text
|
|
[업무 사용자 인증]
|
|
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_USER` 및 `CB_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.env`의 `OAUTH_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` |
|
|
|
|
### AIPF callback 호환 보정
|
|
|
|
현재 AIPF는 화면에 표시하는 callback과 달리 OAuth authorization request에는 `/agentFactory`가 빠진 `https://aipf.cloud-handson.com/v1/tools/mcp/callback`을 보낸다. AIPF Nginx는 이 경로를 같은 origin의 실제 callback으로 302 전환하도록 구성했다.
|
|
|
|
```text
|
|
/v1/tools/mcp/callback
|
|
-> /agentFactory/v1/tools/mcp/callback
|
|
```
|
|
|
|
이 전환은 OAuth `code`와 `state`를 보존하고 브라우저가 `/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 대화에서 다음 요청을 실행한다.
|
|
|
|
```text
|
|
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 `1` → `DDS_U_1` | 허용된 지식 행만 반환 |
|
|
| `dds-bob` | application user `2` → `DDS_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로 신뢰한다.
|
|
|
|
```text
|
|
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_ROLE` → `CB_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`이다. 이는 권한 허용과 데이터 존재 여부를 구분한 결과다.
|
|
|
|
## 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. 완료 기준
|
|
|
|
- [x] `dds.test` AD forest와 두 테스트 사용자가 생성되었다.
|
|
- [x] Keycloak LDAP bridge가 HTTPS OIDC JWT를 발급하고 각 사용자가 서로 다른 안정 subject를 가진다.
|
|
- [x] MCP가 JWT signature/issuer/audience를 검증하고 `issuer + sub` 매핑을 해석한다.
|
|
- [x] subject 매핑을 통해 각 요청에 대응하는 DDS END USER context만 attach된다.
|
|
- [x] 서로 다른 권한의 동일 MCP 호출에서 데이터 행/열 결과가 달라진다.
|
|
- [ ] 미매핑·만료·issuer/audience 불일치 요청은 데이터 접근 전에 거부된다.
|
|
- [ ] 종료 시 테스트 VM과 전용 네트워크 리소스의 정리 여부 및 비용 상태를 Redmine에 기록한다.
|