教程:使用部署脚本创建自签名证书Tutorial: Use deployment scripts to create a self-signed certificate

了解如何使用 Azure 资源管理器模板(ARM 模板)中的部署脚本。Learn how to use deployment scripts in Azure Resource Manager templates (ARM templates). 可以使用部署脚本执行无法由 ARM 模板完成的自定义步骤。Deployment scripts can be used to perform custom steps that can't be done by ARM templates. 例如,创建自签名证书。For example, creating a self-signed certificate. 在本教程中,你将创建一个模板来部署 Azure 密钥保管库,然后在同一模板中使用 Microsoft.Resources/deploymentScripts 资源来创建证书并将证书添加到密钥保管库。In this tutorial, you create a template to deploy an Azure key vault, and then use a Microsoft.Resources/deploymentScripts resource in the same template to create a certificate and then add the certificate to the key vault. 若要详细了解部署脚本,请参阅使用 ARM 模板中的部署脚本To learn more about deployment script, see Use deployment scripts in ARM templates.

重要

在同一资源组中会创建两个部署脚本资源(一个存储帐户和一个容器实例),用于执行脚本和进行故障排除。Two deployment script resources, a storage account and a container instance, are created in the same resource group for script execution and troubleshooting. 当脚本执行达到某个最终状态时,这些资源通常会被脚本服务删除。These resources are usually deleted by the script service when the script execution gets in a terminal state. 在这些资源删除之前,这些资源会一直向你收费。You are billed for the resources until the resources are deleted. 若要了解详细信息,请参阅清理部署脚本资源To learn more, see Clean up deployment script resources.

本教程涵盖以下任务:This tutorial covers the following tasks:

  • 打开快速入门模板Open a quickstart template
  • 编辑模板Edit the template
  • 部署模板Deploy the template
  • 调试失败的脚本Debug the failed script
  • 清理资源Clean up resources

先决条件Prerequisites

若要完成本文,需要做好以下准备:To complete this article, you need:

  • 包含资源管理器工具扩展的 Visual Studio CodeVisual Studio Code with the Resource Manager Tools extension. 请参阅快速入门:使用 Visual Studio Code 创建 ARM 模板See Quickstart: Create ARM templates with Visual Studio Code.

  • 用户分配的托管标识。A user-assigned managed identity. 此标识用于在脚本中执行特定于 Azure 的操作。This identity is used to perform Azure-specific actions in the script. 若要创建一个标识,请参阅用户分配的托管标识To create one, see User-assigned managed identity. 部署模板时需要此标识 ID。You need the identity ID when you deploy the template. 标识符的格式为:The format of the identity is:

    /subscriptions/<SubscriptionID>/resourcegroups/<ResourceGroupName>/providers/Microsoft.ManagedIdentity/userAssignedIdentities/<IdentityID>
    

    使用以下 CLI 脚本通过提供资源组名称和标识名称来获取 ID。Use the following CLI script to get the ID by providing the resource group name and the identity name.

    echo "Enter the Resource Group name:" &&
    read resourceGroupName &&
    az identity list -g $resourceGroupName
    

打开快速入门模板Open a Quickstart template

无需从头开始创建模板,可以通过 Azure 快速入门模板打开一个模板。Instead of creating a template from scratch, you open a template from Azure Quickstart Templates. Azure 快速入门模板是 ARM 模板的存储库。Azure Quickstart Templates is a repository for ARM templates.

本快速入门中使用的模板名为创建 Azure 密钥保管库和机密The template used in this quickstart is called Create an Azure Key Vault and a secret. 该模板将创建一个密钥保管库,然后向该密钥保管库中添加机密。The template creates a key vault, and then adds a secret to the key vault.

  1. 在 Visual Studio Code 中,选择“文件” > “打开文件”。 From Visual Studio Code, select File > Open File.

  2. 在“文件名”中粘贴以下 URL:In File name, paste the following URL:

    https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.keyvault/key-vault-create/azuredeploy.json
    
  3. 选择“打开”以打开该文件。Select Open to open the file.

  4. 选择“文件” > “另存为”,将该文件作为 azuredeploy.json 保存到本地计算机。 Select File > Save As to save the file as azuredeploy.json to your local computer.

