feat: enable RBAC by making PulpServiceAccessPolicy the default - #1535
Conversation
Reviewer's GuideThis PR switches the service from DomainBasedPermission to PulpServiceAccessPolicy by default, updates endpoint-specific authorization and safe context handling, extends policy queryset scoping for RBAC grants without tenant leaks, and reconciles the functional suite around the resulting 200/403/404 semantics and restored domain/content access. Sequence diagram for RBAC domain creationsequenceDiagram
actor User
participant API as CreateDomainView
participant Context as authorization context
participant Signal as post_create_domain signal
participant DB as Domain database
User->>API: POST domain
API->>API: IsAuthenticated
API->>Context: set_domain_create_context(request)
API->>DB: serializer.save()
DB->>Signal: post_create_domain
Signal->>Context: consume group, org_id, user_id
DB-->>API: created domain
API-->>User: 201 Created
API->>Context: clear context in finally
Sequence diagram for RBAC content read and queryset scopingsequenceDiagram
actor User
participant API as Content endpoint
participant Policy as PulpServiceAccessPolicy
participant Scope as scope_queryset
participant DB as Content database
User->>API: GET content
API->>Policy: has_permission
Policy->>Policy: _is_domain_content_read
alt domain member has core.view_content
Policy-->>API: allow
API->>Scope: scope_queryset
Scope-->>API: return domain content queryset
API->>DB: fetch orphan and repository content
DB-->>User: 200 content
else missing content permission
Policy-->>API: deny
API-->>User: 403 Forbidden
end
Flow diagram for tenant-safe domain listingflowchart TD
Start[Authenticated domain listing] --> Policy[PulpServiceAccessPolicy scope_queryset]
Policy --> Admin{Admin-readonly member?}
Admin -->|Yes| All[Return global queryset]
Admin -->|No| Domain[Apply domain and public-domain rules]
Domain --> Role{Readonly policy group?}
Role -->|Yes| Allowed[Include policy domain and public-* domains]
Role -->|No| RBAC[Apply standard RBAC object scoping]
Allowed --> Result[Return tenant-safe results]
RBAC --> Result
All --> Result
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="dev-container/settings.py" line_range="49" />
<code_context>
REST_FRAMEWORK__DEFAULT_PERMISSION_CLASSES = (
- "pulp_service.app.authorization.DomainBasedPermission",
+ "pulp_service.app.access_policy.PulpServiceAccessPolicy",
)
</code_context>
<issue_to_address>
**issue (broader_impact):** Generic `DomainsApi` domain creation no longer runs `DomainBasedPermission.has_permission`, which previously populated `user_id_var` and `org_id_var` before the `post_create_domain` signal. When an RBAC user still has `core.add_domain` and creates a domain through that generic endpoint, the signal receives no creator context, skips the DomainOrg/RBAC role dual-write and default content-guard provisioning, and leaves the newly created domain without its expected access assignments.
**Triggers:** When a non-superuser with `core.add_domain` creates a domain through the generic DomainsApi rather than `/create-domain/`.
**Suggested fix:** Ensure the generic domain-create path also calls `set_domain_create_context` and cleans up the context, or explicitly deny generic non-admin domain creation and route all permitted creation through `CreateDomainView`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 1 finding to address first, and this changes the default permission class for the entire REST API, including domain creation, object scoping, public-domain bypasses, and subscription-based grants; an incorrect decision could broadly expose tenant data or authorize unintended operations from the moment it ships. Reverting stops future requests under the new policy, but it cannot undo access or data exposure that already occurred.
Blocking findings: dev-container/settings.py:49
ed4b035 to
44ca497
Compare
|
@dkliban RBAC enablement PR |
1857749 to
bdd9759
Compare
|
/retest |
|
All PipelineRuns for this commit have already succeeded. Use |
|
/retest pulp-bonfire-tekton |
There was a problem hiding this comment.
Sorry @CryptoRodeo, you've used your own review budget of 250,000 diff characters for the last 7 days.
You can request another review in 6 days and 8 hours by commenting @sourcery-ai review. Upgrade to get a review now.
e37696f to
f629162
Compare
|
@dkliban heads up, Patch 0018 patches pulp_container's |
We can remove patch 18. It was only needed so we could use DomainBasedPermission class for pulp_container. |
This flips the default permission class from the old DomainBasedPermission to
PulpServiceAccessPolicy, so RBAC is actually the thing enforcing access now
instead of the org_id/DomainOrg checks. The settings change lives in both
dev-container/settings.py and deploy/clowdapp.yaml; the code side updates the
few viewsets that hard-coded the old class:
- PyPIYankMonitorViewSet drops DomainBasedPermission.
- CreateDomainView / MigrateDomainView move to [IsAuthenticated] plus explicit
checks (set_domain_create_context for create, has_perm("core.change_domain")
for migrate) so they don't lean on the old permission class implicitly.
The bulk of the diff is test reconciliation. A pile of RBAC-focused functional
tests had been merged to main but couldn't run for real until RBAC was on, so
they're restored here, plus the existing suite is updated where it asserted the
old permission model's behavior. The one thing worth calling out: under RBAC,
"denied" doesn't always mean 403.
- Repository listing lets any authenticated user in and scopes the queryset by
role, so a caller with no role on the domain gets an empty 200 (no leak), not
a 403. Those assertions changed to 200 + count == 0.
- Content listing is different: ACCESS_POLICIES["content/file/files"] gates the
list action on has_domain_perms:core.view_content, so a caller without that
permission is denied outright with a 403. Those assertions stay 403.
- Non-public domains now get a default identity content guard, so anonymous
GETs against them are denied. The one auth test that assumed an unguarded
domain now uses a public- domain (the only unguarded case).
The main thing I set out to protect: the orphaned-content fix still works under
RBAC. When you upload content that isn't in any repository yet, the domain owner
still has to be able to read it back. PulpServiceAccessPolicy.scope_queryset
handles this first (via _is_domain_content_read) before any of the other
read-grant branches, so a domain member holding core.view_content gets the
content queryset back unscoped. Verified end-to-end:
test_content_view_after_upload.py passes 3/3 in the dev container (owner reads
orphan content via both the generic and typed endpoints; an unrelated org is
denied with 403).
One tenant-isolation guard: scope_queryset's per-domain read grants
(DOMAIN_ACCESS_POLICIES subscription/readonly-group) only return a queryset
unscoped for models base.py already domain-scoped (those with a pulp_domain FK).
Global models (Domain, Group, User, Role) fall through instead -- Domain to its
dedicated block that shows only the policy domain + public-*. Without this a
Lightwell-ReadOnly member hitting GET /api/pulp/lightwell/api/v3/domains/ would
have listed every tenant's domain. admin-readonly stays a global read by design.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Bryan ramos <bramos@redhat.com>
f629162 to
81e5254
Compare
Enabling RBAC (pulp#1535) made PulpServiceAccessPolicy the default, and pulpcore's AccessPolicyFromSettings *replaces* rather than merges a viewset's policy with the matching settings.ACCESS_POLICIES entry. That silently 403'd three endpoints for non-superuser domain-admin service accounts: content/file/files shared a list-only policy that dropped pulp_file's create/upload/label statements; artifacts/ kept pulpcore's admin-only default because it was never overridden; and orphans/cleanup, a urlpattern-less ViewSet the settings lookup can't reach, fell through to the admin-only module default. Give content/file/files its own policy that reproduces pulp_file's create/upload/set_label/unset_label statements verbatim while keeping the orphan-content read goal (list/retrieve on core.view_content, queryset_scoping=None). Add an artifacts policy opening read+create to domain admins (destroy stays admin-only). Open orphans/cleanup by setting DEFAULT_ACCESS_POLICY on the class at plugin startup from ready(), the only path that reaches a viewset with no urlpattern. Adds structural unit guards and behavioral functional tests for all three. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Bryan ramos <bramos@redhat.com>
Enabling RBAC (pulp#1535) made PulpServiceAccessPolicy the default, and pulpcore's AccessPolicyFromSettings *replaces* rather than merges a viewset's policy with the matching settings.ACCESS_POLICIES entry. That silently 403'd three endpoints for non-superuser domain-admin service accounts: content/file/files shared a list-only policy that dropped pulp_file's create/upload/label statements; artifacts/ kept pulpcore's admin-only default because it was never overridden; and orphans/cleanup, a urlpattern-less ViewSet the settings lookup can't reach, fell through to the admin-only module default. Give content/file/files its own policy that reproduces pulp_file's create/upload/set_label/unset_label statements verbatim while keeping the orphan-content read goal (list/retrieve on core.view_content, queryset_scoping=None). Add an artifacts policy opening read+create to domain admins (destroy stays admin-only). Open orphans/cleanup by setting DEFAULT_ACCESS_POLICY on the class at plugin startup from ready(), the only path that reaches a viewset with no urlpattern. Adds structural unit guards and behavioral functional tests for all three. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Bryan ramos <bramos@redhat.com>
Enabling RBAC (pulp#1535) made PulpServiceAccessPolicy the default, and pulpcore's AccessPolicyFromSettings *replaces* rather than merges a viewset's policy with the matching settings.ACCESS_POLICIES entry. That silently 403'd three endpoints for non-superuser domain-admin service accounts: content/file/files shared a list-only policy that dropped pulp_file's create/upload/label statements; artifacts/ kept pulpcore's admin-only default because it was never overridden; and orphans/cleanup, a urlpattern-less ViewSet the settings lookup can't reach, fell through to the admin-only module default. Give content/file/files its own policy that reproduces pulp_file's create/upload/set_label/unset_label statements verbatim while keeping the orphan-content read goal (list/retrieve on core.view_content, queryset_scoping=None). Add an artifacts policy opening read+create to domain admins (destroy stays admin-only). Open orphans/cleanup by setting DEFAULT_ACCESS_POLICY on the class at plugin startup from ready(), the only path that reaches a viewset with no urlpattern. Adds structural unit guards and behavioral functional tests for all three. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Signed-off-by: Bryan ramos <bramos@redhat.com>
Enabling RBAC (#1535) made PulpServiceAccessPolicy the default, and pulpcore's AccessPolicyFromSettings *replaces* rather than merges a viewset's policy with the matching settings.ACCESS_POLICIES entry. That silently 403'd three endpoints for non-superuser domain-admin service accounts: content/file/files shared a list-only policy that dropped pulp_file's create/upload/label statements; artifacts/ kept pulpcore's admin-only default because it was never overridden; and orphans/cleanup, a urlpattern-less ViewSet the settings lookup can't reach, fell through to the admin-only module default. Give content/file/files its own policy that reproduces pulp_file's create/upload/set_label/unset_label statements verbatim while keeping the orphan-content read goal (list/retrieve on core.view_content, queryset_scoping=None). Add an artifacts policy opening read+create to domain admins (destroy stays admin-only). Open orphans/cleanup by setting DEFAULT_ACCESS_POLICY on the class at plugin startup from ready(), the only path that reaches a viewset with no urlpattern. Adds structural unit guards and behavioral functional tests for all three. Signed-off-by: Bryan ramos <bramos@redhat.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This flips the default permission class from the old DomainBasedPermission to PulpServiceAccessPolicy, so RBAC is actually the thing enforcing access now instead of the org_id/DomainOrg checks. The settings change lives in both dev-container/settings.py and deploy/clowdapp.yaml; the code side updates the few viewsets that hard-coded the old class:
The bulk of the diff is test reconciliation. A pile of RBAC-focused functional tests had been merged to main but couldn't run for real until RBAC was on, so they're restored here, plus the existing suite is updated where it asserted the old permission model's behavior. The one thing worth calling out: under RBAC, "denied" doesn't always mean 403.
The main thing I set out to protect: the orphaned-content fix still works under RBAC. When you upload content that isn't in any repository yet, the domain owner still has to be able to read it back. PulpServiceAccessPolicy.scope_queryset handles this first (via _is_domain_content_read) before any of the other read-grant branches, so a domain member holding core.view_content gets the content queryset back unscoped. Verified end-to-end: test_content_view_after_upload.py passes 3/3 in the dev container (owner reads orphan content via both the generic and typed endpoints; an unrelated org is denied with 403).
One tenant-isolation guard: scope_queryset's per-domain read grants (DOMAIN_ACCESS_POLICIES subscription/readonly-group) only return a queryset unscoped for models base.py already domain-scoped (those with a pulp_domain FK). Global models (Domain, Group, User, Role) fall through instead -- Domain to its dedicated block that shows only the policy domain + public-*. Without this a Lightwell-ReadOnly member hitting GET /api/pulp/lightwell/api/v3/domains/ would have listed every tenant's domain. admin-readonly stays a global read by design.
Summary by Sourcery
Make RBAC the default authorization model and restore equivalent domain and content access while enforcing tenant-safe queryset scoping.
New Features:
Bug Fixes:
Enhancements:
Build:
Deployment:
Documentation:
Tests:
Chores: