【Azure】デプロイ時にApp ServiceのSSLサーバ証明書をKey Vaultから自動インポートするTech TIPS

「Azure Key Vault」でSSLサーバ証明書を管理する場合、App ServiceによるWebサイトやWeb APIをデプロイする時に、Key Vaultから証明書を自動でインポートしたいところだ。そのためのARM(Azure Resource Manager)テンプレート(Bicep)を注意点とともに紹介する。

» 2023年11月29日 05時00分 公開
[島田広道デジタルアドバンテージ]
「Tech TIPS」のインデックス

連載目次

App Serviceデプロイ時にKey Vaultから自動的にSSL証明書をインポート

対象:Azure App Service、Azure Key Vault(キーコンテナ)、Bicep、Azure CLI


 Azureの「App Service」で使うSSLサーバ証明書は、「Key Vault」に格納しておくと利用しやすくなるのは、Tech TIPS「Key Vault(キーコンテナ)にSSLサーバ証明書をインポートして利用する(App Service編)」で説明した通りだ。

 ただ、上述のTech TIPSは既存のApp Serviceが対象である。これからデプロイするApp Serviceに対しては別の手段が必要だ。

 そこで本Tech TIPSでは、App Serviceのデプロイ時にSSLサーバ証明書をKey Vaultから自動的にインポートする方法を紹介する。デプロイには、Bicepで記述したARM(Azure Resource Manager)テンプレートを用いる。

 前提条件として、まずApp ServiceプランとKey Vaultはデプロイ済みとする。対象のSSLサーバ証明書もKey vaultにインポート済みとする(インポートする方法は前出のTech TIPS参照)。

 また、説明をシンプルにするため、SSLサーバ証明書はApp Serviceに対してSNI(Server Name Indication)で割り当て、IPアドレスごとには割り当てないものとしている。

■執筆時の各種ツール/APIのバージョン

  • Azure CLI: Ver. 2.54.0
  • Bicep CLI: Ver. 0.23.1
  • Bicepでのデプロイ時のAPIバージョン: 2022-09-01(App Service)

【準備】Key VaultからApp Serviceへの証明書インポートを許可する

 Key VaultからApp Serviceへ証明書をインポートするには、App ServiceにKey Vaultへのアクセス許可を明示的に与える必要がある。それには、以下のように「az keyvault set-policy」コマンドを実行する。

az keyvault set-policy -n <Key Vault名> -g <Key Vaultリソースグループ名> --secret-permissions get --certificate-permissions get --spn abfa0a7c-a6b6-4736-8310-5855508787cd

【Azure CLI】Azure App ServiceにKey Vault内の証明書の「取得」を許可する
※Microsoftのレファレンス: az keyvault set-policy

 詳しくはTech TIPS「Key Vault(キーコンテナ)にSSLサーバ証明書をインポートして利用する(App Service編)」を参照していただきたい。

【準備】テンプレートからのデプロイ時にKey Vaultを参照可能にする

 App Serviceのデプロイ時にKey Vault上の証明書などを参照するには、Key Vaultの設定を変更する必要がある。それには、以下のように「az keyvault update」コマンドを実行する。

az keyvault update -n <Key Vault名> -g <Key Vaultリソースグループ名> --enabled-for-template-deployment true

【Azure CLI】テンプレートからのデプロイ時にKey Vaultを参照可能にする
※Microsoftのレファレンス: az keyvault update

 正しく実行できると、対象のKey Vaultの設定一覧が(デフォルトではJSON形式で)表示されるので、「properties.enabledForTemplateDeployment」キーの値が「true」になっていることを確認する。

【準備】ドメインを検証するためのDNSレコードを作成する

 App ServiceにSSLサーバ証明書を割り当てるということは、独自のホスト名(カスタムドメイン)を割り当てるということだ。それには、そのカスタムドメインを所有していることをAzure(Microsoft)に証明するために、検証用の「DNSレコード」をあらかじめ作成しなければならない。

 このDNSレコードは、対象のカスタムドメインのDNSゾーンに作成する必要がある。DNSレコードの作成(追加)手順はDNSサーバ/サービスによって異なるので、それらのマニュアルやヘルプを参照していただきたい。また、そのゾーンの内容を変更する権限を持っていない場合は、そのゾーンの管理者に問い合わせてほしい。

 さて、カスタムドメインに「www.example.jp」というようにサブドメインが付いている場合は、以下のようなDNSレコードを作成する。

