Skip to content

[Test] 실제 HTTP 권한 회수와 OpenSQL 승격 후 차단 검증 - #544

Merged
Gimini-3 merged 2 commits into
developfrom
feature/542
Oct 17, 2026
Merged

Gimini-3 merged 2 commits into
developfrom
feature/542

Conversation

@Gimini-3

@Gimini-3 Gimini-3 commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

목적

Closes #542

문서 접근 권한을 회수한 뒤 DB Primary가 바뀌어도, 사용자의 새 HTTP 요청에서 회수한 문서가 다시 노출되지 않는지 확인했습니다. DB의 권한 행이 삭제됐다는 확인을 실제 로그인·JWT 인증·상세/본문/다운로드·새 검색 응답까지 연결하는 시험입니다.

확인하려는 질문은 다음과 같습니다.

  1. 권한 회수가 204로 확정된 뒤 Primary를 중단해도 회수 기록이 새 Primary에 남는가?
  2. 예전 접근 허용 캐시가 남아 있어도 상세·본문·파일·새 검색에서 차단되는가?
  3. 모든 요청을 막아서 통과하는 것이 아니라, 권한이 유지된 대조 문서는 계속 이용할 수 있는가?

최종 회차 http-primary-run04는 통과했습니다. 승격 뒤 대상 문서는 403 9개, 대조 문서는 200 9개, 새 검색은 200 3개였으며, 이 21개 새 응답에서 금지 문구는 0건이었습니다. 공개 서비스 설정과 기능 플래그는 변경하지 않았습니다.

상세 결과·실행별 원장 · 최종 집계 · 설계

테스트 환경

실행일은 **2026-10-17, 시각 기준은 KST(UTC+09:00)**입니다. 애플리케이션과 HTTP 클라이언트는 로컬에서, DB·Proxy는 기존 라이선스 호스트의 별도 시험 컨테이너에서 실행했습니다.

구성 실제 사용한 환경과 역할
HTTP·인증 실제 루프백 Tomcat, Java HTTP client, /auth/login, BCrypt, JWT 필터, SecurityConfig, CurrentUser
권한·데이터 실제 Controller·Service·JPA, 권한 회수 원장, 문서·버전·Chunk·Embedding·검색 접수/결과 DB 저장
캐시 로컬 전용 Valkey. 회수 뒤 의도적으로 오래된 접근 허용 캐시를 복원해 최종 권한 검사를 자극
DB 격리 OpenSQL 3노드. PostgreSQL 17.8·Patroni 4.0.5, 지정 동기 모드·strict 활성화, 동기 Standby 수 1
연결 전용 SSH/IAP 경로 → 시험 OpenProxy → 역할 어댑터 → 격리 OpenSQL
원본 파일 외부 저장소 대신 실행별 고유 합성 바이트를 담은 메모리 저장소. 다운로드 바이트와 SHA-256을 대조
검색·답변 질의 임베딩·검색 후보·답변 생성은 통제된 시험 대역. 후보에 회수 대상도 포함시켜 실제 최종 인가가 제거하도록 구성
격리 범위 공개 DB와 컨테이너·데이터·포트·DCS 경로 분리. 기존 호스트와 일부 기반 인프라는 공유
그림 1 — 실제 실행 경로와 시험 대역의 경계

로컬 Java HTTP client
  ├─ 소유자/대상 사용자: POST /auth/login → 실제 JWT 발급
  └─ JWT로 문서·권한·검색 API 호출
            │
            ▼
실제 Tomcat → JWT 필터·CurrentUser → Controller·Service
            │
            ├─ 권한 회수: DocumentPermissionRevocationFacade.revoke()
            ├─ 조회: 실제 권한 원장으로 접근 가능 여부 판정
            └─ 검색: SearchFacade의 최종 Primary 인가
                      │
         시험 OpenProxy → 역할 어댑터 → 격리 OpenSQL 3노드
                      │
          실제 문서·권한·회수 기록·검색 결과 저장

시험 대역: 파일 원본 바이트 / 임베딩 / 검색 후보 / 합성 답변
실제 확인: 인증·HTTP 상태·최종 인가·DB 저장·자동 승격

로그인과 HTTP 인증을 생략한 시험이 아닙니다. 다만 실제 Ollama·GCS를 사용한 전체 서비스 E2E와도 구분합니다. 설치된 원본 OpenProxy의 동기 역할 호환성 제약 때문에 역할 어댑터를 거친 결과입니다.

