Skip to content

BCH-1342: Scope ssp-export-offering subscribe to an instance-level downstream allow-list (Phase 6)#452

Merged
saltpy-cs merged 2 commits into
mainfrom
feat/BCH-1342
Jul 10, 2026
Merged

BCH-1342: Scope ssp-export-offering subscribe to an instance-level downstream allow-list (Phase 6)#452
saltpy-cs merged 2 commits into
mainfrom
feat/BCH-1342

Conversation

@saltpy-cs

@saltpy-cs saltpy-cs commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds SSPExportOfferingAllowedDownstream, a per-offering allow-list of downstream SSPs permitted to subscribe.
  • New admin CRUD surface (GET/POST/DELETE .../export-offerings/:offeringId/allowed-downstreams[/:downstreamSspId]), gated by the existing ssp:export guard.
  • Subscribe now denies (403) a downstream SSP not on a non-empty allow-list, even if that user holds the contributor role. An offering with no allow-list rows keeps today's type-level default (backwards compatible).
  • The allowed_downstreams: set<uuid> attribute is declared in internal/authz/manifest.yaml for documentation/future engine wiring, but is not enforced by the PDP — BCH-1319's C1/C2 resource-attribute resolution isn't honoured by any shipped driver yet (no resource-side PIP, ScopeParam is C0-only and unwired, neither driver reads Resource.Props). Enforcement is a handler-level check in Subscribe (isDownstreamAllowed), mirroring the existing authorizeDownstreamUpdate ad-hoc pattern already in that same handler. This is called out explicitly in the manifest and code comments.

Test plan

  • go build ./..., go vet ./... (incl. -tags integration), golangci-lint run ./..., go mod tidy all clean
  • go test ./... (unit) passes, including new CRUD round-trip/idempotency/validation and allow-list enforcement tests
  • go test -tags integration ./internal/api/handler/oscal/... passes (real Postgres via testcontainers), through the real Cedar-driven PEP — proves the AC's literal "even with the contributor role" wording, an offering with no allow-list still allows any downstream, and an allow-listed downstream succeeds
  • make swag docs regenerated for the 3 new endpoints

Summary by CodeRabbit

  • New Features

    • Added management of downstream SSP allow-lists for export offerings.
    • Added API support to list, add, and remove allowed downstream SSPs.
    • Subscriptions are now blocked when a downstream SSP is not allow-listed; offerings without an allow-list remain open by default.
  • Documentation

    • Updated API documentation with the new endpoints, request formats, and responses.
  • Tests

    • Added coverage for allow-list management, validation, idempotency, and subscription behavior.

…wnstream allow-list (Phase 6)

Adds an allowed-downstreams allow-list per export offering, managed via a
new admin CRUD surface and enforced by a handler-level check in Subscribe.
An offering with no allow-list keeps today's type-level default (any
downstream with the contributor role may subscribe); once an allow-list
exists, only listed downstream SSPs may subscribe, even holding that role.

Ships as a handler-level check rather than generic PDP/manifest-driven ABAC
because BCH-1319's C1/C2 resource-attribute resolution isn't honoured by any
shipped driver yet (no resource-side PIP, ScopeParam is C0-only and unwired,
neither driver reads Resource.Props) — documented in the manifest and code
comments so it isn't mistaken for that engine capability landing.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@saltpy-cs, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6b95af90-c6de-4b16-b3ee-7d116eea009a

📥 Commits

Reviewing files that changed from the base of the PR and between 82a24c8 and 7e5a2e6.

📒 Files selected for processing (6)
  • docs/docs.go
  • docs/swagger.json
  • docs/swagger.yaml
  • internal/api/handler/oscal/ssp_export_offering_allowlist_test.go
  • internal/api/handler/oscal/ssp_export_offerings.go
  • internal/api/handler/oscal/ssp_leverage.go
📝 Walkthrough

Walkthrough

Adds an OSCAL export-offering downstream allow-list model, CRUD API, OpenAPI documentation, subscription enforcement, migration wiring, and unit/integration tests covering default, allowed, denied, idempotent, and invalid cases.

Changes

Export offering downstream allow-list

