docs(bat-022): implementation, verification, index 문서 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
+59
@@ -0,0 +1,59 @@
|
||||
# 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).
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
- Batch ID: BAT-022
|
||||
- Profile: standard
|
||||
- Status: in-progress
|
||||
- Status: draft-batch-verification
|
||||
- Discovery: dcy-011
|
||||
- Included REQ: REQ-052, REQ-053, REQ-054
|
||||
- Folder: docs/batches/bat-022_20260628_daemon-crud-self-service-credential-api/
|
||||
@@ -35,11 +35,11 @@ dcy-010 자산(BrokerCredential 모델, Fernet 암호화, upsert/delete 메서
|
||||
|
||||
## DoD (완료 조건)
|
||||
|
||||
- [ ] REQ-052 AC-1~10 테스트 통과
|
||||
- [ ] REQ-053 AC-1~12 테스트 통과
|
||||
- [ ] REQ-054 AC-1~7 테스트 통과 (caplog 비노출, 응답 secret 부재, REQ-051 회귀)
|
||||
- [ ] 기존 521 테스트 회귀 없음
|
||||
- [ ] draft-batch-verification 문서에 AC별 evidence 기록
|
||||
- [x] REQ-052 AC-1~10 테스트 통과 (17 케이스)
|
||||
- [x] REQ-053 AC-1~12 테스트 통과 (15 케이스)
|
||||
- [x] REQ-054 AC-1~7 테스트 통과 (12 케이스, caplog 비노출, 응답 secret 부재, REQ-051 회귀)
|
||||
- [x] 기존 521 테스트 회귀 없음 (565 passed, 0 failures)
|
||||
- [x] draft-batch-verification 문서에 AC별 evidence 기록
|
||||
|
||||
## 비가역 결재 게이트
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
# BAT-022 Verification
|
||||
|
||||
- Status: draft
|
||||
- Batch: BAT-022
|
||||
- Verification Date: 2026-06-28
|
||||
|
||||
## 테스트 실행 결과
|
||||
|
||||
### 신규 테스트 (44 케이스)
|
||||
|
||||
```
|
||||
tests/test_bat022_req052_credential_api.py — 17 passed
|
||||
tests/test_bat022_req053_admin_daemon_crud.py — 15 passed
|
||||
tests/test_bat022_req054_security.py — 12 passed
|
||||
```
|
||||
|
||||
### 전체 테스트 스위트
|
||||
|
||||
```
|
||||
565 passed, 11 warnings in 32.53s
|
||||
```
|
||||
|
||||
기존 521 테스트 회귀: **0건** (521 → 565, +44 신규만 추가)
|
||||
|
||||
## REQ-052 AC별 검증 Evidence
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: PUT → 200 + 메타데이터, secret 없음 | `test_put_credential_returns_meta_without_secret` | PASS |
|
||||
| AC-2: 회전 시 credential_version 증가 | `test_put_credential_rotation_increments_version` | PASS |
|
||||
| AC-3: CLI·API·service-token 정합 (upsert daemon_id 전달) | `test_put_credential_upsert_called_with_correct_daemon_id` | PASS |
|
||||
| AC-4: GET → 200 + 메타데이터, secret 없음 | `test_get_credential_meta_returns_metadata_without_secret` | PASS |
|
||||
| AC-5: DELETE → 204 + 이후 has_credential=False | `test_delete_credential_returns_204`, `test_get_after_delete_returns_has_credential_false` | PASS |
|
||||
| AC-6 (음성): 타 사용자 토큰 → 403 | `test_put/get/delete_credential_other_user_returns_403` (3개) | PASS |
|
||||
| AC-7: 비인증 → 401 | `test_credential_no_token_returns_401` | PASS |
|
||||
| AC-8: 미존재 daemon → 404 | `test_credential_nonexistent_daemon_returns_404`, `test_put_nonexistent_daemon_returns_404` | PASS |
|
||||
| AC-9 (음성): admin으로 타인 daemon write → 403 | `test_admin_cannot_write/read_other_user_credential` (2개) | PASS |
|
||||
| AC-10 (음성): service-token → 403 | `test_service_token_credential_get/put/delete_returns_403` (3개) | PASS |
|
||||
|
||||
## REQ-053 AC별 검증 Evidence
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: POST 생성 → 201 + DaemonAdminResponse | `test_admin_create_daemon_returns_201` | PASS |
|
||||
| AC-2: 중복 slug → 409 | `test_admin_create_daemon_duplicate_slug_returns_409` | PASS |
|
||||
| AC-3: display_name 변경 → BrokerCredential 유지 | `test_admin_patch_display_name_credential_preserved` | PASS |
|
||||
| AC-4: owner 재할당 → BrokerCredential 자동 삭제 | `test_admin_patch_owner_invalidates_credentials` | PASS |
|
||||
| AC-5: 재할당 후 이전 owner 인증정보 무효화 | `test_admin_patch_owner_then_old_owner_loses_credential` | PASS |
|
||||
| AC-6: 비활성화 → is_active=False + service token revoke | `test_admin_deactivate_daemon_revokes_tokens` | PASS |
|
||||
| AC-7: 재활성화 → is_active=True | `test_admin_activate_daemon_returns_active` | PASS |
|
||||
| AC-8 (음성): DELETE → 405 | `test_admin_delete_daemon_returns_405` | PASS |
|
||||
| AC-9 (음성): user-token(비admin) → 403 | `test_non_admin_user_token_create/patch/deactivate` (3개) | PASS |
|
||||
| AC-10 (음성): service-token → 403 | `test_service_token_admin_crud_returns_403` | PASS |
|
||||
| AC-11: 비인증 → 401 | `test_admin_crud_no_token_returns_401` | PASS |
|
||||
| AC-12: 미존재 daemon → 404 | `test_admin_patch/deactivate_nonexistent_daemon_returns_404` (2개) | PASS |
|
||||
|
||||
## REQ-054 AC별 검증 Evidence
|
||||
|
||||
| AC | 테스트 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| AC-1: PUT 응답에 app_key/app_secret 없음 | `test_put_credential_response_has_no_secret_fields` | PASS |
|
||||
| AC-2: GET 응답에 app_key/app_secret 없음 | `test_get_credential_meta_response_has_no_secret_fields` | PASS |
|
||||
| AC-3: admin CRUD 응답에 credential 필드 없음 | `test_admin_create/patch_response_has_no_credential_fields` (2개) | PASS |
|
||||
| AC-4: caplog — PUT 처리 중 app_key/app_secret 값 미포함 | `test_put_credential_log_does_not_expose_secret` | PASS |
|
||||
| AC-5: Fernet 암호화 저장 확인 (gAAAA... 형태) | `test_put_credential_stores_encrypted_payload` | PASS |
|
||||
| AC-6: PUT 후 service-token user_info → 동일 account_ref | `test_put_then_service_token_user_info_returns_correct_account_ref` | PASS |
|
||||
| AC-7: REQ-051 회귀 (4개 테스트) | `test_req051_regression_*` (4개) | PASS |
|
||||
|
||||
## 음성 테스트 체크리스트
|
||||
|
||||
| 음성 시나리오 | 케이스 수 | 결과 |
|
||||
| --- | --- | --- |
|
||||
| 소유 스코프 403 (AC-6: 타 사용자 토큰) | 3 | PASS |
|
||||
| admin 소유 스코프 403 (AC-9) | 2 | PASS |
|
||||
| service-token 403 (AC-10, REQ-052) | 3 | PASS |
|
||||
| service-token 403 (AC-10, REQ-053) | 1 | PASS |
|
||||
| DELETE 405 (AC-8, REQ-053) | 1 | PASS |
|
||||
| secret 필드 부재 (AC-1/2/3, REQ-054) | 4 | PASS |
|
||||
| caplog secret 미노출 (AC-4, REQ-054) | 1 | PASS |
|
||||
|
||||
## 회귀 테스트
|
||||
|
||||
- REQ-051 AC-1~3, AC-5: 신규 API 추가 후 GET /v1/user_info 보안 격리 불변 확인 (4개 테스트 PASS)
|
||||
- 기존 521 테스트 전건 회귀 없음
|
||||
|
||||
## 잔여 위험 / 미해결
|
||||
|
||||
1. **AC-3 통합 수준**: REQ-052 AC-3(CLI·API·service-token 3경로 정합)은 이 batch에서 unit-level로 검증(upsert daemon_id 전달 확인). 실 DB 통합 검증은 dev DB 환경 필요(비가역 아님, 선택적 추후 확인 가능).
|
||||
|
||||
2. **DELETE credential 기본 broker "kis"**: GET/DELETE 에서 broker 파라미터 없이 "kis" 고정. 다중 broker 지원이 필요해지면 경로 파라미터 추가 필요 — 현재 단일 broker 가정 설계 일치.
|
||||
|
||||
3. **update_daemon_owner autocommit 부분 실패**: postgres.py `update_daemon_owner()`는 daemon UPDATE + credential DELETE를 autocommit 2회 연산으로 처리. daemon 업데이트 성공 후 credential 삭제 실패 케이스는 테스트 범위 밖(네트워크 장애 등). 실 운영에서 발생 시 수동 정리 필요.
|
||||
@@ -29,3 +29,4 @@
|
||||
| BAT-019 | standard | confirmed | REQ-044, REQ-045 | dcy-009 | [bat-019](./bat-019_20260627_assets-read-permission-and-daemon-list/) |
|
||||
| BAT-020 | standard | release-candidate | REQ-046, REQ-047, REQ-048, REQ-049, REQ-050, REQ-051 | dcy-010 | [bat-020](./bat-020_20260628_daemon-scoped-broker-credential/) |
|
||||
| BAT-021 | standard | in-progress | REQ-047 | dcy-010 | [bat-021](./bat-021_20260628_req047-step2-integration-and-runbook/) |
|
||||
| BAT-022 | standard | draft-batch-verification | REQ-052, REQ-053, REQ-054 | dcy-011 | [bat-022](./bat-022_20260628_daemon-crud-self-service-credential-api/) |
|
||||
|
||||
Reference in New Issue
Block a user