# 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 반영 필요 여부를 검토한다.