<カスタムドメイン名>. <TTL(Time To Live)> IN CNAME <App Service名>.azurewebsites.net.
asuid.<カスタムドメイン名>. <TTL> IN TXT "<カスタムドメインの検証ID>"



 一方、「example.jp」「example.co.jp」のようにサブドメインが付いていない、いわゆる「ルートドメイン」「ネイキッドドメイン」である場合は、以下のようなDNSレコードを作成する。

<カスタムドメイン名>. <TTL> IN A <App Serviceの受信IPアドレス>
asuid.<カスタムドメイン名>. <TTL> IN TXT "<カスタムドメインの検証ID>"



 これらのうち、CNAME/Aレコードは、カスタムドメインを参照するとApp Serviceへアクセスされるようにする、名前解決のためのレコードだ。一方、TXTレコードはドメイン検証用のレコードである(詳細はMicrosoft Learnの「既存のカスタム DNS 名を Azure App Service にマップする」を参照していただきたい)。

 <App Serviceの受信IPアドレス>は通常、デプロイ先のApp Serviceプランごとに決まっている。そのため、そのプラン上に既存のApp Serviceがあるなら、以下の「az webapp config hostname get-external-ip」コマンドで取得できる。

az webapp config hostname get-external-ip --webapp-name <既存App Service名> -g <既存App Serviceのリソースグループ名>

【Azure CLI】既存App Serviceから受信IPアドレスを取得する
※Microsoftのレファレンス: az webapp config hostname get-external-ip

 <カスタムドメインの検証ID>は16進数の文字列で、デプロイ先のAzureサブスクリプションごとに決まっている。これも既存のApp Serviceから、以下の「az webapp show」コマンドで取得できる(App Serviceプランはどれでもよい)。

az webapp show -n <既存App Service名> -g <既存App Serviceのリソースグループ名> --query "customDomainVerificationId"

【Azure CLI】既存App Serviceからカスタムドメインの検証IDを取得する
※Microsoftのレファレンス: az webapp show

 以下にこれらのレコードの記述例を記す。「www」付きドメインとネイキッドドメインの双方に設定している。

www.example.jp. 60 IN CNAME app-examplejp-prod-je-01.azurewebsites.net.
asuid.www.example.jp. 60 IN TXT "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
example.jp. 60 IN A 198.51.100.2
asuid.example.jp. 60 IN TXT "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"

カスタムドメインとSSLサーバ証明書をApp Serviceに割り当てるためのDNSレコードの例

項目 内容
カスタムドメイン www.example.jp」「example.jp」の2つ
TTL(Time To Live) 60秒=1分(正しく検証できたら延ばすという前提)
App Service名 app-examplejp-prod-je-01.azurewebsites.net
受信IPアドレス 198.51.100.2
検証ID 0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
上記のDNSレコードの例で指定している各項目の値

Key VaultからSSL証明書をインポートしつつApp Serviceをデプロイする

 以下に、SSLサーバ証明書をKey VaultからインポートしながらApp ServiceをデプロイするためのARMテンプレートの例を紹介していく(長いので3つに分割している)。

 まず、カスタムドメインとSSLサーバ証明書の名前などを定義している部分を記す。

// カスタムドメイン一覧
param customDomains array = [
  {
    hostName: 'www.example.jp' // 「www」付きドメイン
    certName: 'cert-www-example-jp' // 割り当てるSSLサーバ証明書の名前
  }
  {
    hostName: 'example.jp' // ネイキッドドメイン
    certName: 'cert-www-example-jp' // 「www」用の証明書でネイキッドドメインにも対応
  }
  {
    hostName: 'manage.example.jp' // もう1つのサブドメイン付き
    certName: 'cert-manage-example-jp' // 別の証明書
  }
]

// Key VaultからインポートするSSLサーバ証明書の名前一覧
var dupCertNames = map(customDomains, cd => cd.certName) // 重複あり
var certNames = union(dupCertNames, dupCertNames) // 重複排除

// 証明書名を導出するためのインデックス一覧。
// 生成した証明書リソースのコレクションからThumbprint(母印)を抽出するのに利用
var certIndexes = map(customDomains, cd => indexOf(certNames, cd.certName))

