Azure AD を扱っていると、やはりどうしても Infrastructure as Code を実現したくなる瞬間が多々あります。そんな時、Terraform の Azure Active Directory Providerを用いるという選択肢は有用でしょう。
便利そうですね! 使ってみましょう。
私の場合、Azure AD azuread_application
という Resource を扱う必要があり、更に今回の用途では、Microsoft Graph API のパーミッションを付与する必要がありました。これの resource_app_id
が必要そうです。Graph Explorer で見つけてみましょう。
GET https://graph.microsoft.com/beta/servicePrincipals?$filter=displayName eq 'Microsoft Graph'
(中略)
"appDisplayName": "Microsoft Graph",
"appId": "00000003-0000-0000-c000-000000000000",
(中略)
"appRoles": [
{
"allowedMemberTypes": [
"Application"
],
"description": "Allows the app to manage all schedules, schedule groups, shifts and associated entities in the Teams or Shifts application without a signed-in user.",
"displayName": "Read and write all schedule items",
"id": "b7760610-0545-4e8a-9ec3-cce9e63db01c",
"isEnabled": true,
"origin": "Application",
"value": "Schedule.ReadWrite.All"
},
{
"allowedMemberTypes": [
"Application"
],
"description": "Allows the app to read all schedules, schedule groups, shifts and associated entities in the Teams or Shifts application without a signed-in user.",
"displayName": "Read all schedule items",
"id": "7b2ebf90-d836-437f-b90d-7b62722c4456",
"isEnabled": true,
"origin": "Application",
"value": "Schedule.Read.All"
},
Microsoft Graph API の app_id
は 00000003-0000-0000-c000-000000000000
であることがわかりますね!
Microsoft Graph API が用意しているパーミッションの ID などもずらりと出てきており、これを使うと良さそうです。
それでは Terraform に落とし込んでいきましょう……。
resource "azuread_application" "foo_app" {
name = "foo_app"
required_resource_access {
# Microsoft Graph API Application ID
resource_app_id = "00000003-0000-0000-c000-000000000000"
resource_access {
# Directory.Read.All
id = "06da0dbc-49e2-44d2-8312-53f166ab848a"
type = "Scope"
}
resource_access {
# User.Read
id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
type = "Scope"
}
}
}
問題はなさそうです。ですが、冗長で Terraform らしくない感じがしますね。
Azure Active Directory Provider のドキュメントを眺めていると、 azuread_service_principalというデータソースが存在している事にそのうち気付くでしょう。
これを使えば、 Microsoft Graph API の application_id
さえ記述すれば、データソースの返り値をうまいこと使い回せそうです……定義してみましょう。
data "azuread_service_principal" "microsoft_graph" {
application_id = "00000003-0000-0000-c000-000000000000"
}
resource "azuread_application" "foo_app" {
name = "foo_app"
required_resource_access {
resource_app_id = data.azuread_service_principal.microsoft_graph.application_id
resource_access {
# Directory.Read.All
id = "06da0dbc-49e2-44d2-8312-53f166ab848a"
type = "Scope"
}
resource_access {
# User.Read
id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d"
type = "Scope"
}
}
}
いいですね!
microsoft_graph
というデータソースには、そのサービスプリンシパルで利用できる全パーミッションがぶら下がっています。 resource_access
ブロック内の各パーミッションの ID も、もう少し Terraform らしく書けそうです。
つまり、全パーミッションの中から、実際に利用したいパーミッションだけをフィルタしつつ、 resource_access
を繰り返し記述せずに済むように記述していきましょう。
data "azuread_service_principal" "microsoft_graph" {
application_id = "00000003-0000-0000-c000-000000000000"
}
resource "azuread_application" "foo_app" {
name = "foo_app"
required_resource_access {
resource_app_id = data.azuread_service_principal.microsoft_graph.application_id
dynamic "resource_access" {
for_each = matchkeys(
data.azuread_service_principal.microsoft_graph.app_roles.*.id,
data.azuread_service_principal.microsoft_graph.app_roles.*.value,
[
"Directory.Read.All",
"User.Read"
]
)
content {
id = resource_access.value
type = "Role"
}
}
}
}
いいですね、 Terraform らしい記述になったのではないでしょうか。
Dynamic configuration blocks と matchkeys
Functions は Terraform v0.12 で使えるようになった機能なので、Terraform 本体のバージョンが古い場合は先に Terraform のアップデートをする必要があるでしょう。
私の場合、Azure Portal 上で手動で設定したものを Terraform に落とし込む必要がありました。 terraform import
を行ってから terraform plan
を流してみましょう……。
おっと、 matchkey
Function はマッチした第一引数のインデックス順に従うため、 data.azuread_service_principal.microsoft_graph.app_roles
の順序に従うことになり、Azure AD 側で既に設定されているパーミッションの順序と差分が生じてしまいました。
具体的には、実際には User.Read
が先頭に、 Directory.Read.All
が2番目に設定されているようです。
おしゃれな記述を諦めて、データソースを使いつつ、実際に設定されているパーミッションとの順序に従うように Terraform を書き換えてみましょう。
data "azuread_service_principal" "microsoft_graph" {
application_id = "00000003-0000-0000-c000-000000000000"
}
resource "azuread_application" "foo_app" {
name = "foo_app"
required_resource_access {
resource_app_id = data.azuread_service_principal.microsoft_graph.application_id
resource_access {
id = [for x in data.azuread_service_principal.microsoft_graph.app_roles : x.id if x.value == "User.Read"][0]
type = "Role"
}
resource_access {
id = [for x in data.azuread_service_principal.microsoft_graph.app_roles : x.id if x.value == "Directory.Read.All"][0]
type = "Role"
}
}
}
Terraform 経由でこのリソースを作成していれば、Dynamic configuration blocks でおしゃれに記述できたものの、あいにく、既存のリソースを import する形になった為に、おしゃれな Terraform を諦める形になってしまいましたが、それでも諸々の ID をベタ書きするよりは遥かにマシな記述になったのではないでしょうか。
みなさんもぜひ現実と Terraform の理想との折り合いをつけていきながら、快適な Terraform 生活を過ごしていきましょう!