教學課程:使用部署指令碼建立自我簽署憑證

了解如何使用 Azure Resource Manager 範本 (ARM 範本) 中的部署指令碼。 部署指令碼可用來執行無法由 ARM 範本完成的自訂步驟。 例如,建立自我簽署憑證。 在本教學課程中,您將建立用來部署 Azure 金鑰保存庫的範本,然後在相同的範本中使用 Microsoft.Resources/deploymentScripts 資源建立憑證,再將憑證新增至金鑰保存庫。 若要深入了解部署指令碼,請參閱使用 ARM 範本中的部署指令碼

重要

系統會在相同的資源群組中建立兩個部署指令碼資源 (儲存體帳戶和容器執行個體),用以執行指令碼和疑難排解。 當指令碼執行處於終止狀態時,指令碼服務通常會刪除這些資源。 在資源刪除之前,您需支付資源費用。 若要深入了解,請參閱清除部署指令碼資源

本教學課程涵蓋下列工作:

  • 開啟快速入門範本
  • 編輯範本
  • 部署範本
  • 對失敗的指令碼進行偵錯
  • 清除資源

如需涵蓋部署指令碼的 Learn 模組,請參閱使用部署指令碼擴充 ARM 範本

必要條件

若要完成本文,您需要:

  • Visual Studio Cod 搭配 Resource Manager Tools 擴充功能。 請參閱快速入門:使用 Visual Studio Code 建立 ARM 範本

  • 使用者指派的受控識別。 此身分識別可用來在指令碼中執行 Azure 的特定動作。 若要建立身分識別,請參閱使用者指派的受控識別。 您在部署範本時將需要身分識別的識別碼。 此身分識別的格式為:

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

    使用下列 CLI 指令碼,藉由提供資源組名和識別名稱來取得識別碼。

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

開啟快速入門範本

您可以從 Azure 快速入門範本開啟範本,而無須從頭建立範本。 Azure 快速入門範本是 ARM 範本的存放庫。

本快速入門中使用的範本名為建立 Azure Key Vault 和秘密。 此範本會建立金鑰保存庫,然後將秘密新增至金鑰保存庫。

  1. 在 Visual Studio Code 中,選取 [檔案]>[開啟檔案]

  2. 在 [檔案名稱] 中,貼上下列 URL:

    https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/quickstarts/microsoft.keyvault/key-vault-create/azuredeploy.json
    
  3. 選取 [開啟] 以開啟檔案。

  4. 選取 [檔案]>[另存新檔],在您的本機電腦上將檔案另存為 azuredeploy.json

編輯範本

對範本進行下列變更:

清除範本 (選擇性)

原始範本會將秘密新增至金鑰保存庫。 若要簡化教學課程,請移除下列資源:

  • Microsoft.KeyVault/vaults/secrets

移除以下兩個參數定義:

  • secretName
  • secretValue

如果您選擇不移除這些定義,則必須在部署期間指定參數值。

設定金鑰保存庫存取原則

部署指令碼會將憑證新增至金鑰保存庫。 請設定金鑰保存庫存取原則,為受控識別授與權限:

  1. 新增參數以取得受控識別的識別碼:

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

    注意

    Visual Studio Code 的 Resource Manager 範本擴充功能尚無法格式化部署指令碼。 請勿使用 Shift+Alt+F 來格式化 deploymentScripts 資源,如下所示。

  2. 新增參數以設定金鑰保存庫存取原則,讓受控識別可以將憑證新增至金鑰保存庫:

    "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. 將現有的金鑰保存庫存取原則更新為:

    "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')]"
        }
      }
    ],
    

    系統定義了兩個原則,一個用於登入的使用者,另一個用於受控識別。 已登入的使用者只需要清單權限來驗證部署。 為了簡化本教學課程,我們會為受控識別和已登入的使用者指派相同的憑證。

