# Gateway 필드 변환 및 보안 문제 해결 ## 오전 10:58 ### 문제 1: 헬스체크 엔드포인트 추가 **상황**: - 각 서비스마다 다른 헬스체크 엔드포인트 **해결**: ```python # rb10508_micro/app/main.py @app.get("/healthz") async def healthz(): """단순 헬스체크 - 서비스 생존 확인만""" return {"status": "ok"} ``` - 루트 레벨에 `/healthz` 추가 - API 프리픽스 없이 접근 가능 - 빠른 응답 (< 100ms) ### 문제 2: UUID 형식 오류 **원인**: - Frontend가 username(`happybell80`) 전송 - Gateway가 UUID 형식 기대 - `invalid UUID 'happybell80': length must be between 32..36 characters` **해결**: ```python # robeing-gateway/app/database.py async def get_robeing_info(username: str): """username으로 직접 조회""" query = text(""" SELECT ... FROM workspace_member wm JOIN user u ON wm.user_id = u.id WHERE u.username = :username """) ``` - users 테이블 JOIN으로 username → UUID 변환 - UUID 체크 로직 제거 (Frontend는 항상 username 사용) ### 문제 3: nginx 직접 프록시 vs Gateway 라우팅 **현재 구조 분석**: ``` # 원래 설계 (문서) Frontend → nginx(/gateway/) → Gateway(8100) → rb10508_micro # 실제 nginx 설정 location ^~ /rb10508/ { proxy_pass http://192.168.219.52:10508/; # 직접 프록시 } location ^~ /gateway/ { proxy_pass http://localhost:8100/; # Gateway 서비스 } ``` **문제점**: - `/rb10508/`는 Gateway 우회 - `message` → `text` 필드 변환 안됨 - 422 Unprocessable Entity 에러 **해결 방안 검토**: 1. nginx 모든 요청을 Gateway로 → **Gateway 부하 문제** 2. rb10508_micro가 두 필드 모두 지원 → **실용적** ### 문제 4: 사용자 인증 검증 부재 (보안 위험) **현재 상황**: ```javascript // 누구나 헤더 조작 가능 fetch('/gateway/api/chat', { headers: {'X-User-Id': 'admin'} // 위장 가능! }) ``` **위험**: - 사용자 위장 가능 - 로빙 접근 권한 우회 - 데이터 유출 위험 **필요한 조치**: - JWT 토큰 검증 로직 추가 - auth-server와 연동 - 실제 사용자 확인 필수 ## 교훈 ### 1. **헬스체크 설계** - 빠른 응답 우선 (복잡한 체크는 별도 엔드포인트) - 루트 레벨 배치로 프리픽스 문제 회피 ### 2. **식별자 설계** - Frontend는 사용자가 이해하는 값 사용 (username) - Backend에서 내부 ID 변환 - UUID는 내부용으로만 사용 ### 3. **Gateway 아키텍처** - 모든 요청을 Gateway로 보내면 병목 발생 - 실용적 접근: 서비스가 유연하게 처리 - 점진적 마이그레이션 전략 필요 ### 4. **보안 최우선** - 헤더 기반 인증은 위험 - JWT 토큰 검증 필수 - 신뢰 경계 명확히 설정 ## 현업 패턴 ### Gateway 부하 해결: 1. **Service Mesh**: 각 서비스에 Sidecar 프록시 2. **Gateway 클러스터링**: 여러 Gateway 인스턴스 3. **하이브리드**: 중요 요청만 Gateway, 나머지 직접 ### 실용적 해결: ```python class MessageRequest(BaseModel): text: Optional[str] = None message: Optional[str] = None def get_text(self): return self.text or self.message ``` - 두 경로 모두 지원 - Gateway 유무와 관계없이 작동 - 마이그레이션 유연성 확보 ## 다음 작업 1. **즉시**: rb10508_micro에 message 필드 지원 추가 2. **긴급**: JWT 토큰 검증 구현 3. **계획**: Gateway 스케일링 전략 수립 4. **장기**: Service Mesh 도입 검토 --- ## 오후 9:42 ### 문제 5: Frontend 빌드 시 환경변수 미적용 **증상**: - .env 파일에 `VITE_ROBEING_API_URL=https://ro-being.com/gateway` 설정됨 - 하지만 빌드된 JS 파일에는 하드코딩 기본값 `/rb10508` 사용 - 결과: nginx가 Gateway 우회하여 직접 프록시 **원인 발견**: ```yaml # .gitea/workflows/deploy.yml - name: Build application run: | export VITE_API_URL=http://localhost:8001 # VITE_ROBEING_API_URL 누락! npm run build ``` **해결**: ```yaml export VITE_API_URL=http://localhost:8001 export VITE_ROBEING_API_URL=https://ro-being.com/gateway # 추가 npm run build ``` ### 교훈 1. **빌드 환경변수 체크** - CI/CD 스크립트에서 모든 환경변수 설정 확인 - .env 파일이 있어도 빌드 시점에 로드되는지 검증 - 빌드된 결과물에서 실제 값 확인 2. **기본값 함정** - 하드코딩 기본값은 디버깅을 어렵게 만듦 - 환경변수 미설정 시 명확한 에러가 낫다 3. **근본 원인 vs 임시방편** - rb10508_micro 수정은 임시방편 - Gitea Actions 수정이 근본 해결 - 구조적으로 올바른 해결 우선 4. **서버 관리자 관점** - Gateway가 이미 모든 기능 제공 - 직접 프록시는 필드 변환 누락 - Gateway 경유가 정석