しばやん雑記

Azure Serverless とメイドさんが大好きなフリーランスのプログラマーのブログ

Managed Identity への Microsoft Graph アクセス権の付与を Terraform Azure AD Provider で行う

前回は Terraform Provider for Azure AD を使って Azure AD アプリケーションの登録を行って、App Service Authentication への設定まで全て自動化してみましたが、今回は App Service で有効化された Managed Identity に対して特定の API アクセス権を追加してみます。

Managed Identity は実体が Service Principal なので、Terraform Provider for Azure AD でも Object ID ベースで扱えるはずです。シナリオとしては公式ドキュメントにもあるように Graph API のアクセス権を付与します。

ユーザーとして Graph API を呼び出すのではなく、アプリケーションとして呼び出すのがミソです。Service Principal を使えば比較的簡単にアクセス権を付与出来ますが、Managed Identity の場合は方法が異なります。

以前に Managed Identity を使って Graph API を呼び出すエントリは書いたことがありますが、この時は Azure CLI や PowerShell を使ってアクセス権を付与する必要があり、ぶっちゃけかなり面倒でした。

アクセス権の付与は ARM Template や Bicep では表現できないですが、Terraform では Azure AD Provider と組み合わせることで簡単に実現できます。

まずは Azure AD にセキュリティグループが存在していることを確認します。サンプルでは Managed Identity を使ってグループの一覧を取得するつもりなので、存在していなければ困ります。

f:id:shiba-yan:20220222021014p:plain

これまで PowerShell などを使っていた App Role の割り当てですが、Terraform Provider for Azure AD には任意のプリンシパルに対して App Role を割り当てる azuread_app_role_assignment リソースが用意されているので、これを使って App Service と同時に作成された Managed Identity に対してロールを割り当てます。

azuread_app_role_assignment | Resources | hashicorp/azuread | Terraform Registry

Managed Identity を有効化した App Service は出力として Principal ID が用意されているので、その値と割り当てたいロールの ID を azuread_app_role_assignment に渡すだけで完了します。

一部のリソースは省略していますが、App Service の Managed Identity に対して Microsoft Graph の Group.Read.All ロールを割り当てる Terraform 定義は以下のようになります。

terraform {
  required_providers {
    azurerm = {
      version = "> 2.0"
    }
    azuread = {
      version = "> 2.0"
    }
  }
}

provider "azurerm" {
  features {}
}

data "azuread_application_published_app_ids" "well_known" {}

data "azuread_service_principal" "msgraph" {
  application_id = data.azuread_application_published_app_ids.well_known.result.MicrosoftGraph
}

resource "azurerm_function_app" "test" {
  name                       = "func-terraform-mi-test"
  resource_group_name        = azurerm_resource_group.test.name
  location                   = azurerm_resource_group.test.location
  app_service_plan_id        = azurerm_app_service_plan.test.id
  storage_account_name       = azurerm_storage_account.test.name
  storage_account_access_key = azurerm_storage_account.test.primary_access_key

  version                 = "~4"
  enable_builtin_logging  = false
  https_only              = true

  site_config {
    http2_enabled            = true
    dotnet_framework_version = "v6.0"
  }

  identity {
    type = "SystemAssigned"
  }

  app_settings = {
    "APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.test.instrumentation_key
    "FUNCTIONS_WORKER_RUNTIME"       = "dotnet"
  }
}

resource "azuread_app_role_assignment" "test" {
  app_role_id         = data.azuread_service_principal.msgraph.app_role_ids["Group.Read.All"]
  principal_object_id = azurerm_function_app.test.identity[0].principal_id
  resource_object_id  = data.azuread_service_principal.msgraph.object_id
}

Azure AD は至る所で GUID ベースの ID を指定する必要がありますが、そのあたりを Terraform Provider for Azure AD では azuread_application_published_app_idsazuread_service_principal で上手く扱えるようになっているのがかなり便利です。GUID を定数で指定する必要が全くないことが分かると思います。

この定義に対して terraform apply を実行すると、App Service と同時に作成された Managed Identity に対して Graph API のロールが割り当てられていることが確認できます。

f:id:shiba-yan:20220222014916p:plain

ちなみに Managed Identity は Azure AD の Enterprise Application から確認出来ます。このロール確認画面からは追加・削除が出来ないのが非常に不便な原因となっています。

これだけの定義で Managed Identity で Graph API が実行できるようになっているので、後はアプリケーションをデプロイして動作を確認します。必要な SDK は Microsoft.GraphAzure.Identity だけで、Azure Functions の場合は以下のようなシンプルなコードで行えます。

public class Function1
{
    [FunctionName("Function1")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        var graphServiceClient = new GraphServiceClient(new DefaultAzureCredential());

        var groups = await graphServiceClient.Groups.Request().GetAsync();

        return new OkObjectResult(groups.Select(x => new { x.Id, x.DisplayName }));
    }
}

このテストコードを Azure Functions にデプロイしてから、実行してみると Graph API からセキュリティグループの一覧が取得できていることが確認できます。Azure Functions には Service Principal など設定していませんが、問題なくアクセス出来ています。

f:id:shiba-yan:20220222021339p:plain

まだ軽く触った程度ですが Terraform Provider for Azure AD を使うとかなり楽で良い感じです。

これまでは全て Azure CLI + 自分の MSA で入った環境で Terraform を実行してきましたが、GitHub Actions や Terraform Cloud で動かす場合には Service Principal が必要になります。

設定手順は Terraform Provider for Azure AD の公式ドキュメントに記載されています。

Configuring a User or Service Principal to manage Azure Active Directory | Guides | hashicorp/azuread | Terraform Registry

基本は公式ドキュメントを読めばよいですが、スクリーンショットがあった方が嬉しいと思うので、簡単にですが必要な Service Principal の作成方法を書いておきます。

Service Principal 自体はこれまで通り Azure CLI で作成するのが簡単です。

az ad sp create-for-rbac --name TerraformForAzureAD

Azure CLI で作成した Service Principal は Azure AD の App registrations から確認出来るはずなので、作成した Service Principal を選択して API permissions を開きます。

ここからは以下のように Microsoft Graph の Application permissions を追加すれば完了です。

f:id:shiba-yan:20220222152534p:plain

必要な Graph API のロール名は、Terraform リソースの公式ドキュメントに記載されているので親切です。

この後は必要に応じて Azure RBAC の設定を Service Principal に追加すれば、Terraform Provider for Azure と Azure AD の両方が同時に利用できる Service Principal が完成します。