Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
7.8 KiB
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
- Given web user 토큰(token_type이 service가 아닌 token)으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환(credential_payload 미노출).
- Given 비인증 요청으로 GET /v1/user_info 호출, When 호출, Then HTTP 401 반환.
- Given daemon_id=None인 service token으로 GET /v1/user_info 호출, When 호출, Then HTTP 403 반환.
- Given daemon_id="d1"인 service token으로 GET /v1/user_info 호출, When DB에 daemon_id="d2"의 인증정보만 존재하면, Then
broker: null반환 (다른 데몬의 인증정보 절대 반환하지 않음). - Given GET /v1/user_info 응답 외 다른 엔드포인트(GET /v1/portfolios, GET /v1/assets, GET /health 등) 응답, When 응답 JSON 확인, Then
credential_payload,app_secret,app_key필드가 포함되지 않는다. - Given DB 오류로
list_broker_credentials실패, When GET /v1/user_info 호출, Then HTTP 200 +broker: null반환 (500 크래시 없음), 경고 로그 기록. - Given Fernet 복호화 실패(잘못된
CREDENTIAL_ENCRYPTION_KEY), When GET /v1/user_info 호출, Then HTTP 200 +broker.credential_payload: null반환, 경고 로그 기록(app_secret 로그 미노출). - 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 머지(mergeCommitd4acfd4), 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 근거 확보됨.