LoginSignup
2
2

More than 3 years have passed since last update.

Terraform で Azure AD アプリケーションをオシャレに定義しようとして諦める

Last updated at Posted at 2020-03-02

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_id00000003-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 blocksmatchkeys 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 生活を過ごしていきましょう!

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2