Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
ca2ec2a
feat(BL-P2-085): Phase 1+2 foundation — guard + dispatched action + e…
bluejayA Apr 27, 2026
e68d50b
feat(BL-P2-085): Phase 3 Step 4 — CrossProjectBlockEvent + AuditLogge…
bluejayA Apr 27, 2026
53d7292
fix(BL-P2-085): cross_project_audit::emit rotation parity (Codex P2)
bluejayA Apr 27, 2026
626cd64
feat(BL-P2-085): Phase 4 Step 5+6 — RBAC project-scope check + roles …
bluejayA Apr 27, 2026
482af90
feat(BL-P2-085): Phase 5 Step 7 — action_to_kind exhaustive + action_…
bluejayA Apr 27, 2026
df646fd
chore(devflow): session-end markers for BL-P2-085 (Phase 5 완료, Phase …
bluejayA Apr 27, 2026
b4b4c44
fix(BL-P2-085): atomic RBAC scope decision (Codex P1 review)
bluejayA Apr 28, 2026
26ac5dc
chore(devflow): BL-P2-085 resume + Codex P1 review markers
bluejayA Apr 28, 2026
73b347d
feat(BL-P2-085): Phase 6 Step 8 — ScopeProvider trait
bluejayA Apr 28, 2026
8401945
chore(devflow): BL-P2-085 Phase 6 Step 8 + 2회차 session-end markers
bluejayA Apr 28, 2026
1f80968
feat(BL-P2-085): Phase 6 Step 9+10 — ActionSender 스탬핑 + scope_provide…
bluejayA Apr 28, 2026
59cda3f
chore(devflow): BL-P2-085 Phase 6 Step 9+10 + 3회차 session-end markers
bluejayA Apr 28, 2026
1604113
feat(BL-P2-085): Phase 7 Step 11a — worker origin guard hook
bluejayA Apr 29, 2026
f06a29c
feat(BL-P2-085): Phase 7 Step 11b — AuditLogger integration on worker…
bluejayA Apr 29, 2026
50fc538
feat(BL-P2-085): Phase 7 Step 11c — AppEvent::CrossProjectBlocked + t…
bluejayA Apr 29, 2026
0b63233
fix(BL-P2-085): Phase 7 폴리싱 — actor context live read + username fall…
bluejayA May 4, 2026
5e47aac
chore(devflow): BL-P2-085 4회차 session-end markers (Phase 7 완료)
bluejayA May 4, 2026
fa5efaa
feat(BL-P2-085): Phase 8 Step 12 — Neutron tenant_id query injection
bluejayA May 6, 2026
9b79a99
chore(devflow): BL-P2-085 5회차 session-end markers (Phase 8 Step 12 완료)
bluejayA May 6, 2026
021dbe0
feat(BL-P2-085): Phase 8 Step 13a — scope_refilter pure helper
bluejayA May 6, 2026
2258298
chore(devflow): BL-P2-085 6회차 session-end markers (Step 13a 완료 + Step…
bluejayA May 6, 2026
0e66271
feat(BL-P2-085): Phase 8 Step 13b-1 — HasTenantId impl for Neutron li…
bluejayA May 7, 2026
0b1ecd8
chore(devflow): BL-P2-085 7회차 session-end markers (Step 13b-1 완료)
bluejayA May 7, 2026
f2e66f3
feat(BL-P2-085): Phase 8 Step 13b-2 — Neutron adapter audit ctx + lis…
bluejayA May 7, 2026
fd2d2e5
feat(BL-P2-085): Phase 8 Step 13b-3 — wire NeutronAuditCtx through re…
bluejayA May 7, 2026
da38cb9
fix(BL-P2-085): Phase 8 polish — cargo-review APPROVE-WITH-MINOR feed…
bluejayA May 7, 2026
aebe242
chore(devflow): BL-P2-085 8회차 session-end markers (Phase 8 feature-co…
bluejayA May 7, 2026
8e0edc9
refactor(BL-P2-085): Step-14-precedent-refactor-1 — RefilterScope struct
bluejayA May 7, 2026
bb009b5
refactor(BL-P2-085): Step-14-precedent-refactor-2 — AuditEmitter + re…
bluejayA May 8, 2026
153c659
refactor(BL-P2-085): Step-14-precedent-refactor-3 — AdapterAuditConfi…
bluejayA May 8, 2026
87bfe10
feat(BL-P2-085): Step 14a — Nova defense-in-depth refilter + ScopedIt…
bluejayA May 11, 2026
0892e18
feat(BL-P2-085): Step 14b — Cinder defense-in-depth refilter (list_vo…
bluejayA May 11, 2026
fd60d83
fix(BL-P2-085): cargo-review S-class follow-ups — RefilterScope empty…
bluejayA May 11, 2026
f864f49
feat(BL-P2-085): Phase 9 Step 15 — FormSelectedIdValidator (scope_val…
bluejayA May 11, 2026
e91cca1
feat(BL-P2-085): Phase 9 Step 16 — Glance DeleteImage pre-mutation FR…
bluejayA May 11, 2026
a2bce46
chore(backlog): register BL-P2-089/090 — BL-P2-085 Step 16 follow-ups
bluejayA May 11, 2026
0a5ff50
feat(BL-P2-085): Phase 10 Step 17 — cross_project_toast.rs (FR6 user-…
bluejayA May 11, 2026
2fbe353
docs(BL-P2-085): Phase 11 Step 18 — background poll FR2 architectural…
bluejayA May 11, 2026
e2644a3
chore(devflow): audit.md trailing edit from backlog hook
bluejayA May 11, 2026
b40f3af
fix(BL-P2-085): cargo-review branch-full S-class — empty-active guard…
bluejayA May 12, 2026
c614aeb
chore(backlog): register BL-P2-091/092/093/094 — BL-P2-085 cargo-revi…
bluejayA May 12, 2026
9c26fc6
style(BL-P2-085): Phase-11-final-pass fmt 일괄 — accumulated pre-existi…
bluejayA May 12, 2026
bc0e28a
Merge remote-tracking branch 'origin/main' into feature/bl-p2-085-cro…
bluejayA May 12, 2026
a892e5a
fix(BL-P2-085): post-merge action_to_kind exhaustive — wire `Action::…
bluejayA May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ uuid = { version = "1", features = ["v4"] }
async-trait = "0.1"
reqwest = { version = "0.12", features = ["json"] }
chrono = { version = "0.4", features = ["serde"] }
sha2 = "0.10"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tracing-appender = "0.2"
Expand Down
151 changes: 151 additions & 0 deletions devflow-docs/audit.md

Large diffs are not rendered by default.

140 changes: 140 additions & 0 deletions devflow-docs/backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,146 @@ BL-P2-080 Unit 3(`.github/workflows/ci.yml::devstack-integration`)은 placeholde

**Ref**: 2026-04-21 Codex adversarial-review v3 finding M1, BL-P2-081 옵션 β 타협안 follow-up.

### BL-P2-089: Glance `UpdateImage` Action + FR4 cross-project guard (BL-P2-085 follow-up)
**Priority**: Medium
**Parent**: BL-P2-085 Step 16 (descope)
**Category**: Security / Functional

**Description**: BL-P2-085 Step 16에서 `Action::DeleteImage` 경로에는 FR4 pre-mutation 가드 (Glance `get_image` → `check_image_owner_scope` → `Fr4Form` audit + reject)를 적용했으나, image **update** path는 `Action::UpdateImage` variant 자체가 미존재해 가드를 적용할 곳이 없었음. Glance `update_image` adapter는 이미 존재하지만 worker가 호출하지 않음.

본 BL에서:
1. `Action::UpdateImage { id, params: ImageUpdateParams }` variant 신규 (`src/action.rs`)
2. `module/image/mod.rs`에 update 키 핸들러 (`PendingAction::UpdateImage` 또는 직접 form submit)
3. `worker.rs::action_to_kind`에 `Action::UpdateImage => ActionKind::Update` 분류 + `action_name` 매핑 추가
4. `worker.rs::handle_action(Action::UpdateImage)` 분기 — `DeleteImage`와 동일 패턴:
- pre-mutation `glance.get_image(&id).await`
- `check_image_owner_scope(&image, active)` 호출 (Step 16 pure helper 재사용)
- Block이면 `emit_form_block_audit(reason, "UpdateImage", "image", &id, ...)` + `AppEvent::CrossProjectBlocked { reason, action: "UpdateImage" }`
- 진행 시 `glance.update_image(&id, &params).await`
5. 3 신규 tests (worker.rs::tests):
- `test_update_image_rejects_cross_project_owner`
- `test_update_image_allows_same_project_owner`
- `test_update_image_emits_fr4_form_event` — `emit_form_block_audit`로 "UpdateImage" action_type stamp 확인

**Out of scope**: image visibility/min_disk/min_ram 등 update field 전체 surface — 본 BL은 FR4 가드 적용에 집중. Form UI 추가/변경은 별도 cycle.

**Ref**: BL-P2-085 Step 16 commit `e91cca1` ("Plan adjustment: descoped `UpdateImage` to a follow-up BL because no `Action::UpdateImage` variant exists today").

### BL-P2-090: `MockGlanceWithImage` configurable mock for `handle_action` FR4 integration test (BL-P2-085 follow-up)
**Priority**: Low
**Parent**: BL-P2-085 Step 16 (test gap)
**Category**: Testing Infrastructure

**Description**: BL-P2-085 Step 16의 pure helper (`check_image_owner_scope` / `emit_form_block_audit`) 5 tests로 결정 매트릭스는 cover됐으나, `handle_action(Action::DeleteImage)` **통합 경로** 자체는 단위 테스트로 검증되지 않음. 원인: 기본 `MockGlanceAdapter::get_image`가 `Err(ApiError::NotFound)`만 반환하므로 cross-project owner 시나리오를 `handle_action`에 끝까지 흘려보낼 수 없음.

본 BL에서:
1. `MockGlanceAdapter`에 configurable behavior 추가 — 다음 중 1택:
- Option (a): `MockGlanceAdapter::with_image(Image)` builder + 내부 `Mutex<Option<Image>>`로 get_image 반환 제어
- Option (b): 별 mock struct `MockGlanceWithImage { owner: Option<String>, delete_called: AtomicBool }`로 분리
- Option (c): 더 일반화된 `MockAdapter<T>` 패턴 도입 (다른 adapter mock에도 확장 가능)
2. `worker.rs::tests`에 통합 test 추가:
- `test_handle_action_delete_image_emits_cross_project_blocked_when_owner_mismatch` — AdapterRegistry(with mock) + active=A → `handle_action` returns `AppEvent::CrossProjectBlocked`, mock이 `delete_image`를 호출받지 않았는지 확인 (Atomic flag)
- `test_handle_action_delete_image_proceeds_when_owner_matches` — owner=A + active=A → audit log empty, `delete_image` 호출됨
- `test_handle_action_delete_image_falls_through_on_pre_get_error` — pre-GET이 NotFound 등이면 기존 delete 흐름 (현재 동작 보존)

**Out of scope**: 다른 adapter mock 일반화 (별도 BL). Option (c)를 선택할 경우 다른 adapter에도 적용 여부는 follow-up.

**Ref**: BL-P2-085 Step 16 commit `e91cca1` ("Pure helpers cover the decision matrix; `handle_action` integration is not unit-tested directly because `MockGlanceAdapter::get_image` returns `NotFound`").

### BL-P2-091: Glance FR1 — `ScopedItem for Image` + `GlanceHttpAdapter::with_audit` (BL-P2-085 follow-up)
**Priority**: Medium
**Parent**: BL-P2-085 cargo-review branch-full Correctness #1 / Suggestions #4
**Category**: Security / Functional

**Description**: BL-P2-085가 Neutron/Nova/Cinder 3개 list adapter에 FR1 (`refilter_response` + `AdapterFilterViolation` audit emit)을 wire했으나, GlanceHttpAdapter는 **FR1 미적용**. 결과적으로 `FetchImages`가 admin 토큰 등에서 cross-project image를 list UI에 노출할 수 있고, 차단은 FR4 (DeleteImage pre-mutation) 경로에서만 발생. atomic security PR의 "FR1+FR2+FR3+FR4 layered defense" 약속과 비대칭.

본 BL에서:
1. `src/adapter/http/scope_refilter.rs`에 `impl ScopedItem for Image` 추가 — `Image.owner: Option<String>` → `tenant_id()`, `Image.id: String` → `resource_id()`
2. `src/adapter/http/glance.rs`:
- `audit_ctx: Option<Arc<GlanceAuditCtx>>` 필드 + `with_audit(ctx)` builder (Nova/Cinder/Neutron 패턴 mirror)
- `refilter_response<T: ScopedItem>` helper
- `list_images` 본체에서 `refilter_response(resp, filter.all_tenants, "FetchImages", "image")` 호출
3. `src/adapter/http/neutron_audit.rs`에 `pub type GlanceAuditCtx = AuditCtx;` + `AdapterAuditConfig.glance: Option<Arc<GlanceAuditCtx>>` 필드 추가 + `build_audit_config`에서 `glance` 채우기
4. `src/adapter/registry.rs::new_http` body에 `audit.glance` 소비 (`glance.with_audit(ctx)`)
5. 신규 tests: `test_image_has_scoped_item_returns_some_when_present` / `_returns_none_when_absent` / `_build_audit_config_returns_glance_with_service_glance` / `test_glance_with_audit_attaches_ctx_default_none`

**Out of scope**: Glance image visibility 의미론 (`public`/`private`/`shared`/`community`)의 정밀한 처리 — `owner` 비교만으로 부족할 수 있는 케이스는 별도 BL.

**Ref**: cargo-review branch-full report 2026-05-12 (Correctness #1, Suggestions #4).

### BL-P2-092: Cross-resource pre-mutation FR4 (FIP/port, Volume/server project mismatch) (BL-P2-085 follow-up)
**Priority**: Medium
**Parent**: BL-P2-085 cargo-review branch-full Suggestions #5
**Category**: Security / Functional

**Description**: BL-P2-085 FR4 (`check_image_owner_scope` / `validate_form_scope`)는 단일 resource의 owner-vs-active 비교만 처리. 그러나 cross-resource mutation은 두 resource가 서로 다른 project에 속할 가능성에 대한 pre-mutation check가 부재:
- `AssociateFloatingIp { fip_id, port_id }` — fip가 proj-A, port가 proj-B 소속일 수 있음
- `DisassociateFloatingIp { fip_id }` — fip가 active scope와 다른 project일 수 있음
- `AttachVolume { volume_id, server_id }` — volume과 server가 다른 project일 수 있음
- `DetachVolume` / `ForceDetachVolume` — 동일
- `LiveMigrateServer` / `Evacuate` 등 — destination host가 다른 project AZ일 수 있음 (별 BL 영역일 수도)

현재는 OpenStack 서버측 RBAC에 의존 — admin 토큰이면 통과 가능. atomic security의 client-side defense-in-depth가 비대칭.

본 BL에서:
1. `Action::AssociateFloatingIp` 분기에 pre-mutation GET 두 번 (fip + port) → 각자 project_id 추출 → active_tenant와 비교 → 첫 mismatch면 `Fr4Form` audit emit + `AppEvent::CrossProjectBlocked`
2. `Action::AttachVolume` / `DetachVolume` / `ForceDetachVolume` 동일 패턴 (volume + server)
3. `Action::DisassociateFloatingIp` — fip만 비교
4. helper `check_pair_scope(left_project, right_project, active)` pure fn 도입 (이번 BL의 단일 resource helper 일반화)
5. 5+ tests — 각 action별 cross-project deny + same-project allow + missing project_id fail-safe

**Out of scope**: Live-migrate destination host AZ scope (BL-P2-086 직후 영역). Phase 9 plan에는 FIP/Volume만 있었음.

**Ref**: cargo-review branch-full report 2026-05-12 (Suggestions #5).

### BL-P2-093: `actor_ctx.user_id` cloud-switch live sync (BL-P2-085 follow-up)
**Priority**: High
**Parent**: BL-P2-085 cargo-review branch-full Suggestions #1
**Category**: Security / Audit Attribution

**Description**: BL-P2-085 Phase 7 폴리싱에서 `actor_ctx.cloud`는 `App::handle_event(ContextChanged)`에서 live update 되도록 wire됐으나, `actor_ctx.user_id`는 **wire-startup 시점의 `wire_username`으로 고정**. 즉 사용자가 cloud-A → cloud-B로 switch한 뒤 다른 자격증명으로 재인증해도, 그 후 발생하는 모든 cross-project block audit entry는 **cloud-A의 user_id를 사용**한다.

영향:
1. Audit attribution 손상 — multi-cloud 환경에서 누가 block을 trigger했는지 잘못 기록
2. **fingerprint v1 dedup 깨짐** — canonical `"v1|user|active|origin|target|action|resource_id"`에 user가 포함됨 → 동일 user가 두 cloud에서 동일 cross-project pattern 시도 시 fingerprint 다르게 생성 → dedup 미발동

본 BL에서:
1. `ContextTarget` 또는 `ContextChanged` event payload에 새 토큰의 `user.id` 포함 (Keystone token의 user UUID 사용)
2. `App::handle_event(ContextChanged)`에서 `actor_ctx.write().user_id = new_user_id`로 갱신
3. `KeystoneAuthAdapter::get_token_info()`이 user_id를 노출하는지 확인 + wire
4. 신규 test: `test_actor_ctx_user_id_updates_on_cloud_switch` (RwLock mutate → 다음 emit이 새 user_id 반영) — Phase 7 폴리싱의 `test_emit_origin_block_audit_picks_up_actor_context_mutation` 패턴 mirror

**Out of scope**: token refresh 중 user_id 변경 hook (BL-P2-052의 token refresh 작업과 결합). Username/UUID 매핑 (현재는 UUID 우선).

**Ref**: cargo-review branch-full report 2026-05-12 (Suggestions #1).

### BL-P2-094: Fingerprint v2 schema — `|` escape + length-prefix (BL-P2-085 follow-up)
**Priority**: Low (v2 cycle 시 필수)
**Parent**: BL-P2-085 cargo-review branch-full Suggestions #2
**Category**: Schema Hardening

**Description**: BL-P2-085 fingerprint v1 canonical은 `"v1|user|active|origin|target|action|resource_id"` 형태로 field 값을 unescape된 채 `|`로 join. 어느 하나가 `|`를 포함하면 collision 가능:
- `(user="a|b", active="c")` ↔ `(user="a", active="b|c")` 동일 fingerprint
- Keystone user_id는 UUID라 안전하지만 `action_type` (예: "Action|with|pipe"), `resource_id` (사용자 정의 가능한 경우) 등 customizable fields는 위험

v1 schema는 LOCKED라 즉시 변경 X. **다음 schema bump 시점에 반드시 escape rule을 같이 도입**.

본 BL에서:
1. fingerprint v2 canonical 설계:
- 옵션 (a): `\|` / `\\` escape rule + version prefix `"v2|escaped(user)|escaped(active)|..."`
- 옵션 (b): length-prefix encoding `"v2|3:foo|5:bar|..."` (collision 불가, simple)
- 옵션 (c): JSON canonicalization (serde_json with `sort_keys`) — 가독성 ↓이지만 표준
2. v1 → v2 migration: 기존 v1 entry 보존 (rotation 이전 데이터). 새 entry는 v2.
3. `CrossProjectBlockEvent::fingerprint` 갱신 + `tests/cross_project_audit.rs` schema-stable tests 갱신
4. v1 LOCKED 약속 해제 — state.md "Schema-Stable 결정" 갱신
5. **호환성**: audit consumer (grep/jq script 등)는 v1/v2 둘 다 지원하도록 release notes

**Out of scope**: hex 변환 최적화 (cargo-review Suggestions #6, byte-level lookup) — 본 BL과 함께 진행하면 자연.

**Trigger**: 새 schema field 추가 (예: `service` from refactor-3 / `correlation_id` epoch propagation) 또는 v1 LOCKED 해제 결정 시.

**Ref**: cargo-review branch-full report 2026-05-12 (Suggestions #2).

### BL-P2-052: Rescoped 토큰 자동 refresh + ContextChanged handler
**Priority**: High (Part A 기준) — **BL-P2-080 / BL-P2-081 / BL-P2-083 이후**
**Category**: Auth / Functional Regression
Expand Down
Loading
Loading