Terraform aws_iam_policy_attachment Policy 竞争问题

自从  Terraform  resource "aws_iam_role" 不推荐使用 "inline_policy" 和  "managed_policy_arns" 以后,就尝试了用 "aws_iam_policy_attachment" 来为 iam role 指定 AWS  内置和自定义的 IAM policy。因为在官方文档 aws_iam_role 中最先看到的就是 aws_iam_policy_attachment, 其实仔细阅读该文档的话,建议是


  1. 用 "aws_iam_role_policy" 来代替 "inline_policy"
  2. 用  "aws_iam_role_policy_attachment"  来代替 "managed_policy_arns"

然后在项目中为避免 inline_policy 和 managed_policy_arns 的警告而选择了通用的 aws_iam_policy_attachment, 同时原来也用的 "aws_iam_policy",而非 "aws_iam_role_policy。 所以才撞入到以前一直用 aws_iam_role(inline_policy, managed_policy_arns) + aws_iam_policy 就没出过问题。

问题重现

下面来重现一下问题,想要在独产的两个模块中(有各自的 terraform 状态文件),分别创建一个 IAM role,分别赋予自定义的 Policy 和系统 Policy AWSLambdaBasicExecutionRole, 即

IAM Role: test1-lambda-role IAM Role: test2-lambda-role               

它们的 Terraform 代码相似,分别是两个目录中 test1/main.tf 和  test2/main.tf

test1/main.tf 文件内容为
 1resource "aws_iam_role" "test1_lambda_role" {
 2  name = "test1-lambda-role"
 3  assume_role_policy = jsonencode(
 4    {
 5      Version = "2012-10-17",
 6      Statement = [
 7        {
 8          Effect = "Allow",
 9          Principal = {
10            Service = "lambda.amazonaws.com"
11          },
12          Action = "sts:AssumeRole"
13        }
14      ]
15  })
16}
17
18resource "aws_iam_policy_attachment" "lambda-execution-role" {
19  name       = "test1-lambda-attach-lambda-execution-role"
20  roles      = [aws_iam_role.test1_lambda_role.name]
21  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
22}
23
24resource "aws_iam_policy_attachment" "lambda-policy" {
25  name       = "test1-lambda-attach-lambda-policy"
26  roles      = [aws_iam_role.test1_lambda_role.name]
27  policy_arn = aws_iam_policy.test1-inline-policy.arn
28}
29
30resource "aws_iam_policy" "test1-inline-policy" {
31  name = "test1-lambda-inline-policy"
32  policy = jsonencode({
33    Version = "2012-10-17"
34    Statement = [
35      {
36        "Action" : [
37          "s3:GetObject"
38        ],
39        "Effect" : "Allow",
40        "Resource" : [
41          "arn:aws:s3:::test1/*"
42        ]
43      }
44    ]
45  })
46}

test2/main.tf,文件内容与 test1/main.tf 基本一致,只是把所有出现的 "test1" 替换为 "test2",所以此处不再列出来

第一次执行创建的 test1-lambda-role 和 test2-lambda-role 的权限都正常, 就是如最前面两张图所显示的那样

创建 test1-lambda-role
cd test1
terraform apply
完后用命令 aws iam list-attached-role-policies --role-name test1-lambda-role 看到的
 1{
 2    "AttachedPolicies": [
 3        {
 4            "PolicyName": "test1-lambda-inline-policy",
 5            "PolicyArn": "arn:aws:iam::069762108088:policy/test1-lambda-inline-policy"
 6        },
 7        {
 8            "PolicyName": "AWSLambdaBasicExecutionRole",
 9            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
10        }
11    ]
12}

创建 test2-lambda-role
cd ../test2
terraform apply
完后用命令 aws iam list-attached-role-policies --role-name test2-lambda-role 看到的
 1{
 2    "AttachedPolicies": [
 3        {
 4            "PolicyName": "test2-lambda-inline-policy",
 5            "PolicyArn": "arn:aws:iam::069762108088:policy/test2-lambda-inline-policy"
 6        },
 7        {
 8            "PolicyName": "AWSLambdaBasicExecutionRole",
 9            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
10        }
11    ]
12}

