tjinjin's blog

さらなる高みに1nmずつ近づくブログ

Terraformのmoduleを使ってIAMロールを管理する

About

これまでコンソール上からIAMロールを作っていましたが、手動で作るのが疲れたのでTerraformでやるようにしました。

前提

会社の環境では複数のAWSアカウントを扱った開発をしています。それぞれのAWSアカウントでIAMユーザを作成しても良いのですが、ユーザの切り替えが大変です。そこで、任意のAWSアカウントで一つだけIAMユーザを作成し、他のアカウントにはIAMロールを作成することでSwitchRoleしています。

Switch Roleについてはこのあたりをご覧下さい!

ロールの切り替え(AWS マネジメントコンソール) - AWS Identity and Access Management

Swith Roleで複数のAWSアカウント間を切替える - Qiita

IAMロールを作る際には通常のpolicyの設定とAssume Roleの設定が必要でそのあたりの紐付けが面倒なのでTerraformを使うことにしました。

今回やりたかったこと

IAMロールは特定のグルーピングに応じて権限を分けたいと思っています。例えば、

  • インフラメンバーは全AWSアカウントのAdmin権限
  • 開発リーダーは全AWSアカウントのPowerUser権限
  • 開発メンバーは関わっているアカウントのPowerUser権限

などなど。なので、それぞれのグループは重複がない集合とします。

TerraformでIAMロールを作る

Terraformのリソースを見るとdata source/resourceに様々なリソースがあるので利用します。

aws_iam_policy_document

これはIAM policyをJSONで書くのではなく、HCL?っぽく書けるリソースです。

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["arn:aws:iam::123456789012::role/hogehoge"]
    }
  }
}

注意 aws_iam_policy_attachment

検証中にこのリソースを使ったときにテスト環境を壊しそうになったのでメモしておきます。ドキュメントにも書いてありますが、このリソースはある一つのポリシーにアタッチされているすべてのリソースに対して変更を加えようとします。例えば"AdministratorAccess"権限をIAMユーザ・IAMロール・IAMグループに設定している場合は、Terraform上に既存リソースを含む全てのリソースを書かないといけません。

例えばPowerUserをすでにアタッチされているIAMリソースがいるとした時に、下記のようなファイルを作ります。

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["*"]
    }
  }
}

resource "aws_iam_role" "role" {
  name = "test-role"
  assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
}

resource "aws_iam_policy_attachment" "test-attach" {
  name       = "test-attachment"
  roles      = ["${aws_iam_role.role.name}"]
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}

この状態で一度applyします。その後planをしてみると、何故かTerraformで管理していないリソースに変更が入ろうとしている状態になります。

~ aws_iam_policy_attachment.test-attach
    roles.#:          "12" => "1"
...

tfstateを見るとわかりますが、PowerUserが付いているIAMロールなどがTerraform管理下に入るようになっています。

The aws_iam_policy_attachment resource is only meant to be used once for each managed policy. All of the users/roles/groups that a single policy is being attached to should be declared by a single aws_iam_policy_attachment resource.

AWS: aws_iam_policy_attachment - Terraform by HashiCorp

とりあえずできたやつ

現状はこんな書き方に落ち着きました。もっときれいにかけそうだとは思っていますが、なかなか(ぐぬぬ

$ tree
.
├── README.md
├── aws_provider.tf
├── aws_provider.tf.sample
├── main.tf
├── main.tf.sample
├── module
│   ├── group
│   │   ├── admin
│   │   │   ├── main.tf
│   │   │   └── variables.tf
│   │   ├── developer
│   │   │   ├── main.tf
│   │   │   └── variables.tf
│   │   └── other
│   │       ├── main.tf
│   │       └── variables.tf
│   └── iam_role
│       ├── main.tf
│       └── variable.tf

aws_provider.tfやmain.tfは.gitignoreでコミットしない形にしています。環境によって設定を変える想定です。

ルートのmain.tfで各グループ毎にpolicyを指定しています。

# policy_arn
# set AWS maneged policy
# ex)
# policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
# policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
# policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"

module "admin" {
  source = "./module/group/admin"

  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

module "developer" {
  source = "./module/group/developer"

  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
}

module "other" {
  source = "./module/group/other"

  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

groupというディレクトリ配下でグルーピングしたユーザ群を作っています。ここはそれぞれのユーザ毎に設定が違うので頑張って書きました。。。

## module/group/admin/main.tf
module "user1" {
  source = "../../iam_role"

  role_arn      = "arn:aws:iam::123456789012:user/user1"
  iam_role_name = "user1"
  policy_arn    = "${var.policy_arn}"
}

module "user2" {
  source = "../../iam_role"

  role_arn      = "arn:aws:iam::123456789012:user/user2"
  iam_role_name = "user2"
  policy_arn    = "${var.policy_arn}"
}

うん、まあ、という感じですね。。。List Mapで書ければもうちょっとスマートに書けそうだと思ってますが、うまく行かなかったので今後の課題です。

実際の設定を書くところはこんな感じになりました。

## module/iam_role/main.tf
data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "AWS"
      identifiers = ["${var.role_arn}"]
    }
  }
}

resource "aws_iam_role" "role" {
  name               = "${var.iam_role_name}"
  assume_role_policy = "${data.aws_iam_policy_document.assume_role.json}"
}

resource "aws_iam_role_policy_attachment" "hoge" {
  role       = "${aws_iam_role.role.name}"
  policy_arn = "${var.policy_arn}"
}

まとめ

moduleを使ったのが初めてだったんですが、結構簡単でよかったです。最初moduleのsourceに変数を使うつもりで書いてましたが、どうやら議論中?のようで早く方向性決まるといいなーと思いました。同じようなことをしている方やTerraform職人の知見を求めています!

terraform get: can't use variable in module source parameter? · Issue #1439 · hashicorp/terraform · GitHub