Terraform CloudをAzureで使ってみた

[bitFlyer Advent Calendar 2022](https://qiita.com/advent-calendar/2022/bitflyer)の18日目のエントリです。
こんにちは。最近はお家時間のQoLを上げるためDIYにハマって、玄関に棚を増設したりしている、bitFlyer SRE部の遠地（とおち）です。
本記事では、Terraform CloudをAzure向けに構築しましたのでその知見をシェアしたいと思います。

# tl;dr
- 「開発と運用の職務分離」や「最小権限の原則」のために、Terraform Cloudの設定自体をコード化
- いくつかの落とし穴回避ポイント
    - ドメイン`app.terraform.io`を社内プロキシの対象から外す
    - Azure DevOpsのApplicationの追加はProject Collection Administrator権限を持ったユーザで行う
    - Terraform CloudのユーザはActive Directory側で定義
- Terraform Cloudにprojectの概念が欲しかった日々だった

# 背景

令和ちゃん四さいが気圧スライダーで遊んでいる昼下がり。とあるSREエンジニアは頭を抱えていました。サービス数が増えてきたので、terraformを触る人が増えてほしいなぁ、と。

当社においてマイクロサービス移行プロジェクトが目下進行中でした。そうです、マイクロサービス化した分だけ、クラウド上で管理するインフラも増えていくやつです。そこで、SRE部はボトルネックとならないよう、インフラをterraformコード化して、開発チームへ管理を委譲してきました。

しかし、terrformリポジトリが増えるにつれ、だんだんとCI/CDとtfstateの管理や、ワークフローの説明の負荷が増えてきます。かくして、
「Terraform Cloudを導入しよう！　みんながもっと簡単に使えるように！」
というビッグウェーブは必然的に訪れ、PoCは幕明けたのでした。

# 要件

当社の業種は金融系ですので、原則としてしっかりとしたアクセス制御が求められます。

- 開発と運用の職務分離
サービスの開発をする人と、デプロイ・保守する人を分ける。
- 最小権限の原則
最低限必要なアクセス権限だけを付与する。

また、下記のような環境を前提に構成します。
- クラウドはAzureを使用
- Active DirectoryでSSO連携

つまり「開発と運用の職務分離」や「最小権限の原則」の要件を満たしつつ、Active DiretoryでSSO連携したTerraform Cloudから、Azureのリソースを構築したい、ということになります。

# 構成の検討

「開発と運用の職務分離」のため、開発チームと運用チームを設定するとします。開発チームは検証環境にのみアクセスでき、運用チームは検証および本番環境にアクセスできます。

想定できるTerraform Cloud構成としては、下記の2つから選択することになるかと思います。
- 2つのorganizationを使って根本から分離
- 1つのorganization内でチームの機能を使って分離

下記の表のようなことですね。

| | 複数organization構成 | 単一organization構成 |
|:--:|:--:|:--:|
| Organizations | 開発チーム, 運用チーム | - |
| Teams           | -    | 開発チーム, 運用チーム |

複数organization構成だと、明確に分離できそうなので安心なのですが、一方で単一organization構成だと、下記のようにメリットが多いです。
- 開発チームは本番環境のterraform planの結果を同一organization内で確認できる
- Registry moduleが一箇所で済む
- Organization毎に発生するライセンス料を節約できる

よって当社では、単一organization構成を選択しました。そして、チーム分けをしっかり運用するために、Terraform Cloudの設定をコード化し、そのためのCI/CD環境も構築していきます。

# 構成案

単一organization構成でチーム分けをする構成です。
![terraform-cloud-teams.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236794/60611835-5962-4dbe-8eac-e028ab011c19.png)

- Active DirectoryでSSO連携します。
- Active Directoryのgroupと、Terraform Cloud上のteamを対応させます。
- Terraform Cloudのteamごとのアクセス制御をコード化し、Azure DevOpsにCI/CDパイプラインを構築します。

# 手順

## Active DirectoryでSSO連携

[Microsoft Azure AD - Single Sign-on](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/single-sign-on/azure-ad)を参考にSSO連携を構築します。

:::note info
もし社内からのインターネットアクセスに、社内プロキシなどを使用している場合で、認証のうまく行かない時は、ドメイン`app.terraform.io`を対象から外しましょう。
:::

## Active Directoryのgroupと、Terraform Cloud上のteamを対応させる

### Active Directoryの設定
[Microsoft Azure AD - Single Sign-on](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/single-sign-on/azure-ad#team-and-username-attributes)の「Team and Username Attributes」に項に説明があるものの、理解するのにActive Directoryの知識が前提として必要で難易度が高いです。ですので、下記に実際の構築手順を示します。

Enterprise Application › シングル サインオン › 属性とクレーム › 編集
「グループ要求を追加する」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236794/4711c307-a7db-5ac6-b2d6-b48578d27a1c.png)

「すべてのグループ」を選びます。
「グループ要求の名前をカスタマイズする」をチェックし、「名前」に`MemberOf`と入力し、「保存」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236794/a23468db-3beb-68c0-f099-2f740dfcae6f.png)