现在一切正常。但如果再回过头再执行 test1 的 terraform apply 时奇怪的事情就发生了
cd ../test1
terraform apply
现在看到的 plan 是
 1  # aws_iam_policy_attachment.lambda-execution-role will be updated in-place
 2  ~ resource "aws_iam_policy_attachment" "lambda-execution-role" {
 3        id         = "test1-lambda-attach-lambda-execution-role"
 4        name       = "test1-lambda-attach-lambda-execution-role"
 5      ~ roles      = [
 6          - "AWSDeepRacerLambdaAccessRole",
 7          - "test2-lambda-role",
 8            # (1 unchanged element hidden)
 9        ]
10        # (3 unchanged attributes hidden)
11    }
12
13Plan: 0 to add, 1 to change, 0 to destroy.

明明是在操作 test1 模块,却看到了 test2-lambda-role 的内容,而且 AWSDeepRacerLambdaAccessRole 也是凭空出现的。

输入 yes 应用更改,然后查看 test1-lambda-role 和 test2-lambda-role 是否有变化
 1  ~ aws iam list-attached-role-policies --role-name test1-lambda-role
 2{
 3    "AttachedPolicies": [
 4        {
 5            "PolicyName": "test1-lambda-inline-policy",
 6            "PolicyArn": "arn:aws:iam::069762108088:policy/test1-lambda-inline-policy"
 7        },
 8        {
 9            "PolicyName": "AWSLambdaBasicExecutionRole",
10            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
11        }
12    ]
13}
14  ~ aws iam list-attached-role-policies --role-name test2-lambda-role
15{
16    "AttachedPolicies": [
17        {
18            "PolicyName": "test2-lambda-inline-policy",
19            "PolicyArn": "arn:aws:iam::069762108088:policy/test2-lambda-inline-policy"
20        }
21    ]
22}

test1-lambda-role 没问题,但 Terraform 在执行 test1 模块时却把 test2-lambda-role 给影响了,把它持有的 AWSLambdaBasicExecutionRole 给拿走了。

再执行 test2 的 Terraform 代码
cd ../test2
terraform apply
plan 里的显示的内容更有意思了
 1  # aws_iam_policy_attachment.lambda-execution-role will be updated in-place
 2  ~ resource "aws_iam_policy_attachment" "lambda-execution-role" {
 3        id         = "test2-lambda-attach-lambda-execution-role"
 4        name       = "test2-lambda-attach-lambda-execution-role"
 5      ~ roles      = [
 6          - "test1-lambda-role",
 7          + "test2-lambda-role",
 8        ]
 9        # (3 unchanged attributes hidden)
10    }

输入 yes, 应用更改,再检查 test1-lambda-role 和 test2-lambda-role 的内容
 1  ~ aws iam list-attached-role-policies --role-name test1-lambda-role
 2{
 3    "AttachedPolicies": [
 4        {
 5            "PolicyName": "test1-lambda-inline-policy",
 6            "PolicyArn": "arn:aws:iam::069762108088:policy/test1-lambda-inline-policy"
 7        }
 8    ]
 9}
10  ~ aws iam list-attached-role-policies --role-name test2-lambda-role
11{
12    "AttachedPolicies": [
13        {
14            "PolicyName": "test2-lambda-inline-policy",
15            "PolicyArn": "arn:aws:iam::069762108088:policy/test2-lambda-inline-policy"
16        },
17        {
18            "PolicyName": "AWSLambdaBasicExecutionRole",
19            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
20        }
21    ]
22}

看到应用 test2 时,把 test1-lambda-role 的 AWSLambdaBasicExecutionRole 给偷走了,相当于是因为 test1-lambda-role 和 test2-lambda-role 共享了 AWSLambdaBasicExeutionRole,执行 test1 和 test2 时会在这两个 role 之间移动 AWSLambdaBasicExecutionRole,都试图独占这个 Policy。

