Skip to content

AAP-78640 Block role assignments where you do not have assigned permissions - #1039

Open
AlanCoding wants to merge 11 commits into
ansible:develfrom
AlanCoding:firewalled_actions
Open

AAP-78640 Block role assignments where you do not have assigned permissions#1039
AlanCoding wants to merge 11 commits into
ansible:develfrom
AlanCoding:firewalled_actions

Conversation

@AlanCoding

@AlanCoding AlanCoding commented Jun 29, 2026

Copy link
Copy Markdown
Member

Summary

  • When assigning a role, verify the user holds every permission in that role — not just the management gate permission. This prevents escalation where a user with change could grant adhoc or execute permissions they don't have.
  • Remove the check_manage_permission_completeness validator that forced roles containing the manage action to include ALL other permissions. The per-assignment escalation check makes it unnecessary and allows useful partial roles like "editor" (change + view only).
  • Document the interplay between ANSIBLE_BASE_CREATOR_DEFAULTS and ANSIBLE_BASE_MANAGE_PERMISSION_ACTION — together these settings define the full delegation chain, enabling separation of "provisioner" users (CRUD only) from "operator" users (action permissions).

Details

The escalation problem

Previously, check_content_obj_permission only checked a single gate: does the user have the manage permission (default change) on the object? If yes, they could assign any role, even one containing permissions they don't hold. For example, a user with only change_inventory could assign a role containing adhoc_inventory to themselves or others.

The fix

check_content_obj_permission now accepts an optional role_definition parameter. When the manage-action gate passes, a new _check_role_permissions function iterates every permission in the role being assigned and verifies the user has each one. If any is missing, the assignment is denied with a message naming the missing permission.

Two cases

Scenario Gate check Escalation check
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION set (e.g. 'change') User has the manage action User has every permission in the assigned role
Setting is None / model lacks the action User has ALL permissions for the object type Redundant (already stricter)

Creator defaults interaction

ANSIBLE_BASE_CREATOR_DEFAULTS (default ['add', 'change', 'delete', 'view']) controls what permissions a creator receives. Action permissions like execute or adhoc are deliberately excluded, so creators cannot delegate them. An administrator with those permissions must grant them explicitly.

Linked: AAPRFE-2838

Test plan

  • New tests verify escalation is blocked when user lacks permissions in the assigned role
  • New tests verify assignment succeeds when user has all permissions in the assigned role
  • Existing tests for ImmutableTask (model without change) still pass
  • All 567 RBAC tests pass with zero failures
  • ANSIBLE_BASE_MANAGE_PERMISSION_ACTION=None and ='administrate' cases covered

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Role assignment controls now support a configurable management permission, with update-style access by default.
    • Added protection against permission escalation when assigning roles.
    • Custom resource actions can be used in role-assignment permission checks.
    • Added support for managing role assignments on resources with custom permissions.
  • Bug Fixes

    • Improved authorization checks for custom and inherited resource permissions.
    • Added safe fallback handling when a resource does not support the configured management action.
  • Documentation

    • Expanded RBAC guidance with updated creator-permission and role-assignment delegation examples.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b49412f4-9cbd-4ad5-a46b-7b5b1917bb13

📥 Commits

Reviewing files that changed from the base of the PR and between 709741a and ba5f41b.

📒 Files selected for processing (2)
  • ansible_base/rbac/policies.py
  • test_app/tests/rbac/api/test_user_permissions.py

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

Adds configurable role-assignment permission gates with a default change action. Policy checks support custom actions, all-permission fallback, and escalation prevention. Serializer wiring, test models, migrations, tests, and RBAC documentation are updated.

Changes

RBAC delegation controls

Layer / File(s) Summary
Delegation settings and policy inputs
ansible_base/lib/dynamic_config/settings_logic.py, ansible_base/rbac/api/serializers.py, ansible_base/rbac/policies.py
Defines the manage-action setting and passes the role definition into content-object permission checks.
Permission gate and escalation validation
ansible_base/rbac/policies.py
Assignment checks now support configured actions, all-permission fallback, direct role-permission validation, and escalation prevention.
Custom permission fixtures and validation coverage
test_app/models.py, test_app/migrations/0019_resourcewithadminperm.py, test_app/migrations/0020_alter_resourcewithadminperm_options.py, test_app/tests/rbac/api/test_assignment_permissions.py
Adds a resource with custom permissions and tests manage-action gates, custom actions, permission subsets, escalation prevention, and creator-derived permissions.
Scoped permission resolution tests
test_app/tests/rbac/api/test_user_permissions.py
Tests child-model permissions with cached and uncached parent evaluations, team-inherited permissions, and global-to-organization role assignments.
Delegation configuration documentation
docs/apps/rbac/for_app_developers.md
Documents creator defaults, manage-action gates, escalation prevention, and removal behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Merge Risk: ⚪ Minimal · up to ba5f4

