# REQ-051: 브로커 인증정보 보안 격리 — service-token 전용·web 토큰 차단 - Status: Approved - Type: Non-Functional - Priority: High - Owner: Justin Ko ## Intent - 복호화된 app_secret(KIS API 비밀키)은 실계좌 실자금에 직결되는 최민감 시크릿이다. 이 정보가 복호화된 형태로 service-token 경로 외 다른 경로(web user 토큰, admin 토큰, 비인증 접근)로 유출되지 않아야 한다. - portfolio-hub가 M3에서 trex-web 프론트에 노출될 예정이므로, web 노출 표면과 인증정보 전달 경로의 격리를 명시적으로 요구사항으로 정의하고 테스트로 검증한다. - dcy-010 NFR-1(보안 격리), NFR-2(일관성), NFR-3(가용성/degrade)의 독립 측정·검증 필요성을 근거로 별도 REQ로 승격한다. ## Requirement - `GET /v1/user_info`는 service token(token_source="db", token_type="service")으로만 접근 가능하다. web user 토큰(token_type="session" 또는 client 타입), 비인증 요청, admin 토큰(token_type이 service가 아닌 경우)은 HTTP 403을 반환해야 한다. - 복호화된 `credential_payload`(`app_secret` 포함)는 오직 `GET /v1/user_info` 응답을 통해서만 반환되어야 하며, 다른 엔드포인트(portfolio, asset, health, admin 등)의 응답에 포함되어서는 안 된다. - `GET /v1/user_info`에서 daemon_id가 없는(None) 토큰은 HTTP 403으로 거부한다(REQ-049와 동일 — 보안 정책으로도 명시). - 인증정보 조회·복호화 실패(DB 오류, 복호화 실패)는 엔드포인트 전체를 크래시시키지 않는다. 실패 시 `broker: null`(또는 `credential_payload: null`)을 반환하고 경고 로그를 남긴다. - 인증정보 관련 조회 로그에는 `credential_payload`의 내용(app_key, app_secret)이 포함되어서는 안 된다. 로그에는 broker 이름, daemon_id, 오류 유형만 기록한다. - universe의 daemon-scoped 인가(`managed_by_daemon_id` 일치 검사, REQ-028)와 동일한 규율로, broker 인증정보 접근도 daemon_id 일치를 강제한다(daemon_id 불일치 → 403, 다른 데몬의 인증정보 반환 불가). ## Acceptance Criteria 1. Given web user 토큰(token_type이 service가 아닌 token)으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환(credential_payload 미노출). 2. Given 비인증 요청으로 GET /v1/user_info 호출, When 호출, Then HTTP 401 반환. 3. Given daemon_id=None인 service token으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환. 4. Given daemon_id="d1"인 service token으로 GET /v1/user_info 호출, When DB에 daemon_id="d2"의 인증정보만 존재하면, Then `broker: null` 반환 (다른 데몬의 인증정보 절대 반환하지 않음). 5. Given GET /v1/user_info 응답 외 다른 엔드포인트(GET /v1/portfolios, GET /v1/assets, GET /health 등) 응답, When 응답 JSON 확인, Then `credential_payload`, `app_secret`, `app_key` 필드가 포함되지 않는다. 6. Given DB 오류로 `list_broker_credentials` 실패, When GET /v1/user_info 호출, Then HTTP 200 + `broker: null` 반환 (500 크래시 없음), 경고 로그 기록. 7. Given Fernet 복호화 실패(잘못된 `CREDENTIAL_ENCRYPTION_KEY`), When GET /v1/user_info 호출, Then HTTP 200 + `broker.credential_payload: null` 반환, 경고 로그 기록(app_secret 로그 미노출). 8. Given GET /v1/user_info 정상 처리 중 로그 검사, When 로그 출력 확인, Then `app_key`, `app_secret` 값이 로그에 출력되지 않는다. ## Impacted Area - Docs: - `docs/interface-contract.md` — GET /v1/user_info 보안 정책 섹션: service-token 전용, daemon_id 없음 → 403, web 토큰 차단, credential 로그 비노출 명시. - Modules: - `app/api/v1_user_info.py` — `_require_service_token` 함수(기존 service-token gate, 변경 없거나 daemon_id 검사 보완). - `app/api/v1_user_info.py` — 로깅 경고 메시지에서 credential 값 제외 확인. - Tests: - web 토큰 → 403 테스트(AC-1). - 비인증 → 401 테스트(AC-2). - daemon_id=None → 403 테스트(AC-3). - 다른 데몬 인증정보 → null 테스트(AC-4). - 타 엔드포인트 credential 미노출 테스트(AC-5): 주요 엔드포인트 응답 필드 검사. - DB 오류 graceful degrade 테스트(AC-6). - 복호화 실패 degrade 테스트(AC-7): 기존 테스트 확인/보완. ## Notes - dcy-010 NFR-1(보안 격리), NFR-2(일관성), NFR-3(degrade)가 모두 독립 측정 가능하고 테스트로 검증할 수 있어 별도 REQ로 승격한다. 이 세 NFR을 단일 REQ로 묶은 것은 모두 동일한 엔드포인트(GET /v1/user_info)의 보안 속성에 관한 것이기 때문이다. - AC-4(다른 데몬 인증정보 반환 불가)는 REQ-049 AC-4/5와 overlap하지만, 보안 관점의 요구사항으로 이 REQ에서 독립적으로 추적한다. - 로그 비노출(AC-8)은 현재 구현의 `logger.warning("credential decryption failed for broker %s: %s", cred.broker, exc)` 패턴이 app_secret을 exc에 포함할 가능성을 배제해야 함을 의미한다. 구현 시 확인이 필요하다. - `CREDENTIAL_ENCRYPTION_KEY` 자체의 보안 관리(환경변수 주입, 키 회전 정책)는 dcy-010 A-3(외부 시크릿 매니저 Out of Scope)에 따라 이 REQ의 범위 밖이다. 현재 환경변수 기반 유지. - Source: dcy-010 NFR-1, NFR-2, NFR-3. ## Change Log ### CHG-20260628-01 - Date: 2026-06-28 - Author: Justin Ko #### Change Summary - 신규 생성 (Proposed) — dcy-010 NFR-1/2/3 산출물. #### Intent - dcy-010 NFR-1(보안 격리)·NFR-2(일관성)·NFR-3(degrade) 세 NFR을 독립 측정·테스트 가능한 요구사항으로 묶어 Non-Functional REQ로 정규화한다. #### Acceptance Criteria Delta - Added: - AC-1 ~ AC-8 전체 (신규 초안) - Updated: 없음 - Removed: 없음 #### Impacted Area Delta - Docs: - `docs/srs/Non-Functional/req-051_broker-credential-security-isolation.md` 신규 생성 - `docs/interface-contract.md` 갱신 예고 - Modules: - `app/api/v1_user_info.py` 보안 게이트 및 로깅 확인 - Tests: - 보안 격리 테스트 신규(AC-1~4, AC-8), degrade 테스트(AC-6/7) #### Delivery Trace Delta - Batches: 미정 (suggest-batch-reqs 대기) - Releases: 미정 #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: confirm-req 후 batch 배정. REQ-049와 동일 batch에 묶어 전달하는 것이 자연스럽다. #### Validation Plan - pytest로 AC-1~8 각 케이스 확인. AC-8(로그 비노출)은 caplog fixture로 검증. ### CHG-20260628-02 - Date: 2026-06-28 - Author: 팀장(Claude) - confirm-req: `Proposed` → `Approved`. dcy-010 NFR-1/2/3 흡수 적절(동일 엔드포인트 보안속성). service-token 전용·web 토큰 차단·타 엔드포인트 credential 미노출(AC-5)·로그 비노출(AC-8)·degrade(AC-6/7) testable. REQ-049와 동일 batch 권고. blocker 없음.