インフラ

【Azure Bicep 入門】キーコンテナーからシークレットを取得する方法

arnold

はじめに

こんにちは、DWSのアーノルドです!

今回のブログでは、Azure Bicepテンプレートを使用してAzure キーコンテナーから機密情報を取得する方法をご紹介したいと思います。

シナリオ

今回の目標は、自動ドメイン参加機能を備えたWindows Server VMを展開するためのBicepテンプレートを作成することです。ドメインへの参加に必要な認証情報は、Azure キーコンテナーに格納されています。したがって、Bicepテンプレートを使用してこれらの認証情報を取得する必要があります。

前提条件

今回ご紹介するシナリオを進めるための前提条件は以下の通りです:

  1. Azure サブスクリプション:
    • この記事でご紹介する手順を進めるためには、有効な Azure サブスクリプションが必要です。まだサブスクリプションをお持ちでない場合は、Azure ポータル でサブスクリプションを作成することができます。
  2. Azure Bicep の基本的な理解:
    • Bicep を使用したインフラストラクチャのデプロイや、テンプレート管理方法について基本的な理解が必要です。
  3. Windows Active Directory ドメインサービスの環境:
    • ドメイン参加の拡張機能をテストするために、Windows Active Directory ドメインサービスのインフラストラクチャを展開し、セットアップしておく必要があります。Active Directory の基本的な構成や設定を行ってください。
  4. 事前に展開された VNet:
    • ドメイン参加のためにActive Directory ドメインコントローラと通信できる環境が必要です。ドメインコントローラーが存在すると同じVNetを利用するか、VNet ピアリング、VNet-to-VNet VPN 接続、S2S VPN 接続などを使用して事前に環境を揃えておく必要があります。公式ドキュメンテーションをご参照ください。
  5. 事前に展開されたキーコンテナー:
    • ドメイン参加のためのドメイン管理者パスワードを保管するために使用する Azure キーコンテナーを事前に展開しておく必要があります。キーコンテナーのデプロイおよびセットアップ手順については公式ドキュメンテーションをご参照ください。このチュートリアルの範囲外です。
  6. キーコンテナーに対するデプロイ権限:
    • VMをデプロイするユーザーは、ドメインパスワードのシークレットを格納するキーコンテナーへのアクセス権を持っていなければなりません。以下、最小権限の原則に従ったアクセス権を与えるためのカスタムロールの例を提示します:
      {
        "Name": "Key Vault CM resource manager template deployment operator",
        "IsCustom": true,
        "Description": "Lets you deploy a resource manager template with the access to the secrets in the Key Vault.",
        "Actions": [
          "Microsoft.KeyVault/vaults/deploy/action"
        ],
        "NotActions": [],
        "DataActions": [],
        "NotDataActions": [],
        "AssignableScopes": [
      "/subscriptions/*キーコンテナーのサブスクリプションID*/resourceGroups/*キーコンテナーのリソースグループ*/providers/Microsoft.KeyVault/vaults/*キーコンテナー名*"
        ]
      }

      上記のカスタムロールを使用すると、ユーザは、実際の秘密をプレーンテキストで表示することできずに、デプロイのためにキーコンテナーに保存された秘密を使用することができます。キーコンテナーは同じサブスクリプションまたは異なるサブスクリプションに存在しても問題ありません。

    ※コード内の キーコンテナーのサブスクリプションIDキーコンテナーのリソースグループキーコンテナー名 を正しい値に置き換えることを忘れずに!

ドメイン参加拡張機能付きVM のテンプレート

このセクションでは、既存のサブネット内にドメイン参加拡張機能を含むVMをデプロイするための簡単なテンプレートを提示します。以下のGitHubリポジトリーをご参照ください:
https://github.com/arnold-nowik/bicep-demo-templates/blob/main/vm-domainjoin-keyvault/vm-main.bicep

テンプレートの構造は以下の通りです:

パラメータ定義

param vnetResourceGroup string
仮想ネットワークが所属するリソース グループの名前

param existingVnetName string
既存の仮想ネットワークの名前

param existingSubnetName string
// 既存のサブネットの名前

param vmName string
仮想マシンの名前

param vmSize string
仮想マシンのサイズ

param windowsVersion string
Windows バージョン(Server 2019 または Server 2022)

param vmUserName string
仮想マシンの管理者ユーザー名

param vmPass string
仮想マシンの管理者パスワード(セキュアなパラメータ)

param storageAccountType string
ストレージ アカウントの種類

param domainToJoin string
参加するドメインのFQDN

param ouPath string
参加する組織単位のパス

param domainUsername string
ドメイン管理者のユーザー名

param domainPassword string
ドメイン管理者のパスワード

変数

var subnetId
サブネットの ID

var image
Windows バージョンごとの OS イメージ情報

リソース定義