No actionable merge-blocking risk remains; the change is merge-ready after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant AssignmentAPI
  participant BaseAssignmentSerializer
  participant RBACPolicies
  participant RoleDefinition
  AssignmentAPI->>BaseAssignmentSerializer: create role assignment
  BaseAssignmentSerializer->>RBACPolicies: check_content_obj_permission(role_definition)
  RBACPolicies->>RoleDefinition: validate delegated permissions
  RoleDefinition-->>RBACPolicies: return role permissions
  RBACPolicies-->>AssignmentAPI: allow or deny assignment
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: preventing role assignments without the required assigned permissions.
Docstring Coverage ✅ Passed Docstring coverage is 80.65% which is sufficient. The required threshold is 80.00%.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/apps/rbac/for_app_developers.md (1)

309-313: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add language specifier to fenced code block.

Line 309 triggers markdownlint MD040 (fenced-code-language). Add python after the opening backticks.

+```python
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION = 'change' # default
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION = 'administrate' # dedicated permission
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION = None # require ALL permissions

🤖 Prompt for 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.

In `@docs/apps/rbac/for_app_developers.md` around lines 309 - 313, The fenced code
block in the ANSIBLE_BASE_MANAGE_PERMISSION_ACTION example is missing a language
tag, triggering markdownlint MD040. Update the opening fence in this
documentation snippet to use a Python specifier so the block is labeled
consistently with its Python-like content.

Source: Linters/SAST tools

🤖 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 `@ansible_base/lib/dynamic_config/settings_logic.py`:
- Around line 252-256: The manage-permission default in settings_logic is
treating an explicit None as if it were unset, which overwrites the stricter
mode with 'change'. Update the logic around the
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION handling so the defaults path preserves an
explicit None while still falling back to 'change' only when the setting is
truly missing. Use the existing manage-permission initialization flow in
settings_logic to distinguish “unset” from “set to None” before applying any
fallback.

In `@ansible_base/rbac/policies.py`:
- Around line 124-128: The manage-permission check in the permission loop is
using a prefix match on permission.codenames, which can incorrectly treat
broader names like administrate_<model> as matching a configured action such as
admin. Update the logic in the same block that iterates over permissions to
require an exact match against the remote manage codename, consistent with
_model_has_permission_action, and keep the fallback behavior for remote objects
unchanged.

---

Nitpick comments:
In `@docs/apps/rbac/for_app_developers.md`:
- Around line 309-313: The fenced code block in the
ANSIBLE_BASE_MANAGE_PERMISSION_ACTION example is missing a language tag,
triggering markdownlint MD040. Update the opening fence in this documentation
snippet to use a Python specifier so the block is labeled consistently with its
Python-like content.
🪄 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: CHILL

Plan: Enterprise

Run ID: 8659624d-be56-4413-bdc9-4619d4cd269d

📥 Commits

Reviewing files that changed from the base of the PR and between 23c0bf4 and 03a28af.

📒 Files selected for processing (7)
  • ansible_base/lib/dynamic_config/settings_logic.py
  • ansible_base/rbac/api/serializers.py
  • ansible_base/rbac/policies.py
  • docs/apps/rbac/for_app_developers.md
  • test_app/migrations/0019_resourcewithadminperm.py
  • test_app/models.py
  • test_app/tests/rbac/api/test_assignment_permissions.py

Comment thread ansible_base/lib/dynamic_config/settings_logic.py
Comment thread ansible_base/rbac/policies.py
@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 94.81%. Comparing base (5d64aaf) to head (9832abd).

@@            Coverage Diff             @@
##            devel    #1039      +/-   ##
==========================================
+ Coverage   94.77%   94.81%   +0.03%     
==========================================
  Files         259      259              
  Lines       14485    14519      +34     
  Branches     2218     2227       +9     
==========================================
+ Hits        13728    13766      +38     
+ Misses        757      753       -4     
Flag Coverage Δ
py312 94.79% <100.00%> (+0.06%) ⬆️
py312-sqlite 94.15% <100.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
ansible_base/rbac/api/serializers.py 92.96% <100.00%> (ø)
ansible_base/rbac/policies.py 98.33% <100.00%> (+2.98%) ⬆️

... and 1 file with indirect coverage changes


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 5d64aaf...9832abd. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AlanCoding AlanCoding changed the title Prevent privilege escalation in role assignment delegation Block role assignments where you do not have assigned permissions Jun 29, 2026
@AlanCoding
AlanCoding force-pushed the firewalled_actions branch from 03a28af to dd10058 Compare June 29, 2026 18:55
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
1 New issue

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@AlanCoding

Copy link
Copy Markdown
Member Author

Current values of ANSIBLE_BASE_CREATOR_DEFAULTS

https://github.com/ansible/awx/blob/8ab5deb54aa56953790e0cd1e13515abc0525523/awx/settings/defaults.py#L1099

Nothing in eda-server.

So expected followup change in AWX (among other things, possibly migrations) would be removing that or shortening the list.

@AlanCoding AlanCoding changed the title Block role assignments where you do not have assigned permissions AAP-78640 Block role assignments where you do not have assigned permissions Aug 20, 2026
AlanCoding and others added 8 commits August 20, 2026 12:18
When assigning a role to an object, the user must now have every
permission contained in that role, not just the management gate
permission (e.g. "change"). This prevents a user from granting
permissions they do not themselves hold.

The check_manage_permission_completeness validator is removed — it
forced roles containing the manage action to include ALL other
permissions, which was a blunt proxy for escalation prevention. The
new per-assignment check makes it unnecessary and allows useful
partial roles like "editor" (change + view, no delete).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ANSIBLE_BASE_CREATOR_DEFAULTS and ANSIBLE_BASE_MANAGE_PERMISSION_ACTION
together define the full delegation chain: creator defaults set what
permissions a creator starts with, and the escalation check constrains
what they can delegate onward. This is the mechanism for separating
"provisioner" users from "operator" users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…faults

The 'use' permission is relational — it gates whether a resource can
reference another (e.g. attaching a credential to an inventory). Without
it in creator defaults, a user who creates a resource can't wire it into
their own scaffolding without a separate grant, breaking the separation
between setup and action workflows.

Add 'bop' and 'twist' custom action permissions to ResourceWithAdminPerm
to enable targeted escalation testing. New TestEscalationWithCustomActions
covers: selective delegation (has bop but not twist), full delegation
(has both), blocked delegation (has neither), CRUD-only delegation,
gate-less user rejection, and a full creator lifecycle test proving
creators get CRUD but not custom action permissions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The _check_role_permissions escalation check used has_obj_perm per
permission, which fails for child-model permissions (e.g. view_team
in an org-scoped role) because RoleEvaluation entries for those live
on child objects, not the parent.

When ANSIBLE_BASE_CACHE_PARENT_PERMISSIONS is True, eval entries exist
on the parent so has_obj_perm works. When False (the default), fall
back to comparing role definition permissions directly — query which
RoleDefinitions the user holds on the object (directly or via team
membership) and check the superset.

Also check has_super_permission first so superusers and users with
global roles that include the needed permissions are handled correctly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… team)

Use has_obj_perm for same-content-type permissions and only fall back to
role definition lookup for cross-content-type cases. This correctly handles
permissions inherited from parent objects (org admin assigning team roles)
without needing to traverse the parent hierarchy.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AlanCoding and others added 3 commits August 20, 2026 13:42
- Add type hints to all new helper methods in policies.py
- Move `from django.db.models import Q` to top-level imports
- Rename _check_role_permissions to _check_assignment_permissions_non_cached
  to clarify it's an assignment-specific escalation check using the
  non-cached path for cross-content-type permissions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The helper functions added for escalation checks were typed as
Model, but check_content_obj_permission also receives RemoteObject
instances which are not Django Model subclasses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…permission

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

DVCS PR Check Results:

PR appears valid (JIRA key(s) found)

@sonarqubecloud

Copy link
Copy Markdown

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