1
0
forked from baron/baron-sso

Add personnel dataset backup filtering

This commit is contained in:
2026-06-22 12:38:29 +09:00
parent 95485632a8
commit 1351c981a8
13 changed files with 1031 additions and 45 deletions

View File

@@ -0,0 +1,225 @@
# Production Personnel Dataset Backup and Staging Restore Design
## Estimate Time
Estimate Time: 2.5d
## 목적
프로덕션의 "인력정보" 관련 데이터만 주기적으로 백업하고, 스테이징에 복구해 운영 데이터에 가까운 검증 환경을 구축한다. 이 기능은 재해 복구용 full backup이 아니라 staging rehearsal용 논리 데이터셋 이관 기능이다.
기존 `make dump`/`make restore`는 저장소 단위 full backup을 목표로 한다. 이번 기능은 Hydra와 RP 정보를 제외해야 하므로 저장소 단위 필터인 `DUMP_SERVICES=postgres,ory-postgres`만으로는 충분하지 않다. 별도의 dataset profile을 두어 테이블/행/민감값을 명시적으로 제한해야 한다.
## 정책 전제
- Ory Stack은 identity, authorization, OAuth/OIDC 상태의 SoT다.
- Backend DB의 `users`는 Ory에 저장되지 않거나 Ory API만으로 조회하기 어려운 Baron 운영 read model이다.
- Hydra client, consent, token/session state와 Baron RP metadata는 이번 데이터셋 대상이 아니다.
- staging restore는 운영 복구가 아니므로 production credential/session을 그대로 들고 오지 않는다.
- Wiki는 사용 중이지만, 정책 업데이트 초안은 `docs/` 문서로 남기고 사람이 검토 후 Wiki에 반영한다.
## 제안 인터페이스
기존 백업 명령을 유지하면서 dataset profile을 추가한다.
```bash
make dump DUMP_SERVICES=postgres,ory-postgres DUMP_DATASET=personnel DUMP_MODE=maintenance
make restore RESTORE_SERVICES=postgres,ory-postgres RESTORE_DATASET=personnel CONFIRM_RESTORE=baron-sso
make restore-plan RESTORE_DATASET=personnel BACKUP=backups/baron-sso-backup-YYYYMMDD-HHMMSSZ
```
운영 자동화에서는 production에서 full backup만 만들고, staging에서 받은 full backup을 personnel dataset으로 필터링한 뒤 복구한다.
```bash
# Production
make dump DUMP_SERVICES=all DUMP_MODE=maintenance
# Staging
make filter-personnel-dump \
BACKUP=backups/prod-full-backup-YYYYMMDD-HHMMSSZ \
OUTPUT_BACKUP=backups/prod-personnel-filtered-YYYYMMDD-HHMMSSZ
make restore \
BACKUP=backups/prod-personnel-filtered-YYYYMMDD-HHMMSSZ \
RESTORE_DATASET=personnel \
RESTORE_SERVICES=postgres \
CONFIRM_RESTORE=baron-sso
```
`filter-personnel-dump`는 full backup의 `baron.dump`, `ory_kratos.dump`, `ory_keto.dump`를 staging scratch DB에 일시 복원한 뒤 `personnel` JSONL dataset을 생성한다. Hydra dump와 RP metadata는 filtered backup에 복사하지 않는다.
추가 환경 변수:
| 변수 | 기본값 | 의미 |
| --- | --- | --- |
| `DUMP_DATASET` | `full` | `full`은 기존 동작, `personnel`은 인력정보 논리 데이터셋 |
| `RESTORE_DATASET` | manifest 기준 | 복구할 dataset profile |
| `FILTER_SERVICES` | `postgres,ory-postgres` | full backup에서 personnel dataset으로 필터링할 서비스 범위 |
| `OUTPUT_BACKUP` | empty | `filter-personnel-dump`의 personnel dataset 출력 경로 |
| `PERSONNEL_TENANT_ROOT_SLUGS` | empty | 비어 있으면 전체 인력정보, 값이 있으면 지정 tenant root 하위만 |
| `PERSONNEL_INCLUDE_KRATOS_IDENTITIES` | `true` | staging 로그인/subject 일치 검증이 필요할 때 Kratos identity subset 포함 |
| `PERSONNEL_RESET_CREDENTIALS` | `true` | Kratos credential/session을 production 그대로 복구하지 않도록 강제 |
| `PERSONNEL_INCLUDE_WORKSMOBILE_MAPPING` | `true` | WORKS externalKey 비교와 조직도 검증용 mapping 포함 |
| `PERSONNEL_INCLUDE_OUTBOX` | `false` | queue state는 기본 제외, 장애 재현 시에만 별도 허용 |
`DUMP_DATASET=personnel``DUMP_SERVICES=all`에서도 Hydra DB와 RP metadata를 포함하지 않도록 내부적으로 차단한다. 사용자가 `DUMP_DATASET=personnel DUMP_SERVICES=ory-postgres`를 지정해도 `ory_hydra` dump는 생성하지 않는다.
## 백업 산출물 구조
```text
baron-sso-backup-YYYYMMDD-HHMMSSZ/
manifest.json
checksums.sha256
datasets/
personnel/
dataset-manifest.json
postgres/
users.jsonl
user_login_ids.jsonl
tenants.jsonl
tenant_domains.jsonl
user_groups.jsonl
worksmobile_resource_mappings.jsonl
ory_kratos/
identities.jsonl
identity_credentials.reset-plan.jsonl
ory_keto/
relation_tuples.jsonl
reports/
row-counts.json
exclusions.json
restore-plan.md
```
`personnel` dataset은 `pg_dump -Fc`만으로 만들지 않는다. 행 필터, 민감값 제거, RP/Hydra 제외를 안전하게 보장하려면 `COPY (SELECT ...) TO STDOUT` 또는 `psql` JSONL export를 사용한다. full backup은 기존 `postgres/baron.dump`, `postgres/ory_*.dump` 형식을 그대로 유지한다.
## 포함 범위
### Baron Postgres 포함
| 테이블/데이터 | 포함 이유 | 처리 |
| --- | --- | --- |
| `users` | 인력 기본 정보, 상태, 조직 표시 read model | 포함. `relying_party_id`는 null 처리 또는 제외 검증 |
| `user_login_ids` | 사번/로그인 ID 등 인력 식별자 | 포함 |
| `tenants` | 회사/조직/사용자 그룹 계층 | `COMPANY_GROUP`, `COMPANY`, `ORGANIZATION`, `USER_GROUP` 중심 포함 |
| `tenant_domains` | 회사 도메인 기반 소속 판단 | 포함 |
| `user_groups` | 조직도/부서 계층 | 포함 |
| `worksmobile_resource_mappings` | WORKS externalKey 기반 비교/동기화 기준 | `USER`, `ORGUNIT`만 포함 |
### Ory Kratos 포함
`PERSONNEL_INCLUDE_KRATOS_IDENTITIES=true`일 때만 포함한다.
- `identities`: subject UUID와 traits 기반 식별을 위해 포함한다.
- credential/session/recovery/verifiable address는 production 값을 그대로 복구하지 않는다.
- restore 단계에서 staging용 임시 credential 정책 또는 password reset-required 상태를 만든다.
- Kratos DB 직접 조작은 일반 write path가 아니므로 maintenance guard와 별도 확인값을 요구한다.
### Ory Keto 포함
- 사용자, tenant, user group membership/ownership 관계 tuple만 포함한다.
- RP namespace 또는 RP object를 참조하는 tuple은 제외한다.
- restore 후 Keto relation tuple subject/object가 복구된 user/tenant/group만 참조하는지 검증한다.
## 제외 범위
| 제외 대상 | 이유 |
| --- | --- |
| Hydra DB 전체 | OAuth2 client, consent, token/session은 staging RP 상태를 오염시킬 수 있음 |
| Baron RP metadata | 사용자가 명시한 비대상이며 staging RP 설정은 별도 관리 대상 |
| `rp_user_metadata` | RP별 custom claim 데이터라 personnel 공통 데이터가 아님 |
| `client_consents` | Hydra/RP consent read model 성격 |
| API key/client secret류 | staging secret과 충돌 위험 |
| audit/clickhouse logs | 인력정보 환경 구축의 필수 원장이 아님 |
| `worksmobile_outbox` | 큐 처리 상태라 반복 restore 시 중복 작업 위험 |
| Redis | 휘발성 cache/session |
## Restore 전략
staging restore는 DB 전체 drop/restore가 아니라 scoped replace 방식으로 설계한다.
1. restore 전 `restore-plan`을 생성해 포함/제외 테이블, row count, tenant scope, Hydra/RP exclusion을 표시한다.
2. backend worker, WORKS relay, Keto outbox relay를 중지한다.
3. dataset을 staging scratch schema 또는 임시 DB에 적재한다.
4. row count, foreign key, soft-delete, tenant hierarchy, user-login collision을 검증한다.
5. staging의 Hydra/RP 관련 테이블과 설정은 건드리지 않는다.
6. Baron personnel tables를 dependency order에 따라 replace/upsert한다.
7. Kratos identity subset을 포함한 경우 production credential/session을 제거하고 staging credential policy를 적용한다.
8. Keto personnel tuple만 replace/upsert하고 RP tuple은 보존한다.
9. `restore-verify`에서 다음 항목을 검증한다.
검증 항목:
- `users.id`와 Kratos `identities.id` 일치 여부
- `user_login_ids.user_id`, `tenant_id` 참조 무결성
- `tenants.parent_id`, `user_groups.parent_id` 계층 무결성
- `users.relying_party_id`가 남아 있지 않은지
- `rp_user_metadata`, Hydra dump 파일, Hydra restore step이 생성되지 않았는지
- Keto tuple이 복구 대상 user/tenant/group만 참조하는지
- WORKS mapping의 Baron resource 참조가 존재하는지
## 구현 위치
제안 파일:
- `scripts/backup/lib/dataset.sh`: dataset profile validation과 공통 manifest helper
- `scripts/backup/lib/personnel_dataset.sh`: personnel export/import SQL, exclusion guard
- `scripts/backup/dump.sh`: `DUMP_DATASET` 분기 추가
- `scripts/backup/restore.sh`: `RESTORE_DATASET` 분기와 scoped restore 추가
- `scripts/backup/restore-plan.sh`: dataset restore plan 출력
- `scripts/backup/lib/report.sh`: dataset row-count/exclusion report 표시
- `Makefile`: `DUMP_DATASET`, `RESTORE_DATASET`, `PERSONNEL_*` 변수 전달
## 테스트 계획
구현 전 RED 테스트를 먼저 추가한다.
1. `test/personnel_dataset_backup_policy_test.sh`
- `DUMP_DATASET=personnel` dry-run 또는 fixture run에서 Hydra dump가 계획되지 않는지 확인한다.
- `rp_user_metadata`, `client_consents`, `relying_parties` 계열 데이터가 dataset manifest에 포함되지 않는지 확인한다.
- unknown dataset profile을 거부하는지 확인한다.
2. `test/personnel_dataset_restore_policy_test.sh`
- `RESTORE_DATASET=personnel``CONFIRM_RESTORE=baron-sso` 없이 실패하는지 확인한다.
- restore-plan에 포함/제외 테이블과 credential reset policy가 표시되는지 확인한다.
- non-empty target guard와 scoped restore guard가 동시에 동작하는지 확인한다.
3. `scripts/backup/lib/personnel_dataset.sh` 단위 shell test
- tenant root scope가 있을 때 users/tenants/user_groups 쿼리가 같은 scope로 제한되는지 확인한다.
- `PERSONNEL_INCLUDE_OUTBOX=false` 기본값에서 outbox가 제외되는지 확인한다.
4. 통합 테스트
- fixture Postgres에 users/tenants/RP/Hydra 유사 데이터를 넣는다.
- personnel dataset dump를 실행한다.
- 빈 staging fixture DB에 restore한다.
- 인력정보는 들어오고 Hydra/RP/consent/custom claim 데이터는 남지 않는지 검증한다.
5. E2E 또는 smoke
- staging restore 후 AdminFront/User list와 OrgFront 조직도 조회가 정상인지 확인한다.
- 화면 변화가 있는 기능은 아니므로 스냅샷 업로드는 구현 범위에서 제외한다. 단, 복구 후 조직도 화면 검증이 필요하면 별도 E2E 이슈로 분리한다.
## 위험과 결정 필요 사항
1. Kratos identity 포함 여부
- 포함하면 staging에서 production과 같은 subject UUID로 검증할 수 있다.
- 대신 credential/session 민감값 제거가 필수이고, Kratos DB maintenance 예외 정책을 문서화해야 한다.
- 제외하면 인력/조직 화면은 검증 가능하지만 실제 로그인 subject 일치 검증은 제한된다.
2. restore 방식
- table replace는 단순하지만 staging 고유 사용자와 충돌할 수 있다.
- upsert는 staging 고유 데이터 보존에 유리하지만 삭제/퇴사 반영 정책이 복잡해진다.
- 기본안은 `PERSONNEL_RESTORE_MODE=replace-scoped`로 두고, staging 고유 tenant slug allowlist는 보호한다.
3. WORKS mapping 포함 여부
- externalKey 비교를 위해 포함하는 것이 좋다.
- outbox는 중복 실행 위험이 있어 기본 제외가 맞다.
## 구현 순서
1. Gitea 이슈에 본 설계와 테스트 RED 계획을 등록한다.
2. RED 테스트를 먼저 추가하고 실패를 확인한다.
3. dataset profile validation과 manifest/exclusion guard를 구현한다.
4. Baron Postgres personnel export/import를 구현한다.
5. Kratos/Keto subset 처리는 guard와 reset policy를 먼저 구현한 뒤 활성화한다.
6. `make restore-plan`, report, verification을 보강한다.
7. 테스트 통과 후 문서와 Wiki 반영 필요 여부를 검토한다.