Azure 磁盘加密示例脚本

重要

虚拟机和虚拟机规模集的 Azure 磁盘加密将于 2028 年 9 月 15 日停用。 新客户应在主机上对所有新 VM 使用 加密 。 现有客户应计划在停用日期之前将当前已启用 ADE 的 VM 迁移到主机加密,以避免服务中断 -- 请参阅 从 Azure 磁盘加密迁移到主机加密

适用于:✔️ Windows VM

本文提供了用于准备预加密 VHD 和其他任务的示例脚本。

注释

除非另有说明,否则所有脚本均引用最新的非 AAD 版本的 ADE。

Azure 磁盘加密的示例 PowerShell 脚本

  • 列出订阅中所有已加密的 VM

    可以使用此 PowerShell 脚本在订阅中存在的所有资源组中找到所有 ADE 加密的 VM 和扩展版本。

    另外,这些 cmdlet 将显示所有 ADE 加密的 VM(但不显示扩展版本):

    $osVolEncrypted = {(Get-AzVMDiskEncryptionStatus -ResourceGroupName $_.ResourceGroupName -VMName $_.Name).OsVolumeEncrypted}
    $dataVolEncrypted= {(Get-AzVMDiskEncryptionStatus -ResourceGroupName $_.ResourceGroupName -VMName $_.Name).DataVolumesEncrypted}
    Get-AzVm | Format-Table @{Label="MachineName"; Expression={$_.Name}}, @{Label="OsVolumeEncrypted"; Expression=$osVolEncrypted}, @{Label="DataVolumesEncrypted"; Expression=$dataVolEncrypted}
    
  • 列出订阅中所有已加密的 VMSS 实例

    可以使用此 PowerShell 脚本在订阅中存在的所有资源组中找到所有 ADE 加密的虚拟机规模集实例和扩展版本。

  • 列出 Key Vault 中用于加密 VM 的所有磁盘加密机密

Get-AzKeyVaultSecret -VaultName $KeyVaultName | where {$_.Tags.ContainsKey('DiskEncryptionKeyFileName')} | format-table @{Label="MachineName"; Expression={$_.Tags['MachineName']}}, @{Label="VolumeLetter"; Expression={$_.Tags['VolumeLetter']}}, @{Label="EncryptionKeyURL"; Expression={$_.Id}}

使用 Azure 磁盘加密先决条件 PowerShell 脚本

如果你已熟悉进行 Azure 磁盘加密的先决条件,则可以使用 Azure 磁盘加密先决条件 PowerShell 脚本。 有关此 PowerShell 脚本的用法示例,请参阅有关加密 VM 的快速入门。 可以删除脚本的某个部分中的注释(从第 211 行开始),以加密现有资源组中现有 VM 的所有磁盘。

下表显示了可在 PowerShell 脚本中使用的参数:

参数 Description 强制?
$resourceGroupName KeyVault 所属的资源组的名称。 如果不存在具有此名称的资源组,则会新建一个资源组。 True
$keyVaultName 要将加密密钥放到的 KeyVault 的名称。 如果不存在具有此名称的保管库,则会新建一个保管库。 True
$location KeyVault 的位置。 请确保 KeyVault 和要加密的 VM 位于同一位置。 使用 Get-AzLocation 获取位置列表。 True
$subscriptionId 要使用的 Azure 订阅的标识符。 可以使用 Get-AzSubscription 获取订阅 ID。 True
$aadAppName 用于将机密写入 KeyVault 的 Microsoft Entra 应用程序的名称。 如果该应用程序不存在,则会使用此名称创建新的应用程序。 如果此应用已存在,则将 aadClientSecret 参数传递给脚本。
$aadClientSecret 之前创建的 Microsoft Entra 应用程序的客户端密码。
$keyEncryptionKeyName KeyVault 中的可选密钥加密密钥的名称。 如果不存在具有此名称的密钥,则会新建一个密钥。

资源管理器模板

在不使用 Microsoft Entra 应用的情况下加密或解密 VM

使用 Microsoft Entra 应用加密或解密 VM(早期版本)

准备预加密的 Windows VHD

以下部分介绍了必要操作,让你在 Azure IaaS 中准备将预加密的 Windows VHD 部署为加密 VHD。 使用该信息在 Azure Site Recovery 或 Azure 上准备和启动全新的 Windows VM (VHD)。 有关如何准备和上传 VHD 的详细信息,请参阅上传通用化 VHD 并使用它在 Azure 中创建新的 VM

更新组策略以允许使用非 TPM 保护 OS

