エムスリーテックブログ

エムスリー(m3)のエンジニア・開発メンバーによる技術ブログです

1つの terraform で複数 AWS Account をまとめて構築・管理する

この記事は terraform Advent Calendar 2019, エムスリー Advent Calendar 2019 の 3 日目の記事です。

f:id:Saiya:20191110233938p:plain:w300
All your AWS Accounts are belong to us. *1

こんにちは、ここ数年で terraform で書いた aws_vpc + google_compute_network の数がようやっと 30 個ぐらいになろうかというエンジニア/CTOの矢崎 id:saiya です。

弊社でまとまった規模の AWS 環境を扱う際に、1 つの terraform プロジェクトで複数の AWS アカウントに対してまとめて plan & apply できるようにしていたのですが、その方法が意外と調べにくい様子でしたので、まとめてみました。

なお、やり方が分かってしまえば実施するのは結構簡単です。

これによって出来ること

  • 1 回の terraform apply で複数の AWS アカウントを一度に操作できるようになります
  • 1 つの terraform の中で複数の AWS アカウントのリソースを相互に参照できるので、アカウント間で相互依存する記述を非常に素直・シンプルに記述できます
    • 例えば VPC Peering の これこれ のペアを 1 ファイルにまとめて書いたりできます。Transit Gateway でも同様。

なお、今回紹介する手法ではアカウントが同一 AWS Organization に属する必要もありません *2

前提条件

  • "terraform 実行側アカウント" 1 つが作成済みであり、 "terraform 実行対象アカウント" も 1 つ以上既にあるものとします
    • 後述するように "terraform 実行側アカウント" はインフラの中央管理に特化したアカウントにしておく方が、セキュリティ上扱いやすいです
  • "terraform 実行側アカウント" には terraform 実行用の IAM User や API Key が設定済みであるとします
    • 特に policy (権限)を持たない User を作成して API Key を発行しておくだけで良いです

Step 1. terraform 実行用の IAM group の準備

terraform 実行側アカウント側に IAM group を作成します (以降、terraform group)。この group 作成作業は AWS アカウントの数に関係なく 1 回行うだけです。

Group の作り方には特殊な点はありません。Group 自体に付与する policy (権限) も何も設定する必要はありません。

ただし、この group で terraform 実行側アカウント自体への terraform 適用なども行うのであれば、ここで AdministratorAccess などの権限を付与しておくと良いです。

Group を用意したら、terraform の実行に使う IAM user を group に所属させておきます。

以降の手順でこの group からの terraform 実行対象アカウントへの操作を許可します。

( なお、group にしなくても IAM user でも実現は可能ですが、設定の量が terraform を実行する user 数 * account 数 の掛け算になってしまうので、IAM group を使う手順をここでは紹介しています )

Step 2. terraform 実行対象アカウントで terraform group 用の Role を作成

terraform 実行対象アカウント側で以下のようにして IAM Role を作成します。この手順は terraform 実行対象アカウントそれぞれ全てで実施する必要があります。

f:id:Saiya:20191110235940p:plain

まず、上記のように role 作成画面で "Another AWS Account" を選択し、terraform 実行側アカウントの ID を指定します。なお、よくあるハイフンありの形式はバリデーションエラーになるので、ハイフン無しで入れる必要があります。

f:id:Saiya:20191111000239p:plain

次に role に設定する policy を選択します。ここでは terraform を実行するために必要な権限を指定すれば良いです。組織の運用・セキュリティポリシー次第ですが、IAM も含めて terraform 管理にするならば AdministratorAccess にしてしまって良いでしょう。

その次には tag の設定画面が出ますが、本手順には無関係なのでお好みで設定の上で次に進みます。

f:id:Saiya:20191111000610p:plain

最後に Role 名の入力画面 兼 確認画面 が出ますので、お好みのロール名を入力し、 Trusted entities に書かれているアカウント ID が terraform 実行側アカウントの ID であることをしっかり確認してから *3 、Create role しましょう。

注: ここで誤ったアカウント ID を指定してしまうとかなりのセキュリティリスクになります

Step 3. terraform 実行用の IAM group に AssumeRole 権限を付与

terraform 実行側アカウント側の IAM group が、先の手順で作成した role に成り代わる許可(AssumeRole)を設定します。

f:id:Saiya:20191111002550p:plain

Step 1 にて IAM で用意した terraform group の編集画面を開き、inline policy の編集画面を開きます。既に存在する場合は既存の policy を再編集して追記します。コンソール画面で作業する場合、inline policy 編集画面の開き方が分かりにくいのですが、以下のようにします:

  • 初回の場合: (上記の画像を参照) "Inline Policies" のシェブロン(下向き三角)をクリックし、出てきた "To create one, click here." リンクを押す
  • 2 回目以降: "Inline Policies" に作成済みの policy があるので "Edit Policy" リンクを押します

f:id:Saiya:20191111003012p:plain

初回の場合、上のように policy の作成方法を聞かれると思いますが、Custom Policy を選択し "Select" ボタンを押すことで JSON の編集画面を開きます (なぜかボタンのラベルが "Select" ですが、これが JSON 編集画面への導線です)。

f:id:Saiya:20191111003257p:plain

JSON の編集画面では、上記のように sts:AssumeRole を記述します。"Resource" には Step 2 にて terraform 実行対象アカウント側で作成した Role の ARN を書きます。Policy name は好きな名前で設定してください。

最後に Apply Policy ボタンを押すことで保存します。

これで AWS IAM 周りのセットアップは全て完了です。

Step 4. terraform 側の provider 記述

