Files
portfolio-hub/docs/srs/Non-Functional/req-051_broker-credential-security-isolation.md
T

7.8 KiB

REQ-051: 브로커 인증정보 보안 격리 — service-token 전용·web 토큰 차단

  • Status: Implemented
  • 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 없음.

CHG-20260628-03

  • Date: 2026-06-28
  • Author: 팀장(Claude)

Change Summary

  • confirm-req-implemented: Approved → Implemented. PR #17 머지(mergeCommit d4acfd4), BAT-020, QC PASS-WITH-NOTES(blocker 0), 테스트 통과.

Intent

  • BAT-020 delivery(PR #17 머지)에서 보안 격리(service-token 전용, web 토큰 차단, 타 엔드포인트 credential 미노출, graceful degrade) 구현 및 AC-1~8 테스트 통과가 확인되어 Implemented로 전환한다.

Acceptance Criteria Delta

  • Added: 없음
  • Updated: 없음
  • Removed: 없음

Impacted Area Delta

  • 없음 (상태 전환만)

Delivery Trace Delta

  • Batches: BAT-020 (PR #17, mergeCommit d4acfd4)
  • Releases: 미정

Revalidation Impact

  • Reverification Needed: no
  • Existing Implementation Invalidated: no
  • Status Recommendation: implemented

Validation Plan

  • BAT-020 verification 문서 및 QC PASS-WITH-NOTES 결과(blocker 0)로 AC-1~8 근거 확보됨.