### Terraform CloudでSSO team IDの設定
Active Directoryのgroupの`Object ID`をメモしておきます。
Terraform Cloud › Organization Settings › Teams
「SSO team ID」に`Object ID`を入力して「Update team」ボタンを押します。
これをチームの数だけ繰り返し設定してください。

以上で、Active Directoryのgroupと、Terraform Cloud上のteamを対応させられたはずです。[SSOでサインイン](https://app.terraform.io/sso/sign-in)して確認してみましょう。

## Terraform Cloudのteamごとのアクセス制御をコード化

それでは、Azure DevOpsのPipelinesから、Terraform Cloudの設定を行えるようにしていきましょう。

### VCS providerの追加

[Azure DevOps Services - VCS Providers - Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/vcs/azure-devops-services)を参考にVCS providerを追加します。

::: note info
Azure DevOpsのApplicationの追加は`Project Collection Administrator`権限を持ったユーザで行ってください。そうしないと、追加したVCS providerにwebhookを作成する権限がなく、CI/CDからTerraform Cloudのworkspaceを作成できなくなります。
:::

### Terraform Cloudトークンを生成
[API Tokens - Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/api-tokens) を参考にして、管理体制に合うTerraform Cloudのトークンを生成し、Terraform CloudのCI/CDパイプラインから環境変数`TFE_TOKEN`として参照できる場所に格納します。（当社ではAzure Key Vaultに格納しています。）

## VCS Providerを追加
[Azure DevOps Services - VCS Providers - Terraform Cloud](https://developer.hashicorp.com/terraform/cloud-docs/vcs/azure-devops-services)を参考にして、Azure用のterraformコードの置いてあるバージョン管理システムを登録します。

### Azureの認証情報の設定
Terraform CloudのVariable Setsに、Azureリソースを操作するためのService Principalの認証情報を入れます。

```sh
ARM_SUBSCRIPTION_ID
ARM_TENANT_ID
ARM_CLIENT_ID
ARM_CLIENT_SECRET  # sensitive attributeとして追加
```

検証環境と本番環境とで、それぞれ定義します。

### Terraform Cloudのteam, workspace, variable setsのアクセス制御をコーディング

[TFE provider](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs)を参考にしつつ、アクセス制御をコーディングしましょう。

これでTerraform Cloudから、Azureのリソースを構築する準備が完了しました🎉

# 解説

## なぜコード側ではなくActive Directoryによってチーム分けをするか

Terraform Cloudにおいて「organization membership IDが新しく振られてしまう問題」というものがありました。
例えば[tfe_organization_membership](https://registry.terraform.io/providers/hashicorp/tfe/latest/docs/resources/organization_membership)であらかじめユーザの所属状況を定義しておいたとします。その後、ユーザが初めてSSOサインインした際に、新しくID (organization membership ID) が振られてしまい、tfstateと不整合が生じてしまう、というものです。

つまり、あらかじめ「ユーザのチーム所属状態」を定義しておくことができず、下記のいずれかの回避策を講じる必要があります。
- SSOサインインを待ってからCI/CDを走らせる
- あるいは、新しく振られたIDをterraform importする

これだと運用の難易度が高くなってしまいますので、回避策として、terraformでユーザのチーム所属を定義するのを諦め、Active Directoryのgroup側で定義するようにしました。

## Azure DevOps ApplicationをProject Collection Administratorで作成する際の注意点

Azure DevOps ApplicationをProject Collection Administratorの権限を持った人に作成してもらう必要があります。

Terraform CloudのAzure DevOps連携に、そのような強い権限を持つApplicationを接続することにリスクがあるものの、一方でworkspaceの作成をコード化しないと、管理者クラスの人が全てのworkspaceの作成を対応する必要があり、とても手間です。

よって、Terraform Cloudのコードレビューなどにより、間違いのあるコードの混入を防ぐ対策が必要です。

## Terraform Cloudにプロジェクトという概念が欲しかった日々だった
Terraform Cloudの感触としては、下記の図のように「project（仮）」の概念を新たに導入して、workspaceやvariable setsのアクセス範囲を簡単に定義できたら良いな、と思いました。
![terraform-cloud-project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236794/b6eccb03-e5ef-3538-33c3-b4a8e9f2981a.png)

今回は「開発チーム」と「運用チーム」に分離する例をとりあげましたが、その例で言うと「検証環境」と「本番環境」のprojectを作っておけば、teamとprojectの関係性を定義するだけで済み、Terraform Cloudの設定をさらにシンプルにできるはずです。

# エンジニア募集中です！
bitFlyerでは、このようなワークフロー改善や、サービスの安定化および効率化のための業務を日々行う[SREエンジニアの仲間を募集中](https://bitflyer.com/ja-jp/recruit/offers/224)です。ご興味を持たれた方、ぜひご応募ください！