适用于 Linux VM 的 Azure 磁盘加密示例脚本

注意

本文引用了 CentOS,这是一个接近生命周期结束 (EOL) 状态的 Linux 发行版。 请相应地考虑你的使用和规划。 有关详细信息,请参阅 CentOS 生命周期结束指导

适用于:✔️ Linux 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 加密的 VMSS 实例和扩展版本。

  • 列出 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 脚本中使用的参数:

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

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

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

在正在运行的 Linux VM 上加密 OS 驱动器

OS 磁盘加密的先决条件

  • VM 必须使用与 Azure 磁盘加密支持的操作系统中列出的 OS 磁盘加密兼容的分发版
  • 必须从 Azure 资源管理器中的市场映像创建 VM。
  • Azure VM,至少具有 4 GB RAM(建议大小为 7 GB)。 有关详细信息,请参阅内存要求
  • (针对 RHEL 和 CentOS)禁用 SELinux。 若要禁用 SELinux,请参阅 SELinux User's and Administrator's Guide(SELinux 用户和管理员指南)中针对 VM 的“4.4.2. Disabling SELinux(4.4.2. 禁用 SELinux)”。
  • 禁用 SELinux 后,重启 VM 至少一次。

步骤

  1. 通过之前指定的分发版之一创建 VM。

  2. 根据需要配置 VM。 如果打算加密所有(OS + 数据)驱动器,则需要指定数据驱动器且可从 /etc/fstab 处装载数据驱动器。

    注意

    使用 UUID =... 在 /etc/fstab 中指定数据驱动器(而不是指定 /dev/sdb1 等块设备名称)。 在加密过程中,驱动器的顺序将在 VM 上有所改变。 如果 VM 依赖于特定块设备顺序,加密后将无法装载。

  3. 注销 SSH 会话。

  4. 若要加密 OS,请在启用加密时将 volumeType 指定为“All”或“OS”。

    注意

    未作为 systemd 服务运行的所有用户空间进程应使用 SIGKILL 终止。 重启 VM。 在正在运行的 VM 上启用 OS 磁盘加密时,请计划 VM 停机时间。

  5. 使用下一部分中的说明,定期监视加密进度。

  6. Get-AzVmDiskEncryptionStatus 显示“VMRestartPending”后,通过登录 VM 或使用门户/PowerShell/CLI 重启 VM。

    C:\> Get-AzVmDiskEncryptionStatus  -ResourceGroupName $ResourceGroupName -VMName $VMName
    -ExtensionName $ExtensionName
    
    OsVolumeEncrypted          : VMRestartPending
    DataVolumesEncrypted       : NotMounted
    OsVolumeEncryptionSettings : Microsoft.Azure.Management.Compute.Models.DiskEncryptionSettings
    ProgressMessage            : OS disk successfully encrypted, reboot the VM
    

    重启之前,建议保存 VM 的启动诊断

监视 OS 加密进度

可通过三种方法监视 OS 加密进度:

  • 使用 Get-AzVmDiskEncryptionStatus cmdlet 并检查“ProgressMessage”字段:

    Get-AzVMDiskEncryptionStatus -ResourceGroupName $_.ResourceGroupName -VMName $_.Name
    
    OsVolumeEncrypted          : EncryptionInProgress
    DataVolumesEncrypted       : NotMounted
    OsVolumeEncryptionSettings : Microsoft.Azure.Management.Compute.Models.DiskEncryptionSettings
    ProgressMessage            : OS disk encryption started
    

    VM 变为“OS 磁盘加密开始”后,在支持高级存储的 VM 上将需要花费大约 40-50 分钟。

    由于 WALinuxAgent 出现问题 #388OsVolumeEncryptedDataVolumesEncrypted 在某些发行版中显示为 Unknown。 在 WALinuxAgent 2.1.5 版及更高版本中,将自动修复此问题。 如果在输出中看到 Unknown,可通过使用 Azure 资源浏览器验证磁盘加密状态。

    转到 Azure 资源浏览器,然后在左侧的选择面板中展开此层次结构:

    |-- subscriptions
       |-- [Your subscription]
            |-- resourceGroups
                 |-- [Your resource group]
                      |-- providers
                           |-- Microsoft.Compute
                                |-- virtualMachines
                                     |-- [Your virtual machine]
                                          |-- InstanceView
    

    在 InstanceView 中,向下滚动以查看驱动器的加密状态。

    VM 实例视图

  • 查看启动诊断。 来自 ADE 扩展的消息应带有前缀 [AzureDiskEncryption]

  • 通过 SSH 登录 VM 并从以下位置获取扩展日志:

    /var/log/azure/Microsoft.Azure.Security.AzureDiskEncryptionForLinux

    建议不要在 OS 加密正在进行时登录 VM。 仅当其他两个方法都失败时复制日志。