테스트 방법

추가한 시험 구성

파일·구성 역할
PermissionHttpFailoverIntegrationTest.java 실제 로그인부터 회수·승격 후 HTTP 응답과 최종 DB 원장을 연결해 검증
backend/build.gradle 수동 실행용 permissionHttpHaTest, 별도 태그·전용 루프백 주소·새 실행 디렉터리 검사
설계 문서 실제 경로·시험 대역·합격 기준·격리 경계 설명
시험 결과 디렉터리 회차별 HTTP 응답·앱 로그·장애 제어·수집 한계와 집계를 분리 보존

실행 절차와 합격 기준

  1. 실행별 고유 문서 2개를 준비합니다. 하나는 권한을 회수할 대상, 다른 하나는 정상 접근을 확인할 대조 문서입니다.
  2. 소유자와 대상 사용자가 실제 로그인합니다. 인증 없는 상세 조회는 401, 회수 전 두 문서의 상세·본문·파일은 200이어야 합니다. 새 검색에는 결과·합성 근거가 각각 2개 있어야 합니다.
  3. 소유자가 DELETE /permissions/documents/{documentId}/{permissionId}를 요청 키 K와 함께 호출합니다. 204를 확인한 뒤, 시험 목적으로 오래된 USER 접근 허용 캐시를 다시 유효하게 만듭니다.
  4. 회수 직후 대상은 403, 대조 문서는 200, 새 검색에서는 대상 문서·문구가 제외되는지 확인합니다.
  5. 격리 Primary만 중단하고 native Patroni의 자동 승격을 관측합니다. 이전 Primary는 중지한 상태로 유지합니다.
  6. 새 Primary 경로에서 상세·본문·파일·새 검색을 3회 반복합니다. 대상 차단과 대조 문서 정상 이용을 동시에 확인합니다.
  7. 새 Primary에서 K의 회수 원장 1행·대상 권한 0행을 조회합니다. 차단된 파일 요청이 원본 저장소를 읽지 않는지도 검사합니다.
그림 2 — 회수 확정부터 승격 후 사용자 응답까지

회수 전: 대상 권한 있음 → 두 문서 조회 200 / 검색 결과 2개
  │
소유자: 요청 키 K로 권한 회수
  → Facade: 동기 커밋을 거쳐 회수 확정 → HTTP 204
  → 시험 코드: 예전 허용 캐시를 의도적으로 복원
  → 실제 조회·검색: 대상 차단, 대조 문서 정상
  │
외부 시험 제어기: 격리 Primary 중단
  → Patroni: 새 Primary 자동 승격 관측
  → 이전 Primary는 중지 상태 유지
  │
대상 사용자: 새 Primary 경로로 3회 반복
  ├─ 회수 대상 상세·본문·파일 → 403 × 9
  ├─ 대조 문서 상세·본문·파일 → 200 × 9
  └─ 새 검색 → 200 × 3, 대조 결과·합성 근거만 각 1개
  │
최종 DB 감사: 요청 키 K 1행 / 회수 대상 권한 0행

예전 캐시를 복원한 이유는 캐시가 오래됐을 때도 최종 DB 권한 검사가 차단하는지 확인하기 위해서입니다. 대조 문서는 서비스 전체 차단을 성공으로 오인하지 않기 위한 기준입니다.

실행 명령과 기록 방식

./backend/gradlew -p backend permissionHttpHaTest

전용 루프백 JDBC·migration 주소, 시험 비밀번호·JWT 비밀, 새 회차 디렉터리와 격리 장애 제어기 준비가 필요합니다. 명령만 실행하면 환경 생성·장애 주입까지 자동으로 끝나는 구성은 아닙니다. 안전 검사가 맞지 않으면 실행을 거부하며, 기본 CI에서는 이 수동 태그를 제외합니다.

응답 상태·지연·금지 문구 검출 여부·파일 해시를 실행 중 http.jsonl에 기록했습니다. 앱 로그와 장애 제어 원장은 별도 파일입니다. 로그인 비밀번호·JWT·원시 로그인 응답·비공개 인프라 식별자는 기록하지 않았습니다.

관측 결과

최종 회차: http-primary-run04