Layer / File(s) Summary
Allow-list model and migrations
internal/service/relational/ssp_offerings.go, internal/service/migrator.go, internal/tests/migrate.go, internal/api/handler/oscal/ssp_leverage_unit_test.go
Adds the composite-key allow-list model and includes it in application and test database setup and teardown.
Allow-list management API and contract
internal/api/handler/oscal/ssp_export_offerings.go, internal/api/handler/oscal/ssp_export_offering_allowlist_test.go, docs/docs.go, docs/swagger.json, docs/swagger.yaml
Adds nested GET, POST, and DELETE endpoints with UUID validation, idempotent insertion, not-found handling, tests, and OpenAPI schemas.
Subscription enforcement
internal/api/handler/oscal/ssp_leverage.go, internal/api/handler/oscal/ssp_leverage_allowlist_test.go, internal/authz/manifest.yaml
Checks downstream membership for an offering during subscription and preserves allow-by-default behavior when no entries exist.
Integration coverage
internal/api/handler/oscal/ssp_leverage_allowlist_integration_test.go
Verifies denied non-allow-listed subscriptions, successful allow-listed subscriptions, and default behavior without an allow-list.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Subscribe
  participant AllowListCheck
  participant Database
  Client->>Subscribe: Submit offering subscription
  Subscribe->>AllowListCheck: Check downstream membership
  AllowListCheck->>Database: Query offering allow-list
  Database-->>AllowListCheck: Return matching entries
  AllowListCheck-->>Subscribe: Allow or deny
  Subscribe-->>Client: Return subscription response
Loading

Possibly related PRs

Poem

A bunny checks the downstream gate,
With UUIDs lined up straight.
Add, list, remove—hop, hop, hooray!
Cedar guards the route today.
Empty lists let travelers through,
Listed SSPs get passage too.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: per-offering downstream allow-listing for SSP subscribe behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/docs.go`:
- Around line 20841-20899: Add a swaggo `@Description` annotation to the Go
handler implementing DELETE
/oscal/system-security-plans/{id}/export-offerings/{offeringId}/allowed-downstreams/{downstreamSspId},
alongside its existing annotations, then re-run swag init to regenerate the docs
artifact with the description field.

In `@internal/api/handler/oscal/ssp_export_offering_allowlist_test.go`:
- Around line 112-120: Add a test alongside
TestRemoveAllowedDownstreamNotFoundReturns404 named
TestRemoveAllowedDownstreamInvalidUUIDReturns400, using newSSPLeverageTestDB,
newLeverageFixture, and NewSSPExportOfferingHandler; construct the DELETE
context with downstreamSspId set to "not-a-uuid", invoke
RemoveAllowedDownstream, and assert a 400 Bad Request response.

In `@internal/api/handler/oscal/ssp_export_offerings.go`:
- Around line 776-785: Replace uuid.MustParse in the handler constructing
SSPExportOfferingAllowedDownstream with uuid.Parse, handle its returned error by
returning an appropriate client error response, and use the parsed UUID for
DownstreamSSPID. Prefer reusing a parsed UUID from validate() if that method can
return one, while preserving the existing database error handling and success
response.

In `@internal/api/handler/oscal/ssp_leverage.go`:
- Around line 445-452: Move the isDownstreamAllowed check from the
pre-transaction handler flow into the write transaction callback, using tx
rather than h.db so the allow-list read and subsequent write are atomic.
Propagate a dedicated sentinel error from the callback when downstreamAllowed is
false, then translate that sentinel into the existing HTTP 403 response while
preserving other errors as 500 responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f5daae63-fd6e-4023-a1db-9874b16186eb

📥 Commits

Reviewing files that changed from the base of the PR and between 0c6e943 and 82a24c8.

📒 Files selected for processing (13)
  • docs/docs.go
  • docs/swagger.json
  • docs/swagger.yaml
  • internal/api/handler/oscal/ssp_export_offering_allowlist_test.go
  • internal/api/handler/oscal/ssp_export_offerings.go
  • internal/api/handler/oscal/ssp_leverage.go
  • internal/api/handler/oscal/ssp_leverage_allowlist_integration_test.go
  • internal/api/handler/oscal/ssp_leverage_allowlist_test.go
  • internal/api/handler/oscal/ssp_leverage_unit_test.go
  • internal/authz/manifest.yaml
  • internal/service/migrator.go
  • internal/service/relational/ssp_offerings.go
  • internal/tests/migrate.go

Comment thread docs/docs.go
Comment thread internal/api/handler/oscal/ssp_export_offering_allowlist_test.go
Comment thread internal/api/handler/oscal/ssp_export_offerings.go
Comment thread internal/api/handler/oscal/ssp_leverage.go Outdated
… panic risk, and doc/test gaps

Moves the allow-list check inside Subscribe's write transaction so the read
and the leverage-link write are atomic, closing a window where a concurrent
allow-list change could race an in-flight subscribe. Also replaces a
MustParse with proper error handling in AddAllowedDownstream, documents
RemoveAllowedDownstream's swagger description, and adds the missing
invalid-UUID test case for it.
@saltpy-cs saltpy-cs merged commit 0612e80 into main Jul 10, 2026
5 checks passed
@saltpy-cs saltpy-cs deleted the feat/BCH-1342 branch July 10, 2026 15:40
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