编辑模板Edit the template

对模板进行以下更改:Make the following changes to the template:

清理模板(可选)Clean up the template (optional)

原始模板向密钥保管库中添加机密。The original template adds a secret to the key vault. 为了简化本教程,请删除以下资源:To simplify the tutorial, remove the following resource:

  • Microsoft.KeyVault/vaults/secrets

删除以下两个参数定义:Remove the following two parameter definitions:

  • secretName
  • secretValue

如果选择不删除这些定义,则需要在部署过程中指定参数值。If you choose not to remove these definitions, you need to specify the parameter values during the deployment.

配置密钥保管库访问策略Configure the key vault access policies

部署脚本将证书添加到密钥保管库。The deployment script adds a certificate to the key vault. 配置密钥保管库访问策略,以便向托管标识授予权限:Configure the key vault access policies to give the permission to the managed identity:

  1. 添加参数以获取托管标识 ID:Add a parameter to get the managed identity ID:

    "identityId": {
      "type": "string",
      "metadata": {
        "description": "Specifies the ID of the user-assigned managed identity."
      }
    },
    

    备注

    Visual Studio Code 的资源管理器模板扩展尚不能格式化部署脚本。The Resource Manager template extension of Visual Studio Code isn't capable to format deployment scripts yet. 不要使用 Shift+Alt+F 设置 deploymentScripts 资源的格式,如下所示。Don't use Shift+Alt+F to format the deploymentScripts resources, like the following one.

  2. 添加一个参数,用于配置密钥保管库访问策略,以便托管标识可以将证书添加到密钥保管库:Add a parameter for configuring the key vault access policies so that the managed identity can add certificates to the key vault:

    "certificatesPermissions": {
      "type": "array",
      "defaultValue": [
        "get",
        "list",
        "update",
        "create"
      ],
      "metadata": {
      "description": "Specifies the permissions to certificates in the vault. Valid values are: all, get, list, update, create, import, delete, recover, backup, restore, manage contacts, manage certificate authorities, get certificate authorities, list certificate authorities, set certificate authorities, delete certificate authorities."
      }
    }
    
  3. 将现有的密钥保管库访问策略更新为:Update the existing key vault access policies to:

    "accessPolicies": [
      {
        "objectId": "[parameters('objectId')]",
        "tenantId": "[parameters('tenantId')]",
        "permissions": {
          "keys": "[parameters('keysPermissions')]",
          "secrets": "[parameters('secretsPermissions')]",
          "certificates": "[parameters('certificatesPermissions')]"
        }
      },
      {
        "objectId": "[reference(parameters('identityId'), '2018-11-30').principalId]",
        "tenantId": "[parameters('tenantId')]",
        "permissions": {
          "keys": "[parameters('keysPermissions')]",
          "secrets": "[parameters('secretsPermissions')]",
          "certificates": "[parameters('certificatesPermissions')]"
        }
      }
    ],
    

    定义了两个策略,一个用于已登录用户,另一个用于托管标识。There are two policies defined, one for the signed-in user, and the other is for the managed identity. 已登录用户仅需要“列表”权限来验证部署。The signed-in user only needs the list permission to verify the deployment. 为了简化本教程,为托管标识和已登录用户分配了相同的证书。To simplify the tutorial, the same certificate is assigned to both the managed identity and the signed-in users.

