さらに一歩楽しむterraform. moduleでIAM UserとPolicy管理を簡素化しよう

俺です。

世界70億のterraform職人の皆様こんばんは。teraform職人の夜は遅いですね。

さてmodule使ってますか？
最近といってもつい数時間前から今まで自分が作ったterraform tfファイルのリファクタリングを始めました。

※同じ組織の方はGHEにpushしてるorenoシリーズリポジトリみてね(業務連絡)

今回はIAM Userとpolicyをmodule化して、
楽々使いまわせるようにする例を載せちゃいます。

# 参考というかこたえ

terraformのcommunity-moduleリポジトリがあるのでズバリこれで。
https://github.com/terraform-community-modules

さてやってみましょー

# moduleの作成

moduleとなるterraform tfファイルを作ります

例として以下の様なディレクトリ/ファイル構成にしておきます。
1tfにまとめてもいいんじゃねって思うのですが
まあーresourceはながーくなることもあるし、community-modulesがこんな感じになってるので
この辺は準拠しておいたほうがよさそうな感じですかねー。
各プロジェクトでルール作ってしまえば良いと思いますね。

## moduleのファイル用途

|ファイル名|用途|
|---|---|
|variables.tf|変数定義|
|iam_***.tf|resource定義|
|outputs.tf|戻り値定義|

## moduleのディレクトリ/ファイル構造

```
orenomac$ tree module/iam
module/iam
├── group
│   ├── iam_group.tf
│   ├── outputs.tf
│   └── variables.tf
├── policy
│   ├── aws_iam_policy.tf
│   ├── outputs.tf
│   └── variables.tf
├── role
└── user
    ├── aws_iam_user.tf
    ├── outputs.tf
    └── variables.tf
```

## IAM Group用モジュールを作る

- variables.tf

module resourceの引数に指定するvariableを定義します。

```
variable "aws_iam_groupname" {
  description = "iam group name"
}

variable "aws_iam_grouppath" {
  description = "iam group path(/users/)"
  default = "/"
}
```

- iam_group.tf

moduleで動作するresourceを定義します

```
resource "aws_iam_group" "iam-group" {
  name = "${var.aws_iam_groupname}"
  path = "${var.aws_iam_grouppath}"
}
```

- outputs.tf

module外から参照されるresourceでmodule内で定義された変数を参照するための戻り値を定義します。
outputを定義しておかないと、moduleで作られたresource内variableを、外部resourceで参照することができません。

```
output "name" {
  value = "${aws_iam_group.iam-group.name}"
}
```

## IAM User用モジュールを作る

- variables.tf

```
variable "aws_iam_username" {
  description = "iam user name"
}
variable "aws_iam_grouppath" {
  description = "iam group path(/users/)"
  default = "/"
}
```

- aws_iam_user.tf

```
resource "aws_iam_user" "iam-user" {
  name = "${var.aws_iam_username}"
  path = "${var.aws_iam_grouppath}"
}
```

- outputs.tf

```
output "name" {
  value = "${aws_iam_user.iam-user.name}"
}
```

## IAM Policy用モジュールを作る

- variables.tf

```
variable "aws_iam_policy_name" {
  description = "Policy Name"
  default = "iam-policy"
}

variable "aws_iam_policy_path" {
  description = "iam policy path"
  default = "/policy/"
}

variable "aws_iam_policy_description" {
  description = "iam policy description"
  default = "iam policy"
}
variable "aws_iampolicy_json" {
  description = "iampolicy json filename"
  default = "iam_policy.json"
}
```

- aws_iam_policy.tf

policyにはjsonファイルをそのまま放り込めるように組み込み関数fileを使います。

```
resource "aws_iam_policy" "iam-policy" {
  name = "${var.aws_iam_policy_name}"
  path = "${var.aws_iam_policy_path}"
  description = "${var.aws_iam_policy_description}"
  policy = <<EOF
${file("${var.aws_iampolicy_json}")}
EOF
}
```

- outputs.tf

arnを返すようにします。

```
output "name" {
  value = "${aws_iam_policy.iam-policy.name}"
}

output "arn" {
  value = "${aws_iam_policy.iam-policy.arn}"
}
```

# moduleを利用するtfの作成

こんなかんじです

```
orenomac$ tree examples/
examples/
├── datawanwan_policy.json
└── test_terraform.tf
```

- test_terraform.tf

メインとなるtfではmoduleに変数指定して呼び出すだけです。
sourceには使用するmoduleが格納されているディレクトリまでのパスを記述します。
本例ではディレクトリパスを指定してますが、github/bitbucketなどのパスを指定することもできます。