【Bicep】App ServiceのARMテンプレート――カスタムドメインとSSLサーバ証明書の定義

 配列「customDomains」内の「hostName」キーには各カスタムドメインのFQDNを、「certName」キーにはKey Vault内での証明書の名前をそれぞれ指定する。

 配列「certIndexes」は、後述する証明書リソースのコレクション「webCert」からThumbprint(母印)を抽出するのに利用している(詳しくは後述)。

 次は、既存リソースのKey VaultとApp Serviceプランを定義している部分だ。

// 既存リソース: Key Vault
param keyVaultRG string
param keyVaultName string

resource keyVault 'Microsoft.KeyVault/vaults@2022-11-01' existing = {
  name: keyVaultName
  scope: resourceGroup(keyVaultRG)
}

// 既存リソース: App Serviceプラン
param appServicePlanRG string
param appServicePlanName string

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' existing = {
  name: appServicePlanName
  scope: resourceGroup(appServicePlanRG)
}

【Bicep】App ServiceのARMテンプレート――既存リソースの定義
※Microsoftのレファレンス: Microsoft.KeyVault/vaultsMicrosoft.Web/serverfarms

 最後は、新たなリソースを生成している部分である。App Service(サイト本体)の他、Key VaultからインポートされるSSLサーバ証明書と、カスタムドメインのサイトへの割り当てについても、リソース生成が必要だ。

param location string = resourceGroup().location
param siteName string // App Serviceの名前

// リソース生成: SSLサーバ証明書(Key VaultからApp Serviceプランへのインポート)
@batchSize(1) // certNamesと同じ順番で生成
resource webCert 'Microsoft.Web/certificates@2022-09-01' = [for certName in certNames: {
  name: '${certName}-${siteName}' // 元の証明書とApp Serviceがすぐ分かるように名付け
  location: location
  properties: {
    keyVaultId: keyVault.id // インポート元のKey Vault
    keyVaultSecretName: certName // インポート元での証明書名
    serverFarmId: appServicePlan.id // インポート先のApp Service Plan
  }
}]

// リソース生成: App Serviceのサイト本体
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
  name: siteName // App Service名
  location: location
  properties: {
    hostNameSslStates: [for (cd, i) in customDomains: { // 全カスタムドメイン
      name: cd.hostName // カスタムドメイン名
      thumbprint: webCert[certIndexes[i]].properties.thumbprint
      // ↑証明書のThumbprint(母印)
      // 生成したwebCertリソースのコレクションはcertNames配列と並びが一致する。
      // そのためCertNamesのインデックスの配列certIndexesを指定すれば、名前が一致する証明書を導出できる
      // なお、webCertのようなリソースのコレクションをラムダ関数やループ配列などに直接指定することはできない
      sslState: 'SniEnabled' // SNI(Server Name Indication)
      hostType: 'Standard'
    }]
    // ……<省略>……
  }
}

// リソース生成: カスタムドメイン
@batchSize(1) // エラーが生じるので、同時ではなく1つずつ順番に生成
resource webAppHostname 'Microsoft.Web/sites/hostNameBindings@2022-09-01' = [for (cd, i) in customDomains: {
  name: cd.hostName // カスタムドメイン名
  parent: webApp // App Serviceの子リソースという位置付け
  properties: {
    sslState: 'SniEnabled'
    thumbprint: webCert[certIndexes[i]].properties.thumbprint
  }
}]

【Bicep】App ServiceのARMテンプレート――新規リソースの生成
※Microsoftのレファレンス: Microsoft.Web/sitesMicrosoft.Web/certificatesMicrosoft.Web/sites/hostNameBindings

 注意が必要なのは、App Serviceやカスタムドメインの「properties」でもSSLサーバ証明書に関する指定が必要な点だ。これらの間では、矛盾のない共通の値を設定する必要がある。

 証明書リソースのコレクション「webCert」からThumbprintを抽出する部分が複雑なのは、リソースのコレクションを使ってループさせたり、filterやmapといったラムダ関数を併用して別の配列を生成したりできないためだ。インデックス値を指定すればコレクション内のリソースを特定できるので、配列「certIndexes」に証明書名一覧のインデックス値を格納し、それを使って証明書リソースを特定している。

「Tech TIPS」のインデックス

Tech TIPS

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。