准备预加密的 Linux VHD

预加密 VHD 的准备过程根据分发版的不同而异。 我们提供了有关准备 Ubuntu、openSUSE 和 CentOS 7 的示例。

通过执行以下步骤在分发版安装过程中配置加密:

  1. 对磁盘进行分区时选择“配置加密卷” 。

    Ubuntu 16.04 安装 - 配置加密卷

  2. 创建一个单独的不得加密的启动驱动器。 对根驱动器进行加密。

    Ubuntu 16.04 安装 - 选择要加密的设备

  3. 提供通行短语。 这是上传到 Key Vault 的通行短语。

    Ubuntu 16.04 安装 - 提供通行短语

  4. 完成分区。

    Ubuntu 16.04 安装 - 完成分区

  5. 启动 VM 并被要求提供密码时,请使用步骤 3 中提供的密码。

    Ubuntu 16.04 安装 - 在启动时提供通行短语

  6. 使用这些说明准备 VM 以上传到 Azure。 暂时不要运行最后一个步骤(取消预配 VM)。

执行以下步骤,配置适用于 Azure 的加密:

  1. /usr/local/sbin/azure_crypt_key.sh 下创建文件,其内容如以下脚本所示。 请注意 KeyFileName,因为它是 Azure 使用的通行短语文件名。

    #!/bin/sh
    MountPoint=/tmp-keydisk-mount
    KeyFileName=LinuxPassPhraseFileName
    echo "Trying to get the key from disks ..." >&2
    mkdir -p $MountPoint
    modprobe vfat >/dev/null 2>&1
    modprobe ntfs >/dev/null 2>&1
    sleep 2
    OPENED=0
    cd /sys/block
    for DEV in sd*; do
    
        echo "> Trying device: $DEV ..." >&2
        mount -t vfat -r /dev/${DEV}1 $MountPoint >/dev/null||
        mount -t ntfs -r /dev/${DEV}1 $MountPoint >/dev/null
        if [ -f $MountPoint/$KeyFileName ]; then
                cat $MountPoint/$KeyFileName
                umount $MountPoint 2>/dev/null
                OPENED=1
                break
        fi
        umount $MountPoint 2>/dev/null
    done
    
      if [ $OPENED -eq 0 ]; then
        echo "FAILED to find suitable passphrase file ..." >&2
        echo -n "Try to enter your password: " >&2
        read -s -r A </dev/console
        echo -n "$A"
     else
        echo "Success loading keyfile!" >&2
    fi
    
  2. /etc/crypttab 中更改加密配置。 它看起来应该如下所示:

     xxx_crypt uuid=xxxxxxxxxxxxxxxxxxxxx none luks,discard,keyscript=/usr/local/sbin/azure_crypt_key.sh
    
  3. 将可执行文件的权限添加到脚本:

     sudo chmod +x /usr/local/sbin/azure_crypt_key.sh
    
  4. 通过追加行编辑 /etc/initramfs-tools/modules

     vfat
     ntfs
     nls_cp437
     nls_utf8
     nls_iso8859-1
    
  5. 运行 update-initramfs -u -k all 更新 initramfs 以使 keyscript 生效。

  6. 现在可以取消预配 VM。

    Ubuntu 16.04 安装 - 更新 initramfs

  7. 继续执行下一步,将 VHD 上传到 Azure 中。

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

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

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

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

使用 Microsoft Entra 应用(以前的版本)加密时,必须上传前面获取的磁盘加密机密作为 Key Vault 中的机密。 Key Vault 需要具有对 Microsoft Entra 客户端启用的磁盘加密等权限。

 $AadClientId = "My-AAD-Client-Id"
 $AadClientSecret = "My-AAD-Client-Secret"

 $key vault = New-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -Location $Location

 Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -ServicePrincipalName $AadClientId -PermissionsToKeys all -PermissionsToSecrets all
 Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -ResourceGroupName $ResourceGroupName -EnabledForDiskEncryption

未使用 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 `
            -Linux `
            -CreateOption FromImage `
            -DiskEncryptionKeyVaultId $KeyVault.ResourceId `
            -DiskEncryptionKeyUrl $SecretUrl

使用 KEK

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

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