ここまでに用意した group や role を活用するために、terraform 側の記述にも一捻り加えます。

普段 terraform を使う場合は aws provider を 1 つだけ記述することが多いと思いますが、今回は管理対象のアカウントの数だけ以下のように記述します:

provider "aws" {
  alias = "accountA"

  version = "~> 2.35"
  region = "ap-northeast-1"

  assume_role {
    role_arn = "arn:aws:iam::アカウントID:role/ロール名"
  }
}

provider "aws" {
  alias = "accountB"

  version = "~> 2.35"
  region = "ap-northeast-1"

  assume_role {
    role_arn = "arn:aws:iam::アカウントID:role/ロール名"
  }
}

... 以下略 ...

そして、AWS の resource を定義する際には以下のように指定します:

resource "aws_ec2_transit_gateway_vpc_attachment_accepter" "sample" {
  provider = aws.accountA    // ← この resource を accountA で作成する
  
  ...
}

このように provider を明示することで resource をどのアカウントに作るかを制御できます。なお provider を明示しない場合、alias 名を持たない provider がデフォルトで利用されます。

ここまでで目的は実現されていますので、上記のような記述をした状態で terraform apply などができます。権限の設定に問題がなければ、1 つの terraform project から複数の account の resource を制御できるはずです。

また、見ての通り普通に terraform の resource として書いているだけです。いつもの terraform と同じく aws_hogehoge.id.attribute という形の式で resource の値も参照でき、アカウントをまたいでも何事もなく機能します。さらに terraform の data resource 機能も当たり前に使えるため、各 account の状態に依存する terraform すらも書けます。

Tips: provider の module への受け渡し & module の汎用化

ところで、毎回 provider = を resource に書くのは面倒ではないでしょうか。

実は以下のように module 機能を使うと楽をすることができます:

module "accountA_main_vpc" {
  source = "./modules/vpc"

  providers = {
    aws = aws.accountA
  }
}

このようにすると、module の中で provider を省略した場合に、親 module における alias = accountA の provider が使用されるので、毎回 provider を書く必要がありません。

しかもこの方法では module の中で provider 名を決め打ちすることを防げるため、1 つの module のソースを汎用的に使い回すことができます。例えば上記の例に加えて以下のようにすることができます:

module "accountB_main_vpc" {
  source = "./modules/vpc"   // 上の例と同じ module のソースコード

  providers = {
    aws = aws.accountB  // ここを変えている
  }
}

このようにすることで 1 つの module のソースで、相異なる AWS アカウントに同じ resource を構築でき、大変 DRY に記述できます。

なお、terraform の機能では module に複数の provider を渡すことも(もちろん)可能です (Transit Gateway, VPC Peering などで有用)。そういった応用については 公式ドキュメント で説明されている機能一覧を見ていただけると、ここまでの説明と併せておわかりになられるものと思います。

セキュリティ面

1 terraform project で複数アカウントを管理するのは便利ですが、以下の点では留意が必要でしょう:

  1. terraform 実行側のアカウントの terraform group に入るだけで沢山のアカウントに対する強い権限が得られてしまう
    • 当たり前ですが terraform 実行側アカウントの IAM 系の権限を与えると容易に沢山のアカウントを触れる状態が生まれます
      • ちょっとした運用ツールを作るために権限を渡して〜 とかやるときに要注意
    • terraform 実行側のアカウントは沢山のアカウントを一括管理する用途だけに限定して使うようにしましょう
  2. セキュリティ上の single point of failure になる
    • terraform 実行側アカウントの IAM User のクレデンシャル漏洩などが起きるとシャレにならないです
    • 2要素認証の必須化や GuardDuty の全リージョン構築 あたりはやっておいたほうが良いでしょう

Pro tips: terraform の分割

今回は 1 つの terraform project で複数の環境を管理する話をしましたが、実際には 1 つではなくいくつかの terraform project に分けたほうが良いことも多いです。特に VPC・ネットワーク周り と 個別のアプリケーション・システム は管理主体・変更頻度・影響範囲などが異なるので分けたほうが良いでしょう。

しかし terraform project を分けるとしても、今回紹介したノウハウがあれば AWS Account の境界に縛られずに terraform project の構成を考えることが出来るのは大きいです。例えば Transit Gateway を使う時は VPC, Subnet などのネットワーク足回りは 1 project で構築してしまったほうが大分扱いやすいです。

また、実は 1 terraform project を後で分けることも可能です。意外に知られていないのですが terraform state 系コマンドを上手く使うことで楽に分割できます (需要があればこの話もまとめてみようと思っています)。その意味でも、1 terraform project で集約して構築する手法は選択肢としてアリなのではないかと思っています。

併せて読みたい

qiita.com

We are hiring!

エムスリーでは AWS, GCP の利用を推進しております(すでに結構使っていますが)。また、クラウドに限らず、あらゆるテクノロジーを活かすことで生産性・コストパフォーマンスを向上させ、医療コストを減らし社会課題に対して貢献することや 1 人でも健康で幸せな人を増やそうとしております。

もし少しでもご興味を持っていただけましたら、お気軽にカジュアル面談などを下記ページよりご連絡いただけますと幸甚であります:

jobs.m3.com

*1:ずいぶん変な英語だなぁ、と思った方はこちらをご参照ください: https://ja.wikipedia.org/wiki/All_your_base_are_belong_to_us

*2:Transit Gateway などの活用を考えると属していたほうが便利ですが

*3:図では削除しているのでアカウント ID が見えないですが、実際はそこに表示されるはずです