docs: add rel-005 production operator runbook and credential matrix (REQ-020~022)
Release: REL-005_20260512 — Production Operator Runbook and Credential Matrix Requirements Approved (REQ-020~022): - REQ-020: production admin bootstrap and steady-state operator access runbook (Deployment) - REQ-021: release credential matrix and token provenance procedures (Documentation) - REQ-022: operator log access and release evidence recording procedures (Documentation) Batch: BAT-009 - Enhance README.md Production Operator Runbook section with: - Deployment secret custodian role labels - Release approver and production operator role definitions - Credential matrix table (public/read/trade-write/admin permission levels) - Canonical smoke environment specifications - HTTP status code interpretation (401/403/404) - Release evidence recording fields Documentation Updates: - Add README.md Section 2: deployment roles and responsibilities - Add README.md Section 3: 4-tier credential matrix with token usage patterns - Enhance README.md Section 6: smoke test procedures and evidence capture - Add bootstrap/steady-state/break-glass 3-tier access pathway clarification - Create REQ-020 Deployment runbook document - Create REQ-021 credential matrix procedures document - Create REQ-022 operator log access procedures document Discovery & Release: - Update dcy-005 with operator access requirements analysis - Create rel-005 with docs-only profile and deployment notes - Update docs/batches/index.md with BAT-009 entry - Update docs/srs/index.md with REQ-020~022 entries and change log
This commit is contained in:
@@ -105,9 +105,11 @@ pfhub user create --username alice --password password123 --email alice@example.
|
||||
pfhub user get <username>
|
||||
pfhub user update <username> --username alice-updated --force
|
||||
pfhub user delete <username> --force
|
||||
pfhub service-token list --user alice
|
||||
pfhub service-token issue nightly --user alice --ttl-days 30
|
||||
pfhub service-token issue permanent --user alice --no-expiry
|
||||
pfhub service-token revoke nightly --force
|
||||
pfhub service-token rotate permanent --force
|
||||
pfhub portfolio list --user <username>
|
||||
pfhub portfolio status 005930:tv --recent 10
|
||||
pfhub portfolio create 005930:tv --symbol 005930 --user <username> --cash-balance 10000000 --force
|
||||
@@ -128,8 +130,10 @@ Supported commands:
|
||||
- `user create`
|
||||
- `user update`
|
||||
- `user delete`
|
||||
- `service-token list`
|
||||
- `service-token issue`
|
||||
- `service-token revoke`
|
||||
- `service-token rotate`
|
||||
- `portfolio list`
|
||||
- `portfolio status`
|
||||
- `portfolio create`
|
||||
@@ -338,6 +342,93 @@ The default `.env.example` includes example tokens for development.
|
||||
|
||||
When `pfhub_DB_ENABLED=true` and both `pfhub_BOOTSTRAP_USERNAME` and `pfhub_BOOTSTRAP_PASSWORD` are set, the app creates that admin user automatically on startup only if the `users` table is empty. This is intended for first-run bootstrap, not recurring synchronization.
|
||||
|
||||
## Production Operator Runbook
|
||||
|
||||
이 섹션은 실배포 환경에서 admin 접근, release smoke credential 관리, provenance 확인, 로그 증적 수집을 반복 가능한 절차로 정리한다.
|
||||
|
||||
### 1. Canonical admin access path
|
||||
|
||||
- steady-state 운영 경로는 운영자 개인 소유 admin 사용자 계정으로 로그인해 얻은 session token 또는 그 컨텍스트에서 발급한 client token이다.
|
||||
- `POST /auth/login`으로 admin auth context를 확보한 뒤, 같은 컨텍스트로 `POST /auth/service-tokens`를 호출해 smoke용 service-token을 발급한다.
|
||||
- bootstrap 환경 변수 기반 admin 생성은 `users` 테이블이 비어 있는 신규 환경 또는 disaster recovery 직후 admin 사용자 부재가 확인된 경우에만 허용한다.
|
||||
- 환경 변수 기반 admin token은 `/opt/portfoliohub/.env`에서만 꺼내는 break-glass fallback으로만 사용하고, 정기 release smoke의 표준 경로로 사용하지 않는다.
|
||||
- 현재 프로덕션 secret material location은 `portfoliohub` systemd service의 `EnvironmentFile=/opt/portfoliohub/.env` 이다. 로컬 워크스페이스 파일이나 release 문서는 canonical storage가 아니다.
|
||||
|
||||
### 2. Smoke credential ownership policy
|
||||
|
||||
역할 책임:
|
||||
- **deployment secret custodian** (repository-maintainers 역할 내): secret material 보관 위치(`/opt/portfoliohub/.env`) 및 접근 통제 관리 책임자.
|
||||
- **production operator / release approver**: 실배포 smoke 수행 및 release 승인 책임자. read/trade-write smoke service-token 공동 소유.
|
||||
|
||||
정책:
|
||||
- admin 로그인 자격증명은 named operator가 개인별로 소유하고 공유하지 않는다.
|
||||
- read/trade-write smoke service-token은 개인 소유가 아니라 환경 단위 운영 credential로 취급하고, release approver 또는 repository-maintainers 역할이 공동 소유한다.
|
||||
- raw password, raw session token, raw client token, raw service-token은 현재 프로덕션 기준 `/opt/portfoliohub/.env`와 동일 접근 통제 하의 운영 저장소에만 저장한다. 관리 주체: deployment secret custodian.
|
||||
- release 문서와 운영 메모에는 token name, token_prefix, permission, runtime_id, issued_at, revoked_at만 남기고 raw secret은 남기지 않는다.
|
||||
- 토큰은 release smoke 전 발급 또는 rotate하고, 노출 의심, 담당자 변경, 장애 대응 후에는 revoke 또는 rotate한다.
|
||||
|
||||
### 3. Release credential matrix
|
||||
|
||||
| Credential | 용도 | 발급 주체 | 보관 위치 | 소유 형태 | 폐기 조건 |
|
||||
|---|---|---|---|---|---|
|
||||
| public/no-auth | `GET /health`, `/docs`, `/openapi.json` | 없음 | 없음 | 해당 없음 | 해당 없음 |
|
||||
| read service-token | `GET /v1/assets`, `GET /v1/user_info` | admin context로 `POST /auth/service-tokens` | `/opt/portfoliohub/.env` 관리 경로 | 환경 단위 공동 소유 (release approver / repository-maintainers) | 노출 의심, 담당자 변경, 장애 후 revoke/rotate |
|
||||
| trade-write service-token | `POST /v1/assets/{asset_id}/ledger/trades` | admin context로 `POST /auth/service-tokens` | `/opt/portfoliohub/.env` 관리 경로 | 환경 단위 공동 소유 (release approver / repository-maintainers) | 노출 의심, 담당자 변경, 장애 후 revoke/rotate |
|
||||
| admin auth context | `POST /auth/service-tokens` (token 발급) | `/auth/login` (steady-state) 또는 break-glass env token | 개인 소유 (공유 금지) | named operator 개인 소유 | 담당자 퇴임, 노출 의심 |
|
||||
|
||||
### 4. Recommended production smoke flow
|
||||
|
||||
1. 운영자 개인 admin 계정으로 로그인해 admin session token을 확보한다.
|
||||
2. 필요하면 동일 컨텍스트로 client token을 발급해 짧은 작업 단위 credential로 전환한다.
|
||||
3. admin auth context로 `POST /auth/service-tokens`를 호출해 read smoke token과 trade-write smoke token을 분리 발급한다.
|
||||
4. raw token은 즉시 `/opt/portfoliohub/.env` 기준 운영 저장소에 저장하고, release evidence에는 token name, token_prefix, permission, runtime_id만 기록한다.
|
||||
5. public/no-auth, read service-token, trade-write service-token, admin auth context를 분리해 smoke를 수행한다.
|
||||
|
||||
예시:
|
||||
|
||||
```bash
|
||||
curl -X POST "<BASE_URL>/auth/login" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username":"<ADMIN_USERNAME>","password":"<ADMIN_PASSWORD>"}'
|
||||
|
||||
curl -X POST "<BASE_URL>/auth/service-tokens" \
|
||||
-H "Authorization: Bearer <ADMIN_SESSION_OR_CLIENT_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"prod-smoke-read","username":"<SMOKE_USERNAME>","permission":"read"}'
|
||||
|
||||
curl -X POST "<BASE_URL>/auth/service-tokens" \
|
||||
-H "Authorization: Bearer <ADMIN_SESSION_OR_CLIENT_TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"name":"prod-smoke-trade-write","username":"<SMOKE_USERNAME>","permission":"trade-write"}'
|
||||
```
|
||||
|
||||
### 5. Provenance check procedure
|
||||
|
||||
- `pfhub service-token list --user <username>`로 token name, token_prefix, token_id, user_id, permission, created_at, last_used_at, revoked_at를 확인한다.
|
||||
- `pfhub service-token rotate <name> --force`로 무기한 token을 rotate하고 새 runtime_id를 확보한다.
|
||||
- `pfhub user get <username>`로 사용자에 연결된 service token 이름 목록을 교차 확인한다.
|
||||
- `GET /v1/user_info`를 live service-token으로 호출해 runtime_id와 permission을 확인하고, 발급 또는 rotate 시 기록한 runtime_id와 대조한다.
|
||||
|
||||
### 6. Log and evidence collection
|
||||
|
||||
**Canonical smoke environment**: production을 canonical acceptance 환경으로 사용한다. staging smoke는 optional preflight로만 사용하며 release acceptance 기준이 되지 않는다. 절차의 source of truth는 이 README이며, 실행 evidence의 canonical record는 해당 release 문서에 남긴다.
|
||||
|
||||
**로그 접근 경로:**
|
||||
- 현재 production log stack은 Linux/Ubuntu host에서 systemd로 구동되는 `portfoliohub` uvicorn 서비스와 Caddy reverse proxy다.
|
||||
- startup 및 애플리케이션 로그는 `sudo journalctl -u portfoliohub -n 200 --no-pager` 또는 `sudo journalctl -u portfoliohub -f`로 확인한다.
|
||||
- reverse proxy 또는 access 수준 로그는 `sudo journalctl -u caddy -n 200 --no-pager` 또는 `sudo journalctl -u caddy -f`로 확인한다.
|
||||
- Loki는 `LOKI_ENABLED`와 실제 수집 파이프라인이 확인된 환경에서만 보조 집계 경로로 취급하며, 현재 canonical production path는 아니다.
|
||||
- 외부 URL만으로는 로그 접근이 되지 않으므로, 운영자는 production host의 systemd journal 조회 권한을 가져야 한다.
|
||||
|
||||
**HTTP 오류 분류:**
|
||||
- `401 Unauthorized`: bearer token 미제공 또는 유효하지 않은 token. 인증 정보 자체가 없거나 만료된 경우.
|
||||
- `403 Forbidden`: token은 유효하나 해당 엔드포인트에 필요한 permission 미보유. read token으로 trade-write 엔드포인트 호출 시 발생.
|
||||
- `404 Not Found`: 인증/권한은 통과했으나 대상 리소스(portfolio, asset 등)가 DB에 존재하지 않음. 권한 문제와 혼동하지 않도록 주의.
|
||||
|
||||
**Release evidence 기록 필드:**
|
||||
- 포함: 시각(executed_at, completed_at), endpoint, HTTP status code, source system(journalctl unit name), token name, token_prefix, runtime_id
|
||||
- 제외: raw secret, raw token 값
|
||||
|
||||
## Planned next steps
|
||||
|
||||
1. Apply and verify Alembic migrations against a real PostgreSQL/Timescale instance.
|
||||
|
||||
Reference in New Issue
Block a user