# BAT-022 Implementation - Status: completed - Commit: 6a0e14d ## 변경 파일 목록 ### 신규 파일 | 파일 | 역할 | | --- | --- | | `app/api/v1_admin_daemons.py` | REQ-053 admin daemon CRUD 라우터 (`/v1/admin/daemons`) | | `app/api/v1_daemon_credential.py` | REQ-052 소유 스코프 credential 관리 라우터 (`/v1/daemons/{id}/credential`) | | `tests/test_bat022_req052_credential_api.py` | REQ-052 AC-1~10 테스트 (17 케이스) | | `tests/test_bat022_req053_admin_daemon_crud.py` | REQ-053 AC-1~12 테스트 (15 케이스) | | `tests/test_bat022_req054_security.py` | REQ-054 AC-1~7 + REQ-051 회귀 (12 케이스) | ### 변경 파일 | 파일 | 변경 내용 | | --- | --- | | `app/schemas/v1.py` | `CredentialSetRequest`, `CredentialMetaResponse`, `DaemonCreateRequest`, `DaemonUpdateRequest`, `DaemonAdminResponse` 추가 | | `app/repositories/postgres.py` | `get_broker_credential_by_daemon`, `delete_credentials_by_daemon_id`, `update_daemon_owner` 추가 | | `app/repositories/portfolio_repository.py` | Protocol에 신규 3개 메서드 추가 | | `app/main.py` | `v1_admin_daemons_router`, `v1_daemon_credential_router` include | ## REQ별 구현 요약 ### REQ-052 (소유 스코프 인증정보 관리 API) - `GET /v1/daemons/{daemon_id}/credential` — 소유 스코프 검증 후 BrokerCredential 메타데이터 반환 (has_credential, version, last_rotated_at). secret 없음. - `PUT /v1/daemons/{daemon_id}/credential` — `app_key`/`app_secret` Fernet 암호화 후 `upsert_broker_credential(daemon_id=...)` 호출. 응답 메타데이터만. - `DELETE /v1/daemons/{daemon_id}/credential` — `delete_broker_credential(daemon_id=...)` 호출, 204 반환(멱등). - 소유 스코프 게이트: `daemon.owner_user_id != auth.user_id` → 403. admin role도 동일 검사(AC-9). - service-token 거부: `token_source == "db" and token_type == "service"` → 403(AC-10). - 기존 `upsert_broker_credential`, `delete_broker_credential`(dcy-010 자산) 재사용. CLI와 동일 repo 메서드. ### REQ-053 (admin 데몬 CRUD API) - `POST /v1/admin/daemons` → 기존 `create_daemon()` 재사용, 201. - `PATCH /v1/admin/daemons/{id}` → owner 변경 시 `update_daemon_owner()`(새 메서드), display_name만 변경 시 기존 `update_daemon()`. - `POST .../deactivate` → `update_daemon(is_active=False)` + `revoke_tokens_by_daemon_id()`(REQ-022 재사용). - `POST .../activate` → `update_daemon(is_active=True)`. - `DELETE .../` → 명시적 405 핸들러 등록. - `require_admin` dependency 사용 (기존 `app/core/auth.py`). ### REQ-054 (보안 격리) - 응답 스키마: `CredentialMetaResponse`/`DaemonAdminResponse` 모두 secret 필드 부재. - 로그: `PUT credential` 처리 시 broker/daemon_id만 기록 (app_key/app_secret 값 미포함). - Fernet at-rest: `encrypt_payload()` 호출 후 `upsert_broker_credential()` — 평문 미저장(AC-5). - REQ-051 decrypt 경로 불변: `/v1/user_info` 코드 무변경, 회귀 4개 테스트 PASS(AC-7). ## 설계 결정 트레이스 (design.md 참조) - OQ-1 해소: `/v1/daemons/{daemon_id}/credential` — daemon-scoped, REST 정합. - OQ-2 해소: `/v1/admin/daemons` — admin 전용 prefix, 기존 GET /v1/daemons(caller-scoped)와 분리. - service-token 403: `token_source == "db" and token_type == "service"` 조건. env-token은 token_type=None이므로 해당 없음. - admin + 소유 스코프: admin role도 ownership 검사 통과 → 403(REQ-052 AC-9).