在“本地计算机策略”“计算机设置”>“管理模板”>“Windows 组件”>下配置名为“BitLocker 驱动器加密”的 BitLocker 组策略设置。 如下图所示,将此设置更改为“操作系统驱动器”>“启动时需要附加身份验证”>“没有兼容的 TPM 时允许 BitLocker”

Azure 中的 Microsoft Antimalware

安装 BitLocker 功能组件

对于 Windows Server 2012 或更高版本,请使用以下命令:

dism /online /Enable-Feature /all /FeatureName:BitLocker /quiet /norestart

对于 Windows Server 2008 R2,请使用以下命令:

ServerManagerCmd -install BitLockers

使用 bdehdcfg 为 BitLocker 准备 OS 卷

若要压缩 OS 分区并为 BitLocker 准备计算机,请根据需要执行 bdehdcfg

bdehdcfg -target c: shrink -quiet

使用 BitLocker 保护 OS 卷

使用 manage-bde 命令在使用外部密钥保护程序的引导卷上启用加密。 此外将外部密钥(.bek 文件)放在外部驱动器或卷上。 下次重启后,会在系统/引导卷上启用加密。

manage-bde -on %systemdrive% -sk [ExternalDriveOrVolume]
reboot

注释

使用独立的数据/资源 VHD 准备 VM,以使用 BitLocker 获取外部密钥。

将加密的 VHD 上传到 Azure 存储帐户

启用 BitLocker 加密后,需要将本地加密的 VHD 上传到存储帐户。

    Add-AzVhd [-Destination] <Uri> [-LocalFilePath] <FileInfo> [[-NumberOfUploaderThreads] <Int32> ] [[-BaseImageUriToPatch] <Uri> ] [[-OverWrite]] [ <CommonParameters>]

将预加密 VM 的机密上传到密钥保管库

必须将前面获取的磁盘加密机密作为机密上传到密钥保管库中。 为此,你必须向将上传机密的帐户授予 set secret 权限和 wrapkey 权限。

# Typically, account Id is the user principal name (in user@domain.com format)
$upn = (Get-AzureRmContext).Account.Id
Set-AzKeyVaultAccessPolicy -VaultName $kvname -UserPrincipalName $acctid -PermissionsToKeys wrapKey -PermissionsToSecrets set

# In local Shell, the account ID is a managed service identity, so specify the username directly
# $upn = "user@domain.com"
# Set-AzKeyVaultAccessPolicy -VaultName $kvname -UserPrincipalName $acctid -PermissionsToKeys wrapKey -PermissionsToSecrets set

# When running as a service principal, retrieve the service principal ID from the account ID, and set access policy to that
# $acctid = (Get-AzureRmContext).Account.Id
# $spoid = (Get-AzureRmADServicePrincipal -ServicePrincipalName $acctid).Id
# Set-AzKeyVaultAccessPolicy -VaultName $kvname -ObjectId $spoid -BypassObjectIdValidation -PermissionsToKeys wrapKey -PermissionsToSecrets set

未使用 KEK 加密的磁盘加密机密

若要在密钥保管库中设置机密,请使用 Set-AzKeyVaultSecret。 将密码编码为 base64 字符串,然后将其上传到密钥保管库。 此外,请确保在 Key Vault 中创建机密时设置以下标记。


 # This is the passphrase that was provided for encryption during the distribution installation
 $passphrase = "contoso-password"

 $tags = @{"DiskEncryptionKeyEncryptionAlgorithm" = "RSA-OAEP"; "DiskEncryptionKeyFileName" = "LinuxPassPhraseFileName"}
 $secretName = [guid]::NewGuid().ToString()
 $secretValue = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($passphrase))
 $secureSecretValue = ConvertTo-SecureString $secretValue -AsPlainText -Force

 $secret = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name $secretName -SecretValue $secureSecretValue -tags $tags
 $secretUrl = $secret.Id

在下一步中使用 $secretUrl 以便在不使用 KEK 的情况下附加 OS 磁盘

使用 KEK 加密的磁盘加密机密