同僚の皆様GHEにpushしとくので使って下さいww(業務連絡

```
provider "aws" {
  region = "ap-northeast-1"
}

module "iam-group" {
  source = "../module/iam/group"
  aws_iam_groupname = "oreore"
  aws_iam_grouppath = "/oreore/"
}

module "iam-user" {
  source = "../module/iam/user"
  aws_iam_username = "iam-user"
  aws_iam_grouppath = "/oreore/users/"
}

module "iam-datawanwan" {
  source = "../module/iam/user"
  aws_iam_username = "iam-datawanwan"
  aws_iam_grouppath = "/oreore/users/"
}

module "iam-datawanwan_policy" {
  source = "../module/iam/policy/"
  aws_iam_policy_name = "datawanwan"
  aws_iam_policy_path = "/policy/"
  aws_iam_policy_description =  "datawanwan policy"
  aws_iampolicy_json = "datawanwan_policy.json"
}

/* moduleで定義されたresourceのvariablesを外部resourceから参照する例 */

resource "aws_iam_policy_attachment" "datawanwan" {
  name = "datawanwan"
  users = ["${module.iam-datawanwan.name}"]
  policy_arn = "${module.iam-datawanwan_policy.arn}"
}
```

- datawanwan_policy.json

datadogのドキュメントに書いてる通りのポリシーを準備しておきます。
module

```
{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Action": [
        "autoscaling:Describe*",
        "cloudformation:DescribeStacks",
        "cloudformation:DescribeStackEvents",
        "cloudformation:DescribeStackResources",
        "cloudformation:GetTemplate",
        "cloudfront:Get*",
        "cloudfront:List*",
        "cloudtrail:DescribeTrails",
        "cloudtrail:GetTrailStatus",
        "cloudwatch:Describe*",
        "cloudwatch:Get*",
        "cloudwatch:List*",
        "dynamodb:GetItem",
        "dynamodb:BatchGetItem",
        "dynamodb:Query",
        "dynamodb:Scan",
        "dynamodb:DescribeTable",
        "dynamodb:ListTables",
        "ec2:Describe*",
        "elasticache:Describe*",
        "elasticbeanstalk:Check*",
        "elasticbeanstalk:Describe*",
        "elasticbeanstalk:List*",
        "elasticbeanstalk:RequestEnvironmentInfo",
        "elasticbeanstalk:RetrieveEnvironmentInfo",
        "elasticloadbalancing:Describe*",
        "iam:List*",
        "iam:Get*",
        "route53:Get*",
        "route53:List*",
        "rds:Describe*",
        "rds:ListTagsForResource",
        "s3:List*",
        "sdb:GetAttributes",
        "sdb:List*",
        "sdb:Select*",
        "ses:Get*",
        "ses:List*",
        "sns:Get*",
        "sns:List*",
        "sqs:GetQueueAttributes",
        "sqs:ListQueues",
        "sqs:ReceiveMessage"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
```

# いざterraform plan/apply..のまえに

terraform getでmoduleを取得します

```
orenomac$ cd <path>/<to>/examples
orenomac$ terraform get
Get: file:///orenoterraform/module/iam/group
Get: file:///orenoterraform/module/iam/user
Get: file:///orenoterraform/module/iam/user
Get: file:///orenoterraform/module/iam/policy
```

# plan

planを実行します

```
orenomac$ terraform plan
Refreshing Terraform state prior to plan...


The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

+ aws_iam_policy_attachment.datawanwan
    name:             "" => "datawanwan"
    policy_arn:       "" => "${module.iam-datawanwan_policy.arn}"
    users.#:          "" => "1"
    users.XXXXXXXXXXX: "" => "iam-datawanwan"

+ module.iam-datawanwan_policy.aws_iam_policy.iam-policy
    arn:         "" => "<computed>"
    description: "" => "datawanwan policy"
    name:        "" => "datawanwan"
..省略..
```

# apply

apply叩く前にACCESS_KEYとSECRET_KEYは設定しておきましょうー。
direnvとか環境変数のexportで。

```
orenomac$ terraform apply
module.iam-user.aws_iam_user.iam-user: Creating...
  arn:       "" => "<computed>"
  name:      "" => "iam-user"
  path:      "" => "/oreore/users/"
  unique_id: "" => "<computed>"
module.iam-datawanwan_policy.aws_iam_policy.iam-policy: Creating...
  arn:         "" => "<computed>"
..省略..
```

```
orenomac$ terraform show
aws_iam_policy_attachment.datawanwan:
  id = datawanwan
  groups.# = 0
  name = datawanwan
  policy_arn = arn:aws:iam::XXXXXXXXXXX:policy/policy/datawanwan
  roles.# = 0
  users.# = 1
  users.XXXXXXXXXXXX = iam-datawanwan
```


ちなみに
moduleのoutputは、terraform outputで参照することはできません。

```
orenomac$ terraform output
The state file has no outputs defined. Define an output
in your configuration with the `output` directive and re-run
`terraform apply` for it to become available.
```

みんなterraform module使って楽しましょう的~

完