확인 항목 관측값 해석
실제 로그인 소유자·대상 사용자 각 200 실제 JWT 인증 경로로 시험
인증 없는 상세 401 1개 인증 없는 요청 차단
회수 전 두 문서 상세·본문·파일 200, 검색 결과·합성 근거 각 2개 회수 전 접근 가능한 상태 확인
회수 요청 K에 대해 204 회수 확정 뒤 장애 주입
승격 후 회수 대상 상세·본문·파일 각 3회, 403 9개 새 응답에서 회수 권한이 되살아나지 않음
승격 후 대조 문서 상세·본문·파일 각 3회, 200 9개 정상 문서 접근·다운로드 해시 유지
승격 후 새 검색 200 3개, 결과·합성 근거 매번 각 1개 회수 대상 문서 ID·문구 제외
금지 내용 승격 후 21개 새 응답에서 0건 이 시험 요청 범위에서 노출 없음
최종 DB K 원장 1행, 대상 권한 0행 회수 기록 보존·권한 부활 없음
수집 응답 로그인 2개 + 그 외 37개 = 총 39개 승격 후 응답 21개는 전체의 일부
그림 3 — 장애 시간과 최종 상태의 의미

14:19:48.173526 KST  격리 Primary 중단 확인
          │
          │ 19.451936초
          ▼
14:20:07.625462 KST  native Patroni 자동 승격 관측
          │
          ▼
새 Primary에서 실제 HTTP 반복·최종 DB 감사 완료
  ├─ 금지 내용 0건 / 대조 문서 정상
  └─ 회수 원장 1행 / 대상 권한 0행

측정값: '중단 확인 → DB 승격 관측' 시간
미측정: 사용자 관점의 전체 서비스 복구 시간(RTO)

약 19.452초는 위 두 관측 시각의 차이입니다. 전체 서비스 RTO나 이전 Primary의 재가입 시간으로 해석하지 않습니다.

기본 CI 실행은 backend·frontend가 통과했습니다. 이 수동 클라우드 시험의 통과 근거는 위 실행별 원장이며 기본 CI 통과와 구분합니다. CodeRabbit은 저장소의 수동 검토 요구로 리뷰를 건너뛰었습니다.

검증 과정에서 확인된 사항

준비·관측 방법을 보완한 회차

회차 확인한 사항 대응과 최종 판정에서의 취급
http-primary-run01 local 프로필의 hybrid 설정이 의도한 검색 후보 대역을 우회하여 준비 단언을 통과하지 못함 설정을 명시적으로 고정. 장애 주입 전 종료했으며 최종 통과 집계에서 제외
http-primary-run02 반복 fixture의 같은 바이트가 파일 해시·크기 유일 제약에 걸림 실행별 고유 바이트로 변경. 장애 주입 전 종료했으며 최종 통과 집계에서 제외
http-primary-run03 회수 204와 자동 승격은 관측했지만 이전 Primary 재가입 대기가 90초를 넘어서 후속 HTTP 확인을 완료하지 못함 해당 회차는 미완료 판정을 유지. 사후 읽기 감사에서 원장 1·권한 0을 확인했지만 전체 회차 통과로 바꾸지 않음
http-primary-run04 새 Primary의 HTTP 차단 확인을 이전 노드 재가입과 분리해 수행 위 합격 기준 통과. 이전 노드 자동 재가입 성공은 검증하지 않음

run03의 사후 진단에서 pg_rewind 진행을 관측했지만 재가입 지연의 정확한 원인은 확정하지 않았습니다. 격리 데이터·DCS를 재초기화한 뒤 run04를 별도로 수행했습니다.

run01 제어 기록 일부는 복수 기록자의 쓰기로 한 줄이 손상됐습니다. 원본·수집 한계를 보존하고 시간·성공 집계에서 제외했습니다. 초기 실행 로그의 위치 표기도 실행기 분기 누락으로 부정확했으며 실제 GCP 경로를 결과 문서에 명시했습니다. 최종 실행 소스 SHA-256은 원장에 보존했습니다.

이번 결과로 확인하지 않은 범위

  • 실제 Ollama의 답변 품질·비동기 RAG Worker·GCS 원본·공개 브라우저·과거 대화 재조회는 포함하지 않았습니다.
  • 원본 OpenProxy의 동기 역할 직접 지원이나 공급사 지원 성공을 입증한 시험은 아닙니다.
  • 차단 결과는 회수 확정 후 새로 보낸 요청 기준입니다. 이미 사용자에게 전달된 내용의 소급 회수까지 주장하지 않습니다.