新增部署指令碼

  1. 新增部署指令碼所使用的三個參數:

    "certificateName": {
      "type": "string",
      "defaultValue": "DeploymentScripts2019"
    },
    "subjectName": {
      "type": "string",
      "defaultValue": "CN=contoso.com"
    },
    "utcValue": {
      "type": "string",
      "defaultValue": "[utcNow()]"
    }
    
  2. 新增 deploymentScripts 資源:

    注意

    因為內嵌部署指令碼會以雙引號括住,所以部署指令碼內的字串必須以單引號括住。 PowerShell 逸出字元 是倒引號 (`)。

    {
      "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
    
            # 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')
    
            $newCert = Get-AzKeyVaultCertificate -VaultName $vaultName -Name $certificateName
            $DeploymentScriptOutputs['certThumbprint'] = $newCert.Thumbprint
            $newCert | Out-String
          }
        ",
        "cleanupPreference": "OnSuccess",
        "retentionInterval": "P1D"
      }
    }
    

    deploymentScripts 資源取決於金鑰保存庫資源和角色指派資源。 其屬性包括:

    • identity:部署指令碼會使用使用者指派的受控識別,來執行指令碼中的作業。
    • kind:指定指令碼的類型。 目前僅支援 PowerShell 指令碼。
    • forceUpdateTag:判斷即使在指令碼來源並未變更的情況下是否仍要執行部署指令碼。 可以是目前的時間戳記或 GUID。 若要深入了解,請參閱執行指令碼多次
    • azPowerShellVersion:指定要使用的 Azure PowerShell 模組版本。 部署指令碼目前支援 2.7.0、2.8.0 和3.0.0 版。
    • timeout:以 ISO 8601 格式指定指令碼執行時間的允許上限。 預設值為 P1D
    • arguments:指定參數值。 多個值應以空格分隔。
    • scriptContent:指定指令碼內容。 若要執行外部指令碼,請改用 primaryScriptURI。 如需詳細資訊,請參閱使用外部指令碼。 只有在本機電腦上測試指令碼時,才需要宣告 $DeploymentScriptOutputs。 宣告此變數可讓指令碼直接在本機電腦和 deploymentScript 資源中執行,而無須進行變更。 指派給 $DeploymentScriptOutputs 的值可作為部署中的輸出。 如需詳細資訊,請參閱使用 PowerShell 部署腳本的輸出使用 CLI 部署腳本的輸出
    • cleanupPreference:指定何時要刪除部署指令碼資源的喜好設定。 預設值為一律,這表示即使處於終止狀態 (成功、失敗、已取消),仍會刪除部署指令碼資源。 本教學課程中會使用 OnSuccess,以讓您有機會檢視指令碼執行結果。
    • retentionInterval:指定服務在指令碼資源到達結束狀態後保留該資源的間隔。 此持續時間到期後,即會刪除資源。 持續時間以 ISO 8601 模式為基礎。 本教學課程使用 P1D,這表示一天。 當 cleanupPreference 設為 OnExpiration 時,就會使用此屬性。 目前並未啟用此屬性。

    部署指令碼接受三個參數: keyVaultNamecertificateNamesubjectName。 指令碼會建立憑證,然後將憑證新增至金鑰保存庫。

    $DeploymentScriptOutputs 用來儲存輸出值。 如需深入了解,請參閱使用 PowerShell 部署腳本的輸出使用 CLI 部署腳本的輸出

    您可以在這裡找到完成的範本。

  3. 若要查看偵錯程序,請將以下這一行新增至部署指令碼,以程式碼中放入錯誤:

    Write-Output1 $keyVaultName
    

    正確的命令是 Write-Output ,而不是 Write-Output1

  4. 選取 [檔案]>[儲存],以儲存檔案。

部署範本

  1. 登入 Azure Cloud Shell

  2. 藉由選取左上角的 PowerShellBash (適用於 CLI) 來選擇您慣用的環境。 切換時必須重新啟動殼層。

    Azure portal Cloud Shell upload file

  3. 選取 [上傳/下載檔案],然後選取 [上傳]。 請參閱上一個螢幕擷取畫面。 選取您在前一節中儲存的檔案。 上傳檔案之後,您可以使用 ls 命令和 cat 命令來確認檔案是否已成功上傳。

  4. 然後執行下列 Azure CLI 或 Azure PowerShell 指令碼來部署範本。

    echo "Enter a project name that is used to generate resource names:" &&
    read projectName &&
    echo "Enter the location (i.e. centralus):" &&
    read location &&
    echo "Enter your email address used to sign in to Azure:" &&
    read upn &&
    echo "Enter the user-assigned managed identity ID:" &&
    read identityId &&
    adUserId=$((az ad user show --id ${upn}) | jq -r '.id') &&
    resourceGroupName="${projectName}rg" &&
    keyVaultName="${projectName}kv" &&
    az group create --name $resourceGroupName --location $location &&
    az deployment group create --resource-group $resourceGroupName --template-file "$HOME/azuredeploy.json" --parameters identityId=$identityId keyVaultName=$keyVaultName objectId=$adUserId
    

    部署指令碼服務必須建立其他部署指令碼資源,以執行指令碼。 除了實際的指令碼執行時間以外,準備和清除程序最多可能需要一分鐘才能完成。

    部署失敗,因為在指令碼中使用的命令 Write-Output1 無效。 您應該會收到錯誤,指出:

    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.
    

    部署指令碼的執行結果會儲存在部署指令碼資源中,以供疑難排解之用。

對失敗的指令碼進行偵錯

  1. 登入 Azure 入口網站

  2. 開啟資源群組。 這是附加 rg 的專案名稱。 您應該會在資源群組中看到兩個額外的資源。 這些資源稱為部署指令碼資源

    Resource Manager template deployment script resources

    這兩個檔案都具有 azscripts 尾碼。 其中一個是儲存體帳戶,另一個則是容器執行個體。

    請選取 [顯示隱藏的類型],以列出 deploymentScripts 資源。

  3. 選取具有 azscripts 尾碼的儲存體帳戶。

  4. 選取 [檔案共用] 磚。 您會看到包含部署指令碼執行檔案的 azscripts 資料夾。

  5. 選取 [azscripts]。 您應該會看到兩個資料夾:azscriptinputazscriptoutput。 輸入資料夾包含系統 PowerShell 指令檔和使用者部署指令檔。 輸出檔案夾包含 executionresult.json 和指令碼輸出檔案。 您可以在 executionresult.json 中查看錯誤訊息。 輸出檔案不存在,因為執行失敗。

移除 Write-Output1 行,並重新部署範本。

當第二個部署執行成功時,指令碼服務應該會移除部署指令碼資源,因為 cleanupPreference 屬性設定為 OnSuccess

清除資源

不再需要 Azure 資源時,可藉由刪除資源群組來清除您所部署的資源。

  1. 在 Azure 入口網站中,選取左側功能表中的 [資源群組]
  2. 在 [依名稱篩選] 欄位中輸入資源群組名稱。
  3. 選取資源群組名稱。
  4. 從頂端功能表中選取 [刪除資源群組]

下一步

在本教學課程中,您已了解如何使用 ARM 範本中的部署指令碼。 若要深入了解如何根據條件部署 Azure 資源,請參閱: