refs #744: add OCI AD Bridge migration runbook

This commit is contained in:
devmrko
2026-08-05 11:06:39 +09:00
parent de0956ef03
commit 90c771af57

View File

@@ -1,7 +1,7 @@
# 설계서: Windows AD 기반 DDS END USER 매핑 테스트 (#744)
> **상태**: OIDC JWT → MCP → OCI IAM → DDS context 및 Alice/Bob 권한 차이 검증 완료
> **최종수정**: 2026-08-04
> **상태**: Keycloak 기반 검증 완료 · OCI IAM AD Bridge + delegated authentication 전환 진행 중
> **최종수정**: 2026-08-05
> **추적성**: Redmine #744 · 선행 설계: [DDS MCP END USER Context](../617-dds-mcp-end-user-context/README.md)
## 1. 목적과 범위
@@ -10,6 +10,253 @@ Microsoft Entra ID 테넌트가 아직 준비되지 않은 상황에서, OCI 격
이 환경은 **Microsoft Entra access token을 Oracle DB가 직접 검증하는 환경이 아니다.** AD DS는 사용자·그룹·UPN을 제공하고, 이후 Keycloak 또는 별도 검증 서비스가 AD LDAP을 기반으로 발급·검증한 토큰의 안정적인 subject를 MCP의 업무 사용자로 해석한다. Entra tenant와 app registration이 준비되면 OIDC issuer/audience/JWKS 검증 경로로 교체 또는 병행한다.
### 2026-08-05: OCI IAM AD Bridge 전환 목표
초기 PoC는 AD DS를 OIDC로 연결하기 위해 Keycloak을 사용했다. 이제 OCI Identity Domain의 **Microsoft Active Directory (AD) Bridge**와 **delegated authentication**을 사용해 Keycloak을 대체한다. AD DS는 계속 사용자·비밀번호의 원천이며, OCI Identity Domain이 AD Bridge를 통해 AD 비밀번호를 확인한 후 AIPF/MCP용 OIDC token을 발급한다.
```text
기존: AD DS → Keycloak → Keycloak user JWT → MCP → DDS
전환: AD DS → OCI AD Bridge + delegated authentication
→ OCI Identity Domain user JWT → MCP → DDS
```
이미 운영 중인 OCI database resource application `DDS_ORACLE_DB_TEST`와 DB service client `DDS_MCP_SERVICE_TEST`는 제거하거나 사용자 로그인 client로 재사용하지 않는다. 이들은 MCP가 Oracle DB DDS context를 여는 **machine-to-machine** 신뢰에 계속 사용한다. 사용자 로그인에는 별도의 OCI confidential OAuth application을 만든다.
#### 전환 원칙
1. Keycloak, 기존 mapping, 기존 AIPF source는 OCI 경로 검증 전까지 유지한다.
2. OCI AD Bridge가 AD 사용자·그룹을 동기화하고 delegated authentication으로 `dds-alice`/`dds-bob`의 **AD 비밀번호**를 확인하는 것을 먼저 검증한다.
3. AIPF 전용 OCI OAuth client를 별도로 만든다. callback URI와 refresh token 사용은 기존 AIPF 호환 설정을 유지한다.
4. MCP는 OCI Identity Domain의 discovery/JWKS, issuer, audience로 검증 대상을 바꾼다. Keycloak token과 OCI token을 동시에 허용하지 않는다.
5. OCI token의 검증된 `iss + sub`를 별도 binding으로 Alice=`DDS_U_1`, Bob=`DDS_U_2`에 등록한다.
6. OCI 경로에서 Alice 1건 반환과 Bob default deny를 확인한 뒤에만 Keycloak source와 DNS/VM 정리 여부를 결정한다.
#### 현재 전환 인벤토리
| 항목 | 현재 값/상태 | 전환 시 처리 |
|---|---|---|
| Windows AD DS | `dds.test`, `hmm-ad-dss-test-isolated`, 실행 중 | AD 사용자 원천으로 유지 |
| OCI Identity Domain | `identityAPAC`, 활성 | AD Bridge와 사용자 OAuth client를 이 Domain에 추가 |
| DB resource app | `DDS_ORACLE_DB_TEST` | 유지 |
| DB service client | `DDS_MCP_SERVICE_TEST` | 유지 |
| 사용자 OIDC issuer | Keycloak `https://ad.cloud-handson.com/realms/dds-test` | OCI Domain issuer로 교체 |
| AIPF OAuth client | Keycloak `aipf-dds-mcp` | OCI Domain의 새 confidential client로 교체 |
| MCP public endpoint | `https://hmm-backoffice.cloud-handson.com/mcp/dds/sse` | 유지 |
| DDS local users/data grants | `DDS_U_1` 허용, `DDS_U_2` grant 없음 | 유지 |
#### OCI AD Bridge 구축 절차
1. `identityAPAC`의 **Directory integrations**에서 Microsoft AD Bridge를 생성한다.
2. OCI가 표시하는 Domain URL, Bridge client ID/secret을 기록하고 Bridge client installer를 내려받는다. 이 client secret은 AIPF OAuth client secret과 다르며, Windows Bridge client 설정에만 사용한다.
3. AD domain에 join된 Windows VM에 Bridge client를 설치한다. 운영은 Domain Controller와 별도 member server를 권장하지만, 이 격리 PoC는 기존 AD VM에서 설치 가능 여부를 우선 검증한다.
4. Bridge service account에는 동기화 대상 OU/그룹에 대한 read 권한과 `cn=Deleted Objects` 읽기 권한을 준다. delegated authentication도 사용할 경우 Oracle이 요구하는 password/lockout attribute 권한을 최소 범위로 부여한다.
5. LDAPS(636)를 사용하도록 구성하고, Bridge VM에서 OCI Domain HTTPS 443 및 AD LDAP/LDAPS 연결을 확인한다.
6. `dds-alice`, `dds-bob`이 포함된 OU와 필요한 AD 그룹만 동기화 대상으로 선택한다.
7. OCI console의 **Delegated authentication**에서 Bridge를 시험하고 활성화한다. 성공하면 OCI 로그인 화면의 password 검증은 AD에서 수행한다.
8. AIPF용 OCI confidential OAuth application을 생성하고, AIPF callback URI, authorization-code/refresh-token grant, `openid` scope를 설정한다.
9. OCI discovery document의 issuer/JWKS와 token의 `aud`, `sub`를 확인한 뒤 MCP runtime과 `CB_EXTERNAL_IDENTITY_BINDING`을 교체한다.
#### 전체 구성도: AD 계정 로그인부터 DDS 권한 적용까지
아래 구성에서 AD에 새로 붙는 구성요소는 **OCI IAM AD Bridge Client**다. 이는 AD Domain Controller 내부의 AD DS를 대체하거나 AD 비밀번호를 복제하는 서버가 아니다. Domain-joined Windows에 설치되는 Windows service로서, AD에는 LDAP/LDAPS로 연결하고 OCI Identity Domain에는 HTTPS 443으로 연결한다.
```mermaid
flowchart LR
subgraph USER[사용자·클라이언트]
U["업무 사용자<br/>dds-alice / dds-bob"]
AIPF["AIPF<br/>MCP OAuth client"]
end
subgraph ADNET[OCI 격리 네트워크 · dds.test]
AD["Windows Server 2022<br/>AD DS Domain Controller<br/>계정·비밀번호·그룹 원천"]
BRIDGE["OCI IAM AD Bridge Client<br/>Windows service<br/>사용자/그룹 동기화 + 인증 위임"]
AD -->|"LDAP 또는 LDAPS<br/>사용자·OU·그룹 읽기"| BRIDGE
BRIDGE -->|"delegated authentication<br/>AD 비밀번호 확인 요청"| AD
end
subgraph OCIID[OCI Identity Domain · identityAPAC]
DOMAIN["OCI Identity Domain<br/>사용자 디렉터리·SSO·OIDC issuer"]
OAUTH["AIPF_DDS_AD_TEST<br/>confidential OAuth client<br/>authorization_code + refresh_token"]
DBAPP["DDS_ORACLE_DB_TEST<br/>database resource / integrated app<br/>DB_ACCESS_SCOPE"]
SVC["DDS_MCP_SERVICE_TEST<br/>confidential service client<br/>client_credentials only"]
DOMAIN --- OAUTH
DOMAIN --- DBAPP
DOMAIN --- SVC
end
BRIDGE -->|"HTTPS 443<br/>AD 사용자·그룹 동기화"| DOMAIN
U -->|"1. AIPF에서 MCP 연결"| AIPF
AIPF -->|"2. OAuth authorization code"| OAUTH
OAUTH -->|"3. AD 비밀번호 검증 위임"| BRIDGE
OAUTH -->|"4. OCI user access token<br/>iss + aud + sub"| AIPF
subgraph MCPHOST[HMM Backoffice VM]
MCP["DDS MCP Server<br/>hmm-backoffice.cloud-handson.com<br/>/mcp/dds/sse"]
JWT["JWT 검증<br/>OCI discovery / JWKS<br/>issuer + audience + exp"]
MAP["Identity binding<br/>OCI iss + sub<br/>→ CB_APP_USER → DDS_U_n"]
DBTOKEN["DB service token 획득<br/>client credentials"]
MCP --> JWT --> MAP
MCP --> DBTOKEN
end
AIPF -->|"5. Authorization: Bearer<br/>OCI user access token"| MCP
MCP -->|"6. discovery/JWKS HTTPS"| DOMAIN
DBTOKEN -->|"7. client ID + secret<br/>DB_ACCESS_SCOPE"| SVC
SVC -->|"8. database-access token"| DBTOKEN
subgraph ADB[Oracle Autonomous Database · HMMAIPOC]
APPID["DB application identity<br/>DDS_MCP_SERVICE_TEST 신뢰"]
CTX["ORA_END_USER_CONTEXT<br/>DDS_U_1 또는 DDS_U_2 attach"]
DDS["Oracle Deep Sec<br/>DATA ROLE / DATA GRANT<br/>default deny"]
DATA["CB_VECTOR_SEARCH_DOCUMENTS<br/>보호 데이터"]
APPID --> CTX --> DDS --> DATA
end
DBTOKEN -->|"9. database-access token"| APPID
MAP -->|"10. 선택된 local END USER"| CTX
DATA -->|"11. 허용된 행만"| MCP
MCP -->|"12. tool result"| AIPF
```
| 번호 | 구간 | 전달되는 것 | 보안 의미 |
|---:|---|---|---|
| 1~4 | 사용자 → AIPF → OCI Domain → AD Bridge → AD | AD 사용자명·비밀번호, OAuth code, OCI user token | AD는 비밀번호 원천으로 남고 OCI Domain이 OIDC issuer 역할 수행 |
| 5~6 | AIPF → MCP → OCI Domain | Bearer user JWT, JWKS/discovery | MCP가 서명·issuer·audience·만료를 검증해 사용자 위조 차단 |
| 7~9 | MCP → OCI Domain → DB | service client credentials, DB 전용 access token | MCP process만 DB context를 열 수 있음; user JWT를 DB service token으로 쓰지 않음 |
| 10~12 | MCP → DB DDS → AIPF | local DDS END USER, 필터된 query 결과 | Alice/Bob 권한은 DB DATA GRANT에서 최종 강제 |
#### AD Bridge가 하는 일과 하지 않는 일
| AD Bridge가 하는 일 | AD Bridge가 하지 않는 일 |
|---|---|
| AD OU의 사용자·그룹 변경을 OCI Domain으로 동기화 | AD Domain Controller 또는 LDAP 서버를 대체하지 않음 |
| OCI Domain의 delegated authentication 요청을 AD에 전달해 AD 비밀번호를 검증 | AD 사용자 비밀번호를 MCP나 AIPF에 전달하지 않음 |
| AD 계정 비활성화·그룹 변경을 OCI user 상태/그룹과 동기화 | AIPF OAuth client secret 또는 DB service client secret을 보관하지 않음 |
| OCI Domain과 AD 사이의 제한된 연결을 담당 | Oracle DB의 DDS data grant를 평가하거나 우회하지 않음 |
Bridge 설치 위치는 운영에서는 AD Domain Controller와 분리한 domain-joined Windows member server가 원칙이다. 이 PoC는 격리된 단일 Windows VM이므로 동일 VM 설치 가능성을 확인하되, 결과 문서에는 이 제약을 남긴다. Bridge VM에는 OCI Domain으로의 outbound HTTPS 443과 AD DS로의 LDAP/LDAPS 389/636만 허용한다.
#### 적용 가이드: OCI AD Bridge와 AIPF OAuth client
이 절은 다음 환경에서 재적용할 때 사용하는 runbook이다. 명령의 secret 값은 출력하거나 Git에 넣지 않는다.
##### 1. 사전 점검
| 점검 | 기대값 | 실패 시 조치 |
|---|---|---|
| OCI Identity Domain | `identityAPAC` 등 대상 Domain이 `ACTIVE` | Domain 관리자 권한 및 Domain type/AD Bridge 허용량 확인 |
| AD DS | `dds.test` Domain Controller와 DNS·LDAP 정상 | Bridge를 Domain-joined Windows에 설치할 수 있는지 확인 |
| 네트워크 | Bridge VM → OCI Domain TCP/443, Bridge VM → AD TCP/636(LDAPS 권장) | NSG, Windows Firewall, proxy, AD 인증서 점검 |
| AD service account | 동기화 OU 읽기 및 delegated authentication에 필요한 최소 attribute 권한 | Domain Admin을 상시 사용하지 말고 전용 service account 생성 |
| AIPF callback | `https://aipf.cloud-handson.com/agentFactory/v1/tools/mcp/callback``/v1/tools/mcp/callback` | 두 URL 모두 OCI OAuth client redirect URI에 등록 |
현재 PoC의 Windows VM은 `hmm-ad-dss-test-isolated`이며 실행 중이다. OCI에서 확인한 public WinRM HTTPS(5986)는 `Connection refused` 상태다. 따라서 Bridge client 설치는 현재 RDP로 Windows에 접속해 수행하거나, 운영 자동화를 원하면 WinRM HTTPS와 Windows Firewall을 별도 구성해야 한다. 이 WinRM 상태는 AD Bridge 자체의 요구사항이 아니라 원격 설치 방식의 제약이다.
##### 2. OCI AD Bridge 생성과 Windows 설치
1. OCI Console에서 **Identity & Security → Domains → 대상 Domain → Directory integrations → Add → Microsoft Active Directory Bridge**로 이동한다.
2. 화면에서 표시되는 **Identity Domain URL, Bridge client ID, Bridge client secret**을 안전한 password manager/secret store에 보관한다. 이것은 AIPF OAuth client secret 및 DB service client secret과 다른 값이다.
3. Bridge client installer를 내려받아 domain-joined Windows에 설치한다.
4. 설치 UI에서 위 OCI Domain URL/client ID/client secret 및 AD Bridge service account를 입력하고 연결을 시험한다.
5. AD 연결은 **LDAPS 사용**을 선택한다. AD DS 인증서가 Bridge Windows trust store에서 신뢰되지 않으면 먼저 CA/서버 인증서를 배포한다. 설치 후 SSL 선택은 변경이 어렵다.
6. Directory integrations 화면에서 동기화할 사용자 OU, 그룹 OU를 최소 범위로 선택한다. `dds-alice`, `dds-bob`과 필요한 역할 그룹이 포함돼야 한다.
7. Bridge initial sync 후 OCI Domain Users/Groups에 두 사용자가 나타나는지 확인한다.
8. **Security → Delegated authentication**에서 `Test Delegated Authentication`으로 AD 사용자명과 AD 비밀번호를 시험하고 성공한 뒤에만 활성화한다.
`dds-alice`/`dds-bob` 로그인은 계속 AD 계정으로 한다. OCI Domain에 동기화된 user 레코드는 SSO/OIDC subject를 위한 cloud-side representation이고, delegated authentication 활성화 뒤의 비밀번호 검증 원천은 AD DS다.
##### 3. AIPF용 OCI OAuth client 생성
OCI Domain에는 DB service client와 별도로 AIPF용 confidential client가 필요하다. 이 PoC에서 만든 application 이름은 `AIPF_DDS_AD_TEST`이며 다음 정책을 사용한다.
| 항목 | 적용값 |
|---|---|
| Application type | Confidential OAuth client (`CustomWebAppTemplateId`) |
| Grants | `authorization_code`, `refresh_token` |
| Refresh token | 허용 (`allowOffline=true`) |
| Redirect URI | AIPF callback 2개 경로 |
| Access token expiry | 3600초 |
| Refresh token expiry | 1209600초(14일) |
| Consent | PoC에서 bypass; 운영 전 사용자 동의/정책 검토 |
OCI CLI로 생성할 경우 Identity Domain App API의 필수 `basedOnTemplate`을 반드시 포함한다.
```json
{
"schemas": ["urn:ietf:params:scim:schemas:oracle:idcs:App"],
"displayName": "AIPF_DDS_AD_TEST",
"basedOnTemplate": {"value": "CustomWebAppTemplateId"},
"active": true,
"isOAuthClient": true,
"clientType": "confidential",
"allowedGrants": ["authorization_code", "refresh_token"],
"redirectUris": [
"https://aipf.cloud-handson.com/agentFactory/v1/tools/mcp/callback",
"https://aipf.cloud-handson.com/v1/tools/mcp/callback"
],
"allowOffline": true
}
```
생성된 app ID/client ID/client secret은 local 검증 환경에서는 git-ignore된 `.runtime/aipf-dds-oci-ad-client.env`에만 저장한다. 파일에는 `OCI_AIPF_DDS_APP_ID`, `OCI_AIPF_DDS_CLIENT_ID`, `OCI_AIPF_DDS_CLIENT_SECRET`만 두며 값은 문서·Redmine·채팅·shell 출력에 기록하지 않는다.
##### 4. AIPF에 입력할 OCI OAuth 값
OCI OpenID discovery 문서에서 endpoint를 확인한다. URL을 임의로 조합하지 말고 아래 discovery endpoint의 실제 응답을 기준으로 한다.
```text
https://<identity-domain>/.well-known/openid-configuration
```
현재 `identityAPAC`에서 확인한 형식은 다음과 같다.
| AIPF 입력 항목 | 값 형식 |
|---|---|
| Server URL | `https://hmm-backoffice.cloud-handson.com/mcp/dds/sse` |
| Authentication mode | `OAuth` |
| OAuth client ID/secret | `AIPF_DDS_AD_TEST` 생성 결과의 client ID/secret |
| Authorization URL | `https://idcs-<domain>.identity.oraclecloud.com:443/oauth2/v1/authorize` |
| Token / Refresh URL | `https://idcs-<domain>.identity.oraclecloud.com:443/oauth2/v1/token` |
| Scopes | `openid profile email offline_access` (실제 app policy에 맞춰 조정) |
##### 5. MCP 및 DB 매핑 전환
Bridge와 AIPF OAuth login을 검증한 뒤, OCI access token을 decode하여 `iss`, `aud`, immutable `sub`를 기록한다. 그 다음에만 다음 값을 교체한다.
| 대상 | 변경 |
|---|---|
| HMM MCP `DDS_MCP_OIDC_ISSUER_URI` | OCI discovery가 반환한 issuer (현재 확인값 `https://identity.oraclecloud.com/`) |
| HMM MCP `DDS_MCP_OIDC_AUDIENCE` | OCI user access token의 실제 `aud` 값 |
| `CB_EXTERNAL_IDENTITY_BINDING` | OCI `iss + sub` → Alice/Bob application user 등록 |
| AIPF MCP source | OCI OAuth client ID/secret/endpoint로 신규 source 생성 |
OCI token을 실제로 받은 뒤 audience를 정하는 이유는 client ID, resource scope, Domain 정책에 따라 access token claim이 달라질 수 있기 때문이다. 추측값으로 MCP를 먼저 재시작하면 기존 Keycloak 검증을 불필요하게 중단시킬 수 있다.
#### 적용 중 확인된 트러블슈팅 기록
| 증상/오류 | 원인 | 해결/판정 |
|---|---|---|
| `401 Authorization Required`, endpoint에 `/admin/v1/admin/v1/Apps` | `oci identity-domains ... --endpoint``/admin/v1`까지 넣어 CLI가 경로를 중복 | CLI endpoint에는 Domain base URL만 넣는다. raw request일 때만 target URI에 `/admin/v1/...`를 넣는다. |
| `No such option: --header` | `oci raw-request``--header`가 아니라 `--request-headers` 사용 | `--request-headers '{"Content-Type":"application/json"}'`로 호출 |
| `Missing required attribute(s): basedOnTemplate.` | OCI App API는 OAuth client 생성 시 template 필수 | `"basedOnTemplate":{"value":"CustomWebAppTemplateId"}` 추가 |
| authorization endpoint가 `302`가 아니라 `HTTP 200 OK` | OCI Domain이 redirect 대신 로그인 HTML과 secure session cookie를 반환 | 정상. browser에서 OCI 로그인 화면이 보이면 OAuth preflight 통과로 판정 |
| OCI console browser automation 불가 | 현재 작업 환경에 browser binding 없음 | OCI Console 화면 조작이 필요한 Bridge 생성은 RDP/관리자 Console session으로 수행; API로 가능한 OAuth app/MCP 설정은 자동화 계속 |
| Windows VM WinRM 5986 `Connection refused` | WinRM HTTPS listener/Windows Firewall/NSG 미구성 | RDP 설치를 사용하거나 WinRM HTTPS를 별도 구성. Bridge 기능의 오류로 해석하지 않음 |
| AD Bridge test가 LDAPS certificate 오류 | AD DS 인증서 체인이 Bridge host trust store에 없음 | 사내 CA/AD 인증서를 Bridge host에 신뢰시키고 LDAPS를 유지 |
| OCI OAuth login은 성공하지만 MCP `401` | MCP issuer/audience가 Keycloak 값 그대로이거나 OCI `sub` binding 없음 | OCI JWT claim 확인 후 MCP env와 `CB_EXTERNAL_IDENTITY_BINDING`을 함께 교체 |
| AIPF source에서 이전 사용자로 로그인 | OCI Domain browser SSO session 유지 | OCI Domain logout 후 source 재연결; AIPF source 저장 token과 browser SSO session을 구분 |
#### 전환 검증 기준
| 단계 | 증거 | 성공 기준 |
|---|---|---|
| AD Bridge 연결 | Bridge 상태 `Connected`, AD 사용자/그룹 동기화 결과 | `dds-alice`, `dds-bob`이 OCI Domain에 존재 |
| delegated authentication | OCI Domain의 test delegated authentication | 두 사용자의 AD 비밀번호로 성공 |
| OAuth code flow | AIPF가 OCI login → callback → MCP source 연결 완료 | OCI issuer의 access token이 저장됨 |
| MCP 검증 | OCI JWT의 signature/issuer/audience 통과 | `iss + sub` mapping 조회 성공 |
| DDS 권한 | 같은 `휴가` tool 호출 | Alice 1건, Bob default deny |
| 회귀/보안 | Keycloak token, 미매핑 OCI token, 만료 token | DB query 전 거부 |
### 이 설계가 답하는 질문
이 PoC는 다음 네 가지를 의도적으로 분리한다.