添加部署脚本Add the deployment script

  1. 添加部署脚本使用的三个参数:Add three parameters that are used by the deployment script:

    "certificateName": {
      "type": "string",
      "defaultValue": "DeploymentScripts2019"
    },
    "subjectName": {
      "type": "string",
      "defaultValue": "CN=contoso.com"
    },
    "utcValue": {
      "type": "string",
      "defaultValue": "[utcNow()]"
    }
    
  2. 添加 deploymentScripts 资源:Add a deploymentScripts resource:

    备注

    由于内联部署脚本是用双引号括起来的,因此部署脚本内的字符串需要改用单引号括起来。Because the inline deployment scripts are enclosed in double quotes, the strings inside the deployment scripts need to be enclosed in single quotes instead. PowerShell 转义字符是反引号 (`)。The PowerShell escape character is the backtick (`).

    {
      "type": "Microsoft.Resources/deploymentScripts",
      "apiVersion": "2020-10-01",
      "name": "createAddCertificate",
      "location": "[resourceGroup().location]",
      "dependsOn": [
        "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]"
      ],
      "identity": {
        "type": "UserAssigned",
        "userAssignedIdentities": {
          "[parameters('identityId')]": {
          }
        }
      },
      "kind": "AzurePowerShell",
      "properties": {
        "forceUpdateTag": "[parameters('utcValue')]",
        "azPowerShellVersion": "3.0",
        "timeout": "PT30M",
        "arguments": "[format(' -vaultName {0} -certificateName {1} -subjectName {2}', parameters('keyVaultName'), parameters('certificateName'), parameters('subjectName'))]", // can pass an argument string, double quotes must be escaped
        "scriptContent": "
          param(
            [string] [Parameter(Mandatory=$true)] $vaultName,
            [string] [Parameter(Mandatory=$true)] $certificateName,
            [string] [Parameter(Mandatory=$true)] $subjectName
          )
    
          $ErrorActionPreference = 'Stop'
          $DeploymentScriptOutputs = @{}
    
          $existingCert = Get-AzKeyVaultCertificate -VaultName $vaultName -Name $certificateName
    
          if ($existingCert -and $existingCert.Certificate.Subject -eq $subjectName) {
    
            Write-Host 'Certificate $certificateName in vault $vaultName is already present.'
    
            $DeploymentScriptOutputs['certThumbprint'] = $existingCert.Thumbprint
            $existingCert | Out-String
          }
          else {
            $policy = New-AzKeyVaultCertificatePolicy -SubjectName $subjectName -IssuerName Self -ValidityInMonths 12 -Verbose
    
            # private key is added as a secret that can be retrieved in the Resource Manager template
            Add-AzKeyVaultCertificate -VaultName $vaultName -Name $certificateName -CertificatePolicy $policy -Verbose
    
            $newCert = Get-AzKeyVaultCertificate -VaultName $vaultName -Name $certificateName
    
            # it takes a few seconds for KeyVault to finish
            $tries = 0
            do {
              Write-Host 'Waiting for certificate creation completion...'
              Start-Sleep -Seconds 10
              $operation = Get-AzKeyVaultCertificateOperation -VaultName $vaultName -Name $certificateName
              $tries++
    
              if ($operation.Status -eq 'failed')
              {
                throw 'Creating certificate $certificateName in vault $vaultName failed with error $($operation.ErrorMessage)'
              }
    
              if ($tries -gt 120)
              {
                throw 'Timed out waiting for creation of certificate $certificateName in vault $vaultName'
              }
            } while ($operation.Status -ne 'completed')
    
            $DeploymentScriptOutputs['certThumbprint'] = $newCert.Thumbprint
            $newCert | Out-String
          }
        ",
        "cleanupPreference": "OnSuccess",
        "retentionInterval": "P1D"
      }
    }
    

    deploymentScripts 资源依赖于密钥保管库资源和角色分配资源。The deploymentScripts resource depends on the key vault resource and the role assignment resource. 它具有以下属性:It has these properties:

    • identity:部署脚本使用用户分配的托管标识在脚本中执行操作。identity: Deployment script uses a user-assigned managed identity to perform the operations in the script.

    • kind:指定脚本类型。kind: Specify the type of script. 目前仅支持 PowerShell 脚本。Currently, only PowerShell scripts are supported.

    • forceUpdateTag:确定是否应执行部署脚本,即使脚本源未更改。forceUpdateTag: Determine whether the deployment script should be executed even if the script source hasn't changed. 可以是当前时间戳或 GUID。Can be current time stamp or a GUID. 若要了解详细信息,请参阅多次运行脚本To learn more, see Run script more than once.

    • azPowerShellVersion:指定要使用的 Azure PowerShell 模块版本。azPowerShellVersion: Specifies the Azure PowerShell module version to be used. 目前,部署脚本支持版本 2.7.0、2.8.0 和 3.0.0。Currently, deployment script supports version 2.7.0, 2.8.0, and 3.0.0.

    • timeout:指定 ISO 8601 格式中规定的脚本执行最大允许时间。timeout: Specify the maximum allowed script execution time specified in the ISO 8601 format. 默认值为 P1DDefault value is P1D.

    • arguments:指定参数值。arguments: Specify the parameter values. 请以空格分隔这些值。The values are separated by spaces.

    • scriptContent:指定脚本内容。scriptContent: Specify the script content. 若要运行外部脚本,请改用 primaryScriptURITo run an external script, use primaryScriptURI instead. 有关详细信息,请参阅使用外部脚本For more information, see Use external script. 仅当在本地计算机上测试脚本时,才需要声明 $DeploymentScriptOutputsDeclaring $DeploymentScriptOutputs is only required when testing the script on a local machine. 通过声明该变量,可在本地计算机和 deploymentScript 资源中运行脚本,而无需进行更改。Declaring the variable allows the script to be run on a local machine and in a deploymentScript resource without having to make changes. 分配给 $DeploymentScriptOutputs 的值可用作部署中的输出。The value assigned to $DeploymentScriptOutputs is available as outputs in the deployments. 有关详细信息,请参阅使用 PowerShell 部署脚本的输出使用 CLI 部署脚本的输出For more information, see Work with outputs from PowerShell deployment scripts or Work with outputs from CLI deployment scripts.

    • cleanupPreference:指定有关何时删除部署脚本资源的首选项。cleanupPreference: Specify the preference on when to delete the deployment script resources. 默认值为 Always,这意味着不管最终状态如何(成功、失败、已取消),都会删除部署脚本资源。The default value is Always, which means the deployment script resources are deleted despite the terminal state (Succeeded, Failed, Canceled). 在本教程中,将使用 OnSuccess,以便你有机会查看脚本执行结果。In this tutorial, OnSuccess is used so that you get a chance to view the script execution results.

    • retentionInterval:指定服务在达到最终状态后保留脚本资源的时间间隔。retentionInterval: Specify the interval for which the service retains the script resources after it reaches a terminal state. 在此持续时间到期时,将删除资源。Resources will be deleted when this duration expires. 持续时间基于 ISO 8601 模式。Duration is based on ISO 8601 pattern. 本教程使用 P1D,这意味着只有一天的时间。This tutorial uses P1D, which means one day. cleanupPreference 设置为 OnExpiration 时将使用此属性。This property is used when cleanupPreference is set to OnExpiration. 当前未启用此属性。This property isn't enabled currently.

    部署脚本采用三个参数:keyVaultNamecertificateNamesubjectNameThe deployment script takes three parameters: keyVaultName, certificateName, and subjectName. 它将创建一个证书,然后将该证书添加到密钥保管库。It creates a certificate, and then adds the certificate to the key vault.

    $DeploymentScriptOutputs 用来存储输出值。$DeploymentScriptOutputs is used to store output value. 若要了解详细信息,请参阅使用 PowerShell 部署脚本的输出使用 CLI 部署脚本的输出To learn more, see Work with outputs from PowerShell deployment scripts or Work with outputs from CLI deployment scripts.

    可在此处找到完成的模板。The completed template can be found here.

  3. 若要查看调试过程,请将以下行添加到部署脚本,从而在代码中放置一个错误:To see the debugging process, place an error in the code by adding the following line to the deployment script:

    Write-Output1 $keyVaultName
    

    正确的命令是 Write-Output 而非 Write-Output1The correct command is Write-Output instead of Write-Output1.

  4. 选择“文件” > “保存”以保存文件。 Select File > Save to save the file.

