diff --git a/docs/discovery/dcy-011_20260628_daemon-crud-and-self-service-credential-api.md b/docs/discovery/dcy-011_20260628_daemon-crud-and-self-service-credential-api.md index 26ca4d4..45de1d1 100644 --- a/docs/discovery/dcy-011_20260628_daemon-crud-and-self-service-credential-api.md +++ b/docs/discovery/dcy-011_20260628_daemon-crud-and-self-service-credential-api.md @@ -5,10 +5,13 @@ - 문서 ID: dcy-011_20260628_daemon-crud-and-self-service-credential-api - 이슈명: 데몬 admin CRUD API + 사용자 self-service 브로커 인증정보 관리 API — 소유 스코프 write/rotate/메타데이터(복호화 secret 미반환), 데몬 생성·할당·(비)활성화는 admin. trex-web dcy-009 백엔드 (교차 발원) - 작성 시각(KST): 2026-06-28 -- 마지막 갱신 시각(KST): 2026-06-28 +- 마지막 갱신 시각(KST): 2026-06-28 (REQ 초안 생성) - 대체됨: 없음 - 후속 Discovery 참조: 없음 -- 생성된 REQ 참조: 없음 +- 생성된 REQ 참조: + - REQ-052: 사용자 소유 스코프 인증정보 관리 HTTP API ([docs/srs/Interface/req-052_user-owned-credential-management-api.md](../srs/Interface/req-052_user-owned-credential-management-api.md)) + - REQ-053: admin 데몬 CRUD HTTP API ([docs/srs/Interface/req-053_admin-daemon-crud-api.md](../srs/Interface/req-053_admin-daemon-crud-api.md)) + - REQ-054: 인증정보 관리 보안 격리 — write-only·secret 미반환·로그 미노출 ([docs/srs/Non-Functional/req-054_credential-management-security-write-only.md](../srs/Non-Functional/req-054_credential-management-security-write-only.md)) # 1. 계획 상태 요약 현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. diff --git a/docs/srs/Interface/req-052_user-owned-credential-management-api.md b/docs/srs/Interface/req-052_user-owned-credential-management-api.md new file mode 100644 index 0000000..0f45639 --- /dev/null +++ b/docs/srs/Interface/req-052_user-owned-credential-management-api.md @@ -0,0 +1,99 @@ +# REQ-052: 사용자 소유 스코프 인증정보 관리 HTTP API + +- Status: Approved +- Type: Interface +- Priority: High +- Owner: Justin Ko + +## Intent +- 현재 BrokerCredential 관리는 CLI(`pfhub credential`)와 service-token read 경로(REQ-051/REQ-049)만 존재하며, HTTP user-token 기반 관리 표면이 없다. +- trex-web dcy-009 사용자 self-service UI가 소비하는 백엔드 경로를 신설한다: 사용자가 자기 소유 데몬의 KIS 인증정보를 HTTP API로 등록·회전·삭제하고, 메타데이터(secret 미포함)를 조회할 수 있도록 한다. +- dcy-010 자산(BrokerCredential 모델, Fernet 암호화, `CREDENTIAL_ENCRYPTION_KEY`, PK `(daemon_id, broker)`)을 재사용하여 CLI와 동일 repository·암호화 경로를 공유한다. + +## Requirement +- 시스템은 user-token으로 인증된 사용자가 **자기 소유 데몬**의 broker 인증정보를 등록·회전·삭제할 수 있는 write 엔드포인트를 제공해야 한다. + - 등록/회전: `app_key` + `app_secret` + `account_ref` 입력을 Fernet 암호화하여 `BrokerCredential`에 upsert. 회전 시 `credential_version` 증가, `last_rotated_at` 갱신. + - 삭제: 해당 `(daemon_id, broker)` 행 제거. +- 시스템은 사용자가 자기 소유 데몬의 인증정보 **메타데이터**를 조회할 수 있는 read 엔드포인트를 제공해야 한다. 응답에는 `broker`, `account_ref`, `credential_version`, `last_rotated_at`, 보유 여부(`has_credential`)만 포함하며 **복호화된 `app_key`/`app_secret`은 절대 포함하지 않는다**. +- **소유 스코프 인가**를 모든 관리 경로에 강제해야 한다: 요청자 `user_id`가 대상 데몬의 `owner_user_id`와 일치하지 않으면 HTTP 403 Forbidden을 반환한다. 인증 토큰이 없는 요청은 HTTP 401을 반환한다. +- 인증정보 write 요청이 성공하면 응답에는 메타데이터(`broker`, `account_ref`, `credential_version`, `last_rotated_at`)만 반환하고 `app_key`/`app_secret`은 포함하지 않는다. +- 엔드포인트 경로·HTTP 메서드의 구체 형태는 draft-batch-design에서 v1 컨벤션(`/v1/daemons/{daemon_id}/credential` 방향 권고, dcy-011 OQ-1 Deferred)에 맞춰 확정한다. +- 기존 CLI(`pfhub credential`)와 동일한 `BrokerCredentialRepository` 메서드 및 `app/core/encryption.py` Fernet 자산을 재사용해야 한다. + +## Acceptance Criteria +1. Given user-token(소유자 A)으로 자기 소유 daemon D에 `app_key`/`app_secret`/`account_ref` 를 등록, When write 엔드포인트를 호출, Then HTTP 200/201 + 메타데이터 응답(`broker`, `account_ref`, `credential_version`, `last_rotated_at`)이 반환되고 응답 JSON에 `app_key`/`app_secret` 필드가 존재하지 않는다. +2. Given 같은 소유자 A가 동일 daemon D 인증정보를 재등록(회전), When write 엔드포인트를 호출, Then 응답의 `credential_version`이 이전보다 1 증가하고 `last_rotated_at`이 갱신된다. +3. Given 소유자 A가 daemon D 인증정보를 등록한 뒤, When service-token `/v1/user_info`(REQ-049)로 해당 daemon D의 인증정보를 fetch하면, Then `broker.account_ref`가 방금 등록한 값과 일치한다(CLI·API·service-token 경로 정합). +4. Given user-token(소유자 A)으로 자기 소유 daemon D 인증정보 메타데이터를 GET, When 조회 엔드포인트를 호출, Then HTTP 200 + `broker`, `account_ref`, `credential_version`, `last_rotated_at`, `has_credential` 반환되고 `app_key`/`app_secret` 필드는 응답에 없다. +5. Given user-token(소유자 A)으로 자기 소유 daemon D 인증정보를 삭제, When delete 엔드포인트를 호출, Then HTTP 200/204 반환 후 해당 daemon D 메타데이터 조회 시 `has_credential: false`(또는 인증정보 없음 응답). +6. Given user-token(사용자 B)으로 **사용자 A 소유** daemon D의 인증정보에 대한 write/read/delete 요청, When 엔드포인트를 호출, Then HTTP 403 Forbidden 반환(소유 스코프 위반). +7. Given 인증 토큰 없는 요청으로 인증정보 관리 엔드포인트 호출, When 호출, Then HTTP 401 반환. +8. Given 존재하지 않는 daemon_id를 대상으로 인증정보 관리 요청, When 호출, Then HTTP 404 반환. +9. Given admin 토큰으로 자기 소유 데몬이 아닌 daemon D의 인증정보 write 엔드포인트 호출, When 호출, Then HTTP 403 반환(관리 API는 소유 스코프 전용 — admin role로 타인 인증정보 조작 불가). +10. Given service-token으로 인증정보 관리 엔드포인트 호출, When 호출, Then HTTP 403 반환(service-token은 read 전용 `/v1/user_info` 경로만 허용). + +## Impacted Area +- Docs: + - `docs/interface-contract.md` — 인증정보 관리 엔드포인트 경로·권한·응답 스키마(secret 미포함 정책) 신규 섹션 추가. +- Modules: + - 신규 라우터 파일(경로·파일명은 design 확정) — 인증정보 관리 write/read/delete 엔드포인트. + - `app/schemas/` — `CredentialSetRequest`(app_key/app_secret/account_ref), `CredentialMetaResponse`(메타데이터 only, secret 미포함) 스키마 신규 정의. + - `app/services/` — 소유 스코프 인가 검사 + Fernet 암호화/upsert/삭제 서비스 메서드 신규 또는 확장. + - `app/repositories/broker_credential_repository.py` — 메타데이터 조회·write 메서드 확장(CLI와 공유). + - `app/core/encryption.py` — 재사용 (변경 없음). + - `app/main.py` 또는 라우터 등록 — 신규 라우터 include. +- Tests: + - AC-1~10 커버 pytest 케이스. 특히 AC-6(소유 스코프 음성), AC-9(admin 소유 스코프 음성), AC-10(service-token 음성), AC-3(CLI·API·service-token 정합) 강제 검증. + +## Notes +- OQ-1(엔드포인트 경로): draft-batch-design 위임(dcy-011 Deferred). 팀장 권고 = daemon-scoped(`/v1/daemons/{daemon_id}/credential`). v1 컨벤션·dcy-009 UI 흐름 정합 설계 단계 확정. +- **복호화 secret 미반환 불변**: 이 REQ의 어떤 응답도 `app_secret`/`app_key` 평문을 포함하지 않는다. 유일 decrypt-return 경로는 service-token `GET /v1/user_info`(REQ-051/REQ-049) — 본 REQ로 변경하지 않는다. +- AC-9(admin 소유 스코프): admin은 데몬 CRUD(REQ-053)를 담당하며, admin role로 타인 인증정보 write/read를 허용하지 않는다. 인증정보는 **사용자 본인 소유 스코프만**. 이 경계가 REQ-052(user-token, 소유 스코프)와 REQ-053(admin, 데몬 CRUD)의 책임 분리이다. +- NFR-1(보안 — secret 미반환·로그 미노출)은 REQ-054에서 독립 Non-Functional REQ로 추적한다. 이 REQ AC는 응답 schema 단의 secret 부재 확인을 포함하며, REQ-054가 전체 보안 격리를 측정한다. +- NFR-3(일관성 — CLI·API 정합): AC-3이 흡수한다. 구현 시 동일 repository 메서드 재사용 강제. +- Source: dcy-011 FR-1, FR-2, FR-3, NFR-2, NFR-3. + +## Change Log + +### CHG-20260628-01 +- Date: 2026-06-28 +- Author: Justin Ko + +#### Change Summary +- 신규 생성 (Proposed) — dcy-011 FR-1/FR-2/FR-3 산출물. + +#### Intent +- dcy-011 confirmed Discovery의 FR-1(인증정보 등록·회전·삭제), FR-2(메타데이터 조회, secret 미반환), FR-3(소유 스코프 인가 강제)를 단일 Interface REQ로 정규화한다. 세 FR이 동일 관리 표면(user-token + 소유 스코프 인증정보 관리)에 속하므로 하나의 REQ로 묶는다. + +#### Acceptance Criteria Delta +- Added: + - AC-1 ~ AC-10 전체 (신규 초안) +- Updated: 없음 +- Removed: 없음 + +#### Impacted Area Delta +- Docs: + - `docs/srs/Interface/req-052_user-owned-credential-management-api.md` 신규 생성 + - `docs/interface-contract.md` 갱신 예고 +- Modules: + - 인증정보 관리 라우터(신규), 스키마(신규), 서비스·repository 확장 +- Tests: + - 소유 스코프 음성(AC-6/9/10), 메타데이터 secret 부재(AC-1/4), CLI 정합(AC-3) 케이스 신규 + +#### 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 후 suggest-batch-reqs. REQ-053/REQ-054와 동일 batch 묶기 권고. OQ-1(경로)은 design 단계 해소. + +#### Validation Plan +- pytest로 AC-1~10 각 케이스 확인. AC-6(타 사용자 소유 스코프 음성), AC-9(admin 소유 스코프 음성), AC-10(service-token 음성)은 음성 테스트로 강제 검증. + +### CHG-20260628-02 +- Date: 2026-06-28 +- Author: 팀장(Claude) +- confirm-req: `Proposed` → `Approved`. dcy-011 FR-1/2/3 추적. 소유 스코프 음성(AC-6/9/10)·메타데이터 secret 부재(AC-1/4)·CLI·API·service-token 정합(AC-3) 적절. **AC-9(admin도 타인 인증정보 write→403)** = 사장님 "self-service 전용·admin 감독 없음" 결정과 정합 확인(admin은 데몬 CRUD만, 인증정보는 본인 소유 스코프). 경로(OQ-1) design 위임. blocker 없음. diff --git a/docs/srs/Interface/req-053_admin-daemon-crud-api.md b/docs/srs/Interface/req-053_admin-daemon-crud-api.md new file mode 100644 index 0000000..4c8be94 --- /dev/null +++ b/docs/srs/Interface/req-053_admin-daemon-crud-api.md @@ -0,0 +1,104 @@ +# REQ-053: admin 데몬 CRUD HTTP API + +- Status: Approved +- Type: Interface +- Priority: High +- Owner: Justin Ko + +## Intent +- 현재 `Daemon` 엔티티는 `GET /v1/daemons`(read 전용, REQ-045)만 HTTP로 노출되며, 생성·변경·(비)활성화 HTTP API가 없다. 관리자(admin)가 HTTP API로 데몬을 provisioning하고 owner를 할당·변경·(비)활성화할 수 있어야 한다. +- trex-web dcy-009 admin UI가 소비하는 백엔드 경로를 신설한다. +- 데몬은 운영 런타임 식별자(service-token daemon_id 바인딩, KIS 계좌 연결)로 하드 삭제는 제공하지 않으며 **비활성화로 대체**한다(dcy-011 FR-4 결정). owner 재할당은 허용하되 **owner 변경 시 해당 데몬의 기존 인증정보를 자동 무효화**한다(이전 owner의 KIS 계좌 이관 금지, dcy-011 OQ-3 결정). + +## Requirement +- 시스템은 admin role로 인증된 요청자가 데몬을 **생성**하고 `owner_user_id`를 할당할 수 있는 POST 엔드포인트를 제공해야 한다. + - 입력: `daemon_slug`(고유 식별자), `display_name`(선택), `owner_user_id`. + - 성공 시 생성된 Daemon의 `id`, `daemon_slug`, `display_name`, `owner_user_id`, `is_active` 반환. +- 시스템은 admin이 기존 데몬의 `display_name` 또는 `owner_user_id`를 **변경**할 수 있는 PATCH 엔드포인트를 제공해야 한다. + - **owner 변경 시 해당 데몬의 기존 `BrokerCredential` 전체를 자동 삭제(무효화)** 해야 한다 — 이전 owner의 KIS 계좌 정보가 새 owner에게 이관되는 것을 방지한다. +- 시스템은 admin이 데몬을 **비활성화**(`is_active=false`) 및 **재활성화**(`is_active=true`)할 수 있는 엔드포인트를 제공해야 한다. + - 비활성화 시 해당 데몬에 바인딩된 service token을 자동 revoke해야 한다(기존 REQ-022 정책 재사용). +- **하드 삭제(DELETE) 엔드포인트는 제공하지 않는다.** 비활성화가 유일한 종료 경로이다. +- admin role이 아닌 요청(user-token, service-token 포함)은 HTTP 403 Forbidden을 반환해야 한다. +- 엔드포인트 경로·HTTP 메서드의 구체 형태는 draft-batch-design에서 v1 admin 컨벤션(dcy-011 OQ-2 Deferred, dcy-006 admin 경로 참고)에 맞춰 확정한다. + +## Acceptance Criteria +1. Given admin 토큰으로 daemon_slug, display_name, owner_user_id를 포함한 생성 요청, When POST 엔드포인트를 호출, Then HTTP 201 + 생성된 Daemon(`id`, `daemon_slug`, `display_name`, `owner_user_id`, `is_active=true`) 반환. +2. Given admin 토큰으로 중복 `daemon_slug`로 생성 요청, When POST 엔드포인트를 호출, Then HTTP 409(또는 422) Conflict 반환(중복 슬러그 거부). +3. Given admin 토큰으로 존재하는 daemon D의 `display_name` 변경 요청(owner 변경 없음), When PATCH 엔드포인트를 호출, Then HTTP 200 + 갱신된 Daemon 반환. 기존 BrokerCredential은 유지된다. +4. Given admin 토큰으로 daemon D의 `owner_user_id`를 사용자 B로 재할당 요청(원래 소유자 A), When PATCH 엔드포인트를 호출, Then HTTP 200 + 갱신된 Daemon 반환 + 해당 daemon D의 BrokerCredential이 모두 삭제(무효화)된다. +5. Given daemon D의 owner가 변경된 직후, When 변경 전 소유자 A의 service-token으로 `/v1/user_info`를 호출, Then `broker: null` 반환(이전 owner 인증정보 이관 차단). +6. Given admin 토큰으로 활성 daemon D 비활성화 요청, When 비활성화 엔드포인트를 호출, Then HTTP 200 + `is_active: false` 반환 + daemon D에 바인딩된 service token 자동 revoke(REQ-022 정책). +7. Given admin 토큰으로 비활성 daemon D 재활성화 요청, When 재활성화 엔드포인트를 호출, Then HTTP 200 + `is_active: true` 반환. +8. Given admin 토큰으로 daemon D에 DELETE 요청(하드 삭제), When 호출, Then HTTP 405 Method Not Allowed(또는 404) 반환 — 하드 삭제 엔드포인트 미제공. +9. Given user-token(admin role 없음)으로 데몬 생성·변경·비활성화 엔드포인트 호출, When 호출, Then HTTP 403 Forbidden 반환. +10. Given service-token으로 데몬 CRUD 엔드포인트 호출, When 호출, Then HTTP 403 Forbidden 반환. +11. Given 인증 토큰 없는 요청으로 데몬 CRUD 엔드포인트 호출, When 호출, Then HTTP 401 반환. +12. Given admin 토큰으로 존재하지 않는 daemon_id에 PATCH/비활성화 요청, When 호출, Then HTTP 404 반환. + +## Impacted Area +- Docs: + - `docs/interface-contract.md` — admin 데몬 CRUD 엔드포인트 경로·권한·owner 재할당 시 인증정보 무효화 정책 신규 섹션 추가. +- Modules: + - 신규 또는 확장 admin 데몬 라우터(경로·파일명은 design 확정). + - `app/schemas/` — `DaemonCreateRequest`, `DaemonUpdateRequest`, `DaemonAdminResponse` 스키마 신규 정의. + - `app/services/` — 데몬 생성·변경·비활성화 서비스 메서드 신규. owner 재할당 시 `BrokerCredential` 무효화 로직 포함. + - `app/repositories/` — `DaemonRepository` create/update/deactivate/reactivate 메서드 신규. + - `app/repositories/broker_credential_repository.py` — owner 재할당 시 credential 삭제 메서드(신규 또는 확장). + - REQ-022(`daemon deactivation service token auto-revoke`) 서비스 로직 재사용 — 비활성화 경로에서 호출. +- Tests: + - AC-1~12 커버 pytest 케이스. AC-4/5(owner 재할당 + 인증정보 무효화), AC-8(DELETE 405 음성), AC-9/10/11(권한 음성) 강제 검증. + +## Notes +- OQ-2(엔드포인트 경로): draft-batch-design 위임(dcy-011 Deferred). `/v1/admin/daemons` vs `/v1/daemons`(admin 분기) — dcy-006 admin 경로 컨벤션 참고. +- **owner 재할당 + 인증정보 자동 무효화(AC-4/5)**: dcy-011 OQ-3 Answered(2026-06-28 결정). 새 owner는 자기 KIS 인증정보를 REQ-052 API로 재등록해야 한다. 이 정책은 이전 owner의 KIS 계좌 정보가 데몬에 잔류하는 보안 위험을 제거한다. +- **하드 삭제 미제공(AC-8)**: 데몬은 운영 런타임 식별자(service-token daemon_id 바인딩)이므로 참조 무결성 보호를 위해 비활성화가 유일 종료 경로. dcy-011 FR-4 결정. +- **비활성화 → service token revoke(AC-6)**: 기존 REQ-022 정책(`daemon deactivation auto-revoke`)을 이 경로에서도 호출. REQ-022 구현 재사용. +- NFR-2(권한 — admin role 강제): AC-9/10/11로 흡수한다. +- daemon_slug 중복 처리(AC-2): HTTP 409 vs 422는 design 단계에서 v1 컨벤션에 맞춰 확정. +- Source: dcy-011 FR-4, NFR-2, OQ-3 Answered. + +## Change Log + +### CHG-20260628-01 +- Date: 2026-06-28 +- Author: Justin Ko + +#### Change Summary +- 신규 생성 (Proposed) — dcy-011 FR-4 산출물. + +#### Intent +- dcy-011 confirmed Discovery의 FR-4(admin 데몬 생성·owner 할당·변경·(비)활성화, 삭제 제외) 및 OQ-3 결정(owner 재할당 허용 + 인증정보 자동 무효화)을 Interface REQ로 정규화한다. + +#### Acceptance Criteria Delta +- Added: + - AC-1 ~ AC-12 전체 (신규 초안) +- Updated: 없음 +- Removed: 없음 + +#### Impacted Area Delta +- Docs: + - `docs/srs/Interface/req-053_admin-daemon-crud-api.md` 신규 생성 + - `docs/interface-contract.md` 갱신 예고 +- Modules: + - admin 데몬 라우터(신규), DaemonCreate/UpdateRequest/AdminResponse 스키마(신규), 서비스·repository 신규 +- Tests: + - owner 재할당+인증정보 무효화(AC-4/5), DELETE 음성(AC-8), 권한 음성(AC-9~11) 케이스 신규 + +#### 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 후 suggest-batch-reqs. REQ-052/REQ-054와 동일 batch 묶기 권고. OQ-2(경로)은 design 단계 해소. + +#### Validation Plan +- pytest로 AC-1~12 각 케이스 확인. AC-4/5(owner 재할당 + 인증정보 무효화) 통합 시나리오, AC-8(DELETE 음성), AC-9(admin role 강제 음성) 강제 검증. + +### CHG-20260628-02 +- Date: 2026-06-28 +- Author: 팀장(Claude) +- confirm-req: `Proposed` → `Approved`. dcy-011 FR-4/OQ-3 추적. 데몬 생성·할당·(비)활성화, **owner 재할당 시 인증정보 자동 무효화(AC-4/5)** 보안정합, **하드 삭제 미제공(AC-8)**·비활성화→service token revoke(REQ-022 재사용, AC-6)·admin role 강제(AC-9/10/11) 적절. 경로(OQ-2)·409/422·405/404 design 위임. blocker 없음. diff --git a/docs/srs/Non-Functional/req-054_credential-management-security-write-only.md b/docs/srs/Non-Functional/req-054_credential-management-security-write-only.md new file mode 100644 index 0000000..0f4e8be --- /dev/null +++ b/docs/srs/Non-Functional/req-054_credential-management-security-write-only.md @@ -0,0 +1,91 @@ +# REQ-054: 인증정보 관리 보안 격리 — write-only·secret 미반환·로그 미노출 + +- Status: Approved +- Type: Non-Functional +- Priority: High +- Owner: Justin Ko + +## Intent +- dcy-011 NFR-1(보안 최우선): 복호화된 `app_secret`/`app_key`(KIS API 비밀키 — 실계좌·실자금 직결)는 user-token/admin 어떤 관리 API 응답·로그에도 포함되어서는 안 된다. +- REQ-052(사용자 소유 스코프 인증정보 관리 API)와 REQ-053(admin 데몬 CRUD API)이 신설됨으로써 응답/로그 노출 표면이 확대된다. 이 표면에서 복호화 secret이 유출되지 않음을 독립적으로 측정·검증한다. +- `GET /v1/user_info`(service-token 전용, REQ-051/REQ-049)가 유일 decrypt-return 경로라는 불변 정책을 신규 관리 API 추가 이후에도 유지한다. + +## Requirement +- 인증정보 관리 API(REQ-052: 소유 스코프 write/read/delete + 메타데이터, REQ-053: admin 데몬 CRUD) 응답 스키마에 `app_key` 및 `app_secret` 필드가 존재해서는 안 된다. +- 인증정보 관리 API 처리 중 생성되는 모든 로그(INFO, WARNING, ERROR 수준 포함)에 `app_secret` 또는 `app_key` 평문이 포함되어서는 안 된다. 로그에는 `broker` 이름, `daemon_id`, 오류 유형만 기록한다. +- 복호화된 `credential_payload`(`app_key`, `app_secret`)를 반환하는 유일 경로는 service-token 전용 `GET /v1/user_info`(REQ-051/REQ-049)이다. 이 REQ-052·REQ-053 신설로 인해 이 경로가 변경되어서는 안 된다. +- 인증정보 쓰기(등록/회전) 후 DB에 저장되는 값은 반드시 Fernet 암호화된 형태여야 한다. 평문 `app_secret`/`app_key`가 `broker_credentials` 테이블에 저장되어서는 안 된다. + +## Acceptance Criteria +1. Given REQ-052 인증정보 write(등록/회전) 엔드포인트 응답 JSON, When 응답 필드를 열거, Then `app_key`, `app_secret` 키가 최상위 및 중첩 객체에 포함되지 않는다. +2. Given REQ-052 인증정보 메타데이터 GET 엔드포인트 응답 JSON, When 응답 필드를 열거, Then `app_key`, `app_secret` 키가 포함되지 않는다. +3. Given REQ-053 admin 데몬 생성·변경·(비)활성화 엔드포인트 응답 JSON, When 응답 필드를 열거, Then `app_key`, `app_secret`, `credential_payload` 키가 포함되지 않는다. +4. Given 인증정보 write/rotate 처리 중 caplog 수집, When 로그 출력을 확인, Then 실제 `app_key`/`app_secret` 값이 로그 메시지에 포함되지 않는다. +5. Given 인증정보 write 요청의 `app_key`/`app_secret` 값으로 DB row를 직접 조회, When `broker_credentials.credential_payload` 컬럼을 확인, Then 평문이 아닌 Fernet 암호화 토큰(`gAAAA...`) 형태로 저장되어 있다. +6. Given REQ-052 인증정보 write 후 service-token `GET /v1/user_info`(REQ-049)로 조회, When 호출, Then `broker.credential_payload.app_secret`이 올바르게 복호화되어 반환된다 — 관리 API 추가가 유일 decrypt 경로(REQ-051)를 파괴하지 않음 확인. +7. Given REQ-052·REQ-053 신규 엔드포인트가 추가된 후 기존 `GET /v1/user_info` 보안 격리 테스트(REQ-051 AC-1~8), When 회귀 실행, Then 전체 PASS — 신규 API 추가가 기존 service-token 전용 경로에 영향 없음 확인. + +## Impacted Area +- Docs: + - `docs/interface-contract.md` — 인증정보 관리 API 보안 정책 섹션: 복호화 secret 미반환·로그 미노출·Fernet at-rest 불변·service-token 유일 decrypt 경로 명시. +- Modules: + - `app/schemas/` — REQ-052 응답 스키마(`CredentialMetaResponse`)에 `app_key`/`app_secret` 필드 부재 강제(Pydantic 모델 설계 단계 확인). + - `app/services/` — 인증정보 write 서비스에서 로그 호출 시 secret 평문 비포함 확인. + - `app/core/encryption.py` — Fernet 암호화 후 저장 경로(REQ-052 write) 재사용. +- Tests: + - caplog 기반 로그 secret 비노출 테스트(AC-4). + - 응답 스키마 secret 필드 부재 테스트(AC-1/2/3). + - DB 저장 암호화 확인 테스트(AC-5). + - REQ-051 회귀 테스트(AC-7): 신규 API 추가 후 기존 service-token gate 테스트 전부 PASS. + +## Notes +- dcy-011 NFR-1(보안 — 최우선): REQ-052/REQ-053의 각 AC에 secret 부재 조건이 포함되어 있으나, 이 REQ-054는 신규 관리 API 전체를 아우르는 **독립 측정·검증 목적** Non-Functional REQ로 분리한다. REQ-051(서비스 토큰 전용 격리)과 나란히 추적한다. +- AC-7(REQ-051 회귀): REQ-052·REQ-053 구현 후 기존 service-token 보안 격리(REQ-051 AC-1~8)가 회귀 없이 통과함을 이 REQ의 검증 항목으로 포함한다. +- at-rest Fernet 암호화(AC-5): `CREDENTIAL_ENCRYPTION_KEY` 관리 정책은 REQ-051 Notes에 따라 이 REQ 범위 밖(환경변수 기반 유지). +- NFR-2(권한)·NFR-3(일관성)의 나머지는 REQ-052/REQ-053 AC에 흡수된다. NFR-1 보안만 별도 Non-Functional REQ로 승격한다. +- Source: dcy-011 NFR-1, R-1(복호화 secret 유출 리스크), S-2(복호화 secret 미포함 성공 기준). + +## Change Log + +### CHG-20260628-01 +- Date: 2026-06-28 +- Author: Justin Ko + +#### Change Summary +- 신규 생성 (Proposed) — dcy-011 NFR-1 산출물. + +#### Intent +- dcy-011 NFR-1(복호화 secret이 user-token/admin 응답·로그에 미포함, REQ-051 패턴)을 신규 관리 API(REQ-052·REQ-053) 표면 전체에 대한 독립 측정 가능 Non-Functional REQ로 정규화한다. + +#### Acceptance Criteria Delta +- Added: + - AC-1 ~ AC-7 전체 (신규 초안) +- Updated: 없음 +- Removed: 없음 + +#### Impacted Area Delta +- Docs: + - `docs/srs/Non-Functional/req-054_credential-management-security-write-only.md` 신규 생성 + - `docs/interface-contract.md` 갱신 예고 +- Modules: + - 응답 스키마(secret 필드 부재), 서비스 로그 확인 +- Tests: + - caplog 로그 비노출(AC-4), 응답 schema secret 부재(AC-1~3), DB 암호화 확인(AC-5), REQ-051 회귀(AC-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 후 suggest-batch-reqs. REQ-052/REQ-053와 동일 batch 묶기 권고. + +#### Validation Plan +- pytest caplog fixture로 AC-4(로그 비노출), 응답 JSON 필드 열거로 AC-1~3, DB row 직접 확인으로 AC-5, REQ-051 회귀 suite로 AC-7 검증. + +### CHG-20260628-02 +- Date: 2026-06-28 +- Author: 팀장(Claude) +- confirm-req: `Proposed` → `Approved`. dcy-011 NFR-1 추적. 신규 관리 API(REQ-052/053) 응답·로그 secret 미포함(AC-1~4)·Fernet at-rest(AC-5)·service-token 유일 decrypt 불변(AC-6)·REQ-051 회귀(AC-7) testable. REQ-051과 나란히 독립 보안 REQ 승격 타당. blocker 없음. diff --git a/docs/srs/index.md b/docs/srs/index.md index bf6208c..5c1391f 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -74,7 +74,7 @@ ## Next Requirement ID -- Current: `REQ-052` +- Current: `REQ-055` - Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. ## Requirement Register @@ -132,11 +132,15 @@ | REQ-049 | GET /v1/user_info daemon-scoped 단일 broker 전달로 확장 | Interface | Implemented | High | Justin Ko | [req-049](./Interface/req-049_user-info-daemon-scoped-broker-delivery.md) | | REQ-050 | credential CLI --daemon-id 옵션 추가 | Interface | Implemented | High | Justin Ko | [req-050](./Interface/req-050_credential-cli-daemon-id-option.md) | | REQ-051 | 브로커 인증정보 보안 격리 — service-token 전용·web 토큰 차단 | Non-Functional | Implemented | High | Justin Ko | [req-051](./Non-Functional/req-051_broker-credential-security-isolation.md) | +| REQ-052 | 사용자 소유 스코프 인증정보 관리 HTTP API | Interface | Approved | High | Justin Ko | [req-052](./Interface/req-052_user-owned-credential-management-api.md) | +| REQ-053 | admin 데몬 CRUD HTTP API | Interface | Approved | High | Justin Ko | [req-053](./Interface/req-053_admin-daemon-crud-api.md) | +| REQ-054 | 인증정보 관리 보안 격리 — write-only·secret 미반환·로그 미노출 | Non-Functional | Approved | High | Justin Ko | [req-054](./Non-Functional/req-054_credential-management-security-write-only.md) | ## Recent Change Log Summary +- 2026-06-28: REQ-052/053/054 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-011 추적·AC testable·blocker 0. 소유 스코프 음성(REQ-052 AC-6/9/10)·owner 재할당+인증정보 무효화(REQ-053 AC-4/5)·secret 미반환·로그 미노출·REQ-051 회귀(REQ-054) 확인. admin도 인증정보는 소유 스코프 전용(self-service 결정 정합). 엔드포인트 경로(OQ-1/2) design 위임. 다음=suggest-batch-reqs. +- 2026-06-28: REQ-052/053/054 신규 생성 (Proposed) — dcy-011 confirmed Discovery 산출물. REQ-052(사용자 소유 스코프 인증정보 관리 HTTP API), REQ-053(admin 데몬 CRUD HTTP API), REQ-054(인증정보 관리 보안 격리 Non-Functional). - 2026-06-28: REQ-047 `Approved` → `Implemented` (confirm-req-implemented) — BAT-021 PR #18 머지(7164f9e), 실 dev PG 통합검증 AC-1~7 PASS(Step1→backfill→Step2→downgrade), QC PASS-WITH-NOTES(blocker 0). **Step2(PK 전환) 실 운영 DB 적용은 별도 release 결재 게이트**(런북 `docs/runbooks/bat-021-req047-step2-migration-runbook.md` 엄수, 배포순서 Step1→backfill→Step2→코드배포, alembic stamp 사전확인). 운영 적용 시 AC-3/4/5 실운영 evidence 재확보. - 2026-06-28: REQ-046/048/049/050/051 `Approved` → `Implemented` (confirm-req-implemented) — BAT-020 PR #17 머지(d4acfd4), QC PASS-WITH-NOTES(blocker 0), 테스트 통과. REQ-047은 실 운영 DB 적용·실 PostgreSQL 통합검증이 비가역 사장님 결재 게이트 미완 → Approved 유지. AC-3/4/5(Step2 적용 후 schema) real-DB evidence 대기. 결재·적용 시 TODO: ensure_schema()·upsert ON CONFLICT를 (daemon_id,broker)로 갱신. - 2026-06-28: REQ-046~051 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-010 daemon-scoped broker credential. 전건 dcy-010 FR/NFR 추적·AC testable·blocker 0 확인. 단계적 마이그레이션(REQ-047 Step1/2)·**REQ-047 Step2 PK 전환은 비가역 결재 게이트**·2데몬 격리(REQ-049 AC-4/5)·보안 격리(REQ-051) 반영. 비차단 design 위임 2건(REQ-048 구현방식, REQ-050 slug/UUID·과도기 정책). - 2026-06-27: REQ-025 name 검증 완화(CHG-20260627-03) — `_PORTFOLIO_NAME_RE` 한글(가-힣)+공백 허용(`^[a-zA-Z0-9가-힣 ._:\-]{1,100}$`). minor-change. 기존 AC 유지, 테스트 3개 추가. -- 2026-06-27: REQ-044/045 `Approved` → `Implemented` — BAT-019 PR #16 main 머지(`7f15c6b`) QC PASS. 472 passed/0 failed. REQ-045 env토큰(user_id=None) 처리 Notes 정정: "HTTP 403" → "빈 목록 [] HTTP 200"(change-req CHG-20260627-02).