Azure Active DirectoryのアプリケーションをTerraformで管理する
この記事はFOLIO Advent Calendar 2022の12/6分の記事です。かなり遅れてサンタクロースがやってきてしまいました。
Azure Active Directory(Azure AD)には「エンタープライズアプリケーション」と「アプリ登録」と呼ばれる管理項目があります。 特に、SAMLやOIDCを利用するアプリケーションを登録するなど、運用する際に使用されることが多いでしょう。
今回はこれらをInfrastructure as Code(IaC)のデファクトスタンダードとなりつつあるTerraformで管理する方法を紹介します。
2023-01-22 追記
azuread terraform providerのv2.33.0にて、新たにservice_principal_token_signing_certificateリソースが追加されました。 そのため、「サービスプロバイダがAzure ADをIdPとしてSAMLを使用して認証」する際の署名証明書の登録方法を、このリソースを使用する方法に変更しました。
- Azure ADの構成をコード管理
- エンタープライズアプリケーションとアプリの登録について
- Terraformでアプリケーションを管理
- ユースケース1. サービスプロバイダがAzure ADをIdPとしてSAMLを使用して認証
- ユースケース2. サービスプロバイダがAzure ADをIdPとしてOIDCを使用して認証
- クレームマッピングについて
- 終わりに
Azure ADの構成をコード管理
Azure ADでアプリケーションを管理する場合、通常、Microsoft Azure Portal(Azure Portal)と呼ばれるポータルサイト上で作業します。 GUIの操作であるため、直感的でわかりやすく、ほとんどの方がこちらで作業をしていることでしょう。
しかし、なぜその変更を加えたのかの記録が残らず、複数人で変更する場合、その操作が適切なものなのかどうかを確認することが難しいです。
インフラ構成をコード管理することで、これらの課題を解決または解消できます。
今回紹介するTerraformでは、Terraformの開発元であるHashiCorp社が管理するAzure AD用のプロバイダを使用します。 このプロバイダは内部でMicrosoft Graph APIを使用しているため、Microsoft Graph APIで操作できるAzure ADの項目は一通り操作できます。
エンタープライズアプリケーションとアプリの登録について
アプリケーションをコード管理するにあたって、Azure Portalにおける管理項目でよく目にする「エンタープライズアプリケーション」と「アプリの登録」について理解しておく必要があります。
「アプリの登録」は、Azure ADに登録するSaaSなどのサービスプロバイダがMicrosoft Graph APIで要求するリソースへのアクセス管理や、サービスプロバイダがアプリケーションにアクセスするためのトークンを発行する方法などを定義します。 Azure ADではアプリケーションオブジェクトと呼ばれ、後述のサービスプリンシパルオブジェクトのひな型となります。
「エンタープライズアプリケーション」は、SaaSアプリケーションのSAMLによるSSOやユーザーやグループのプロビジョニング、Azure ADのユーザーやグループのアサインなどが設定できます。 エンタープライズアプリケーションで一覧化されているオブジェクトは、Azure ADではサービスプリンシパルオブジェクトと呼ばれます。
アプリケーションオブジェクトだけでは何も機能せず、実際にサービスプロバイダと認証情報のやりとりをするのがサービスプリンシパルオブジェクトです。
また、アプリケーションオブジェクトとサービスプリンシパルオブジェクトは、1対多の関係です。
詳しくは「Azure Active Directory のアプリケーション オブジェクトとサービス プリンシパル オブジェクト」を参照してください。
Terraformでアプリケーションを管理
今回は、アプリケーション管理で特に多く使用される、
- サービスプロバイダがAzure ADをIdPとしてSAMLを使用して認証
- サービスプロバイダがAzure ADをIdPとしてOIDCを使用して認証
の2つのユースケースをTerraformでコード管理してみます。
どちらのケースでも以下のように、Azure ADのプロバイダの設定をする必要があります。
terraform {
required_providers {
azuread = {
source = "hashicorp/azuread"
version = "2.33.0" # 執筆時点の最新バーション
}
}
}
provider "azuread" {
tenant_id = "ここにAzure ADのテナントIDをいれる"
}
また、今回はすでにAzure AD上に存在するkenchan0130
ユーザーのみがサービスプロバイダにログインできることとします。
ユースケース1. サービスプロバイダがAzure ADをIdPとしてSAMLを使用して認証
今回は、手元にJamf Proのテナントを持っていたので、こちらのサービスプロバイダにログインできるように、Azure ADの構成をTerraform化します。
.
├── main.tf
└── provider.tf
provider.tf
に前述したプロバイダーの設定、main.tf
に今回のユースケースを構成します。 以下が最低限動作するコードです。
# main.tf
locals {
jamf_pro_certificate_buffer_hour = "8760h" # 1年
}
resource "azuread_application" "jamf_pro" {
display_name = "Jamf Pro by terraform"
identifier_uris = ["api://example-jamf-pro"]
web {
redirect_uris = [
"https://exmaple.jamfcloud.com/saml/SSO"
]
}
}
resource "azuread_service_principal" "jamf_pro" {
application_id = azuread_application.jamf_pro.application_id
app_role_assignment_required = true
preferred_single_sign_on_mode = "saml"
feature_tags {
enterprise = true
custom_single_sign_on = true
}
}
data "azuread_user" "kenchan0130" {
user_principal_name = "kenchan0130@exmaple.com"
}
resource "azuread_app_role_assignment" "jamf_pro" {
for_each = toset([
for user in [
data.azuread_user.kenchan0130
] : user.object_id
])
app_role_id = "00000000-0000-0000-0000-000000000000" # default role ID
resource_object_id = azuread_service_principal.jamf_pro.object_id
principal_object_id = each.value
}
resource "time_rotating" "jamf_pro_certificate" {
rotation_years = 2
}
resource "azuread_service_principal_token_signing_certificate" "jamf_pro" {
service_principal_id = azuread_service_principal.jamf_pro.id
end_date = timeadd(time_rotating.jamf_pro_certificate.rotation_rfc3339, local.jamf_pro_certificate_buffer_hour)
}
resource "null_resource" "jamf_pro_signing_certificate_activation" {
triggers = {
service_principal_id = azuread_service_principal.jamf_pro.id
thumbprint = azuread_service_principal_token_signing_certificate.jamf_pro.thumbprint
}
provisioner "local-exec" {
command = <<-SHELL
az ad sp update \
--id ${self.triggers.service_principal_id} \
--set preferredTokenSigningKeyThumbprint=${self.triggers.thumbprint}
SHELL
}
}
それぞれのリソースについて、行っている内容や注意点などを見ていきます。
SAMLの構成に伴うazuread_applicationリソース
azuread_applicationは、Azure ADのアプリケーションオブジェクトを作成します。
resource "azuread_application" "jamf_pro" {
display_name = "Jamf Pro by terraform"
identifier_uris = ["api://example-jamf-pro"]
web {
redirect_uris = [
"https://exmaple.jamfcloud.com/saml/SSO"
]
}
}
web
ブロックのredirect_uris
は、SAMLのAssertion Consumer Service(ACS)に該当します。
identifier_uris
はSAMLのエンティティIDに該当します。エンティティIDはRFC7522でURIであるであることが定められています。 また、エンティティIDはAzure ADのテナント内で一意、つまり重複しないように設定する必要があります。
identifier_urisを設定するとアプリケーションオブジェクトが作成できない問題
azuread_application
のidentifier_uris
にhttps://exmaple.jamfcloud.com/saml/metadata
を設定しようとするとterraform apply
時に以下のようなエラーが発生しました。
╷
│ Error: Could not create application
│
│ with azuread_application.jamf_pro,
│ on main.tf line 1, in resource "azuread_application" "jamf_pro":
│ 1: resource "azuread_application" "jamf_pro" {
│
│ ApplicationsClient.BaseClient.Post(): unexpected status 400 with OData error: HostNameNotOnVerifiedDomain: Values of identifierUris property must use a
│ verified domain of the organization or its subdomain: 'https://example.jamfcloud.com/saml/metadata'
╵
原因は、「シングル テナント アプリケーションの AppId URI には、既定のスキームまたは検証済みドメインを使用する必要があります」です。 つまり、設定できるのは、検証済みのドメインのURIとapi://
で始まるデフォルトスキームのURIのみです。 Azure Portalでアプリケーションオブジェクトを作成するとこの制約に引っかからないため、API経由のみの制約であると思われます。
そのため今回の例では、api://example-jamf-pro
というデフォルトスキームURIを採用しました。
もし、どうしても検証済みではないURIをエンティティIDとして使用したい場合は、2つの解決方法があります。
1つ目は、2回terraform apply
を実行する方法です。
1回目は、identifier_uris
は設定せずにterraform apply
を実行します。
resource "azuread_application" "jamf_pro" {
display_name = "Jamf Pro by terraform"
web {
redirect_uris = [
"https://exmaple.jamfcloud.com/saml/SSO"
]
}
}
アプリケーションオブジェクトが作成されたら、identifier_uris
に検証されていないドメインを含むURIを設定し、terraform apply
を実行します。
resource "azuread_application" "jamf_pro" {
display_name = "Jamf Pro by terraform"
identifier_uris = ["https://example.jamfcloud.com/saml/metadata"]
web {
redirect_uris = [
"https://exmaple.jamfcloud.com/saml/SSO"
]
}
}
この方法は、どこインフラでも同じ構成を実現するというIaCの目的から若干外れてしまいますが、特別なハックをしなくても良いというのがメリットではあります。
2つ目は、azuread_application
リソースではidentifier_uris
を設定せずに、別のリソースでidentifier_uris
を設定する方法です。
resource "azuread_application" "jamf_pro" {
display_name = "Jamf Pro by terraform"
web {
redirect_uris = [
"https://onishidev.jamfcloud.com/saml/SSO"
]
}
lifecycle {
ignore_changes = [
identifier_uris
]
}
}
resource "null_resource" "application_jamf_pro_identifier_uris" {
# triggersに設定されているされている値が変更された場合、再度local-execが実行される
triggers = {
application_id = azuread_application.jamf_pro.application_id
# triggersはobject(string)の型を要求するため、スペース文字でjoinしている
identifier_uris = join(" ", [
"https://exmaple.jamfcloud.com/saml/metadata"
])
}
provisioner "local-exec" {
command = <<-SHELL
az ad app update --id ${self.triggers.application_id} --identifier-uris ${self.triggers.identifier_uris}
SHELL
}
}
azuread_application
リソースでは、identifier_uris
は設定しません。 合わせて別のリソースで更新されるため、lifecycle
ブロックのignore_changes
でidentifier_uris
を指定しておきます。
null_resource
リソースのlocal-exec
のprovisioner
を使うことで、ローカルの実行ファイルを呼び出せます。 これを使って、Azureのリソースを操作できるCLIであるaz
コマンドを介してアプリケーションオブジェクトのidentifier_uris
を更新しています。1
実行環境に依存してしまう点がデメリットですが、何が起こるのかがtfファイルから読み取れるメリットがあります。
アプリケーションオブジェクトの更新で検証されていないドメインを含むURIを設定できるなら2、作成時にも設定させてほしいです。どうしてこんな変更入れてしまったの、Microsoftさん。
SAMLの構成に伴うazuread_service_principalリソース
azuread_service_principalは、Azure ADのサービスプリンシパルオブジェクトを作成します。
resource "azuread_service_principal" "jamf_pro" {
application_id = azuread_application.jamf_pro.application_id
app_role_assignment_required = true
preferred_single_sign_on_mode = "saml"
feature_tags {
enterprise = true
custom_single_sign_on = true
}
}
app_role_assignment_required
が無効の場合(デフォルトでは無効)はテナントに存在するすべてのアカウントでログインできます。 今回のユースケースのようにサービスプロバイダにログインできる人を制限したい場合は、app_role_assignment_required
を有効にする必要があります。
また、preferred_single_sign_on_mode
をsaml
にするだけでは、SAML認証ができません。 feature_tags
ブロックのcustom_single_sign_on
も合わせて有効にする必要があります。
合わせて、feature_tags
ブロックのenterprise
を有効にすることで、Azure Portal上でエンタープライズアプリケーションとして検索できるため、特段理由がない限りは有効にしておくと良いでしょう。
SAMLの構成に伴うazuread_app_role_assignmentリソース
azuread_app_role_assignmentは、リソースアプリケーションで定義されているアプリケーションのロールを、プリンシパル(ユーザー、グループ、またはサービスプリンシパル)に紐付けるためのリソースです。
data "azuread_user" "kenchan0130" {
user_principal_name = "kenchan0130@exmaple.com"
}
resource "azuread_app_role_assignment" "jamf_pro" {
for_each = toset([
for user in [
data.azuread_user.kenchan0130
] : user.object_id
])
app_role_id = "00000000-0000-0000-0000-000000000000" # default role ID
resource_object_id = azuread_service_principal.jamf_pro.object_id
principal_object_id = each.value
}
azuread_application
のapp_role
ブロックでアプリケーションのロールを定義できます。 たとえは、ロールによってサービスプロバイダに渡す値を動的に変更することで、権限管理を柔軟に行えます。
今回特にロールは定義しておらず、サービスプロバイダにログインできる人を制限したいだけですので、デフォルトロールを使用してサービスプリンシパルオブジェクトとユーザーオブジェクトの紐付けを行っています。
往々にして、サービスプリンシパルオブジェクトには複数のユーザーおよびグループオブジェクトを紐付けたくなります。そのため、今回はfor_each
を使用して複数のユーザーおよびグループオブジェクトを紐付けれるようにしてみました。
SAMLの構成に伴うazuread_service_principal_token_signing_certificateリソース
azuread_service_principal_token_signing_certificateは、Azure ADのサービスプリンシパルオブジェクトに紐付ける署名証明書を発行します。
似たようなものにazuread_service_principal_certificateリソースがありますが、これはローカルやterraform上で署名証明書がある場合に使用するリソースです。3
locals {
jamf_pro_certificate_buffer_hour = "8760h" # 1年
}
resource "time_rotating" "jamf_pro_certificate" {
rotation_years = 2
}
resource "azuread_service_principal_token_signing_certificate" "jamf_pro" {
service_principal_id = azuread_service_principal.jamf_pro.id
end_date = timeadd(time_rotating.jamf_pro_certificate.rotation_rfc3339, local.jamf_pro_certificate_buffer_hour)
}
resource "null_resource" "jamf_pro_signing_certificate_activation" {
triggers = {
service_principal_id = azuread_service_principal.jamf_pro.id
thumbprint = azuread_service_principal_token_signing_certificate.jamf_pro.thumbprint
}
provisioner "local-exec" {
command = <<-SHELL
az ad sp update \
--id ${self.triggers.service_principal_id} \
--set preferredTokenSigningKeyThumbprint=${self.triggers.thumbprint}
SHELL
}
}
time_rotatingリソースとtimeadd関数を使用することで、azuread_service_principal_token_signing_certificate
リソースのend_time
を3年として登録し、terraformをapplyして2年以降に再度terraformをapplyすると証明書の更新が自動で実行されるようにしています。
署名証明書が自動で更新されたくない場合は、time_rotating
リソースは使わずに、end_date
をベタ書きで指定、または何も指定しない(デフォルトでは3年)ことで対応可能です。
resource "azuread_service_principal_token_signing_certificate" "jamf_pro" {
service_principal_id = azuread_service_principal.jamf_pro.id
end_date = "2023-05-01T01:02:03+09:00" # この値を変更すると、署名証明書が再発行される
}
azuread_service_principal_token_signing_certificateで署名証明書を設定してもアクティブにならない問題
azuread_service_principal_token_signing_certificate
リソースでサービスプリンシパルオブジェクトに署名証明書を紐付けたとしても、証明書が有効になっておらず非アクティブの状態です。
残念ながら、署名証明書をアクティブにするTerraformリソースは存在しません。 執筆現在、サービスプリンシパルオブジェクトにPATCHリクエストを投げることでしか署名証明書をアクティブにできないためです。端的に言うと、Microsoft Graph APIの作りがイマイチなのです。
resource "null_resource" "jamf_pro_signing_certificate_activation" {
triggers = {
service_principal_id = azuread_service_principal.jamf_pro.id
thumbprint = azuread_service_principal_token_signing_certificate.jamf_pro.thumbprint
}
provisioner "local-exec" {
command = <<-SHELL
az ad sp update \
--id ${self.triggers.service_principal_id} \
--set preferredTokenSigningKeyThumbprint=${self.triggers.thumbprint}
SHELL
}
}
null_resource
以外にも、azuread_service_principal_token_signing_certificate
リソース内でもprovisioner
ブロックを使用できます。しかし運用上、差分検知の文脈からnull_resource
で設定することをお勧めします。
provisioner
ブロック内では、Azureのリソースを操作できるCLIであるaz
コマンドを介してサービスプリンシパルオブジェクトのpreferredTokenSigningKeyThumbprint
を更新します。1
preferredTokenSigningKeyThumbprint
に証明書のThumbprintが設定されると、サービスプリンシパルオブジェクトに紐付いている署名証明書がアクティブになります。
ユースケース2. サービスプロバイダがAzure ADをIdPとしてOIDCを使用して認証
今回は、Microsoft社がチュートリアルとして出しているOIDCを使用したNode.jsのWebアプリケーションのサービスプロバイダにログインできるように、Azure ADの構成をTerraform化します。
.
├── main.tf
└── provider.tf
provider.tf
に前述したプロバイダーの設定、main.tf
に今回のユースケースを構成します。 以下が最低限動作するコードです。
# main.tf
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
}
locals {
oidc_app_msgraph_api_scopes = [
"openid",
"profile",
"offline_access",
"Mail.Read"
]
}
resource "azuread_application" "oidc_app" {
display_name = "Azure Active Directory OIDC Node.js web app sample by terraform"
web {
redirect_uris = [
"http://localhost:3000/auth/openid/return"
]
implicit_grant {
id_token_issuance_enabled = true
}
}
required_resource_access {
resource_app_id = data.azuread_service_principal.msgraph.application_id
dynamic "resource_access" {
for_each = local.oidc_app_msgraph_api_scopes
content {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids[resource_access.value]
type = "Scope"
}
}
}
}
resource "azuread_application_password" "oidc_app" {
application_object_id = azuread_application.oidc_app.object_id
display_name = "Webアプリケーションで使用するシークレット"
end_date_relative = "2400h30m"
}
resource "azuread_service_principal" "oidc_app" {
application_id = azuread_application.oidc_app.application_id
app_role_assignment_required = true
preferred_single_sign_on_mode = "oidc"
feature_tags {
enterprise = true
}
}
data "azuread_user" "kenchan0130" {
user_principal_name = "kenchan0130@example.com"
}
resource "azuread_app_role_assignment" "oidc_app" {
for_each = toset([
for user in [
data.azuread_user.kenchan0130
] : user.object_id
])
app_role_id = "00000000-0000-0000-0000-000000000000" # default role ID
resource_object_id = azuread_service_principal.oidc_app.object_id
principal_object_id = each.value
}
resource "azuread_service_principal_delegated_permission_grant" "oidc_app" {
service_principal_object_id = azuread_service_principal.oidc_app.object_id
resource_service_principal_object_id = data.azuread_service_principal.msgraph.object_id
claim_values = local.oidc_app_msgraph_api_scopes
}
それぞれのリソースについて、行っている内容やハマった内容などを見ていきます。
OIDCの構成に伴うazuread_applicationリソース
azuread_applicationは、Azure ADのアプリケーションオブジェクトを作成します。
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
}
locals {
oidc_app_msgraph_api_scopes = [
"openid",
"profile",
"offline_access",
"Mail.Read"
]
}
resource "azuread_application" "oidc_app" {
display_name = "Azure Active Directory OIDC Node.js web app sample by terraform"
web {
redirect_uris = [
"http://localhost:3000/auth/openid/return"
]
implicit_grant {
id_token_issuance_enabled = true
}
}
required_resource_access {
resource_app_id = data.azuread_service_principal.msgraph.application_id
dynamic "resource_access" {
for_each = local.oidc_app_msgraph_api_scopes
content {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids[resource_access.value]
type = "Scope"
}
}
}
}
web
ブロックのredirect_uris
は、OIDCのリダイレクトURIに該当します。
今回のサービスプロバイダはOIDCのハイブリッド フローを有効化するように求められているため、implicit_grant
ブロックのid_token_issuance_enabled
を有効にしています。
また、サービスプロバイダはスコープとして、
openid
profile
offline_access
https://graph.microsoft.com/mail.read
を要求しています。 これらのアクセス許可を定義するため、required_resource_access
ブロックでMicrosoft Graph APIで定義されているスコープを許可しています。
Microsoft Graph APIではUUIDでスコープを定義しますが、UUIDが扱いづらいため、Azure ADのTerraformプロバイダにはこれらをうまく参照するためのデータソースが用意されています。
azuread_application_published_app_idsは、Azure ADのTerraformプロバイダが非公式に用意している4、Microsoft社がAzure ADにすでに用意しているアプリケーションのUUID一覧です。
azuread_service_principalは、すでにAzure ADに存在するサービスプリンシパルオブジェクトを参照するためデータソースです。
これらのデータソースにより、比較的簡単に許可するスコープを定義できます。
ちなみに、スコープが複数あったため、resource_access
ブロックをdynamic
ブロックで記載していますが、以下と同義です。
required_resource_access {
resource_app_id = data.azuread_service_principal.msgraph.application_id
resource_access {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids.openid
type = "Scope"
}
resource_access {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids.profile
type = "Scope"
}
resource_access {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids.offline_access
type = "Scope"
}
resource_access {
id = data.azuread_service_principal.msgraph.oauth2_permission_scope_ids["Mail.Read"]
type = "Scope"
}
}
OIDCの構成に伴うazuread_application_passwordリソース
azuread_application_passwordは、Azure ADのアプリケーションオブジェクトに紐付く、クライアントクレデンシャルフローで使用する、クライアントシークレットを作成します。
resource "azuread_application_password" "oidc_app" {
application_object_id = azuread_application.oidc_app.object_id
display_name = "Webアプリケーションで使用するシークレット"
end_date_relative = "2400h30m"
}
azuread_application_password
リソースを使用すると、tfstateファイルにクライアントシークレットの情報が残ってしまいます。 そのため、tfstateファイルを安全に取り扱うなどの対応が必要です。
tfstateに秘密情報を残したくない場合は、Terraformを経由せずにAzure PortalやMicrosoft Graph APIを利用してクライアントシークレットを発行すると良いでしょう。
OIDCの構成に伴うazuread_service_principalリソース
azuread_service_principalは、Azure ADのサービスプリンシパルオブジェクトを作成します。
resource "azuread_service_principal" "oidc_app" {
application_id = azuread_application.oidc_app.application_id
app_role_assignment_required = true
preferred_single_sign_on_mode = "oidc"
feature_tags {
enterprise = true
}
}
app_role_assignment_required
が無効の場合(デフォルトでは無効)はテナントに存在するすべてのアカウントでログインできます。 今回のユースケースのようにサービスプロバイダにログインできる人を制限したい場合は、app_role_assignment_required
を有効にする必要があります。
preferred_single_sign_on_mode
をoidc
にすると、Azure Portal上のエンタープライズアプリケーションの「シングル サインオン」項目に、「アプリ登録」ページへのリンクとJWTのクレームマッピングのカスタム構成ページへのリンクが表示されます。
feature_tags
ブロックのenterprise
を有効にすることで、Azure Portal上でエンタープライズアプリケーションとして検索できるため、特段理由がない限りは有効にしておくと良いでしょう。
OIDCの構成に伴うazuread_app_role_assignmentリソース
こちらについてはSAMLの構成に伴うazuread_app_role_assignmentリソースと同様のため省略します。
OIDCの構成に伴うazuread_service_principal_delegated_permission_grantリソース
azuread_service_principal_delegated_permission_grantは、Azure ADのサービスプリンシパルオブジェクトのAPIアクセス許可の権限委譲が設定できます。
locals {
oidc_app_msgraph_api_scopes = [
"openid",
"profile",
"offline_access",
"Mail.Read"
]
}
resource "azuread_service_principal_delegated_permission_grant" "oidc_app" {
service_principal_object_id = azuread_service_principal.oidc_app.object_id
resource_service_principal_object_id = data.azuread_service_principal.msgraph.object_id
claim_values = local.oidc_app_msgraph_api_scopes
}
今回のサービスプロバイダはスコープとして、
openid
profile
offline_access
https://graph.microsoft.com/mail.read
を要求しており、azuread_application
リソースのrequired_resource_access
でAPIアクセスの許可を定義していました。
しかし、実際にこのスコープにアクセスするには、アプリケーションオブジェクトではなく、サービスプリンシパルオブジェクトごとに管理者の同意が必要です。
azuread_service_principal_delegated_permission_grant
リソースでは、この管理者の同意を設定できます。
claim_values
は、required_resource_access
のようにUUIDではなく、クレームのキーを指定します。 Microsoft Graph APIの一貫性がなくて使いづらい点がにじみ出てきてしまっているのが残念ですが、今回の例ようにlocals
ブロックなどで、変数としてスコープを定義すると扱いやすくなります。
クレームマッピングについて
SAMLではアサーション、OIDCではJWTクレームを介して、サービスプロバイダに値を渡したい場合があります。 その際は、クレームマッピングが使用できます。
以下は、SAMLの構成でクレームマッピングを使い、NameIDにディスプレイ名を割り当てた例です。
resource "azuread_claims_mapping_policy" "jamf_pro" {
display_name = "Jamf Pro Custom Claim Policy"
definition = [
jsonencode(
{
ClaimsMappingPolicy = {
ClaimsSchema = [
{
ID = "displayname"
SamlClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier"
Source = "user"
}
]
IncludeBasicClaimSet = "true"
Version = 1
}
}
)
]
}
resource "azuread_service_principal_claims_mapping_policy_assignment" "jamf_pro" {
claims_mapping_policy_id = azuread_claims_mapping_policy.jamf_pro.id
service_principal_id = azuread_service_principal.jamf_pro.id
}
しかし、このAPIを使用すると、Azure Portal上で現在のクレームマッピングの情報が表示されず、クレームの変更もできなくなってしまいます。
公式ドキュメントにも、
これは、Azure portal で提供しているクレームのカスタマイズの後継機能です。 同一のアプリケーションに対し、このドキュメントで詳述する Microsoft Graph または Powershell を利用した方法に加えて、Azure portal でもクレームをカスタマイズした場合、そのアプリケーションのトークンでは、Azure portal での構成は無視されます。 このドキュメントで詳しく説明した方法で行った構成は、ポータルには反映されません。
と記載されており、仕様のようです。
この仕様は場合によっては運用上支障が出る可能性があるため、注意が必要です。
終わりに
今回はTerraformを使用して、
- サービスプロバイダがAzure ADをIdPとしてSAMLを使用して認証
- サービスプロバイダがAzure ADをIdPとしてOIDCを使用して認証
の構築例を紹介しました。
これは最低限の設定であったため、その他にも設定したい項目がある場合はTerraformのプロバイダの公式ドキュメントやMicrosoft Graphの公式ドキュメントを参考に対応してみると良いと思います。