fix: enforce trade-write permission check on v1 ledger trade endpoint

Security Fix:
- Update v1_ledger.py to require trade_write permission instead of read
- Fix authentication dependency from require_read to require_trade_write
- Ensure trade operations cannot be performed with read-only service tokens

Test Coverage:
- Add test_v1_ledger_trade_requires_trade_write_permission() to verify permission boundary
- Confirm read-only tokens are rejected with 403 Forbidden status
- Validate repository methods are not called on permission failure

Documentation:
- Add new Discovery dcy-005 for production admin bootstrap and operator access
- Update docs/discovery/index.md with dcy-005 entry
- Update release rel-001 with additional verification notes and issue references
This commit is contained in:
2026-05-11 14:17:27 +00:00
parent d20d784452
commit 2e1298a978
5 changed files with 302 additions and 16 deletions
@@ -43,18 +43,18 @@
### 배포 전
- [ ] `CREDENTIAL_ENCRYPTION_KEY` 환경 변수 설정 완료
- [ ] `alembic upgrade head` 성공 (migration 0014)
- [ ] pytest 98개 로컬 PASS 확인 (2026-05-01 완료)
- [x] `CREDENTIAL_ENCRYPTION_KEY` 환경 변수 설정 완료
- [x] `alembic upgrade head` 성공 (migration 0014)
- [x] pytest 로컬 PASS 확인 (`python -m pytest -q` → `129 passed in 4.46s`)
### 배포 후
- [ ] GET /health → 200, body에 `status`, `service`, `version`, `checked_at` 포함 확인
- [ ] GET /health → `checked_at`에 `+09:00` 포함 확인
- [x] GET /health → 200, body에 `status`, `service`, `version`, `checked_at` 포함 확인
- [x] GET /health → `checked_at`에 `+09:00` 포함 확인
- [ ] POST /auth/service-tokens (admin 토큰) → 201, `runtime_id` 포함 확인
- [ ] GET /v1/assets (service-token) → 200, `assets[]` 반환 확인
- [x] GET /v1/assets (service-token) → 200, `assets[]` 반환 확인
- [ ] GET /v1/assets (service-token) → service-token 소유자의 portfolio만 반환되고 다른 사용자 portfolio가 비노출인지 확인 (REQ-006 owner scope 재확인)
- [ ] GET /v1/user_info (service-token) → 200, `trading_enabled`/`brokers[]` 포함 확인
- [x] GET /v1/user_info (service-token) → 200, `trading_enabled`/`brokers[]` 포함 확인
- [ ] POST /v1/assets/{asset_id}/ledger/trades → 201 (신규), 200 (중복 동일 payload) 확인
- [ ] 서비스 startup 로그에 RuntimeError 없음 확인
@@ -75,19 +75,38 @@
- Revision or Artifact:
- (미기록 — 커밋 SHA 배포 후 기록)
- Commands or Procedure:
- (미기록)
- `CREDENTIAL_ENCRYPTION_KEY` 설정 확인 완료
- `python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"` 실행으로 Fernet 키 생성 형식 확인 완료
- 보안상 실제 환경 변수 값과 생성된 키 원문은 문서에 기록하지 않음
- `alembic upgrade head` 실행 완료
- Alembic 출력: `Context impl PostgresqlImpl.` / `Will assume transactional DDL.`
- 추가 revision 실행 로그는 출력되지 않았고, 명시적 오류 메시지는 없었음
- 로컬 회귀 검증: `python -m pytest -q` → `129 passed in 4.46s`
- 배포 public smoke: `curl https://fourblocks2026.com/portfolio-hub/health` → HTTP 200, `{"status":"ok","service":"portfolio-hub","version":"0.1.0","checked_at":"2026-05-11T21:35:54.686819+09:00"}`
- 배포 public contract 확인: `/portfolio-hub/docs` → HTTP 200, `/portfolio-hub/openapi.json` → HTTP 200
- 실배포 설정 파일 `temp/real_portfolio-hub_environment`의 service token으로 protected read smoke 실행
- `GET /v1/assets` → HTTP 200, body 요약: `total=0`, `assets=[]`
- `GET /v1/user_info` → HTTP 200, body 요약: `runtime_id` 존재, `trading_enabled=true`, `permission=read`, `brokers=[]`
- `POST /auth/service-tokens` with same token → HTTP 403 `Insufficient permission` (admin 토큰 미보유)
- `POST /v1/assets/00000000-0000-0000-0000-000000000001/ledger/trades` with same read service token → HTTP 404 `Asset not found ...`
- 위 ledger 결과는 계약상 trade-write 부족이면 403이어야 한다는 기대와 다르므로, write permission enforcement 재확인 필요
- 현재 보유 credential만으로는 admin 성공 경로와 trade-write 성공 경로를 완료할 수 없음
### Profile-Specific Checks
- app-service:
- Health Endpoint:
- (미수행 — 배포 후 확인 필요)
- `GET https://fourblocks2026.com/portfolio-hub/health` → HTTP 200 확인. body에 `status=ok`, `service=portfolio-hub`, `version=0.1.0`, `checked_at=2026-05-11T21:35:54.686819+09:00` 포함.
- Startup Log Fields:
- (미수행 — 배포 후 확인 필요)
- 외부 URL만으로는 startup 로그 접근 불가. 배포 환경 로그 접근 권한 필요.
- Request Log Fields:
- (미수행 — 배포 후 확인 필요)
- 외부 URL만으로는 request 로그 접근 불가. 배포 환경 로그 접근 권한 필요.
- Smoke or Manual Verification:
- (미수행 — Verification Checklist 참조)
- public smoke 완료 (`/health`, `/docs`, `/openapi.json`).
- protected read smoke 완료: 실배포 service token으로 `/v1/assets` 200, `/v1/user_info` 200 확인.
- `/v1/assets` 응답은 빈 목록(`total=0`)이라 owner scope 비노출은 강하게 입증되지 않음.
- same token으로 `/auth/service-tokens`는 403 `Insufficient permission` 확인되어 admin 성공 경로는 미검증.
- same token으로 `/v1/assets/{asset_id}/ledger/trades` 호출 시 404 `Asset not found`가 반환되어, trade-write 미보유 토큰에 대한 403 차단 여부가 계약과 다름. write path는 release blocker로 재확인 필요.
### Rollback Outcome
@@ -103,7 +122,7 @@
- Released Decision:
- (미기록)
- Rationale:
- (미기록)
- public health 및 read service-token smoke는 확인됐으나, admin 성공 경로와 trade-write 성공 경로가 아직 완료되지 않았다. 특히 ledger/trades가 read token에 대해 계약 기대(403) 대신 404를 반환해 permission enforcement 재확인이 필요하므로 released 전환 보류
- Recorded By:
- (미기록)
- Recorded At: