-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add az role deny-assignment create/delete commands (user-assigned deny assignments) #33109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: dev
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -390,6 +390,51 @@ class PrincipalType(str, Enum): | |||||||
| with self.argument_context('role assignment delete') as c: | ||||||||
| c.argument('yes', options_list=['--yes', '-y'], action='store_true', help='Currently no-op.') | ||||||||
|
|
||||||||
| with self.argument_context('role deny-assignment') as c: | ||||||||
| c.argument('scope', help='Scope at which the deny assignment applies. ' | ||||||||
| 'For example, /subscriptions/00000000-0000-0000-0000-000000000000 or ' | ||||||||
| '/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myGroup') | ||||||||
| c.argument('deny_assignment_name', options_list=['--name', '-n'], | ||||||||
| help='The display name of the deny assignment.') | ||||||||
|
|
||||||||
| with self.argument_context('role deny-assignment list') as c: | ||||||||
| c.argument('filter_str', options_list=['--filter'], | ||||||||
| help='OData filter expression to apply. For example, ' | ||||||||
| '"atScope()" to list at the current scope, or ' | ||||||||
| '"gdprExportPrincipalId eq \'{objectId}\'" to list for a specific principal.') | ||||||||
|
|
||||||||
| with self.argument_context('role deny-assignment show') as c: | ||||||||
| c.argument('deny_assignment_id', options_list=['--id'], | ||||||||
| help='The fully qualified ID of the deny assignment including scope, ' | ||||||||
| 'e.g. /subscriptions/{id}/providers/Microsoft.Authorization/denyAssignments/{denyAssignmentId}') | ||||||||
| c.argument('deny_assignment_name', options_list=['--name', '-n'], | ||||||||
| help='The name (GUID) of the deny assignment.') | ||||||||
|
|
||||||||
| with self.argument_context('role deny-assignment create') as c: | ||||||||
| c.argument('deny_assignment_name', options_list=['--name', '-n'], | ||||||||
| help='The display name of the deny assignment.') | ||||||||
| c.argument('description', help='Description of the deny assignment.') | ||||||||
| c.argument('actions', nargs='+', | ||||||||
| help='Space-separated list of actions to deny, e.g. ' | ||||||||
| '"Microsoft.Authorization/roleAssignments/write". ' | ||||||||
| 'Note: read actions (*/read) are not permitted for user-assigned deny assignments.') | ||||||||
| c.argument('not_actions', nargs='+', | ||||||||
| help='Space-separated list of actions to exclude from the deny.') | ||||||||
| c.argument('exclude_principal_ids', nargs='+', options_list=['--exclude-principal-ids'], | ||||||||
| help='Space-separated list of principal object IDs to exclude from the deny. ' | ||||||||
| 'At least one is required for user-assigned deny assignments.') | ||||||||
| c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'], | ||||||||
|
||||||||
| c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'], | |
| c.argument('exclude_principal_types', nargs='+', options_list=['--exclude-principal-types'], | |
| arg_type=get_enum_type(['User', 'Group', 'ServicePrincipal']), |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -550,6 +550,116 @@ def _search_role_assignments(assignments_client, definitions_client, | |||||||||||||||||||||||||||||||||||||||||||||||||
| return assignments | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def list_deny_assignments(cmd, scope=None, filter_str=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """List deny assignments at a scope or for the entire subscription.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| authorization_client = _auth_client_factory(cmd.cli_ctx, scope) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| deny_client = authorization_client.deny_assignments | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if scope: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assignments = list(deny_client.list_for_scope(scope=scope, filter=filter_str)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assignments = list(deny_client.list(filter=filter_str)) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return todict(assignments) if assignments else [] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def show_deny_assignment(cmd, deny_assignment_id=None, deny_assignment_name=None, scope=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """Get a deny assignment by ID or name.""" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| authorization_client = _auth_client_factory(cmd.cli_ctx, scope) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| deny_client = authorization_client.deny_assignments | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if deny_assignment_id: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return deny_client.get_by_id(deny_assignment_id) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if deny_assignment_name and scope: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return deny_client.get(scope=scope, deny_assignment_id=deny_assignment_name) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('Please provide --id, or both --name and --scope.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def create_deny_assignment(cmd, scope=None, deny_assignment_name=None, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| actions=None, not_actions=None, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| description=None, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exclude_principal_ids=None, exclude_principal_types=None, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assignment_name=None): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """Create a user-assigned deny assignment (PP1). | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Under PP1 constraints: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - Principals is always Everyone (SystemDefined, 00000000-0000-0000-0000-000000000000) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - ExcludePrincipals is required (at least one) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - DataActions and NotDataActions are not supported | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - DoNotApplyToChildScopes is not supported | ||||||||||||||||||||||||||||||||||||||||||||||||||
| - Read actions (*/read) are not permitted | ||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if not scope: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('--scope is required for creating a deny assignment.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if not deny_assignment_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('--name is required for creating a deny assignment.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| authorization_client = _auth_client_factory(cmd.cli_ctx, scope) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| deny_client = authorization_client.deny_assignments | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if not actions: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('At least one action is required via --actions.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if not exclude_principal_ids: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('At least one excluded principal is required via --exclude-principal-ids. ' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'User-assigned deny assignments deny Everyone and require at least one exclusion.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Validate no read actions | ||||||||||||||||||||||||||||||||||||||||||||||||||
| for action in actions: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if action.lower().endswith('/read'): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError(f"Read actions are not permitted for user-assigned deny assignments: '{action}'. " | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "Only write, delete, and action operations can be denied.") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if not assignment_name: | ||||||||||||||||||||||||||||||||||||||||||||||||||
| assignment_name = str(uuid.uuid4()) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # Build exclude principals list | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exclude_principals = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if exclude_principal_types and len(exclude_principal_types) != len(exclude_principal_ids): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| raise CLIError('--exclude-principal-types must have the same number of entries as --exclude-principal-ids.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| for i, pid in enumerate(exclude_principal_ids): | ||||||||||||||||||||||||||||||||||||||||||||||||||
| principal = { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'id': pid, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'type': exclude_principal_types[i] if exclude_principal_types else 'ServicePrincipal' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| exclude_principals.append(principal) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| # PP1: Principals must be Everyone (SystemDefined) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| principals = [{'id': '00000000-0000-0000-0000-000000000000', 'type': 'SystemDefined'}] | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| deny_assignment_params = { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'deny_assignment_name': deny_assignment_name, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'description': description or '', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'permissions': [{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'actions': actions or [], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'not_actions': not_actions or [], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'data_actions': [], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'not_data_actions': [] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'scope': scope, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'principals': principals, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'exclude_principals': exclude_principals, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'is_system_protected': False | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+633
to
+644
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| 'deny_assignment_name': deny_assignment_name, | |
| 'description': description or '', | |
| 'permissions': [{ | |
| 'actions': actions or [], | |
| 'not_actions': not_actions or [], | |
| 'data_actions': [], | |
| 'not_data_actions': [] | |
| }], | |
| 'scope': scope, | |
| 'principals': principals, | |
| 'exclude_principals': exclude_principals, | |
| 'is_system_protected': False | |
| 'denyAssignmentName': deny_assignment_name, | |
| 'description': description or '', | |
| 'permissions': [{ | |
| 'actions': actions or [], | |
| 'notActions': not_actions or [], | |
| 'dataActions': [], | |
| 'notDataActions': [] | |
| }], | |
| 'scope': scope, | |
| 'principals': principals, | |
| 'excludePrincipals': exclude_principals, | |
| 'isSystemProtected': False |
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This calls authorization_client.deny_assignments.create(...), but the repo currently pins azure-mgmt-authorization==5.0.0b1 (which does not include denyAssignments create/delete per the PR description). Without bumping the SDK dependency (or adding a fallback implementation / friendly error), this command will raise AttributeError at runtime.
Copilot
AI
Mar 31, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same dependency issue as create: deny_client.delete(...)/delete_by_id(...) will fail at runtime unless the pinned azure-mgmt-authorization version includes these methods. Consider either updating the dependency in this PR or detecting missing methods and raising a clear CLIError instructing users to upgrade.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deny_assignment_nameis defined at therole deny-assignmentgroup level, which makes--name/-nshow up for subcommands likelisteven thoughlist_deny_assignmentsdoesn't accept that parameter. If a user supplies--nameonlist, the handler will receive an unexpected kwarg and fail. Recommend removingdeny_assignment_namefrom the group context and defining--nameonly onshow/create/deletewhere it is supported.