部署模板Deploy the template

  1. 使用管理员权限打开 Powrershell 控制台,并运行以下 PowerShell 脚本以部署模板。Open Powrershell console with administrator priviledge, and run the following PowerShell script to deploy the template.

    $projectName = Read-Host -Prompt "Enter a project name that is used to generate resource names"
    $location = Read-Host -Prompt "Enter the location (i.e. chinaeast)"
    $upn = Read-Host -Prompt "Enter your email address used to sign in to Azure"
    $identityId = Read-Host -Prompt "Enter the user-assigned managed identity ID"
    
    $adUserId = (Get-AzADUser -UserPrincipalName $upn).Id
    $resourceGroupName = "${projectName}rg"
    $keyVaultName = "${projectName}kv"
    
    New-AzResourceGroup -Name $resourceGroupName -Location $location
    
    New-AzResourceGroupDeployment -ResourceGroupName $resourceGroupName -TemplateFile "$HOME/azuredeploy.json" -identityId $identityId -keyVaultName $keyVaultName -objectId $adUserId
    
    Write-Host "Press [ENTER] to continue ..."
    

    部署脚本服务需要为脚本执行创建其他部署脚本资源。The deployment script service needs to create additional deployment script resources for script execution. 除了实际的脚本执行时间外,准备和清理过程最多可能需要一分钟才能完成。The preparation and the cleanup process can take up to one minute to complete in addition to the actual script execution time.

    部署将因为无效的命令而失败,脚本中使用了 Write-Output1The deployment failed because the invalid command, Write-Output1 is used in the script. 你会收到一个错误,指出:You will get an error saying:

    The term 'Write-Output1' is not recognized as the name of a cmdlet, function, script file, or operable
    program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
    

    部署脚本执行结果存储在部署脚本资源中以用于排除故障。The deployment script execution result is stored in the deployment script resources for the troubleshooting purpose.

