1
0
forked from baron/baron-sso

custom claim 권한체크 확인

This commit is contained in:
2026-06-11 08:29:25 +09:00
parent 839ca9d407
commit 4d77060b5d
79 changed files with 4268 additions and 670 deletions

View File

@@ -4,9 +4,11 @@
## 결정
사용자 identity에 대해 PostgreSQL DB projection을 SSOT 일치 보장 대상으로 취급하지 않습니다.
사용자 identity에 대해 Backend DB 복제본이나 claim output을 SSOT 일치 보장 대상으로 취급하지 않습니다.
Baron SSO의 identity 원장은 Ory Kratos입니다. Redis는 Kratos identity를 빠르게 조회하기 위한 mirror/cache 계층이고, PostgreSQL `users`는 Baron 비즈니스 메타데이터, WORKS/Keto/RP 연동 참조, 감사 가능한 로컬 레코드로만 사용합니다.
Baron SSO의 identity 원장은 Ory Kratos입니다. Redis는 Kratos identity를 빠르게 조회하기 위한 mirror/cache 계층이고, Backend DB는 Ory에 저장되지 않거나 Ory API로 필요한 방식의 조회가 불가능한 데이터의 read model로만 사용합니다.
Ory에서 Redis cache로 웜업된 identity/조직 데이터는 frontend나 외부 API가 직접 소비하지 않습니다. Backend가 Redis와 허용된 read model을 조합해 cursor 기반 API로 제공합니다.
## 역할 분리
@@ -14,13 +16,13 @@ Baron SSO의 identity 원장은 Ory Kratos입니다. Redis는 Kratos identity를
| --- | --- | --- |
| Kratos `identities` | identity SSOT | 인증 주체, credentials, recovery/verifiable address의 원장 |
| Redis identity mirror | cache/read mirror | 빠른 목록/검색/단건 조회. stale 가능 |
| PostgreSQL `users` | business local record | tenant, WORKS, RP, Keto 연동에 필요한 Baron 로컬 상태 |
| Backend DB read model | Ory 보완 read model | Ory에 저장되지 않거나 조회 불가능한 업무/운영 데이터 |
PostgreSQL `users`의 visible count를 Kratos identity total로 표시하지 않습니다. 화면과 API에서는 identity mirror count와 local business user count를 분리해서 보여야 합니다.
Backend DB read model의 visible count를 Kratos identity total로 표시하지 않습니다. 화면과 API에서는 identity mirror count와 허용된 read model count를 분리해서 보여야 합니다.
## 현재 필드 대조
현재 코드 기준으로 Kratos traits와 backend DB `users`는 일부 필드를 중복 보관합니다. Redis mirror 전환 이후에는 Kratos traits를 인증/기본 identity 필드 중심으로 줄이고, Baron 업무/조직/연동 정보는 backend DB 전용으로 이동하는 방향을 기준으로 합니다.
현재 코드 기준으로 Kratos traits와 backend DB `users`는 일부 필드를 중복 보관합니다. Redis mirror 전환 이후에는 Kratos traits를 인증/기본 identity 필드 중심으로 줄이고, Baron 업무/조직/연동 정보는 Ory에 저장되지 않거나 조회가 불가능한 경우에만 Backend read model로 유지합니다.
### Kratos에 유지할 identity 필드
@@ -38,32 +40,32 @@ PostgreSQL `users`의 visible count를 Kratos identity total로 표시하지 않
| 필드 | 현재 코드 사용 | 전환 방향 |
| --- | --- | --- |
| `tenant_id` | 대표 테넌트, profile, local user sync | backend `users.tenant_id`와 membership/Keto 기준으로 이동. Kratos에는 최소 claim 캐시로만 허용 |
| `tenant_id` | 대표 테넌트, profile, local user sync | Keto relation과 Backend read model 기준으로 이동. Kratos에는 identity 원장 필드로 추가하지 않음 |
| `department` | 사용자 표시, 조직도, WORKS 비교 | backend `users.department` 또는 tenant membership metadata 기준 |
| `grade` | 직급 표시, role fallback legacy | backend `users.grade`. role fallback 용도 제거 |
| `position` | 직책 표시 | backend `users.position` |
| `jobTitle` | 직무 표시 | backend `users.job_title` |
| `affiliationType` | 내부/외부/게스트 구분 | backend `users.affiliation_type` |
| `relying_party_id` | RP admin profile 보조 | backend/RP relation 기준 |
| `additionalAppointments` | 다중 소속 표시/WORKS 연동 | backend membership metadata 또는 `users.metadata` 기준 |
| `additionalAppointments` | 다중 소속 표시/WORKS 연동 | Keto relation과 Backend read model 기준 |
| `sub_email`, `aliasEmails`, `secondary_emails`, `worksmobileAliasEmails` | WORKS alias 및 보조 이메일 | backend `users.metadata` 또는 명시 테이블 기준 |
| tenant UUID namespaced metadata | tenant별 custom schema 값 | backend `users.metadata` 또는 전용 custom-field storage 기준 |
### backend DB에만 저장되거나 DB가 원장이어야 하는 정보
### Backend read model로만 허용하는 정보
| 데이터 | 저장 위치 | Kratos에 두지 않는 이유 |
| 데이터 | 저장 위치 | Ory SSOT와의 관계 |
| --- | --- | --- |
| soft-delete 상태 | `users.deleted_at` | Baron 운영/감사 로컬 상태. Kratos identity 삭제/비활성화와 의미가 다 |
| Baron 사용자 상태 세부값 | `users.status` | WORKS provision/deprovision, org visible 정책과 결합된 업무 상태 |
| WORKS mapping/outbox/job 상태 | `worksmobile_*` 테이블 | 외부 SaaS 연동 상태이며 identity 인증 원장이 아님 |
| Keto outbox 및 relation sync 상태 | `keto_outboxes`, Keto | 권한/관계 원장과 처리 상태 |
| RP metadata/consent/usage | `rp_user_metadata`, `client_consents`, usage tables | RP 업무 데이터와 위임 상태 |
| tenant tree, slug, visibility, owner/admin | `tenants`, relation/outbox | 조직/권한 원장. Kratos traits에 넣으면 stale claim이 됨 |
| custom field schema 및 tenant별 값 | tenant config, `users.metadata`, related tables | 조회/검색/검증 정책이 tenant별로 달라짐 |
| `user_login_ids` row metadata | `user_login_ids` | Kratos는 identifier 값만 필요. 발급 tenant/field key는 backend 업무 정보 |
| audit/session activity projection | audit/clickhouse/local tables | 감사/운영 분석 데이터 |
| soft-delete 상태 | `users.deleted_at` | Ory Kratos identity 삭제/비활성화와 의미가 다른 Baron 운영 상태 |
| Baron 사용자 상태 세부값 | `users.status` | WORKS provision/deprovision, org visible 정책과 결합된 운영 데이터 |
| WORKS mapping/outbox/job 상태 | `worksmobile_*` 테이블 | 외부 SaaS 연동 상태이며 identity 원장이 아님 |
| Keto outbox 및 relation sync 상태 | `keto_outboxes`, Keto | 권한/관계 원장은 Keto이고 DB는 처리 상태 read model |
| RP metadata/consent/usage | `rp_user_metadata`, `client_consents`, usage tables | Ory에 저장되지 않거나 client 단위 조회가 불가능한 RP 업무 데이터 |
| tenant tree 표시/검색 metadata | `tenants`, relation/outbox | 관계 판단은 Keto, 표시/검색/slug 조회는 Backend read model |
| custom field schema 및 tenant별 값 | tenant config, `users.metadata`, related tables | Ory에 schema/검색 정책을 저장하거나 조회할 수 없는 tenant별 운영 데이터 |
| `user_login_ids` row metadata | `user_login_ids` | Kratos는 identifier 값 원장, 발급 tenant/field key는 Backend 검증용 read model |
| audit/session activity read model | audit/clickhouse/local tables | 감사/운영 분석 데이터 |
정리하면, Kratos에는 “로그인과 subject 확인에 필요한 최소 identity”만 남기고, 조직도/WORKS/RP/Keto/감사/tenant custom schema에 필요한 데이터는 backend DB가 맡습니다.
정리하면, Kratos에는 “로그인과 subject 확인에 필요한 최소 identity”만 남깁니다. 조직도/WORKS/RP/Keto/감사/tenant custom schema에 필요한 데이터도 Ory에 저장되거나 조회 가능한 경우에는 Ory를 기준으로 하고, 그렇지 않은 영역만 Backend read model을 허용합니다.
## 일관성 모델
@@ -87,6 +89,7 @@ Kratos Admin API를 backend 밖에서 직접 수정하는 경로는 운영 정
| 파일 | 역할 | 판정 |
| --- | --- | --- |
| `backend/internal/service/identity_write_service.go` | Kratos identity 변경의 중앙 write boundary. 성공/실패 후 Redis mirror 상태를 갱신 또는 stale 표시 | 허용. 신규 identity write는 이 서비스를 거쳐야 함 |
| `backend/internal/service/kratos_admin_service.go` | Kratos Admin API list/get/create/update/delete/password/session client | 허용. 이후 `IdentityWriteService`의 하위 client로만 사용 |
| `backend/internal/service/ory_service.go` | legacy IDP provider. create/password/verifiable address 변경 시 Kratos Admin API 호출 | 허용하되 write-through 책임은 상위 `IdentityWriteService`로 이동 |
@@ -100,7 +103,7 @@ Kratos Admin API를 backend 밖에서 직접 수정하는 경로는 운영 정
| admin 사용자 bulk 생성 | `backend/internal/handler/user_handler.go` | `OryProvider.CreateUser` | 허용. 부분 성공/실패별 mirror 갱신 필요 |
| admin 사용자 수정 | `backend/internal/handler/user_handler.go` | `KratosAdmin.UpdateIdentity`, 선택적 `OryProvider.UpdateUserPassword` | 허용. password 변경도 identity write audit에 포함 |
| admin 사용자 삭제/bulk 삭제 | `backend/internal/handler/user_handler.go` | `KratosAdmin.DeleteIdentity` | 허용. Redis mirror delete 또는 tombstone 갱신 필요 |
| 일반 회원가입 | `backend/internal/handler/auth_handler.go` | `IdpProvider.CreateUser` | 허용이지만 local DB sync가 goroutine 기반이라 write-through 기준에서는 약함 |
| 일반 회원가입 | `backend/internal/handler/auth_handler.go` | `IdpProvider.CreateUser` | 허용이지만 Backend read model sync가 goroutine 기반이라 write-through 기준에서는 약함 |
| 내 프로필 수정 | `backend/internal/handler/auth_handler.go` | `KratosAdmin.UpdateIdentity` | 직접 `PUT /admin/identities/{id}` 호출 제거 완료. 향후 `IdentityWriteService` write-through 대상 |
| 비밀번호 재설정/내 비밀번호 변경 | `backend/internal/handler/auth_handler.go` | `IdpProvider.UpdateUserPassword` | 허용. traits mirror와 별도 audit event 필요 |
| 조직 그룹 멤버 추가 | `backend/internal/service/user_group_service.go` | Kratos write 없음 | Kratos `tenant_id`, `department` write 제거 완료. 조직/부서 정보는 backend DB/Keto/WORKS 기준 |
@@ -112,6 +115,8 @@ Kratos Admin API를 backend 밖에서 직접 수정하는 경로는 운영 정
| super-admin 보장 CLI | `backend/cmd/adminctl/main.go`, `backend/internal/bootstrap/admin_account.go` | `CreateUser`, `UpdateIdentityPassword` | 운영 bootstrap/정비 경로. 실행 후 Redis mirror 갱신 또는 refresh 필수 |
| 초기 admin seed | `backend/internal/bootstrap/kratos_seed.go` | `IdpProvider.CreateUser` | startup bootstrap 경로. 신규 환경에서만 허용하고 반복 실행 영향 점검 필요 |
| role 보정 CLI | `backend/cmd/fix_kratos_roles.go` | `ListIdentities``UpdateIdentity` | 기본 dry-run. 실제 변경은 `--dry-run=false --maintenance-window --mark-mirror-stale` 없이는 거부 |
| WORKS 기준 Baron 보정 CLI | `backend/cmd/adminctl/worksmobile_sync.go` | `IdentityWriteService.UpdateIdentity` | 중앙 write boundary 강제. 변경 후 Redis mirror stale 표시 |
| RP custom claim traits sync | `backend/internal/handler/dev_handler.go` | `IdentityWriteService.UpdateIdentity` | 중앙 write boundary 강제. RP read model과 Kratos traits 동기화 잔여 경로는 Ory SSOT 전환 대상 |
### backend와 Kratos Admin API를 모두 우회하는 경로
@@ -127,7 +132,7 @@ Kratos Admin API를 backend 밖에서 직접 수정하는 경로는 운영 정
- Kratos identity write는 `IdentityWriteService` 하나로 모으고, 성공한 create/update/delete/password 변경이 audit와 Redis mirror write-through를 남기게 합니다.
- `auth_handler.updateKratosIdentity`처럼 `KRATOS_ADMIN_URL`을 직접 읽어 `admin/identities`를 호출하는 코드는 금지합니다.
- `backend/cmd/fix_kratos_roles.go`와 Kratos DB 직접 UPDATE 스크립트는 `--dry-run`, `--maintenance-window`, `--mark-mirror-stale` 같은 명시적 가드 없이는 실행하지 못하게 합니다.
- shell/SQL로 Kratos DB를 직접 수정한 경우에는 PostgreSQL projection이나 Redis mirror를 신뢰하지 않고, Kratos full refresh와 drift report를 먼저 실행합니다.
- shell/SQL로 Kratos DB를 직접 수정한 경우에는 Backend read model이나 Redis mirror를 신뢰하지 않고, Kratos full refresh와 drift report를 먼저 실행합니다.
- CI에 정적 정책 테스트를 추가해 `admin/identities` write 호출과 `UPDATE identities` SQL이 허용 파일 밖에 생기면 실패시킵니다.
## Redis 키 설계
@@ -155,7 +160,7 @@ Kratos Admin API를 backend 밖에서 직접 수정하는 경로는 운영 정
- `identityTotal`: Redis mirror 기준 Kratos identity 수
- `localUserTotal`: PostgreSQL `users` 기준 Baron 로컬 사용자 수
- `mirrorStatus`: Redis mirror 상태
- `items`: identity mirror와 local business metadata를 조합한 응답
- `items`: identity mirror와 허용된 Backend read model을 조합한 응답
Redis cache miss 발생 시:
@@ -163,11 +168,11 @@ Redis cache miss 발생 시:
2. fallback 성공 시 Redis mirror를 갱신합니다.
3. fallback 실패 시 SSOT 조회 실패로 응답합니다.
목록 조회는 Redis mirror가 `ready`가 아니면 경고 상태를 포함해야 합니다. DB projection을 대체 SSOT처럼 사용하지 않습니다.
목록 조회는 Redis mirror가 `ready`가 아니면 경고 상태를 포함해야 합니다. Backend read model을 대체 SSOT처럼 사용하지 않습니다.
## Front 전송과 cursor 보장
front로 전달되는 사용자 목록은 cursor 기반을 원칙으로 합니다. offset은 하위 호환 파라미터로만 유지하고, 신규 화면 또는 대량 조회 화면은 cursor 외 방식을 사용하지 않습니다.
front/API로 전달되는 사용자 목록은 Backend가 제공하는 cursor 기반을 원칙으로 합니다. offset은 하위 호환 파라미터로만 유지하고, 신규 화면 또는 대량 조회 화면은 cursor 외 방식을 사용하지 않습니다.
### API 계약
@@ -206,8 +211,8 @@ front로 전달되는 사용자 목록은 cursor 기반을 원칙으로 합니
| `adminfront` 사용자 목록 | `useInfiniteQuery``nextCursor` 사용 | 유지 |
| `adminfront` 일부 tenant/user group modal | `fetchUsers(20/100/1000, 0)` 단일 호출 | cursor helper로 전환 |
| `adminfront` bulk upload modal | `fetchUsers(10000, 0)` 단일 호출 | 금지. cursor 수집 helper 또는 서버 검증 API로 전환 |
| `orgfront` 조직도 | `fetchUsers(5000, 0)` 단일 호출 | cursor 기반 전체 수집 helper로 전환 |
| `orgfront` 조직 picker | `fetchUsers(5000, 0)` 단일 호출 | cursor 기반 전체 수집 helper로 전환 |
| `orgfront` 조직도 | Redis orgchart snapshot 기반 | 유지. Backend가 Ory/Redis/read model을 조합해 제공 |
| `orgfront` 조직 picker | Redis orgchart snapshot 기반으로 전환 | 유지. 인증 picker는 `fetchOrgChartSnapshot`, public picker는 token 기반 orgchart API 사용 |
| `orgfront/src/lib/adminApi.ts` | `UserListResponse``nextCursor` 없음 | 타입 계약 보완 |
공통 helper 원칙:
@@ -232,14 +237,15 @@ refresh 중 불일치 또는 실패가 발생하면:
## 금지 사항
- Kratos partial list를 full snapshot으로 간주하지 않습니다.
- PostgreSQL `users` Kratos identity total의 원장으로 사용하지 않습니다.
- Backend read model을 Kratos identity total의 원장으로 사용하지 않습니다.
- Redis mirror refresh 실패를 숨기고 `ready`로 표시하지 않습니다.
- 외부 도구가 Kratos Admin API를 직접 수정하도록 허용하지 않습니다.
## 전환 작업
1. `user_projection` 명칭과 API를 `identity_mirror` 성격으로 분리합니다.
1. legacy user sync 명칭과 API를 `identity_mirror` 성격으로 분리합니다.
2. Redis repository에 Set/Sorted Set 또는 scan 가능한 index 연산을 추가합니다.
3. Kratos create/update/delete 성공 직후 Redis write-through 테스트를 추가합니다.
4. admin 사용자 목록 응답에서 identity count와 local user count를 분리합니다.
5. 기존 DB projection 기반 count 사용하는 화면과 WORKS 비교 경로를 점검합니다.
5. 기존 Backend DB count를 identity count처럼 사용하는 화면과 WORKS 비교 경로를 점검합니다.
6. Kratos identity 변경은 `IdentityWriteService` 경유를 강제하고, 직접 `KratosAdmin.UpdateIdentity` 경로를 정책 테스트로 차단합니다.