resource vm 'Microsoft.Compute/virtualMachines@2022-11-01'
仮想マシンのリソース定義

resource nic 'Microsoft.Network/networkInterfaces@2022-07-01'
ネットワーク インターフェースのリソース定義

resource joinDomainExtension 'Microsoft.Compute/virtualMachines/extensions@2023-03-01'
ドメイン参加拡張のリソース定義

キーコンテナーからdomainPasswordを取得する方法

このセクションでは、キーコンテナーからdomainPasswordのシークレットを取得し、それをドメイン参加拡張機能JsonADDomainExtension に正しく渡す方法を説明します。

パラメータファイル使用

まずは、ドメイン参加拡張機能は、パスワードをセキュアなパラメータとして渡されることを前提としています。セキュアなパラメータを解析できないと、デプロイ時にエラーが発生します。キーコンテナーからシークレットを取得するアプローチの一つは、domainPasswordというパラメータをテンプレートに定義したあと、パラメータファイルを使い、キーコンテナーを参照することです。

  • Bicep 内のパラメータ定義:
    @secure()
    param domainPassword string
  • パラメータファイル
    {
      "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
      "contentVersion": "1.0.0.0",
      "parameters": {
        "domainPassword": {
          "reference": {
            "keyVault": {
    
              "id": "/subscriptions/*サブスクリプションID*/resourceGroups/*キーコンテナーのリソースグループ名*/providers/Microsoft.KeyVault/vaults/*キーコンテナーの名前*"
            },
            "secretName": "domainPassword"
          }
        }
      }
    }

    パラメータファイルの構成は以下通りです:
    $schema:
    デプロイメント パラメータ ファイルの JSON スキーマ バージョンを示します。

    contentVersion:
    デプロイメント パラメータ ファイルの内容のバージョンを示します。通常は "1.0.0.0" などが使用されます。

    parameters:
    テンプレート内で使用する各パラメータに関する情報が含まれます。

    domainPassword パラメータ:
    domainPassword という名前のパラメータに関する情報が記述されています。

    reference オブジェクト:
    domainPassword パラメータの値をキー コンテナのシークレットに参照するための情報を含んでいます。

    keyVault オブジェクト:
    キー コンテナに関する情報を示します。id フィールドは、キー コンテナのリソースの識別子を示しています。

    id フィールド:
    キー コンテナのリソースの完全な ID が指定されています。サブスクリプション ID、リソース グループ名、キー コンテナ名などが含まれます。

    secretName フィールド:
    キー コンテナ内の特定のシークレットの名前が指定されています。このシークレットが domainPassword パラメータに提供される値です。

このパラメータ ファイルは、特定のキー コンテナ内のシークレットを参照して domainPassword パラメータの値を設定するためのものです。デプロイメント時に、実際のサブスクリプション ID、リソース グループ名、キー コンテナ名、シークレット名などに適切な値を指定する必要があります。

デプロイしてみる

Bicep のテンプレートをパラメータファイルを使ってデプロイするには、複数の方法があります。以下、Azure PowerShellを使用する方法の例を示します。

New-AzResourceGroupDeployment -ResourceGroupName 'rgVm001' -TemplateFile vm-module.bicep -TemplateParameterFile vm.params.json -TemplateParameterObject @{
    vnetResourceGroup = 'rgVnetJe01'
    existingVnetName = 'vnetJe01'
    existingSubnetName = 'Subnet01'
    vmName = 'vm001'
    windowsVersion = 'windowsserver2019'
    vmSize = 'Standard_B2ms'
    vmUserName = 'azureuser'
    vmPass = 'MyTestP@ssword'
    storageType = 'StandardSSD_LRS'
    domainToJoin = 'example.com'
    ouPath = 'OU=Servers,DC=example,DC=com'
    domainUsername = 'admin'
}

上記に相当するAzure CLI のコマンドは下記通りとなります:

az deployment group create \
    --resource-group 'rgVm001' \
    --template-file vm-module.bicep \
    --parameters @vm.params.json \
    --parameters vnetResourceGroup='rgVnetJe01' \
    existingVnetName='vnetJe01' \
    existingSubnetName='Subnet01' \
    vmName='vm001' \
    windowsVersion='windowsserver2019' \
    vmSize='Standard_B2ms' \
    vmUserName='azureuser' \
    vmPass='MyTestP@ssword' \
    storageType='StandardSSD_LRS' \
    domainToJoin='example.com' \
    ouPath='OU=Servers,DC=example,DC=com' \
    domainUsername='admin'

パラメータファイルを使用しない方法

ところが、テンプレートとは別のパラメータファイルを使いたくない場合はどうすればいいの でしょうか?たとえば、パラメータファイルが利用できないTemplate Specsを使用して、このテンプレートをセルフサービスのデプロイメントリソースとして提供したい場合はどうすればよいのでしょうか?同じようなキーコンテナー参照を、パラメータファイルに頼らずに直接テンプレートに組み込んでみましょう。