将机密上传到 Key Vault 之前,可根据需要使用密钥加密密钥对其进行加密。 先使用包装 API 加密使用密钥加密密钥的机密。 此包装操作的输出是 base64 URL 编码的字符串,可以使用 Set-AzKeyVaultSecret cmdlet 将其作为机密上传。

    # This is the passphrase that was provided for encryption during the distribution installation
    $passphrase = "contoso-password"

    Add-AzKeyVaultKey -VaultName $KeyVaultName -Name "keyencryptionkey" -Destination Software
    $KeyEncryptionKey = Get-AzKeyVaultKey -VaultName $KeyVault.OriginalVault.Name -Name "keyencryptionkey"

    $apiversion = "2015-06-01"

    ##############################
    # Get Auth URI
    ##############################

    $uri = $KeyVault.VaultUri + "/keys"
    $headers = @{}

    $response = try { Invoke-RestMethod -Method GET -Uri $uri -Headers $headers } catch { $_.Exception.Response }

    $authHeader = $response.Headers["www-authenticate"]
    $authUri = [regex]::match($authHeader, 'authorization="(.*?)"').Groups[1].Value

    Write-Host "Got Auth URI successfully"

    ##############################
    # Get Auth Token
    ##############################

    $uri = $authUri + "/oauth2/token"
    $body = "grant_type=client_credentials"
    $body += "&client_id=" + $AadClientId
    $body += "&client_secret=" + [Uri]::EscapeDataString($AadClientSecret)
    $body += "&resource=" + [Uri]::EscapeDataString("https://vault.azure.cn")
    $headers = @{}

    $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers -Body $body

    $access_token = $response.access_token

    Write-Host "Got Auth Token successfully"

    ##############################
    # Get KEK info
    ##############################

    $uri = $KeyEncryptionKey.Id + "?api-version=" + $apiversion
    $headers = @{"Authorization" = "Bearer " + $access_token}

    $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers

    $keyid = $response.key.kid

    Write-Host "Got KEK info successfully"

    ##############################
    # Encrypt passphrase using KEK
    ##############################

    $passphraseB64 = [Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes($Passphrase))
    $uri = $keyid + "/encrypt?api-version=" + $apiversion
    $headers = @{"Authorization" = "Bearer " + $access_token; "Content-Type" = "application/json"}
    $bodyObj = @{"alg" = "RSA-OAEP"; "value" = $passphraseB64}
    $body = $bodyObj | ConvertTo-Json

    $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers -Body $body

    $wrappedSecret = $response.value

    Write-Host "Encrypted passphrase successfully"

    ##############################
    # Store secret
    ##############################

    $secretName = [guid]::NewGuid().ToString()
    $uri = $KeyVault.VaultUri + "/secrets/" + $secretName + "?api-version=" + $apiversion
    $secretAttributes = @{"enabled" = $true}
    $secretTags = @{"DiskEncryptionKeyEncryptionAlgorithm" = "RSA-OAEP"; "DiskEncryptionKeyFileName" = "LinuxPassPhraseFileName"}
    $headers = @{"Authorization" = "Bearer " + $access_token; "Content-Type" = "application/json"}
    $bodyObj = @{"value" = $wrappedSecret; "attributes" = $secretAttributes; "tags" = $secretTags}
    $body = $bodyObj | ConvertTo-Json

    $response = Invoke-RestMethod -Method PUT -Uri $uri -Headers $headers -Body $body

    Write-Host "Stored secret successfully"

    $secretUrl = $response.id

将在下一步中使用 $KeyEncryptionKey$secretUrl 以便在使用 KEK 的情况下附加 OS 磁盘

附加 OS 磁盘时指定机密 URL

不使用 KEK

附加 OS 磁盘时,需要传递 $secretUrl。 该 URL 是在“不使用 KEK 对磁盘加密机密进行加密”部分中生成的。

    Set-AzVMOSDisk `
            -VM $VirtualMachine `
            -Name $OSDiskName `
            -SourceImageUri $VhdUri `
            -VhdUri $OSDiskUri `
            -Windows `
            -CreateOption FromImage `
            -DiskEncryptionKeyVaultId $KeyVault.ResourceId `
            -DiskEncryptionKeyUrl $SecretUrl

使用 KEK

附加 OS 磁盘时,传递 $KeyEncryptionKey$secretUrl。 该 URL 是在“使用 KEK 加密的磁盘加密机密”部分生成的。

    Set-AzVMOSDisk `
            -VM $VirtualMachine `
            -Name $OSDiskName `
            -SourceImageUri $CopiedTemplateBlobUri `
            -VhdUri $OSDiskUri `
            -Windows `
            -CreateOption FromImage `
            -DiskEncryptionKeyVaultId $KeyVault.ResourceId `
            -DiskEncryptionKeyUrl $SecretUrl `
            -KeyEncryptionKeyVaultId $KeyVault.ResourceId `
            -KeyEncryptionKeyURL $KeyEncryptionKey.Id