refs #744: expand AD OIDC DDS architecture guide
This commit is contained in:
@@ -10,6 +10,19 @@ 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 검증 경로로 교체 또는 병행한다.
|
||||
|
||||
### 이 설계가 답하는 질문
|
||||
|
||||
이 PoC는 다음 네 가지를 의도적으로 분리한다.
|
||||
|
||||
| 질문 | 답 | 담당 구성요소 |
|
||||
|---|---|---|
|
||||
| 직원 계정과 비밀번호는 어디에 있는가? | Windows AD DS가 원천이다. | AD DS |
|
||||
| 사용자가 MCP에 로그인했음을 어떻게 증명하는가? | Keycloak이 AD LDAP 인증을 거쳐 OIDC access token(JWT)을 발급한다. | Keycloak |
|
||||
| MCP 서버가 Oracle DB에 접속해 DDS context를 열 자격은 어떻게 얻는가? | OCI Identity Domain의 confidential service client가 database-access token을 발급받는다. | OCI IAM integrated application / credential app |
|
||||
| Alice와 Bob이 서로 다른 데이터만 보게 하는 최종 판단은 누가 하는가? | Oracle Deep Sec이 요청별 local DDS END USER와 DATA GRANT를 적용한다. | Oracle Database |
|
||||
|
||||
즉, **AD 계정으로 로그인한 사용자 토큰**과 **MCP 서버가 DB에 접속하는 서비스 토큰**은 목적과 발급자가 다른 별개의 token이다. 전자는 “누가 요청했는가”를, 후자는 “어떤 애플리케이션이 DB context를 열 수 있는가”를 증명한다. 하나를 다른 하나의 대체물로 쓰지 않는다.
|
||||
|
||||
## 2. 격리 인프라
|
||||
|
||||
| 항목 | 구성 |
|
||||
@@ -44,6 +57,39 @@ ORA_END_USER_CONTEXT + DDS DATA GRANT / 권한 함수
|
||||
|
||||
`sub`는 Keycloak이 발급하는 안정적인 federated-user 식별자다. AD objectGUID는 원천 디렉터리의 감사 식별자로 유지하되, 실제 권한 키는 **검증된 JWT의 `iss + sub`**로 고정한다. UPN은 로그인 화면·감사 표시용으로 보관할 수 있지만 권한 키로 신뢰하지 않는다. `iss + sub` 조합이 유일하지 않거나, 매핑이 없거나, 사용자가 비활성이면 요청은 fail-closed로 거부한다.
|
||||
|
||||
### 전체 요청 흐름
|
||||
|
||||
```text
|
||||
(1) 사용자가 AIPF에서 dds MCP를 연결
|
||||
│ OAuth authorization code flow
|
||||
▼
|
||||
(2) Keycloak 로그인 화면
|
||||
│ AD LDAP bind: dds-alice / dds-bob의 비밀번호 확인
|
||||
▼
|
||||
(3) Keycloak access token 발급
|
||||
│ iss, aud=dds-mcp, exp, sub 포함 / Keycloak signing key로 서명
|
||||
▼
|
||||
(4) AIPF가 MCP 요청에 Bearer access token을 첨부
|
||||
▼
|
||||
(5) MCP
|
||||
│ Keycloak JWKS로 서명·issuer·audience·만료 검증
|
||||
│ CB_EXTERNAL_IDENTITY_BINDING에서 iss+sub 조회
|
||||
▼
|
||||
(6) MCP가 OCI IAM service token을 별도로 획득
|
||||
│ client credentials: DDS_MCP_SERVICE_TEST
|
||||
▼
|
||||
(7) Oracle DB
|
||||
│ service token으로 trusted application identity 확인
|
||||
│ 조회한 DDS_U_n end-user context attach
|
||||
▼
|
||||
(8) Deep Sec
|
||||
│ DDS_U_n의 DATA ROLE/DATA GRANT만 적용하여 SQL 실행
|
||||
▼
|
||||
(9) MCP가 허용된 결과만 AIPF로 반환
|
||||
```
|
||||
|
||||
MCP는 이 흐름의 **정책 집행점**이다. 사용자의 JWT를 검증하지 못하면 5단계에서 멈추고, DB service token이 유효하지 않으면 7단계에서 멈추며, 둘 다 통과해도 해당 DDS END USER의 grant가 없으면 8단계에서 멈춘다. 어느 단계에서도 실패를 우회하여 공유 DB 계정의 광범위한 권한으로 조회해서는 안 된다.
|
||||
|
||||
### 사용자 인증과 DB 접속 신뢰의 분리
|
||||
|
||||
이 PoC에는 서로 다른 두 신뢰 체인이 있다. 둘을 같은 OAuth token으로 혼동하지 않는다.
|
||||
@@ -70,6 +116,53 @@ MCP service -> OCI IAM DDS_MCP_SERVICE_TEST (client credentials)
|
||||
|
||||
향후 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 매핑과는 별도의 확장 경로다.
|
||||
|
||||
### OCI Identity Domain integrated application을 쓰는 이유와 역할
|
||||
|
||||
여기서 말하는 OCI Domain integrated application은 `DDS_ORACLE_DB_TEST` database resource application이다. 이를 사용자용 웹 로그인 앱으로 오해하면 안 된다. 이 앱은 **Oracle Autonomous Database가 신뢰할 OAuth audience와 scope를 OCI Identity Domain에 선언하는 등록물**이다.
|
||||
|
||||
일반 OAuth access token은 “어느 resource server를 위한 token인지”가 불명확하면 DB가 받아들여서는 안 된다. integrated application은 DB용 resource identity를 만들고, `DB_ACCESS_SCOPE` 같은 전용 scope를 발급 정책에 묶는다. 그러면 DB는 다음 세 가지가 일치할 때만 MCP의 service token을 받아들인다.
|
||||
|
||||
| DB가 확인하는 값 | integrated application에서 정하는 값 | 이 PoC의 의미 |
|
||||
|---|---|---|
|
||||
| resource application | `DDS_ORACLE_DB_TEST` | 이 token의 수신자가 DDS 대상 Oracle DB임을 식별 |
|
||||
| scope | `DB_ACCESS_SCOPE` | DB context 접근이라는 최소 권한을 명시 |
|
||||
| issuer | OCI Identity Domain URL | token을 발급하고 서명키를 제공하는 신뢰 경계 |
|
||||
| OAuth client ID | `DDS_MCP_SERVICE_TEST` | token을 요청한 MCP service를 식별 |
|
||||
|
||||
`DDS_MCP_SERVICE_TEST`는 integrated application 자체가 아니라, 그 resource/scope를 요청할 수 있도록 허가된 **confidential service client**다. client secret을 가진 백엔드 MCP만 client credentials flow로 token을 발급받는다. 브라우저·AIPF·AD 사용자는 이 secret이나 service token을 보거나 보관하지 않는다.
|
||||
|
||||
```text
|
||||
OCI Identity Domain
|
||||
|
||||
DDS_ORACLE_DB_TEST DDS_MCP_SERVICE_TEST
|
||||
(resource / integrated app) (confidential OAuth client)
|
||||
┌───────────────────────┐ ┌─────────────────────────┐
|
||||
│ audience: Oracle DB │<--scope--│ client credentials only │
|
||||
│ scope: DB_ACCESS_SCOPE│ │ secret: MCP host only │
|
||||
└──────────┬────────────┘ └───────────┬─────────────┘
|
||||
│ database-access token │ token request
|
||||
└────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Autonomous DB application identity
|
||||
```
|
||||
|
||||
이 구조를 쓰는 이유는 DB password를 MCP에 장기 보관하거나, 모든 사용자에게 DB 로그인 권한을 주지 않기 위해서다. service client는 DB context를 여는 최소 권한만 보유하고, 사람별 데이터 권한은 local DDS END USER에 남긴다. 서비스 client secret이 노출되더라도 해당 client에 부여하지 않은 데이터 권한이 자동으로 생기는 구조가 아니다. 다만 token 발급과 DB context 생성 자체를 악용할 수 있으므로 secret은 즉시 회전하고, 이 client에는 불필요한 OCI 권한을 부여하지 않는다.
|
||||
|
||||
### OCI IAM과 Keycloak을 함께 쓰는 이유
|
||||
|
||||
현재 AD DS는 LDAP/Kerberos 디렉터리이지 인터넷 서비스가 직접 검증할 OAuth JWT issuer는 아니다. Keycloak은 AD LDAP과 OIDC 사이의 번역 계층이다. 반면 OCI IAM integrated application은 Oracle DB가 지원하는 database-access token contract를 제공한다.
|
||||
|
||||
| 경계 | 입력 | 출력 | 선택 이유 |
|
||||
|---|---|---|---|
|
||||
| AD DS → Keycloak | AD 사용자명/비밀번호, LDAP 사용자·그룹 | Keycloak OIDC JWT | AD를 계정 원천으로 유지하면서 표준 OAuth/OIDC를 제공 |
|
||||
| Keycloak → MCP | Keycloak JWT/JWKS | 검증된 `iss + sub` | MCP가 사용자 identity를 안전하게 해석 |
|
||||
| MCP → OCI IAM | service client ID/secret | DB 전용 access token | DB가 신뢰할 service identity 증명 |
|
||||
| OCI IAM → Oracle DB | database-access token | application identity | DB password 없이 DB context 접근을 제한 |
|
||||
| Oracle DB → DDS | local END USER | 필터된 query 결과 | 데이터 권한을 DB 내부에서 강제 |
|
||||
|
||||
따라서 “OCI Domain credential app이 AD를 신뢰한다”는 표현은 현재 PoC에는 정확하지 않다. 현재는 **Keycloak이 AD를 신뢰해 사용자 인증을 수행**하고, **Oracle DB가 OCI IAM을 신뢰해 MCP service를 인증**한다. 두 체인은 MCP 안에서 만나며, MCP가 Keycloak token의 subject를 local DDS END USER로 결정한다.
|
||||
|
||||
## 4. 구현 단계
|
||||
|
||||
1. Windows Server에 AD DS를 설치하고 `dds.test` forest를 생성한다.
|
||||
@@ -92,6 +185,30 @@ MCP service -> OCI IAM DDS_MCP_SERVICE_TEST (client credentials)
|
||||
|
||||
초기 게시 단계에서는 END USER와 DATA ROLE만 만들고 default deny로 시작한다. 현재 테스트에서는 Alice에만 vector view SELECT data grant를 추가했고 Bob은 grant 없이 유지한다.
|
||||
|
||||
### Keycloak 구성에서 중요한 값
|
||||
|
||||
Keycloak realm `dds-test`은 AD LDAP user federation을 사용한다. 로그인 화면에서 입력한 `dds-alice` 또는 `dds-bob`의 비밀번호 검증은 AD DS가 담당하며, Keycloak은 성공한 LDAP 사용자의 immutable identity를 자신의 `sub`로 유지해 JWT에 넣는다. AD 비밀번호나 LDAP bind credential은 MCP와 AIPF에 전달되지 않는다.
|
||||
|
||||
| Keycloak 항목 | 값/정책 | 필요한 이유 |
|
||||
|---|---|---|
|
||||
| Realm issuer | `https://ad.cloud-handson.com/realms/dds-test` | MCP의 허용 issuer와 identity binding의 namespace |
|
||||
| MCP audience | `dds-mcp` | 다른 Keycloak client용 token의 재사용 방지 |
|
||||
| AIPF confidential client | `aipf-dds-mcp` | AIPF OAuth callback과 token exchange 수행 |
|
||||
| MCP resource client | `dds-mcp` | access token audience 검증 대상 |
|
||||
| Redirect URI | AIPF callback 두 경로와 wildcard 등록 | authorization code를 허용된 AIPF에만 반환 |
|
||||
| PKCE | AIPF client에서 미사용 | 현재 AIPF가 `code_challenge_method`를 보내지 않기 때문; 향후 AIPF 지원 시 S256으로 전환 |
|
||||
| Direct grant | 테스트 client에서만 제한적으로 사용 | CLI 검증용; 운영 browser client에는 사용하지 않음 |
|
||||
|
||||
JWT 검증은 서명만 확인하는 것으로 충분하지 않다. MCP는 최소한 다음을 모두 확인해야 한다.
|
||||
|
||||
1. `alg`가 허용된 비대칭 서명 알고리즘인지 확인하고, Keycloak JWKS의 해당 `kid`로 signature를 검증한다.
|
||||
2. `iss`가 realm issuer와 정확히 같은지 확인한다. URL의 realm·host가 다른 token은 거부한다.
|
||||
3. `aud`에 `dds-mcp`가 포함됐는지 확인한다.
|
||||
4. `exp`, `nbf`, `iat` 및 허용 clock skew를 검사한다.
|
||||
5. 검증된 `iss + sub`로만 `CB_EXTERNAL_IDENTITY_BINDING`을 조회한다. browser가 표시한 email, username, group 문자열만으로 매핑하지 않는다.
|
||||
|
||||
Keycloak signing key rotation 시 MCP의 JWKS cache가 새 `kid`를 다시 조회할 수 있어야 한다. issuer나 realm을 바꾸면 binding table의 issuer 값과 MCP allow-list도 함께 변경해야 한다.
|
||||
|
||||
## 4.2 AIPF MCP 등록 값
|
||||
|
||||
AIPF의 **Edit MCP server** 화면에서 다음 값으로 등록한다. AIPF는 confidential OAuth client를 요구하므로, 일반 테스트용 public client `dds-mcp`가 아니라 AIPF 전용 client `aipf-dds-mcp`를 사용한다.
|
||||
@@ -130,6 +247,24 @@ sed -n 's/^OAUTH_CLIENT_SECRET=//p' .runtime/aipf-dds-mcp-client.env | pbcopy
|
||||
|
||||
이 전환은 OAuth `code`와 `state`를 보존하고 브라우저가 `/agentFactory` 범위의 AIPF 세션 쿠키를 다시 전송하게 한다. AIPF의 OAuth callback 생성 로직이 수정되면 이 Nginx 보정은 제거할 수 있다.
|
||||
|
||||
### AIPF 로그인 상태와 MCP 등록을 구분할 것
|
||||
|
||||
AIPF의 MCP 등록 정보와 Keycloak의 브라우저 SSO 세션은 서로 다르다.
|
||||
|
||||
| 상태 | 보관 위치 | 영향 |
|
||||
|---|---|---|
|
||||
| MCP OAuth client ID/secret, 연결 설정 | AIPF의 MCP source 설정 | 최초 OAuth 연결 및 refresh token 교환에 사용 |
|
||||
| Keycloak SSO 로그인 세션 | 브라우저의 `ad.cloud-handson.com` cookie | 다음 OAuth 연결 시 이전 사용자로 자동 로그인될 수 있음 |
|
||||
| AIPF가 저장한 MCP token/refresh token | AIPF backend source별 저장소 | 등록한 MCP source가 이후 tool 호출할 때 사용 |
|
||||
|
||||
그러므로 Alice에서 Bob을 시험할 때는 source를 `bob-dds`로 새로 만들거나 기존 연결을 끊고, Keycloak logout을 먼저 수행한다. 같은 browser session에서 바로 다시 연결하면 AIPF 설정을 Bob으로 바꿔도 Keycloak SSO가 Alice를 다시 인증할 수 있다. Keycloak logout endpoint는 다음과 같다.
|
||||
|
||||
```text
|
||||
https://ad.cloud-handson.com/realms/dds-test/protocol/openid-connect/logout
|
||||
```
|
||||
|
||||
새 AIPF source에는 OAuth client ID와 secret을 다시 입력한다. source 간에 인증정보가 자동 상속된다고 가정하지 않는다.
|
||||
|
||||
## 4.3 테스트 시나리오
|
||||
|
||||
### A. Alice 인증과 MCP 연결
|
||||
@@ -164,7 +299,7 @@ sed -n 's/^OAUTH_CLIENT_SECRET=//p' .runtime/aipf-dds-mcp-client.env | pbcopy
|
||||
|---|---|---|
|
||||
| OAuth 로그인 | AIPF가 Keycloak 로그인 후 MCP 등록 화면으로 복귀 | 확인 완료 |
|
||||
| JWT 검증/매핑 | `tools/list` 또는 `dds_vector_search`가 Bearer 토큰 검증을 통과 | 확인 완료 |
|
||||
| Alice/Bob 데이터 차이 | 같은 `dds_vector_search` 호출에서 반환 행이 다름 | 대기 — OCI IAM DB service credential 및 DDS DATA GRANT 필요 |
|
||||
| Alice/Bob 데이터 차이 | 같은 `dds_vector_search` 호출에서 반환 행이 다름 | 확인 완료 — Alice 1건 반환, Bob default deny |
|
||||
| 미매핑 토큰 거부 | 매핑 없는 `iss + sub`의 MCP 호출이 `AUTHORIZATION_DENIED` | 구현 완료, 회귀 검증 대기 |
|
||||
|
||||
`dds_vector_search`는 현재 유일한 DDS MCP 도구이며 입력값은 `query`(필수), `limit`(1~100), `embeddingMode`(`DEMO` 또는 `AI`)다. service identity와 Alice 허용/Bob 거부 DDS grant를 반영해 권한 차이까지 검증했다.
|
||||
@@ -181,6 +316,41 @@ OCI IAM DDS_MCP_SERVICE_TEST -- database-access token --> Oracle Deep Sec contex
|
||||
|
||||
구성 순서는 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에 기록한다.
|
||||
|
||||
### 실제 구성 절차와 신뢰 등록 방향
|
||||
|
||||
다음 순서는 “누가 누구를 신뢰하도록 등록하는가”를 기준으로 작성했다. secret, client ID, application ID는 환경별 값이므로 이 문서에 넣지 않고 권한 제한 환경 파일 또는 OCI console에서 관리한다.
|
||||
|
||||
1. OCI Identity Domain에서 database resource/integrated application `DDS_ORACLE_DB_TEST`를 만들고 `DB_ACCESS_SCOPE`를 정의한다. 이것이 DB가 받아들일 audience와 scope의 계약이다.
|
||||
2. 같은 Domain에서 confidential client `DDS_MCP_SERVICE_TEST`를 만든다. grant type은 `client_credentials`만 허용하고, 1단계의 DB scope만 허용한다. 발급된 client secret은 MCP host의 mode 600 환경 파일에만 저장한다.
|
||||
3. Autonomous DB에 OCI IAM external authentication을 활성화하고, Domain URL 및 database resource application ID를 등록한다. DB의 issuer URL은 token `iss`와 정규형까지 일치해야 한다. 이 환경에서는 HTTPS `:443`을 포함한다.
|
||||
4. DB에 OCI IAM signing-key credential을 만들고, `DDS_MCP_SERVICE_TEST`의 OAuth client ID를 DB application identity에 매핑한다. 이 단계로 DB는 “이 client credentials로 발급된 DB token을 가진 서비스”를 신뢰한다.
|
||||
5. HMM MCP runtime에 service client ID, secret, token endpoint, scope, resource 대상 view를 주입하고 서비스 계정을 재시작한다. 사용자 access token이나 AD 비밀번호를 이 파일에 넣지 않는다.
|
||||
6. 별도로 Keycloak `iss + sub` → `CB_APP_USER` → `CB_DDS_END_USER_MAP`을 등록한다. `DDS_U_1_ROLE` 같은 local role에 대상 객체 DATA GRANT를 필요한 사용자에게만 부여한다.
|
||||
|
||||
신뢰의 방향은 아래와 같다.
|
||||
|
||||
```text
|
||||
AD DS ──(LDAP credential 확인)──► Keycloak
|
||||
Keycloak ──(signed user JWT)────► MCP
|
||||
MCP ──(client credentials)──────► OCI Identity Domain
|
||||
OCI Identity Domain ──(signed DB token)──► Oracle DB
|
||||
Oracle DB ──(DDS grant 평가)────► 데이터
|
||||
```
|
||||
|
||||
Oracle DB는 AD DS나 Keycloak을 직접 신뢰하도록 등록되어 있지 않다. 반대로 OCI IAM integrated application도 AD 사용자 credential을 검증하지 않는다. MCP가 두 신뢰 체인의 검증 결과를 결합하는 위치다.
|
||||
|
||||
### MCP 런타임에서 token을 사용하는 방식
|
||||
|
||||
MCP 요청을 처리할 때 서비스는 두 token을 다음 순서로 취급한다.
|
||||
|
||||
| 순서 | token | MCP가 하는 일 | 실패하면 |
|
||||
|---:|---|---|---|
|
||||
| 1 | Keycloak user access token | HTTP `Authorization: Bearer`에서 추출, JWKS 검증, `iss + sub` binding 해석 | `401` 또는 `AUTHORIZATION_DENIED`; DB에 접속하지 않음 |
|
||||
| 2 | OCI IAM database-access token | server-side client credentials로 얻어 DB connection/context attach에 사용 | DDS context 불가; 사용자 token으로 대체하지 않음 |
|
||||
| 3 | 없음(내부 context) | `DDS_U_n`의 data role/data grant로 target view를 query | Oracle default deny; 결과 반환 안 함 |
|
||||
|
||||
MCP endpoint는 `https://hmm-backoffice.cloud-handson.com/mcp/dds/sse`이며, SSE transport의 message endpoint는 `https://hmm-backoffice.cloud-handson.com/mcp/dds/messages`다. browser 주소창으로 SSE URL을 여는 것은 OAuth 로그인 페이지가 아니라 event stream 연결 시도이므로 유효한 동작 검증 방법이 아니다. AIPF OAuth 등록 또는 Bearer token을 포함한 MCP client로 접속해야 한다.
|
||||
|
||||
### 적용 결과와 검증
|
||||
|
||||
| 구성 요소 | 적용값 | 상태 |
|
||||
@@ -196,7 +366,7 @@ OCI IAM DDS_MCP_SERVICE_TEST -- database-access token --> Oracle Deep Sec contex
|
||||
|
||||
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`이다. 이는 권한 허용과 데이터 존재 여부를 구분한 결과다.
|
||||
2026-08-04 검증 결과는 다음과 같다. 동일한 `dds_vector_search` 호출에서 Alice는 DDS context attach 후 query가 성공했고, Bob은 data grant가 없어 보호 객체 조회가 거부됐다. 초기에는 HMM knowledge chunk 데이터가 없어 Alice의 성공 응답 행 수가 `0`이었으나, 이후 비교 가능한 테스트 문서를 추가하여 Alice의 1건 반환까지 재검증했다.
|
||||
|
||||
이후 권한 차이를 눈으로 확인할 수 있도록 `DDS_AD_ALICE_LEAVE_001` 테스트 문서와 `DDS_AD_TEST` 태그를 HMM knowledge 원천에 추가했다. 같은 `휴가` 검색에서 Alice는 이 문서 1건을 반환하고 Bob은 `ORA-00942` 기반 default deny로 거부되는 것을 확인했다.
|
||||
|
||||
@@ -216,11 +386,50 @@ embeddingMode는 DEMO, limit은 10으로 해줘.
|
||||
|
||||
따라서 Bob의 AIPF 화면 문구는 시스템 장애가 아니라 의도한 보안 결과다. 현재 MCP 응답은 SQL 오류를 외부에 노출하지 않기 위해 일반 문구로 변환한다. 운영 UI에서는 이를 “접근 권한이 없습니다”로 표시하도록 개선할 수 있지만, PoC의 권한 검증 자체는 Alice 허용·Bob 거부로 완료됐다.
|
||||
|
||||
## 5. Entra ID로 전환할 때
|
||||
## 5. 운영 보안 기준과 장애 구분
|
||||
|
||||
### secret과 token의 보관 원칙
|
||||
|
||||
| 값 | 소유자 | 허용 위치 | 금지 위치 |
|
||||
|---|---|---|---|
|
||||
| AD 사용자 비밀번호 | 사용자/AD | AD에서 hash로 관리, 사용자가 Keycloak login form에만 입력 | MCP 설정, AIPF secret, Git, 문서 |
|
||||
| Keycloak AIPF client secret | AIPF OAuth client | 권한 제한 secret store 또는 `.runtime/aipf-dds-mcp-client.env` | Git, Redmine 본문, 채팅 |
|
||||
| OCI IAM service client secret | MCP backend | HMM host의 mode 600 환경 파일/secret manager | AIPF browser, Java source, Git |
|
||||
| Keycloak user access token | AIPF/MCP 요청 경로 | AIPF의 보호된 token 저장소, HTTPS request header | URL query, application log |
|
||||
| OCI IAM DB service token | MCP process memory | token endpoint 응답 및 DB context attach | browser, client log, source code |
|
||||
|
||||
secret rotation 시에는 Keycloak 또는 OCI IAM에서 새 secret을 만들고, 해당 runtime secret store만 갱신한 뒤 서비스를 재시작한다. 이전 secret의 폐기는 새 token 발급과 MCP query를 확인한 후 수행한다. secret의 실제 값은 로그·shell history·스크린샷에도 남기지 않는다.
|
||||
|
||||
### default deny가 정상인 경우와 장애인 경우
|
||||
|
||||
| 관측 결과 | 가능한 원인 | 기대 처리/조치 |
|
||||
|---|---|---|
|
||||
| 이전 Alice로 자동 로그인 | Keycloak SSO cookie가 남아 있음 | logout 후 Bob으로 다시 인증 |
|
||||
| `401` 또는 tools discovery 실패 | Bearer token 없음/만료, issuer·audience 검증 실패, AIPF source OAuth 설정 누락 | AIPF OAuth 설정과 Keycloak token claim 확인 |
|
||||
| `invalid_request: Missing parameter: code_challenge_method` | Keycloak client에 PKCE 강제인데 AIPF가 PKCE를 보내지 않음 | 현재 AIPF client의 PKCE 정책을 호환 설정으로 조정; AIPF 지원 후 S256 전환 |
|
||||
| `unauthorized_client` / invalid client credentials | AIPF에 잘못된 Keycloak client secret 저장 | 안전한 runtime 파일에서 secret을 다시 복사하고 source 재연결 |
|
||||
| `DDS_CONTEXT_UNAVAILABLE` | OCI IAM client/scope/token endpoint 또는 DB external auth 구성 누락 | service client scope, DB application identity, domain issuer URL 점검 |
|
||||
| Oracle `ORA-52602` | database-access token issuer/resource/scope와 DB 등록값 불일치 | Domain URL 정규형(이 환경은 `:443` 포함), resource app ID와 scope 점검 |
|
||||
| Alice는 결과, Bob은 “DDS 보호 객체” 오류 | Bob에 DATA GRANT 없음 | 의도한 default deny. 정책상 필요한 경우에만 최소 grant 추가 |
|
||||
| Alice도 Bob도 모두 결과 없음 | source data 부재, vector/embedding 조건, 공통 service token 문제 | 데이터 존재 여부와 DDS context attach를 분리해 점검 |
|
||||
|
||||
권한 거부와 인프라 장애를 구분하기 위해 MCP 내부 log에는 SQLState/Oracle error code를 남길 수 있으나, 외부 MCP 응답에는 table명·SQL·DB credential 정보를 노출하지 않는다. 감사 로그에는 요청 시각, correlation ID, token의 `iss + sub` hash 또는 내부 user ID, 선택된 DDS END USER, tool명, allow/deny 결과를 남긴다.
|
||||
|
||||
### 최소 권한 운영 원칙
|
||||
|
||||
1. `DDS_MCP_SERVICE_TEST`에는 database-access scope 외의 OCI 권한을 부여하지 않는다.
|
||||
2. DB application identity는 MCP runtime 전용이며, 개인 사용자나 AIPF browser가 직접 사용하지 않는다.
|
||||
3. local DDS END USER에는 필요한 DATA ROLE만 붙이고, 권한은 object/row/column 단위로 좁힌다. 새 사용자는 grant 없이 생성하는 default deny를 기본으로 한다.
|
||||
4. `CB_EXTERNAL_IDENTITY_BINDING` 변경은 관리자만 수행하고 issuer와 immutable subject를 audit한다. UPN/email 변경만으로 기존 권한이 다른 계정에 이전되지 않아야 한다.
|
||||
5. HTTPS는 Keycloak, AIPF, MCP 모두에서 강제하고, JWKS·token endpoint 호출의 TLS 검증을 끄지 않는다.
|
||||
6. AD DS VM의 RDP와 LDAP 접근은 관리망/허용 IP로 제한한다. public LDAP/LDAPS를 인터넷에 열지 않는다.
|
||||
7. 테스트 종료 후 Windows VM, public DNS, test clients와 secret의 보존·폐기 여부를 Redmine에 기록한다.
|
||||
|
||||
## 6. 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. 완료 기준
|
||||
## 7. 완료 기준
|
||||
|
||||
- [x] `dds.test` AD forest와 두 테스트 사용자가 생성되었다.
|
||||
- [x] Keycloak LDAP bridge가 HTTPS OIDC JWT를 발급하고 각 사용자가 서로 다른 안정 subject를 가진다.
|
||||
|
||||
Reference in New Issue
Block a user