まずは、JsonADDomainExtensionのprotectedSettingsの中のPasswordプロパティをいじり、先ほどのパラメータファイルと似ているようなキーコンテナーへの参照を作成してみましょう。

resource joinDomainExtension 'Microsoft.Compute/virtualMachines/extensions@2023-03-01' = {
  parent: vm
  name: 'JoinDomain'
  location: resourceGroup().location
  properties: {
    publisher: 'Microsoft.Compute'
    type: 'JsonADDomainExtension'
    typeHandlerVersion: '1.3'
    autoUpgradeMinorVersion: true
    settings: {
      name: domainToJoin
      user: '${domainToJoin}\\${domainUsername}'
      restart: true
      options: '3'
    }
    protectedSettings: {
      password: {
        reference: {
          keyVault: {
            id: keyVaultId
          }
          secretName: 'domainPassword'
        }
      }
    }
  }
}

残念ながら、この書き方だとデプロイ前の検証が無事に済むものの、ドメイン参加時に以下のようなエラーが発生してしまいます。

Screenshot 2023-08-14 at 15.07.14.png (78.8 kB)

RDPでマシンにログインし、ログを見てみると、Json解析のエラーが発生し、パスワードが正しく取得されなかったことがわかりますね。

Screenshot 2023-08-14 at 16.07.00.png (5.1 MB)

次に、protectedSettingsFromKeyVault というプロパティを使用してみましょう

resource joinDomainExtension 'Microsoft.Compute/virtualMachines/extensions@2023-03-01' = {
  parent: vm
  name: 'JoinDomain'
  location: resourceGroup().location
  properties: {
    publisher: 'Microsoft.Compute'
    type: 'JsonADDomainExtension'
    typeHandlerVersion: '1.3'
    autoUpgradeMinorVersion: true
    settings: {
      name: domainToJoin
      ouPath: ouPath
      user: '${domainToJoin}\\${domainUsername}'
      restart: true
      options: '3'
    }
    protectedSettings: {
      Password: {
        protectedSettingsFromKeyVault: {
          secretUrl: 'https://mydomainpasswordvault.vault.azure.net/secrets/domainPassword/xxxxxxxxxxxxxxxxxxxxxx'
          sourceVault: {
            id: keyVaultId
          } 
        }
      }
    }
  }
}

この方法も失敗してしまいますね。エラーは先ほどと同じ内容なので、スクショは割愛します。
エラーをよくみると、protectedSettingsの中のPasswordのプロパティはオブジェクトではなく、文字列を期待していることが明確になります。

では、どうすれば良いの?

Azure Bicepには、キーコンテナーの秘密の受け渡しを簡単にするgetSecretという組み込み関数があります。しかし、この関数をPasswordプロパティに直接入れ込もうとすると、入力が赤色にアンダーラインされ、バリデーションエラーが発生します:
「Function "getSecret" is not valid at this location. It can only be used when directly assigning to a module parameter with a secure decorator.」関数 getSecretはこの場所では有効ではありません。セキュアデコレータでモジュールのパラメータに直接代入する場合にのみ使用できる、ということですね。

Screenshot 2023-08-14 at 16.38.17 copy.jpg (467.6 kB)

上記のエラーにあるように、VMテンプレートはモジュール化し、そのモジュールを参照する別のテンプレートを作成する必要があります。そのテンプレートの中で、既存のキーコンテナーのリソースを定義します。キーコンテナーのサブスクリプションID、リソースグループ、キーコンテナー名を変数(もしくはパラメータ)として渡します。

これらのリソースを定義した後、キーコンテナー リソースの getSecret 関数をVMモジュールの domainPassword パラメータの値として使用することができるようになります。

Screenshot 2023-08-14 at 17.00.27 copy.jpg (267.1 kB)

そうすることによって、無事にVMをデプロイし、ドメインに参加させることができます。

私のGitHubリポジトリには、キー コンテナを正しく実装したサンプル テンプレートとモジュールを用意していますので、ぜひ参照してみてください。
https://github.com/arnold-nowik/bicep-demo-templates/tree/main/vm-domainjoin-keyvault
デプロイ時にサンプルのキーコンテナー名、サブスクリプションID、リソースグループ名、シークレット名などの値を更新することを忘れないように!

トラブルシュート

もし上記の手順に沿ってデプロイしようとしてもキーコンテナーからのシークレット取得に失敗した場合は、以下のスクリーンショットのように、キーコンテナーの「アクセス構成」メニューの「リソース アクセス」セクションにて「Azure Resource Manager (テンプレートの展開用)」というオプションが有効になっていることを確認してみてください。

Screenshot 2023-08-14 at 17.18.57.png (333.2 kB)

AUTHOR
arnold
arnold
記事URLをコピーしました