Skip to content

Commit

Permalink
[Compute] az disk-encryption-set: Add parameter `--federated-client…
Browse files Browse the repository at this point in the history
…-id` to access key vault in a different tenant and new subgroup to support managed identities (#22966)
  • Loading branch information
yanzhudd authored Jun 30, 2022
1 parent 415c45b commit 4e5d3cc
Show file tree
Hide file tree
Showing 6 changed files with 4,514 additions and 4 deletions.
58 changes: 58 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,14 @@
examples:
- name: Create a disk encryption set.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault
- name: Create a disk encryption set with a system assigned identity.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault --mi-system-assigned
- name: Create a disk encryption set with a user assigned identity.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault --mi-user-assigned myAssignedId
- name: Create a disk encryption set with system assigned identity and a user assigned identity.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault --mi-system-assigned --mi-user-assigned myAssignedId
- name: Create a disk encryption set with multi-tenant application client id to access key vault in a different tenant.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault --federated-client-id myFederatedClientId
- name: Create a disk encryption set that supports double encryption.
text: az disk-encryption-set create --resource-group MyResourceGroup --name MyDiskEncryptionSet --key-url MyKey --source-vault MyVault --encryption-type EncryptionAtRestWithPlatformAndCustomerKeys
"""
Expand Down Expand Up @@ -255,6 +263,56 @@
text: |
az disk-encryption-set update --name MyDiskEncryptionSet --resource-group MyResourceGroup --key-url MyKey --source-vault MyVault
crafted: true
- name: Update multi-tenant application client id of a disk encryption set.
text: |
az disk-encryption-set update --name MyDiskEncryptionSet --resource-group MyResourceGroup --key-url MyKey --source-vault MyVault --federated-client-id myFederatedClientId
- name: Clear multi-tenant application client id of a disk encryption set.
text: |
az disk-encryption-set update --name MyDiskEncryptionSet --resource-group MyResourceGroup --key-url MyKey --source-vault MyVault --federated-client-id None
"""

helps['disk-encryption-set identity'] = """
type: group
short-summary: Manage identities of a disk encryption set.
"""

helps['disk-encryption-set identity assign'] = """
type: command
short-summary: Add managed identities to an existing disk encryption set.
examples:
- name: Add a system assigned managed identity to an existing disk encryption set.
text: >
az disk-encryption-set identity assign --name MyDiskEncryptionSet --resource-group MyResourceGroup --system-assigned
- name: Add a user assigned managed identity to an existing disk encryption set.
text: >
az disk-encryption-set identity assign --name MyDiskEncryptionSet --resource-group MyResourceGroup --user-assigned MyAssignedId
- name: Add system assigned identity and a user assigned managed identity to an existing disk encryption set.
text: >
az disk-encryption-set identity assign --name MyDiskEncryptionSet --resource-group MyResourceGroup --system-assigned --user-assigned MyAssignedId
"""

helps['disk-encryption-set identity remove'] = """
type: command
short-summary: Remove managed identities from an existing disk encryption set.
examples:
- name: Remove a system assigned managed identity from an existing disk encryption set.
text: >
az disk-encryption-set identity remove --name MyDiskEncryptionSet --resource-group MyResourceGroup --system-assigned
- name: Remove a user assigned managed identity from an existing disk encryption set.
text: >
az disk-encryption-set identity remove --name MyDiskEncryptionSet --resource-group MyResourceGroup --user-assigned MyAssignedId
- name: Remove all user assigned managed identities from an existing disk encryption set.
text: >
az disk-encryption-set identity remove --name MyDiskEncryptionSet --resource-group MyResourceGroup --user-assigned
"""

helps['disk-encryption-set identity show'] = """
type: command
short-summary: Display managed identities of a disk encryption set.
examples:
- name: Display managed identities of a disk encryption set.
text: |
az disk-encryption-set identity show --name MyDiskEncryptionSet --resource-group MyResourceGroup
"""

helps['image'] = """
Expand Down
23 changes: 23 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1445,6 +1445,29 @@ def load_arguments(self, _):
c.argument('enable_auto_key_rotation', arg_type=get_three_state_flag(), min_api='2020-12-01',
options_list=['--enable-auto-key-rotation', '--auto-rotation'],
help='Enable automatic rotation of keys.')

with self.argument_context('disk-encryption-set create', operation_group='disk_encryption_sets',
min_api='2022-03-02') as c:
c.argument('federated_client_id', help='The federated client id used in cross tenant scenario.')
c.argument('mi_system_assigned', arg_group='Managed Identity', arg_type=get_three_state_flag(),
help='Provide this flag to use system assigned identity. Check out help for more examples')
c.argument('mi_user_assigned', arg_group='Managed Identity', nargs='+',
help='User Assigned Identity ids to be used for disk encryption set. '
'Check out help for more examples')

with self.argument_context('disk-encryption-set update', operation_group='disk_encryption_sets',
min_api='2022-03-02') as c:
c.argument('federated_client_id', help='The federated client id used in cross tenant scenario.')

with self.argument_context('disk-encryption-set identity', operation_group='disk_encryption_sets',
min_api='2022-03-02') as c:
c.argument('mi_system_assigned', options_list=['--system-assigned'],
arg_group='Managed Identity', arg_type=get_three_state_flag(),
help='Provide this flag to use system assigned identity for disk encryption set. '
'Check out help for more examples')
c.argument('mi_user_assigned', options_list=['--user-assigned'], arg_group='Managed Identity', nargs='*',
help='User Assigned Identity ids to be used for disk encryption set. '
'Check out help for more examples')
# endregion

# region DiskAccess
Expand Down
5 changes: 5 additions & 0 deletions src/azure-cli/azure/cli/command_modules/vm/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ def load_command_table(self, _):
g.custom_command('list', 'list_disk_encryption_sets')
g.command('list-associated-resources', 'list_associated_resources', min_api='2020-06-30')

with self.command_group('disk-encryption-set identity', compute_disk_encryption_set_sdk, operation_group='disk_encryption_sets', client_factory=cf_disk_encryption_set, min_api='2022-03-02') as g:
g.custom_command('assign', 'assign_disk_encryption_set_identity')
g.custom_command('remove', 'remove_disk_encryption_set_identity', confirmation=True)
g.custom_show_command('show', 'show_disk_encryption_set_identity')

with self.command_group('disk-access', compute_disk_access_sdk, operation_group='disk_accesses', client_factory=cf_disk_accesses, min_api='2020-05-01') as g:
g.custom_command('create', 'create_disk_access', supports_no_wait=True)
g.generic_update_command('update', setter_name='set_disk_access', setter_type=compute_custom, supports_no_wait=True)
Expand Down
157 changes: 153 additions & 4 deletions src/azure-cli/azure/cli/command_modules/vm/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -1459,6 +1459,13 @@ def patch_vm(cmd, resource_group_name, vm_name, vm):
return LongRunningOperation(cmd.cli_ctx)(poller)


def patch_disk_encryption_set(cmd, resource_group_name, disk_encryption_set_name, disk_encryption_set_update):
client = _compute_client_factory(cmd.cli_ctx)
poller = client.disk_encryption_sets.begin_update(resource_group_name, disk_encryption_set_name,
disk_encryption_set_update)
return LongRunningOperation(cmd.cli_ctx)(poller)


def show_vm(cmd, resource_group_name, vm_name, show_details=False, include_user_data=False):
if show_details:
return get_vm_details(cmd, resource_group_name, vm_name, include_user_data)
Expand Down Expand Up @@ -2012,6 +2019,50 @@ def setter(resource_group_name, vm_name, vm):
identities = [MSI_LOCAL_ID]

return _remove_identities(cmd, resource_group_name, vm_name, identities, get_vm, setter)


# region VirtualMachines Identity
def _remove_disk_encryption_set_identities(cmd, resource_group_name, name,
mi_system_assigned, mi_user_assigned, getter, setter):
IdentityType = cmd.get_models('DiskEncryptionSetIdentityType', operation_group='disk_encryption_sets')
remove_system_assigned_identity = mi_system_assigned is not None

resource = getter(cmd, resource_group_name, name)
if resource is None or resource.identity is None:
return None

user_identities_to_remove = []
if mi_user_assigned is not None:
existing_user_identities = {x.lower() for x in list((resource.identity.user_assigned_identities or {}).keys())}
# all user assigned identities will be removed if the length of mi_user_assigned is 0,
# otherwise the specified identity
user_identities_to_remove = {x.lower() for x in mi_user_assigned} \
if len(mi_user_assigned) > 0 else existing_user_identities
non_existing = user_identities_to_remove.difference(existing_user_identities)
if non_existing:
from azure.cli.core.azclierror import InvalidArgumentValueError
raise InvalidArgumentValueError("'{}' are not associated with '{}', please provide existing user managed "
"identities".format(','.join(non_existing), name))
if not list(existing_user_identities - user_identities_to_remove):
if resource.identity.type == IdentityType.USER_ASSIGNED:
resource.identity.type = IdentityType.NONE
elif resource.identity.type == IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED:
resource.identity.type = IdentityType.SYSTEM_ASSIGNED

resource.identity.user_assigned_identities = None
if remove_system_assigned_identity:
resource.identity.type = (IdentityType.NONE
if resource.identity.type == IdentityType.SYSTEM_ASSIGNED
else IdentityType.USER_ASSIGNED)

if user_identities_to_remove:
if resource.identity.type not in [IdentityType.NONE, IdentityType.SYSTEM_ASSIGNED]:
resource.identity.user_assigned_identities = {}
for identity in user_identities_to_remove:
resource.identity.user_assigned_identities[identity] = None

result = LongRunningOperation(cmd.cli_ctx)(setter(resource_group_name, name, resource))
return result.identity
# endregion


Expand Down Expand Up @@ -3296,6 +3347,25 @@ def _build_identities_info(identities):
return (info, identity_types, external_identities, 'SystemAssigned' in identity_types)


def _build_identities_info_from_system_user_assigned(cmd, mi_system_assigned, mi_user_assigned):
IdentityType, UserAssignedIdentitiesValue = cmd.get_models('DiskEncryptionSetIdentityType',
'UserAssignedIdentitiesValue',
operation_group='disk_encryption_sets')

identity_types = IdentityType.SYSTEM_ASSIGNED
user_assigned_identities = None
if mi_user_assigned:
if mi_system_assigned:
identity_types = IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED
else:
identity_types = IdentityType.USER_ASSIGNED

default_user_identity = UserAssignedIdentitiesValue()
user_assigned_identities = dict.fromkeys(mi_user_assigned, default_user_identity)

return identity_types, user_assigned_identities


def deallocate_vmss(cmd, resource_group_name, vm_scale_set_name, instance_ids=None, no_wait=False):
client = _compute_client_factory(cmd.cli_ctx)
if instance_ids and len(instance_ids) == 1:
Expand Down Expand Up @@ -4578,21 +4648,35 @@ def _set_log_analytics_workspace_extension(cmd, resource_group_name, vm, vm_name
# disk encryption set
def create_disk_encryption_set(
cmd, client, resource_group_name, disk_encryption_set_name, key_url, source_vault=None, encryption_type=None,
location=None, tags=None, no_wait=False, enable_auto_key_rotation=None):
location=None, tags=None, no_wait=False, enable_auto_key_rotation=None, federated_client_id=None,
mi_system_assigned=None, mi_user_assigned=None):
from msrestazure.tools import resource_id, is_valid_resource_id
from azure.cli.core.commands.client_factory import get_subscription_id
DiskEncryptionSet, EncryptionSetIdentity, KeyForDiskEncryptionSet, SourceVault = cmd.get_models(
'DiskEncryptionSet', 'EncryptionSetIdentity', 'KeyForDiskEncryptionSet', 'SourceVault')
encryption_set_identity = EncryptionSetIdentity(type='SystemAssigned')

identity_type, user_assigned_identities = \
_build_identities_info_from_system_user_assigned(cmd, mi_system_assigned, mi_user_assigned)

encryption_set_identity = EncryptionSetIdentity(type=identity_type)
if user_assigned_identities is not None:
encryption_set_identity.user_assigned_identities = user_assigned_identities

if source_vault is not None:
if not is_valid_resource_id(source_vault):
source_vault = resource_id(subscription=get_subscription_id(cmd.cli_ctx), resource_group=resource_group_name,
source_vault = resource_id(subscription=get_subscription_id(cmd.cli_ctx),
resource_group=resource_group_name,
namespace='Microsoft.KeyVault', type='vaults', name=source_vault)
source_vault = SourceVault(id=source_vault)

key_for_disk_emcryption_set = KeyForDiskEncryptionSet(source_vault=source_vault, key_url=key_url)
disk_encryption_set = DiskEncryptionSet(location=location, tags=tags, identity=encryption_set_identity,
active_key=key_for_disk_emcryption_set, encryption_type=encryption_type,
rotation_to_latest_key_version_enabled=enable_auto_key_rotation)

if federated_client_id is not None:
disk_encryption_set.federated_client_id = federated_client_id

return sdk_no_wait(no_wait, client.begin_create_or_update, resource_group_name, disk_encryption_set_name,
disk_encryption_set)

Expand All @@ -4604,7 +4688,7 @@ def list_disk_encryption_sets(cmd, client, resource_group_name=None):


def update_disk_encryption_set(cmd, instance, client, resource_group_name, key_url=None, source_vault=None,
enable_auto_key_rotation=None):
enable_auto_key_rotation=None, federated_client_id=None):
from msrestazure.tools import resource_id, is_valid_resource_id
from azure.cli.core.commands.client_factory import get_subscription_id
if key_url:
Expand All @@ -4620,8 +4704,73 @@ def update_disk_encryption_set(cmd, instance, client, resource_group_name, key_u
if enable_auto_key_rotation is not None:
instance.rotation_to_latest_key_version_enabled = enable_auto_key_rotation

if federated_client_id is not None:
instance.federated_client_id = federated_client_id

return instance


def assign_disk_encryption_set_identity(cmd, client, resource_group_name, disk_encryption_set_name,
mi_system_assigned=None, mi_user_assigned=None):
DiskEncryptionSetUpdate, EncryptionSetIdentity = cmd.get_models('DiskEncryptionSetUpdate', 'EncryptionSetIdentity',
operation_group='disk_encryption_sets')
from azure.cli.core.commands.arm import assign_identity as assign_identity_helper
client = _compute_client_factory(cmd.cli_ctx)

def getter():
return client.disk_encryption_sets.get(resource_group_name, disk_encryption_set_name)

def setter(disk_encryption_set, mi_system_assigned=mi_system_assigned, mi_user_assigned=mi_user_assigned):
IdentityType = cmd.get_models('DiskEncryptionSetIdentityType', operation_group='disk_encryption_sets')
existing_system_identity = False
existing_user_identities = set()
if disk_encryption_set.identity is not None:
existing_system_identity = disk_encryption_set.identity.type in [IdentityType.SYSTEM_ASSIGNED_USER_ASSIGNED,
IdentityType.SYSTEM_ASSIGNED]
existing_user_identities = {x.lower() for x in
list((disk_encryption_set.identity.user_assigned_identities or {}).keys())}

add_system_assigned = mi_system_assigned
add_user_assigned = {x.lower() for x in (mi_user_assigned or [])}

updated_system_assigned = existing_system_identity or add_system_assigned
updated_user_assigned = list(existing_user_identities.union(add_user_assigned))

identity_types, user_assigned_identities = _build_identities_info_from_system_user_assigned(
cmd, updated_system_assigned, updated_user_assigned)

encryption_set_identity = EncryptionSetIdentity(type=identity_types,
user_assigned_identities=user_assigned_identities)

disk_encryption_set_update = DiskEncryptionSetUpdate()
disk_encryption_set_update.identity = encryption_set_identity
return patch_disk_encryption_set(cmd, resource_group_name, disk_encryption_set_name, disk_encryption_set_update)

disk_encryption_set = assign_identity_helper(cmd.cli_ctx, getter, setter)
return disk_encryption_set.identity


def remove_disk_encryption_set_identity(cmd, client, resource_group_name, disk_encryption_set_name,
mi_system_assigned=None, mi_user_assigned=None):
DiskEncryptionSetUpdate = cmd.get_models('DiskEncryptionSetUpdate', operation_group='disk_encryption_sets')
client = _compute_client_factory(cmd.cli_ctx)

def getter(cmd, resource_group_name, disk_encryption_set_name):
return client.disk_encryption_sets.get(resource_group_name, disk_encryption_set_name)

def setter(resource_group_name, disk_encryption_set_name, disk_encryption_set):
disk_encryption_set_update = DiskEncryptionSetUpdate(identity=disk_encryption_set.identity)
return client.disk_encryption_sets.begin_update(resource_group_name, disk_encryption_set_name,
disk_encryption_set_update)

return _remove_disk_encryption_set_identities(cmd, resource_group_name, disk_encryption_set_name,
mi_system_assigned, mi_user_assigned, getter, setter)


def show_disk_encryption_set_identity(cmd, resource_group_name, disk_encryption_set_name):
client = _compute_client_factory(cmd.cli_ctx)
return client.disk_encryption_sets.get(resource_group_name, disk_encryption_set_name).identity

# endregion


Expand Down
Loading

0 comments on commit 4e5d3cc

Please sign in to comment.