此时再回到 test1 去执行 terraform 脚本,那么 AWSLambdaBasicExecutionRole 又被移动到了 test1-lambda-role。如果在该 AWS 帐号下有其他更多的 IAM Role 使用了这种 Terraform 脚本来创建的话,那么会有更多的 IAM Role 加入到这种争夺 AWSLambdaBasicExecutionRole 的混战当中来。

初步怀疑(毫不相关)

怀疑是不是因为  aws_iam_policy_attachment 后的标识有问题,把它们换成带特定前缀的标识

resource "aws_iam_policy_attachment" "lambda-execution-role" 换成 resource "aws_iam_policy_attachment" "test1-lambda-execution-role"

resource "aws_iam_policy_attachment" "lambda-policy" 换成 resource "aws_iam_policy_attachment" "test1-lambda-policy"

在 test2 中作相应用改动,用 test2 作为前缀,但故障依旧。也证明了以往的经验,resource 的标识作用域只在当前模块下的相同资源。

只尝试用 aws_iam_role_policy_attachment(故障依旧)

后来又尝试了把 aws_iam_policy_attachment 替换成了特定的 aws_iam_role_policy_attachment

比如在 test1/main.tf 中的两个 aws_iam_role_policy_attachment 分别变为
1resource "aws_iam_role_policy_attachment" "lambda-execution-role" {
2  role      = aws_iam_role.test1_lambda_role.name
3  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
4}
5
6resource "aws_iam_role_policy_attachment" "lambda-policy" {
7  role      = aws_iam_role.test1_lambda_role.name
8  policy_arn = aws_iam_policy.test1-inline-policy.arn
9}

使用  aws_iam_role_policy_attachment 不用指定 name 了,并且 role 是单数

在 test2/main.tf 中也作相应的修改,注意 test1 的地方换成 test2 就是

没有什么变化,还是一样的故障,互相争抢 AWSLambdaBasicExeutionRole

把 aws_iam_policy 替换成 aws_iam_role_policy(问题解决)