시험 자원 정리와 공개 환경 보존

후속 #543 비교까지 같은 격리 자원을 사용한 뒤 2026-10-17 15:12:53 KST에 최종 정리를 완료했습니다. 정리 원장에 시험 DB·Proxy·Valkey·데이터·시험 DCS·전용 터널·런타임 비밀 제거를 기록했습니다. 기존 VM과 원래 터널은 유지했습니다.

공개 설정 해시 불변, 공개 클러스터 리더 1대·streaming Standby 2대, 동기 모드/strict 미변경을 확인했습니다. 새 OS Login 키·IAM 변경은 0건입니다.

@Gimini-3
Gimini-3 requested a review from kangcheolung October 17, 2026 05:23
@coderabbitai

coderabbitai Bot commented Oct 17, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: Repository: DocGrid/docgrid/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: de101f1b-7fff-43ce-8050-672edfb85081
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Gimini-3 Gimini-3 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수동 검토

HEAD c1478fd990298b08643865b50d6a0148f147e798의 시험 코드와 원본 기록을 대조했습니다.

  • 실제 루프백 Tomcat·HTTP client·로그인·JWT 필터·Valkey를 사용하며 SecurityContext 주입이나 standalone MVC인 것으로 오인할 여지가 없도록 범위를 명시했습니다.
  • 회수 전 양성 대조, 인증 없는 401, 회수 204, stale 캐시 주입, 승격 뒤 3회 차단과 허용 대조를 모두 단언합니다. 503만 반환하는 상태를 성공으로 통과시키지 않습니다.
  • 실제 SearchFacade의 Primary 최종 인가와 검색 저장을 사용하고 파일·임베딩·후보·합성 답변 대역을 구분했습니다. 실제 모델·GCS·과거 대화의 보장을 주장하지 않습니다.
  • 승격 뒤 대상 403 9개·대조 200 9개·새 검색 200 3개, 금지 문구 0건과 집계가 일치합니다. 현재 Primary의 실제 요청 키 1개·권한 행 0개를 단언했습니다.
  • 회차별 실패를 보존했고 이전 Primary 재가입 90초 초과를 숨기지 않았습니다. 최종 통과 회차는 이전 노드를 중지한 상태의 사용자 응답 검증이며 재가입 문제 해결을 주장하지 않습니다.
  • 첫 제어 로그 한 줄 손상과 실행 위치 메타데이터 오류를 원본·수집 한계로 남겼습니다.
  • 기본 CI에서 수동 태그를 제외하고 전용 loopback DB·migration·HTTP 바인딩과 새 원장을 강제합니다. 생산 코드·공개 설정 변경은 없습니다.
  • 파일 24개에서 비공개 식별자·개인키·접속 비밀·로컬 절대 경로 발견 0개이며 실행 소스 해시가 커밋할 Java와 일치했습니다.

후속 #543을 위해 격리 자원을 유지하는 상태를 명시했습니다. 최종 CI 통과 뒤 병합할 수 있으며 CodeRabbit 검토 건너뜀을 사람의 승인으로 간주하지 않습니다.

@Gimini-3
Gimini-3 merged commit 6e14adf into develop Oct 17, 2026
3 checks passed
@Gimini-3

Copy link
Copy Markdown
Contributor Author

후속 격리 비교 #546을 마치고 이 HTTP 시험과 공유했던 자원을 최종 정리했습니다.

  • 시험 Proxy·OpenSQL 컨테이너3개·확인된 시험 이미지3개·데이터 디렉터리·시험 DCS 키·로컬 Valkey·전용 터널·런타임 비밀 파일 제거.
  • 시험 DCS 남은 키0, 시험 리스너0. 원래 터널3개와 기존 VM 유지.
  • 공개 설정 hash 불변, 공개 리더1·streaming Standby2 확인. 새 OS Login 키·IAM 변경0.

정리 원장. 옛 시험 Primary의 자동 재합류가 성공했다는 의미는 아니며, 그 한계는 기존 시험 기록대로 유지합니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Test] 장애·승격 후 실제 HTTP 문서 권한 차단 검증

1 participant