feat: PostgreSQL RLS 기반 사업장 2차 격리 활성화 - #135
Merged
Merged
Conversation
- #125의 V40/V41 다음 V42 migration으로 tenant 보호 테이블 38개의 RLS를 활성화 - migration의 lock timeout과 statement timeout을 설정 - 정책 테이블, RLS 활성 테이블, FORCE 미적용 상태를 schema 테스트로 검증 - RLS 환경에서 runtime timeout 테스트가 실제 tenant context로 UPDATE를 수행하도록 보정
krestar
marked this pull request as draft
August 11, 2026 05:10
12 tasks
Member
|
Migration 번호 조율 공유드립니다. 제가 진행 중인 담당자 변경 PR #143의 공통 Migration에서 V42를 사용하기로 했습니다. 이 PR의 PostgreSQL 전용 권장 병합 순서는 다음과 같습니다.
서로 다른 migration 경로라도 Flyway version은 함께 비교되므로 V42가 겹치지 않도록 조정이 필요합니다. |
- main 브랜치 기준으로 충돌 방지를 위해 수정합니다.
Contributor
Author
|
RLS 활성화 시점 가이드
|
Member
|
Server #198에서 tenant table |
Member
|
#199가 추가한 |
# Conflicts: # src/test/java/com/fowoco/server/PostgreSqlMigrationTests.java
- RLS 활성화 migration을 최신 main 다음 버전인 V58로 재번호화 - stay_verification_case와 worker_archive를 RLS 활성화 대상에 추가 - 제한 runtime role 격리 테스트에 두 테이블의 fixture, 조회 격리, 쓰기 차단, cleanup 검증 추가
This was referenced Aug 17, 2026
krestar
marked this pull request as ready for review
August 17, 2026 10:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
왜 필요한가요?
현재의
ActorContext + company_id 범위 Repository + tenant-aware DB 제약을 유지하면서,PostgreSQL이 다른 사업장의 행을 한 번 더 차단하도록 준비된 RLS 정책을 실제로 활성화합니다.
이 PR은 RLS가 애플리케이션 권한 검사나 Repository의
company_id조건을 대신하게 하지 않습니다.애플리케이션 코드에 범위 조건이 누락되더라도 runtime DB role에서 다른 사업장 행을 조회·생성·수정·삭제할 수 없도록 DB 차단 계층을 추가합니다.
mainmerge 즉시 배포와 수동 Smoke Test가 이어지므로, PR merge만으로 Issue를 자동 종료하지 않습니다.live 검증까지 끝난 후 #34를 수동으로 닫습니다.
무엇이 바뀌나요?
DB migration
V58__enable_postgresql_rls.sql을 추가합니다.ENABLE ROW LEVEL SECURITY를 적용합니다.FORCE ROW LEVEL SECURITY는 적용하지 않습니다.lock_timeout = 5sstatement_timeout = 30s테스트 계약
relrowsecurity = true인 테이블: 동일한 40개relforcerowsecurity = true인 테이블: 0개notification에 더해 최신 main에서 추가된stay_verification_case,worker_archive까지 RLS 활성화 대상에 포함합니다.UPDATE 0 rows로 통과하는 false positive를 막습니다.1인지 검증합니다.영향받는 테이블
총 40개입니다.
company,company_settings,user_account,refresh_token,user_agreement_consent,password_reset_tokenworker,worker_document,stored_file,task,task_checklist_item,task_transition_history,document_request_draft,document_request_draft_type,approval_request,external_submission,task_evidence,audit_event,workflow_case,document_ocr_run,notification,stay_verification_case,worker_archiveworker_link,worker_response,worker_response_upload,worker_document_upload_idempotency,worker_import_job,worker_import_row,worker_import_commit_idempotencyai_run,ai_attempt,ai_question,ai_candidate,ai_candidate_decision_batch,ai_candidate_decision,ai_candidate_decision_taskevent_publication,event_consumption,outbox_manual_retry어떻게 검증했나요?
기존 자동 테스트 기록
PostgreSQL 16.14를 사용한 기존 로컬 test report에서는 다음 결과를 확인했습니다.
실행 명령:
주요 PostgreSQL 검증 결과:
PostgreSqlMigrationTests: 1/1 통과PostgreSqlRuntimeTimeoutBehaviorIntegrationTest: 3/3 통과PostgreSqlRestrictedRoleHttpE2ETest: 7/7 통과PostgreSqlRlsIsolationTest: 1/1 통과PostgreSqlTenantDatabaseContextTest: 7/7 통과OutboxIntegrationTest: 6/6 통과AuthRefreshPostgreSqlConcurrencyTest: 1/1 통과restricted-role negative probe에서 기록되는 SQLSTATE
42501은 의도한 access-denied 결과입니다.기존 수동 RLS 검증에서 확인한 Seed 충돌
과거 38개 정책 대상 상태에서 동일한 RLS 활성화 SQL을 수동 적용해 Seed/RLS 충돌과 runtime 동작을 확인했습니다.
이 기록은 실패 원인에 대한 근거로 유지하되, 현재 V58/40개 대상의 최종 migration 검증을 대신하지 않습니다.
fowoco_migration과fowoco_runtime을 분리해 생성했습니다.DEMO_SEED_ENABLED=true로 기존 Demo/Test fixture를 생성했습니다.EXECUTE권한을 부여했습니다.DEMO_SEED_ENABLED=true재기동이 아래 원인으로 실패함을 확인했습니다.company행을 보지 못함companyinsert를 시도함WITH CHECK가 SQLSTATE42501로 차단함DEMO_SEED_ENABLED=false, outbox enabled 상태로 정상 기동했습니다.이 실패를 근본적으로 해결하는 선행 작업이 #204입니다.
수동 runtime Smoke Test
/api/v1/auth/me성공/documents에서 Demo/Test 계정별 worker/task 격리 확인company조회가 0건임을 확인bootstrap_company_id_by_normalized_email,bootstrap_company_id_by_refresh_token_hash,bootstrap_claim_event_publications호출에 permission/RLS 오류가 없음을 확인EXECUTE권한 확인42501, scheduled-task 오류가 없음을 확인현재 수동 로그의 outbox 검증은 빈 polling 경로까지 확인했습니다.
Merge 전 격리 환경에서는 실제 event 1건의
claim → tenant context 설정 → handler → complete까지 추가로 확인합니다.별도 관찰 사항
GET /api/v1/notifications를 cursor 없이 호출할 때 PostgreSQL이 nullable cursor placeholder 타입을 추론하지 못하는SQLSTATE
42P18이 관찰됐습니다.RLS, bootstrap 권한, outbox와 독립적인 notification 조회 쿼리 문제이며 이 PR에서는 수정하지 않습니다.
Demo Seed 선행 작업 — #204
현재 dev 배포 환경은
DEMO_SEED_ENABLED=true이며 기존 PostgreSQL PVC에 Demo/Test fixture가 저장되어 있습니다.현재
main의 Demo Seed runner는 RLS 활성 상태의 제한 Runtime Role과 호환되지 않기 때문에 #204를 이 PR보다 먼저 개발·배포·검증합니다.#204는 다음을 목표로 합니다.
TenantDatabaseContext를 통한app.company_id설정RLS OFF 기존 fixture → RLS ON + 제한 Runtime Role → 같은 DB 재기동전환 시나리오 검증현재 선택한 배포 경로는 다음과 같습니다.
DEMO_SEED_ENABLED=true상태에서 기존 DB/PVC 재기동 회귀와 멱등성을 확인합니다.DEMO_SEED_ENABLED=true + 제한 Runtime Role + RLS enabled최초/재기동, tenant 격리, fixture 멱등성을 확인합니다.#204가 배포·검증되기 전에 #135를 먼저 배포해야 하는 긴급 상황에서는
DEMO_SEED_ENABLED=false를 임시 우회책으로 사용할 수 있습니다.이 경우 기존 PVC 데이터는 유지되지만 fixture 자동 복구·보충은 중단되며, #204 검증 전에는 Seed를 다시
true로 전환하지 않습니다.현재 migration 순서와 선행 관계
main의 PostgreSQL RLS 준비 migration은 V57까지 존재합니다.V58__enable_postgresql_rls.sql**입니다.stay_verification_case,worker_archive가 포함됩니다.DEMO_SEED_ENABLED=true를 유지한 feat: PostgreSQL RLS 기반 사업장 2차 격리 활성화 #135 배포의 hard prerequisite입니다.main에 갱신하고 전체 PostgreSQL 테스트와 Demo Seed/RLS 전환 리허설을 반복합니다.Merge 조건
아래 항목을 모두 충족하기 전에는 merge하지 않습니다.
코드·migration
V58__enable_postgresql_rls.sql인지 확인stay_verification_case,worker_archive가 포함되는지 확인main에 반영main으로 갱신validate성공 및 pending migration 없음Seed
DEMO_SEED_ENABLED=true재기동 회귀 성공RLS OFF 기존 fixture → RLS ON + 제한 Runtime Role전환 후 Seed 재기동 성공live DB preflight
flyway_schema_history에서 현재 main의 migration 성공 상태와 V58 미적용 확인current_user로 확인rolsuper=false,rolbypassrls=false,rolcreaterole=false,rolcreatedb=falseTRUNCATE,REFERENCES, DDL 권한이 없음EXECUTE=truerollout 준비
mainmerge 중지DISABLE ROW LEVEL SECURITY하는 forward rollback patch 준비 및 리뷰배포 절차
server/.github/workflows/deploy.yml은mainpush마다 자동 배포합니다.따라서 merge가 곧 live DB migration 시작 버튼입니다.
1. Merge 전
DEMO_SEED_ENABLED=true재기동 회귀가 통과했는지 확인합니다.mainmerge를 중지합니다.긴급 우회로 Seed를 비활성화해야 하는 경우에는 Secret 변경 후 기존 Pod를 먼저 재시작해 실제 환경변수 적용과 기존 Demo/Test 데이터 보존을 확인합니다.
2. Merge 및 V58 적용
deployworkflow를 단독으로 모니터링합니다.mainmerge를 하지 않습니다.lock_timeout=5s또는statement_timeout=30s로 실패하면 원인을 확인한 뒤 별도 시간대에 재시도합니다.3. 배포 후 Smoke Test
자동 health check 외에 다음을 제한 Runtime Role로 확인합니다.
/health200/api/v1/auth/mepermission denied, 예상하지 않은 SQLSTATE42501, RLS policy violation, scheduled-task 오류 없음DEMO_SEED_ENABLED=true상태의 동일 DB 재기동 성공장애 대응·rollback
DEMO_SEED_ENABLED의 실제 새 Pod 값을 확인합니다.flyway clean, schema history 수동 수정,flyway repair로 되돌리지 않습니다.DISABLE ROW LEVEL SECURITY를 적용합니다.company_id범위와 tenant-aware 제약은 그대로 유지합니다.DISABLE ROW LEVEL SECURITY는 배포 담당자 승인과 실행 기록이 있는 최후 수단으로만 사용하고,이후 동일 상태를 표현하는 forward migration을 반드시 추가합니다.
보안·개인정보
company_id조건과 tenant-aware FK/UNIQUE를 유지API·DB·운영 영향
V58__enable_postgresql_rls.sqlDEMO_SEED_ENABLED=true유지