调试失败的脚本Debug the failed script

  1. 登录到 Azure 门户Sign in to the Azure portal.

  2. 打开资源组。Open the resource group. 资源组是追加了 rg 的项目名称。It's the project name with rg appended. 你会看到,该资源组中总共有两个其他资源。You will see two additional resources in the resource group. 这些资源称为 部署脚本资源These resources are referred as deployment script resources.

    资源管理器模板部署脚本资源

    这两个文件的后缀都是 azscriptsBoth files have the azscripts suffix. 一个是存储帐户,另一个是容器实例。One is a storage account and the other is a container instance.

    选择“显示隐藏的类型”来列出 deploymentScripts 资源。Select Show hidden types to list the deploymentScripts resource.

  3. 选择带 azscripts 后缀的存储帐户。Select the storage account with the azscripts suffix.

  4. 选择“文件共享”磁贴。Select the File shares tile. 你将看到包含部署脚本执行文件的 azscripts 文件夹。You will see an azscripts folder that contains the deployment script execution files.

  5. 选择“azscripts”。Select azscripts. 你将看到两个文件夹:azscriptinput 和 azscriptoutput 。You will see two folders azscriptinput and azscriptoutput. 输入文件夹包含一个系统 PowerShell 脚本文件和一些用户部署脚本文件。The input folder contains a system PowerShell script file and the user deployment script files. 输出文件夹包含 executionresult.json 和脚本输出文件。The output folder contains a executionresult.json and the script output file. 可以在 executionresult.json 中看到错误消息。You can see the error message in executionresult.json. 输出文件不在那里,因为执行失败。The output file isn't there because the execution failed.

删除 Write-Output1 行并重新部署模板。Remove the Write-Output1 line and redeploy the template.

当第二次部署成功运行时,脚本服务将删除部署脚本资源,因为 cleanupPreference 属性设置为 OnSuccess。When the second deployment runs successfully, the deployment script resources will be removed by the script service, because the cleanupPreference property is set to OnSuccess.

清理资源Clean up resources

不再需要 Azure 资源时,请通过删除资源组来清理部署的资源。When the Azure resources are no longer needed, clean up the resources you deployed by deleting the resource group.

  1. 在 Azure 门户上的左侧菜单中选择“资源组” 。From the Azure portal, select Resource group from the left menu.
  2. 在“按名称筛选”字段中输入资源组名称。Enter the resource group name in the Filter by name field.
  3. 选择资源组名称。Select the resource group name. 应该会看到,该资源组中总共有六个资源。You will see a total of six resources in the resource group.
  4. 在顶部菜单中选择“删除资源组”。Select Delete resource group from the top menu.

后续步骤Next steps

本教程介绍了如何使用 ARM 模板中的部署脚本。In this tutorial, you learned how to use a deployment script in ARM templates. 若要了解如何根据条件部署 Azure 资源,请参阅:To learn how to deploy Azure resources based on conditions, see: