Skip to content

feat(BL-P2-091): Glance FR1 — ScopedItem for Image + with_audit + refilter#87

Merged
bluejayA merged 2 commits into
mainfrom
feat/bl-p2-091-glance-fr1
May 13, 2026
Merged

feat(BL-P2-091): Glance FR1 — ScopedItem for Image + with_audit + refilter#87
bluejayA merged 2 commits into
mainfrom
feat/bl-p2-091-glance-fr1

Conversation

@bluejayA
Copy link
Copy Markdown
Owner

Summary / 요약

  • EN: BL-P2-085 follow-up. Wires Glance FR1 to match Neutron/Nova/Cinder — closes the "FR1+FR2+FR3+FR4 layered defense" asymmetry that atomic security PR feat(BL-P2-085): cross-project scoping atomic security (FR1~FR4) #85 deferred (image-visibility semantics).
  • KR: atomic security PR #85에서 의도적으로 deferred됐던 Glance FR1을 Neutron/Nova/Cinder 패턴 그대로 mirror. `Image.owner` → `ScopedItem.tenant_id()` 매핑 + `GlanceHttpAdapter::with_audit` builder + `list_images` refilter wiring + registry consumes `audit.glance`.

What changed

Layer Change
`scope_refilter.rs` New `impl ScopedItem for Image` (owner → tenant_id, id → resource_id)
`neutron_audit.rs` `pub type GlanceAuditCtx = AuditCtx`; `AdapterAuditConfig.glance` field; `build_audit_config` populates `glance` with `service = "glance"`
`glance.rs` `audit_ctx: Option<Arc>` field + `with_audit` builder + `refilter_response<T: ScopedItem>` helper + `list_images` wraps response through it (action_type="FetchImages", resource_kind="image", correlation_id=0)
`registry.rs::new_http` Consumes `audit.glance` and chains onto adapter, parallel to neutron/nova/cinder

Asymmetry rationale (preserved from BL-P2-085 spec)

Glance v2's image visibility model (`public`/`private`/`shared`/`community`) means `owner` alone may not capture cross-project access intent for `shared`/`community` rows. This BL implements the owner-equality FR1 check — same contract as Neutron/Nova/Cinder. Rows surfaced under strict scoping with non-matching or absent `owner` drop fail-safe; callers wanting global visibility opt in via `filter.all_tenants = true` (already supported by `build_image_query`).

TDD trail

  • RED: 4 spec-required tests fail to compile (E0432/E0599/E0609 — no `GlanceAuditCtx`, no `audit.glance` field, no `with_audit` method).
  • GREEN: same tests pass after wiring (`test_image_has_scoped_item_returns_some_when_present`, `_returns_none_when_absent`, `test_build_audit_config_returns_glance_with_service_glance`, `test_glance_with_audit_attaches_ctx_default_none`).
  • Also updated existing `test_adapter_audit_config_default_all_none` + `test_build_audit_config_returns_none_when_logger_none` to assert the new `glance` slot.

Verification

  • cargo test → 1498 passed (main = 1494 → +4)
  • cargo clippy --all-targets -- -D warnings → clean
  • cargo fmt --all -- --check → clean

Out of scope (registered follow-ups)

  • Richer Glance visibility semantics (BL-P2-091 spec out-of-scope note)
  • `UpdateImage` Action variant + FR4 guard (BL-P2-089)
  • Configurable Glance mock for handle_action FR4 integration tests (BL-P2-090)

Test plan

  • DevStack manual: admin token, project-A active, list images that include a project-B-owned image — confirm the cross-project row is dropped from the list AND an `AdapterFilterViolation` audit line is emitted with `details.guard_layer = fr1_adapter`, `details.resource_id = `.
  • Unit: 4 spec tests
  • Suite: 1498 passing
  • Lint: clippy + fmt clean

Refs: cargo-review branch-full report 2026-05-12 (Correctness #1, Suggestions #4). Parent: PR #85 (BL-P2-085 atomic security).

🤖 Generated with Claude Code

bluejayA and others added 2 commits May 13, 2026 08:54
…ilter

BL-P2-085 deferred Glance FR1 because image-visibility semantics
(public/private/shared/community) need richer handling than `tenant_id`
alone. This change wires the same defense-in-depth refilter as
Neutron/Nova/Cinder for the `owner` check (FR1 contract), closing the
"FR1+FR2+FR3+FR4 layered defense" asymmetry of the atomic security PR.

- `impl ScopedItem for Image` in scope_refilter.rs maps
  `Image.owner: Option<String>` → `tenant_id()` (Glance v2 uses `owner`
  rather than `tenant_id` on the wire) and `Image.id: String` →
  `resource_id()`.
- `GlanceAuditCtx` type alias + `AdapterAuditConfig.glance` field +
  `build_audit_config` slot tagged `service = "glance"`.
- `GlanceHttpAdapter::with_audit` builder + `refilter_response<T>` helper
  mirror the Neutron pattern exactly (correlation_id=0; list_* are not
  bound to a worker dispatch — epoch propagation is a separate cycle).
- `list_images` wraps the `paginated_list` result through
  `refilter_response(_, filter.all_tenants, "FetchImages", "image")`
  so cross-project rows surface as `AdapterFilterViolation` events.
- `registry::new_http` consumes `audit.glance` and chains it onto the
  adapter, parallel to neutron/nova/cinder.

Out of scope (BL-P2-091 spec): richer visibility semantics —
shared/community images surfacing without a matching `owner` under
strict scoping drop fail-safe (caller can opt out with
`all_tenants=true`).

Tests: 4 spec-required tests + existing audit_config tests assert
the `glance` slot is None at default and populated with service tag
when logger is Some. cargo test = 1498 passed (1494 → +4).
clippy clean. fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ilter

Codex review P1: Strict refilter dropped public/community/shared images
under non-admin tokens because their `owner` is routinely a different
project (or absent). That broke the standard `list_images` flow — users
lost visibility into the public Ubuntu image, distro AMIs, etc., and
`all_tenants=true` is not a viable normal-user fallback.

`ScopedItem` now exposes `is_globally_accessible() -> bool` (default
`false` preserves Neutron/Nova/Cinder behaviour where `tenant_id`
equality is the only authoritative scope test). `Image` overrides:
- `public` / `community` / `shared` → true (visibility marker IS the
  access decision; bypass owner refilter).
- `private` and any unrecognized value → false (positive allowlist so a
  new Glance visibility variant doesn't silently relax the check).

`refilter_by_scope` short-circuits keep for any globally-accessible row,
so those rows neither hit `dropped` nor emit AdapterFilterViolation
events (no false-positive leak signal). The FR1 leak signal we still
catch:
  `visibility == "private"` AND `owner != active`

Tests: 6 new (4 visibility-marker tests covering the three globally-
accessible variants + fail-safe for unknown values, 1 cross-project mix
test asserting public bypass + private cross-project drop, 1 sanity
re-assertion on the default sample_image). cargo test = 1504 passed
(1498 → +6). clippy clean. fmt clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluejayA bluejayA merged commit f8db559 into main May 13, 2026
3 checks passed
@bluejayA bluejayA deleted the feat/bl-p2-091-glance-fr1 branch May 13, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant