出力を入力へ

プログラミングに関する自分が考えた事を中心にまとめます

terraformでAWS IAM ロールにポリシーを付与するためにmanaged_role_arnsを利用する

terraformでAWS でIAMロールを構築するとき、ロールとポリシーの設定方法として複数の手段がある。

  1. aws_iam_roleのmanaged_policy_arns属性を利用する
  2. aws_iam_roleのinline_policy属性を利用する
  3. aws_iam_role_policy_attachment を利用する
  4. aws_iam_role_policy を利用する
  5. aws_iam_policy_attachmentを利用する

自分は従来、aws_iam_role_policy_attachment を利用することが多かった。 IAM ロールを作り、IAMポリシーを作り、ロールとポリシーを紐付けるためにattachmentを作成するというのは直感的にもわかりやすかったため。 また、aws_iam_roleのドキュメントには aws_iam_policy_attachmentよりはaws_iam_role_policy_attachmentを推奨すると記載されており、他の方法との比較は記載されていないのでとりあえずはaws_iam_role_policy_attachmentでよいか、くらいの判断。

registry.terraform.io

構成ドリフトの発生

これで大きな問題はなかったが、最近ドリフト検知できないという問題にしばしば遭遇するようになった。IAMロールに手動で追加のポリシーをアタッチしてもterraform上では差分を検知できないので、検証のためにポリシーを付与したことを忘れて付与しっぱなしになっていた。 また、ポリシー付与が蓄積してIAMロールあたりの最大ポリシー数のクオータ(10ポリシーまで)にひっかかりterraform applyエラーになることもあった。

docs.aws.amazon.com

このような問題を回避するためにもterraformコードと実際のリソースが一致していることが重要になった。 これを解決する方法がaws_iam_roleのmanaged_policy_arns属性で、ここでポリシーarnを記載することで余計なポリシーを手動で付与すると差分として検知してくれる。これはより厳密にロールとポリシーを管理する方法としてよいと考えた。 実際試してみてよさそう。

managed_policy_arnsへの移行の注意点

aws_iam_role_policy_attachmentから managed_policy_arnsに移行する上での注意点として、この2つの方法は排他的に動作するので同時に指定できないという問題がある。 これはaws_iam_roleのドキュメントにも注意書きが記載されている。

同時に指定できないというのは1回のterraform applyにおいても同時に登場する場合も正常に動作してくれない。 具体的には1つのプルリクにおいて、aws_iam_role_policy_attachmentを削除して managed_policy_arnsに記載する場合で以下のようなdiffが生じる場合。

diff --git a/main.tf b/main.tf
index bb13f0d..0f3a7d6 100644
--- a/main.tf
+++ b/main.tf
@@ -1,19 +1,18 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
+
+  managed_policy_arns = [
+    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+  ]
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
-
-resource "aws_iam_role_policy_attachment" "this" {
-  role       = aws_iam_role.this.name
-  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
-}

このとき、terraform applyすると aws_iam_role_policy_attachment が削除されて、IAMロールにポリシーが何も付与されない状況が発生する。 ただし、terraform apply後もう1度terraform applyするとmanaged_policy_arnsが反映されるようになる。

$ terraform apply
aws_iam_role_policy_attachment.this: Refreshing state... [id=sample-role-20240123143715911100000001]
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  - destroy

Terraform will perform the following actions:

  # aws_iam_role_policy_attachment.this will be destroyed
  # (because aws_iam_role_policy_attachment.this is not in configuration)
  - resource "aws_iam_role_policy_attachment" "this" {
      - id         = "sample-role-20240123143715911100000001" -> null
      - policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" -> null
      - role       = "sample-role" -> null
    }

Plan: 0 to add, 0 to change, 1 to destroy.

...

$ terraform apply
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_iam_role.this will be updated in-place
  ~ resource "aws_iam_role" "this" {
        id                    = "sample-role"
      ~ managed_policy_arns   = [
          + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
        ]
        name                  = "sample-role"
        tags                  = {}
        # (8 unchanged attributes hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

このため、一時的にIAMロールが正常に動作しないタイミングが発生することを許容するなら2度applyを実行すれば話は簡単。

これが許容できない場合はremoved blockを利用し、一時的にリソースを管理外にするとよい。 具体的には、1回目の変更では aws_iam_role_policy_attachmentを削除しつつ、removed blockでterraformの管理外にする。

diff --git a/main.tf b/main.tf
index bb13f0d..ae5f752 100644
--- a/main.tf
+++ b/main.tf
@@ -1,19 +1,22 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
 
-resource "aws_iam_role_policy_attachment" "this" {
-  role       = aws_iam_role.this.name
-  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
+removed {
+  from = aws_iam_role_policy_attachment.this
+
+  lifecycle {
+    destroy = false
+  }
 }
$ terraform apply
aws_iam_role_policy_attachment.this: Refreshing state... [id=sample-role-20240123145145692300000001]
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

 # aws_iam_role_policy_attachment.this will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "aws_iam_role_policy_attachment" "this" {
        id         = "sample-role-20240123145145692300000001"
        # (2 unchanged attributes hidden)
    }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│ 
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - aws_iam_role_policy_attachment.this
│ 
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

2回目の変更では managed_policy_arnsで指定する。 こうすれば、2回目の変更はapplyで差分がなく、正常に実リソースとコードを一致させることができる。 aws_iam_role_policy_attachmentはIAMロールとIAMポリシーの関係を表現するためのリソースで、実際にAWS上に何かしらのリソースを構築するわけではないのでimportブロックでterraform管理に含める必要がないこともポイント。

diff --git a/main.tf b/main.tf
index ae5f752..0f3a7d6 100644
--- a/main.tf
+++ b/main.tf
@@ -1,22 +1,18 @@
 resource "aws_iam_role" "this" {
   name               = "sample-role"
   assume_role_policy = data.aws_iam_policy_document.assume_lambda.json
+
+  managed_policy_arns = [
+    "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+  ]
 }
 
 data "aws_iam_policy_document" "assume_lambda" {
   statement {
     actions = ["sts:AssumeRole"]
     principals {
       type        = "Service"
       identifiers = ["lambda.amazonaws.com"]
     }
   }
 }
-
-removed {
-  from = aws_iam_role_policy_attachment.this
-
-  lifecycle {
-    destroy = false
-  }
-}
$ terraform plan
data.aws_iam_policy_document.assume_lambda: Reading...
data.aws_iam_policy_document.assume_lambda: Read complete after 0s [id=2690255455]
aws_iam_role.this: Refreshing state... [id=sample-role]

No changes. Your infrastructure matches the configuration.

Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.