-
Notifications
You must be signed in to change notification settings - Fork 2.9k
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
Add KeyVaultAccessControlClient for data plane RBAC #13372
Changes from 4 commits
cbac8aa
47902d2
88c5ba6
90f4ccc
9765bfe
f675ce7
79ee5de
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 |
---|---|---|
@@ -0,0 +1,114 @@ | ||
# ------------------------------------ | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
# ------------------------------------ | ||
from typing import TYPE_CHECKING | ||
|
||
from azure.core.tracing.decorator import distributed_trace | ||
|
||
from ._models import RoleAssignment, RoleDefinition | ||
from ._internal import KeyVaultClientBase | ||
|
||
if TYPE_CHECKING: | ||
from typing import Any, Union | ||
from uuid import UUID | ||
from azure.core.paging import ItemPaged | ||
|
||
|
||
class KeyVaultAccessControlClient(KeyVaultClientBase): | ||
"""Manages role-based access to Azure Key Vault. | ||
|
||
:param str vault_url: URL of the vault the client will manage. This is also called the vault's "DNS Name". | ||
:param credential: an object which can provide an access token for the vault, such as a credential from | ||
:mod:`azure.identity` | ||
""" | ||
|
||
# pylint:disable=protected-access | ||
|
||
@distributed_trace | ||
def create_role_assignment(self, role_scope, role_assignment_name, role_definition_id, principal_id, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We're using just |
||
# type: (str, Union[str, UUID], str, str, **Any) -> RoleAssignment | ||
"""Create a role assignment. | ||
|
||
:param str role_scope: scope the role assignment will apply over | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For the other languages we are using a custom type to provide some context for the valid values that can be passed here ("/", "/keys", "/keys/some specific Key resource Id"). Is there a pythonic way to accomplish the same thing here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's nothing I know of in the standard library quite like .NET's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense - perhaps just some hints in the comment text would be enough. |
||
:param role_assignment_name: a name for the role assignment. Must be a UUID. | ||
chlowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
:type role_assignment_name: str or uuid.UUID | ||
:param str role_definition_id: ID of the role's definition | ||
:param str principal_id: ID of the principal which will be assigned the role. This maps to the ID inside the | ||
chlowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Active Directory. It can point to a user, service principal, or security group. | ||
:rtype: RoleAssignment | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should be ~azure.keyvault.administration.RoleAssignment There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sphinx links this correctly, the class is imported in this module. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh interesting, do you know for sure if microsoft docs will? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't. Trial balloon! 🎈 |
||
""" | ||
create_parameters = self._client.role_assignments.models.RoleAssignmentCreateParameters( | ||
properties=self._client.role_assignments.models.RoleAssignmentProperties( | ||
principal_id=principal_id, role_definition_id=str(role_definition_id) | ||
) | ||
) | ||
assignment = self._client.role_assignments.create( | ||
vault_base_url=self._vault_url, | ||
scope=role_scope, | ||
role_assignment_name=role_assignment_name, | ||
parameters=create_parameters, | ||
**kwargs | ||
) | ||
return RoleAssignment._from_generated(assignment) | ||
|
||
@distributed_trace | ||
def delete_role_assignment(self, role_scope, role_assignment_name, **kwargs): | ||
# type: (str, Union[str, UUID], **Any) -> RoleAssignment | ||
"""Delete a role assignment. | ||
|
||
:param str role_scope: the assignment's scope | ||
:param role_assignment_name: the assignment's name. Must be a UUID. | ||
:type role_assignment_name: str or uuid.UUID | ||
:returns: the deleted assignment | ||
:rtype: RoleAssignment | ||
""" | ||
assignment = self._client.role_assignments.delete( | ||
vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs | ||
) | ||
return RoleAssignment._from_generated(assignment) | ||
|
||
@distributed_trace | ||
def get_role_assignment(self, role_scope, role_assignment_name, **kwargs): | ||
# type: (str, Union[str, UUID], **Any) -> RoleAssignment | ||
"""Get a role assignment. | ||
|
||
:param str role_scope: the assignment's scope | ||
:param role_assignment_name: the assignment's name. Must be a UUID. | ||
:type role_assignment_name: str or uuid.UUID | ||
:rtype: RoleAssignment | ||
""" | ||
assignment = self._client.role_assignments.get( | ||
vault_base_url=self._vault_url, scope=role_scope, role_assignment_name=str(role_assignment_name), **kwargs | ||
) | ||
return RoleAssignment._from_generated(assignment) | ||
|
||
@distributed_trace | ||
def list_role_assignments(self, role_scope, **kwargs): | ||
# type: (str, **Any) -> ItemPaged[RoleAssignment] | ||
"""List all role assignments for a scope. | ||
|
||
:param str role_scope: scope of the role assignments | ||
:rtype: ~azure.core.paging.ItemPaged[RoleAssignment] | ||
""" | ||
return self._client.role_assignments.list_for_scope( | ||
self._vault_url, | ||
role_scope, | ||
cls=lambda result: [RoleAssignment._from_generated(a) for a in result], | ||
**kwargs | ||
) | ||
|
||
@distributed_trace | ||
def list_role_definitions(self, role_scope, **kwargs): | ||
# type: (str, **Any) -> ItemPaged[RoleDefinition] | ||
"""List all role definitions applicable at and above a scope. | ||
|
||
:param str role_scope: scope of the role definitions | ||
:rtype: ~azure.core.paging.ItemPaged[RoleDefinition] | ||
""" | ||
return self._client.role_definitions.list( | ||
self._vault_url, | ||
role_scope, | ||
cls=lambda result: [RoleDefinition._from_generated(d) for d in result], | ||
**kwargs | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
# ------------------------------------ | ||
# Copyright (c) Microsoft Corporation. | ||
# Licensed under the MIT License. | ||
# ------------------------------------ | ||
from typing import TYPE_CHECKING | ||
|
||
if TYPE_CHECKING: | ||
from typing import Any | ||
|
||
|
||
# pylint:disable=protected-access | ||
|
||
class KeyVaultPermission(object): | ||
"""Role definition permissions. | ||
|
||
:ivar list[str] actions: allowed actions | ||
:ivar list[str] not_actions: denied actions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. side note: not sure if this has already been a design discussion, but I feel like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, that's reasonable. I don't know whether there's been a discussion either, I just copied this from @christothes 😊 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using denied actions as well. |
||
:ivar list[str] data_actions: allowed data actions | ||
:ivar list[str] not_data_actions: denied data actions | ||
""" | ||
|
||
def __init__(self, **kwargs): | ||
# type: (**Any) -> None | ||
self.actions = kwargs.get("actions") | ||
self.not_actions = kwargs.get("not_actions") | ||
self.data_actions = kwargs.get("data_actions") | ||
self.not_data_actions = kwargs.get("not_data_actions") | ||
|
||
@classmethod | ||
def _from_generated(cls, permissions): | ||
return cls( | ||
actions=permissions.actions, | ||
not_actions=permissions.not_actions, | ||
data_actions=permissions.data_actions, | ||
not_data_actions=permissions.not_data_actions, | ||
) | ||
|
||
|
||
class RoleAssignment(object): | ||
chlowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Represents the assignment to a principal of a role over a scope""" | ||
|
||
def __init__(self, **kwargs): | ||
# type: (**Any) -> None | ||
self._assignment_id = kwargs.get("assignment_id") | ||
self._name = kwargs.get("name") | ||
self._properties = kwargs.get("properties") | ||
self._type = kwargs.get("assignment_type") | ||
|
||
def __repr__(self): | ||
# type: () -> str | ||
return "RoleAssignment<{}>".format(self._assignment_id) | ||
|
||
@property | ||
def assignment_id(self): | ||
# type: () -> str | ||
"""unique identifier for this assignment""" | ||
return self._assignment_id | ||
|
||
@property | ||
def name(self): | ||
# type: () -> str | ||
"""name of the assignment""" | ||
return self._name | ||
|
||
@property | ||
def principal_id(self): | ||
# type: () -> str | ||
"""ID of the principal this assignment applies to. | ||
|
||
This maps to the ID inside the Active Directory. It can point to a user, service principal, or security group. | ||
""" | ||
return self._properties.principal_id | ||
|
||
@property | ||
def role_definition_id(self): | ||
# type: () -> str | ||
"""ID of the role's definition""" | ||
return self._properties.role_definition_id | ||
|
||
@property | ||
def scope(self): | ||
# type: () -> str | ||
"""scope of the assignment""" | ||
return self._properties.scope | ||
|
||
@property | ||
def type(self): | ||
# type: () -> str | ||
"""the type of this assignment""" | ||
return self._type | ||
|
||
@classmethod | ||
def _from_generated(cls, role_assignment): | ||
return cls( | ||
assignment_id=role_assignment.id, | ||
name=role_assignment.name, | ||
assignment_type=role_assignment.type, | ||
properties=RoleAssignmentProperties._from_generated(role_assignment.properties), | ||
) | ||
|
||
|
||
class RoleAssignmentProperties(object): | ||
chlowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
def __init__(self, **kwargs): | ||
# type: (**Any) -> None | ||
self.principal_id = kwargs.get("principal_id") | ||
self.role_definition_id = kwargs.get("role_definition_id") | ||
self.scope = kwargs.get("scope") | ||
|
||
def __repr__(self): | ||
# type: () -> str | ||
return "RoleAssignmentProperties(principal_id={}, role_definition_id={}, scope={})".format( | ||
self.principal_id, self.role_definition_id, self.scope | ||
)[:1024] | ||
|
||
@classmethod | ||
def _from_generated(cls, role_assignment_properties): | ||
# the generated RoleAssignmentProperties and RoleAssignmentPropertiesWithScope | ||
# models differ only in that the latter has a "scope" attribute | ||
return cls( | ||
principal_id=role_assignment_properties.principal_id, | ||
role_definition_id=role_assignment_properties.role_definition_id, | ||
scope=getattr(role_assignment_properties, "scope", None), | ||
) | ||
|
||
|
||
class RoleDefinition(object): | ||
chlowell marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Role definition. | ||
|
||
:ivar str id: The role definition ID. | ||
:ivar str name: The role definition name. | ||
:ivar str type: The role definition type. | ||
:ivar str role_name: The role name. | ||
:ivar str description: The role definition description. | ||
:ivar str role_type: The role type. | ||
:ivar permissions: Role definition permissions. | ||
:vartype permissions: list[KeyVaultPermission] | ||
:ivar list[str] assignable_scopes: Role definition assignable scopes. | ||
""" | ||
|
||
def __init__(self, **kwargs): | ||
# type: (**Any) -> None | ||
self.id = kwargs.get("id") | ||
self.name = kwargs.get("name") | ||
self.role_name = kwargs.get("role_name") | ||
self.description = kwargs.get("description") | ||
self.role_type = kwargs.get("role_type") | ||
self.type = kwargs.get("type") | ||
self.permissions = kwargs.get("permissions") | ||
self.assignable_scopes = kwargs.get("assignable_scopes") | ||
|
||
def __repr__(self): | ||
# type: () -> str | ||
return "<RoleDefinition {}>".format(self.role_name)[:1024] | ||
|
||
@classmethod | ||
def _from_generated(cls, definition): | ||
return cls( | ||
assignable_scopes=definition.assignable_scopes, | ||
description=definition.description, | ||
id=definition.id, | ||
name=definition.name, | ||
permissions=[KeyVaultPermission._from_generated(p) for p in definition.permissions], | ||
role_name=definition.role_name, | ||
role_type=definition.role_type, | ||
type=definition.type, | ||
) |
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.
Regrading
ApiVersion
, is this the standard in Python? In JavaScript we are standardizing onserviceVersion
.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.
Yes, it's consistent with the Python SDK.