由于 aws_iam_role_policy 自带  role 属性,所以就可以少写下面这个 aws_iam_policy_attachment
resource "aws_iam_policy_attachment" "lambda-policy" {
所以整个  test1/main.tf 除了 aws_iam_role 外的内容就是
 1resource "aws_iam_role_policy_attachment" "lambda-execution-role" {
 2  role      = aws_iam_role.test1_lambda_role.name
 3  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
 4}
 5
 6resource "aws_iam_role_policy" "test1-inline-policy" {
 7  role = aws_iam_role.test1_lambda_role.name
 8  name = "test1-lambda-inline-policy"
 9  policy = jsonencode({
10    Version = "2012-10-17"
11    Statement = [
12      {
13        "Action" : [
14          "s3:GetObject"
15        ],
16        "Effect" : "Allow",
17        "Resource" : [
18          "arn:aws:s3:::test1/*"
19        ]
20      }
21    ]
22  })
23}

在 test2/main.tf 也作相应的修改

这种改法问题可以得到解决,test1-lamba-role 和  test2-lambda-role 不再相关了,此时 test1-lambda-inline-policy (或 test2-lambda-inline-policy)不再是作为 Customer managed policy 了, 而是真正的 Customer inline

这样也更贴合 inline-policy 的初衷,原本就是特别为该 Role 创建的 policy 应该保持为私有  Customer inline, 而不该出现在 AWS IAM 的 Policies 列表中。

这里我们恰好用 aws_iam_role_policy_attachment 避开了一个问题,如果把这里的 aws_iam_role_policy_attachment 换回成 aws_iam_policy_attachment 依然会出现争夺 AWSLambdaBasicExecutionRole 的问题。稍为小结一下,出现故障的三种情况分别是

  1. aws_iam_policy + aws_iam_policy_attachement
  2. aws_iam_policy + aws_iam_role_policy_attachment
  3. aws_iam_role_policy + aws_iam_policy_attachement

用 aws_iam_role_policy + aws_iam_policy_attachment 就正常了,所以 aws_iam_role_policy_attachement 严格来说不能算作 aws_iam_policy_attachment 的泛化。

另一种解法(aws_iam_policy + aws_iam_role_policy_attachment(for_each))

看到上一节的 #2 简单用 aws_iam_policy 和 aws_iam_role_policy_attachment 还是有问题,  但是配合后者的 for_each 就不一样了,在 test1/main.tf 中除去 aws_iam_role 之外的代码可以写成
 1resource "aws_iam_role_policy_attachment" "lambda-execution-role" {
 2  for_each = {
 3    inline = aws_iam_policy.test1-inline-policy.arn,
 4    lambda = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
 5  }
 6
 7  role       = aws_iam_role.test1_lambda_role.name
 8  policy_arn = each.value
 9}
10
11resource "aws_iam_policy" "test1-inline-policy" {
12  name = "test1-lambda-inline-policy"
13  policy = jsonencode({
14    Version = "2012-10-17"
15    Statement = [
16      {
17        "Action" : [
18          "s3:GetObject"
19        ],
20        "Effect" : "Allow",
21        "Resource" : [
22          "arn:aws:s3:::test1/*"
23        ]
24      }
25    ]
26  })
27}

test2/main.tf 中也作相应的更改,这样的话执行 test1 和 test2 的 terraform 后,test1-lambda-role 和 test2-lambda-role 也能互不相关
 1  ~ aws iam list-attached-role-policies --role-name test1-lambda-role
 2{
 3    "AttachedPolicies": [
 4        {
 5            "PolicyName": "test1-lambda-inline-policy",
 6            "PolicyArn": "arn:aws:iam::069762108088:policy/test1-lambda-inline-policy"
 7        },
 8        {
 9            "PolicyName": "AWSLambdaBasicExecutionRole",
10            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
11        }
12    ]
13}
14  ~ aws iam list-attached-role-policies --role-name test2-lambda-role
15{
16    "AttachedPolicies": [
17        {
18            "PolicyName": "terraform-20251114035130238700000001",
19            "PolicyArn": "arn:aws:iam::069762108088:policy/terraform-20251114035130238700000001"
20        },
21        {
22            "PolicyName": "AWSLambdaBasicExecutionRole",
23            "PolicyArn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
24        }
25    ]
26}

重试 aws_iam_policy + aws_iam_policy_attachment(for_each)(仍然失败)

但是对 aws_iam_policy_attachment 还存有一线希望的话,重新试下 aws_iam_policy + aws_iam_policy_attachment(for_each) 的组合
 1resource "aws_iam_policy_attachment" "lambda-execution-role" {
 2  name = "test1-lambda-attach-policy"
 3  for_each = {
 4    inline = aws_iam_policy.test1-inline-policy.arn,
 5    lambda = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
 6  }
 7
 8  roles       = [aws_iam_role.test1_lambda_role.name]
 9  policy_arn = each.value
10}
11
12resource "aws_iam_policy" "test1-inline-policy" {
13    ......

依然不奏效,问题依旧。

总结

通过以上种种尝试,必须要总结一下有效的解决办法是

  1. aws_iam_role_policy + aws_iam_role_policy_attachment: 在 aws_iam_role_policy 可以指定关联的 iam_role, 将此作为 Customer inline policy, 用 aws_iam_role_policy_attachment 挂载别的 policy。有了 aws_iam_role_policy 的加持,即使使用多个  aws_iam_role_policy_attachment 或是 for_each 都可以挂载上多个其他的  policy。
  2. aws_iam_policy 时, 只用一个 aws_iam_role_policy_attachment, 在其中用 for_each 可以同时挂载包括自定义的或系统定义的多个 policy

以上第一种方法为首选,且保持专属的 Policy 为  Customer inline。

用 aws_iam_role_policy_attachment 关联的 policy 是 AWS managed 或 Customer managed.

请优先使用 role_policy 相关的资源,能用  aws_iam_role_policy 就不用  aws_iam_policy, 能用  aws_iam_role_policy_attachment 就不用 aws_iam_policy_attachment。从本文的情景来看使用 aws_iam_policy_attachment 总是会出现竞争共有 Policy 的问题。

最后,以上内容以供参考,实际表现也可能有所不同。 本文链接 https://yanbin.blog/terraform-aws_iam_policy_attachment-policy-issue/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。