Skip to content

Commit fb83fc8

Browse files
authored
[Compute] Add Managed Identity Support in Azure Disk Encryption (#30457)
1 parent a8d8f05 commit fb83fc8

12 files changed

+34466
-8
lines changed

src/azure-cli/azure/cli/command_modules/vm/_help.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,6 +1463,9 @@
14631463
- name: Create a Debian11 VM with both system and user assigned identity.
14641464
text: >
14651465
az vm create -n MyVm -g rg1 --image Debian11 --assign-identity [system] /subscriptions/99999999-1bf0-4dda-aec3-cb9272f09590/resourcegroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myID
1466+
- name: Create a vm with user assigned identity and add encryption identity for Azure disk encryption
1467+
text: >
1468+
az vm create -n MyVm -g rg1 --image Debian11 --assign-identity myID --encryption-identity /subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/myRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myID
14661469
- name: Create a VM in an availability zone in the current resource group's region.
14671470
supported-profiles: latest
14681471
text: >
@@ -1671,6 +1674,9 @@
16711674
- name: Enable disk encryption on the OS disk and/or data disks. Encrypt mounted disks. (autogenerated)
16721675
text: |
16731676
az vm encryption enable --disk-encryption-keyvault MyVault --name MyVm --resource-group MyResourceGroup --volume-type DATA
1677+
- name: Add support for using managed identity to authenticate to customer's keyvault for ADE operation
1678+
text: >
1679+
az vm encryption enable --disk-encryption-keyvault MyVault --name MyVm --resource-group MyResourceGroup --encryption-identity EncryptionIdentity
16741680
crafted: true
16751681
"""
16761682

src/azure-cli/azure/cli/command_modules/vm/_params.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,7 @@ def load_arguments(self, _):
473473
c.argument('enable_vtpm', enable_vtpm_type)
474474
c.argument('user_data', help='UserData for the VM. It can be passed in as file or string.', completer=FilesCompleter(), type=file_type, min_api='2021-03-01')
475475
c.argument('enable_hibernation', arg_type=get_three_state_flag(), min_api='2021-03-01', help='The flag that enable or disable hibernation capability on the VM.')
476+
c.argument('encryption_identity', help='Resource Id of the user managed identity which can be used for Azure disk encryption')
476477

477478
with self.argument_context('vm create', arg_group='Storage') as c:
478479
c.argument('attach_os_disk', help='Attach an existing OS disk to the VM. Can use the name or ID of a managed disk or the URI to an unmanaged disk VHD.')
@@ -1190,6 +1191,9 @@ def load_arguments(self, _):
11901191
c.argument('key_encryption_key', help='Key vault key name or URL used to encrypt the disk encryption key.')
11911192
c.argument('key_encryption_keyvault', help='Name or ID of the key vault containing the key encryption key used to encrypt the disk encryption key. If missing, CLI will use `--disk-encryption-keyvault`.')
11921193

1194+
with self.argument_context('vm encryption enable') as c:
1195+
c.argument('encryption_identity', help='Resource Id of the user managed identity which can be used for Azure disk encryption')
1196+
11931197
for scope in ['vm extension', 'vmss extension']:
11941198
with self.argument_context(scope) as c:
11951199
c.argument('publisher', help='The name of the extension publisher.')

src/azure-cli/azure/cli/command_modules/vm/custom.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -806,8 +806,8 @@ def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_DS1_
806806
storage_account_type=None, vnet_type=None, nsg_type=None, public_ip_address_type=None, nic_type=None,
807807
validate=False, custom_data=None, secrets=None, plan_name=None, plan_product=None, plan_publisher=None,
808808
plan_promotion_code=None, license_type=None, assign_identity=None, identity_scope=None,
809-
identity_role=None, identity_role_id=None, application_security_groups=None, zone=None,
810-
boot_diagnostics_storage=None, ultra_ssd_enabled=None,
809+
identity_role=None, identity_role_id=None, encryption_identity=None,
810+
application_security_groups=None, zone=None, boot_diagnostics_storage=None, ultra_ssd_enabled=None,
811811
ephemeral_os_disk=None, ephemeral_os_disk_placement=None,
812812
proximity_placement_group=None, dedicated_host=None, dedicated_host_group=None, aux_subscriptions=None,
813813
priority=None, max_price=None, eviction_policy=None, enable_agent=None, workspace=None, vmss=None,
@@ -1065,6 +1065,30 @@ def create_vm(cmd, vm_name, resource_group_name, image=None, size='Standard_DS1_
10651065
master_template.add_resource(build_msi_role_assignment(vm_name, vm_id, identity_role_id,
10661066
role_assignment_guid, identity_scope))
10671067

1068+
if encryption_identity:
1069+
if not cmd.supported_api_version(min_api='2023-09-01', resource_type=ResourceType.MGMT_COMPUTE):
1070+
raise CLIError("Usage error: Encryption Identity required API version 2023-09-01 or higher."
1071+
"You can set the cloud's profile to use the required API Version with:"
1072+
"az cloud set --profile latest --name <cloud name>")
1073+
1074+
if 'identity' in vm_resource and 'userAssignedIdentities' in vm_resource['identity'] \
1075+
and encryption_identity.lower() in \
1076+
(k.lower() for k in vm_resource['identity']['userAssignedIdentities'].keys()):
1077+
if 'securityProfile' not in vm_resource['properties']:
1078+
vm_resource['properties']['securityProfile'] = {}
1079+
if 'encryptionIdentity' not in vm_resource['properties']['securityProfile']:
1080+
vm_resource['properties']['securityProfile']['encryptionIdentity'] = {}
1081+
1082+
vm_securityProfile_EncryptionIdentity = vm_resource['properties']['securityProfile']['encryptionIdentity']
1083+
1084+
if 'userAssignedIdentityResourceId' not in vm_securityProfile_EncryptionIdentity or \
1085+
vm_securityProfile_EncryptionIdentity['userAssignedIdentityResourceId'] != encryption_identity:
1086+
vm_resource['properties']['securityProfile']['encryptionIdentity']['userAssignedIdentityResourceId'] \
1087+
= encryption_identity
1088+
else:
1089+
raise CLIError("Encryption Identity should be an ARM Resource ID of one of the "
1090+
"user assigned identities associated to the resource")
1091+
10681092
if workspace is not None:
10691093
workspace_id = _prepare_workspace(cmd, resource_group_name, workspace)
10701094
master_template.add_secure_parameter('workspaceId', workspace_id)

src/azure-cli/azure/cli/command_modules/vm/disk_encryption.py

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,47 @@ def _detect_ade_status(vm):
6161
return True, False # we believe impossible to have both old & new ADE
6262

6363

64+
def updateVmEncryptionSetting(cmd, vm, resource_group_name, vm_name, encryption_identity):
65+
from azure.cli.core.azclierror import ArgumentUsageError
66+
if vm.identity is None or vm.identity.user_assigned_identities is None or encryption_identity.lower() not in \
67+
(k.lower() for k in vm.identity.user_assigned_identities.keys()):
68+
raise ArgumentUsageError("Encryption Identity should be an ARM Resource ID of one of the "
69+
"user assigned identities associated to the resource")
70+
71+
SecurityProfile, EncryptionIdentity = cmd.get_models('SecurityProfile', 'EncryptionIdentity')
72+
updateVm = False
73+
74+
if vm.security_profile is None:
75+
vm.security_profile = SecurityProfile()
76+
if vm.security_profile.encryption_identity is None:
77+
vm.security_profile.encryption_identity = EncryptionIdentity()
78+
if vm.security_profile.encryption_identity.user_assigned_identity_resource_id is None \
79+
or vm.security_profile.encryption_identity.user_assigned_identity_resource_id.lower() \
80+
!= encryption_identity:
81+
vm.security_profile.encryption_identity.user_assigned_identity_resource_id = encryption_identity
82+
updateVm = True
83+
84+
if updateVm:
85+
compute_client = _compute_client_factory(cmd.cli_ctx)
86+
updateEncryptionIdentity \
87+
= compute_client.virtual_machines.begin_create_or_update(resource_group_name, vm_name, vm)
88+
LongRunningOperation(cmd.cli_ctx)(updateEncryptionIdentity)
89+
result = updateEncryptionIdentity.result()
90+
return result is not None and result.provisioning_state == 'Succeeded'
91+
logger.info("No changes in identity")
92+
return True
93+
94+
95+
def isVersionSuppprtedForEncryptionIdentity(cmd):
96+
from azure.cli.core.profiles import ResourceType
97+
from knack.util import CLIError
98+
if not cmd.supported_api_version(min_api='2023-09-01', resource_type=ResourceType.MGMT_COMPUTE):
99+
raise CLIError("Usage error: Encryption Identity required API version 2023-09-01 or higher."
100+
"You can set the cloud's profile to use the required API Version with:"
101+
"az cloud set --profile latest --name <cloud name>")
102+
return True
103+
104+
64105
def encrypt_vm(cmd, resource_group_name, vm_name, # pylint: disable=too-many-locals, too-many-statements
65106
disk_encryption_keyvault,
66107
aad_client_id=None,
@@ -70,7 +111,7 @@ def encrypt_vm(cmd, resource_group_name, vm_name, # pylint: disable=too-many-lo
70111
key_encryption_algorithm='RSA-OAEP',
71112
volume_type=None,
72113
encrypt_format_all=False,
73-
force=False):
114+
force=False, encryption_identity=None):
74115
from azure.mgmt.core.tools import parse_resource_id
75116
from knack.util import CLIError
76117

@@ -109,6 +150,12 @@ def encrypt_vm(cmd, resource_group_name, vm_name, # pylint: disable=too-many-lo
109150
# disk encryption key itself can be further protected, so let us verify
110151
if key_encryption_key:
111152
key_encryption_keyvault = key_encryption_keyvault or disk_encryption_keyvault
153+
if encryption_identity and isVersionSuppprtedForEncryptionIdentity(cmd):
154+
result = updateVmEncryptionSetting(cmd, vm, resource_group_name, vm_name, encryption_identity)
155+
if result:
156+
logger.info("Encryption Identity successfully set in virtual machine")
157+
else:
158+
raise CLIError("Failed to update encryption Identity to the VM")
112159

113160
# to avoid bad server errors, ensure the vault has the right configurations
114161
_verify_keyvault_good_for_encryption(cmd.cli_ctx, disk_encryption_keyvault, key_encryption_keyvault, vm, force)
@@ -553,10 +600,19 @@ def _report_client_side_validation_error(msg):
553600
disk_vault_resource_info = parse_resource_id(disk_vault_id)
554601
key_vault = client.get(disk_vault_resource_info['resource_group'], disk_vault_resource_info['name'])
555602

556-
# ensure vault has 'EnabledForDiskEncryption' permission
557-
if not key_vault.properties or not key_vault.properties.enabled_for_disk_encryption:
558-
_report_client_side_validation_error("Keyvault '{}' is not enabled for disk encryption.".format(
559-
disk_vault_resource_info['resource_name']))
603+
# ensure vault has 'EnabledForDiskEncryption' permission or VM has encryption identity set for ADE operation
604+
if resource_type == 'VM':
605+
vm_encryption_identity = vm_or_vmss
606+
else:
607+
vm_encryption_identity = vm_or_vmss.virtual_machine_profile
608+
609+
if vm_encryption_identity and vm_encryption_identity.security_profile and \
610+
vm_encryption_identity.security_profile.encryption_identity and \
611+
vm_encryption_identity.security_profile.encryption_identity.user_assigned_identity_resource_id:
612+
pass
613+
elif not key_vault.properties or not key_vault.properties.enabled_for_disk_encryption:
614+
_report_client_side_validation_error(
615+
"Keyvault '{}' is not enabled for disk encryption.".format(disk_vault_resource_info['resource_name']))
560616

561617
if kek_vault_id:
562618
kek_vault_info = parse_resource_id(kek_vault_id)

0 commit comments

Comments
 (0)