「Azure Key Vault」でSSLサーバ証明書を管理する場合、App ServiceによるWebサイトやWeb APIをデプロイする時に、Key Vaultから証明書を自動でインポートしたいところだ。そのためのARM(Azure Resource Manager)テンプレート(Bicep)を注意点とともに紹介する。
対象: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アドレスごとには割り当てないものとしている。
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
詳しくはTech TIPS「Key Vault(キーコンテナ)にSSLサーバ証明書をインポートして利用する(App Service編)」を参照していただきたい。
App Serviceのデプロイ時にKey Vault上の証明書などを参照するには、Key Vaultの設定を変更する必要がある。それには、以下のように「az keyvault update」コマンドを実行する。
az keyvault update -n <Key Vault名> -g <Key Vaultリソースグループ名> --enabled-for-template-deployment true
正しく実行できると、対象のKey Vaultの設定一覧が(デフォルトではJSON形式で)表示されるので、「properties.enabledForTemplateDeployment」キーの値が「true」になっていることを確認する。
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のリソースグループ名>
<カスタムドメインの検証ID>は16進数の文字列で、デプロイ先のAzureサブスクリプションごとに決まっている。これも既存のApp Serviceから、以下の「az webapp show」コマンドで取得できる(App Serviceプランはどれでもよい)。
az webapp show -n <既存App Service名> -g <既存App Serviceのリソースグループ名> --query "customDomainVerificationId"
以下にこれらのレコードの記述例を記す。「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"
項目 | 内容 |
---|---|
カスタムドメイン | 「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レコードの例で指定している各項目の値 |
以下に、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))
配列「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)
}
最後は、新たなリソースを生成している部分である。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
}
}]
注意が必要なのは、App Serviceやカスタムドメインの「properties」でもSSLサーバ証明書に関する指定が必要な点だ。これらの間では、矛盾のない共通の値を設定する必要がある。
証明書リソースのコレクション「webCert」からThumbprintを抽出する部分が複雑なのは、リソースのコレクションを使ってループさせたり、filterやmapといったラムダ関数を併用して別の配列を生成したりできないためだ。インデックス値を指定すればコレクション内のリソースを特定できるので、配列「certIndexes」に証明書名一覧のインデックス値を格納し、それを使って証明書リソースを特定している。
■関連リンク
Copyright© Digital Advantage Corp. All Rights Reserved.