Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

iam-eks-role cannot attach a policy created in the same module using role_policy_arns input #193

Closed
SilverXXX opened this issue Feb 19, 2022 · 12 comments · Fixed by #250
Closed

Comments

@SilverXXX
Copy link

Description

When using iam-eks-role i cannot pass a role_policy_arn i created in the same module

Versions

  • Terraform: 1.1.4
  • Provider(s):
    • registry.terraform.io/hashicorp/aws: 3.74.0
  • Module:
    • terraform-aws-modules/eks/aws: 18.2.7
    • terraform-aws-modules/iam/aws//modules/iam-eks-role: 4.13.1

Reproduction

Using the code snippet below and

Code Snippet to Reproduce

resource "aws_iam_policy" "alb_load_balancer" {
    name        = "K8SALBController"
    path        = local.base_iam_path
    description = "Policy that allows k8s load balancer controller to provisione alb/elb"
    policy = file("${path.module}/policies/alb-policy.json")         
}

module "aws_alb_controller_role" {
  source = "terraform-aws-modules/iam/aws//modules/iam-eks-role"
  //source  = "terraform-aws-modules/iam/aws//modules/iam-assumable-role-with-oidc"
  version = "4.13.1"

  create_role      = true
  role_name = "aws-alb-controller"
  role_path = local.base_iam_path
  role_description = "IRSA role for load balancer controller"

  role_policy_arns               = [aws_iam_policy.alb_load_balancer.arn]
  cluster_service_accounts = {
    "${var.cluster_name}" = [
      "kube-system:aws-alb-controller"
    ]
  }
  depends_on = [
    module.eks.cluster_id
  ]
}

Expected behavior

Policy should be attached

Actual behavior

I get the error below

Terminal Output

│ Error: Invalid for_each argument
│
│   on .terraform\modules\eks.aws_node_termination_handler_role\modules\iam-eks-role\main.tf line 76, in resource "aws_iam_role_policy_attachment" "custom":
│   76:   for_each = toset([for arn in var.role_policy_arns : arn if var.create_role])
│     ├────────────────
│     │ var.create_role is true
│     │ var.role_policy_arns is list of string with 1 element
│
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.

Additional context

Using

resource "aws_iam_role_policy_attachment" "custom" {
  role       = module.aws_alb_controller_role.iam_role_name
  policy_arn = aws_iam_policy.alb_load_balancer.arn
}

I am able to attach a policy.
Since this is a common policy i can user terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks, but for a custom policy it wouldn't work

@bryantbiggs
Copy link
Member

ya, this is an issue related to hashicorp/terraform#4149

the policy you are referring to has to exist before it can be referenced in role_policy_arns (i.e. - terraform apply -target aws_iam_policy.alb_load_balancer.arn -y && terraform apply -y)

@lorengordon
Copy link

It's possible to write the module in a way that does not encounter this problem by taking a separate argument that is fully known, in this case the "name" of the policy, and using that in the for_each expression. I always prefer that pattern to avoid exactly this issue.

@bryantbiggs
Copy link
Member

I don't follow, do you have an example?

petur added a commit to petur/terraform-aws-iam that referenced this issue Mar 3, 2022
…ules#193)

This is the same method as is used in iam-assumable-role-with-oidc.
Since the argument to count is a list, not a set, the number of
elements doesn't depend on the values, and terraform can decide how
many elements are needed before creating the policies.
@webdog
Copy link

webdog commented Mar 9, 2022

To the O.P.s comment:

Since this is a common policy i can user terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks, but for a custom policy it wouldn't work

Do you mean a custom policy in the sense of a customer managed policy? I was able to create a policy in a separate module and use aws_iam_role_policy_attachment to attach this new customer-managed policy to the role created by iam_role_for_service_accounts, and avoid the for_each issue.

module.eks_iam is a local module I'm using for IAM policy management, in the code block below.

module "iam_role_for_service_accounts" {
  source = "registry.terraform.io/terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  role_name = lower(var.cluster_info.name)
  version = "4.14.0"

  oidc_providers = {
    one =  {
      provider_arn = module.eks.oidc_provider_arn
      namespace_service_accounts = [ "${var.kube_namespace}:${var.kube_service_account_name}" ]
    }
  }
}

resource "aws_iam_role_policy_attachment" "serviceAccountPolicyAttach" {
  policy_arn = module.eks_iam.policy_arn
  role       = module.iam_role_for_service_accounts.iam_role_name
}

Screenshot of the Role created by this module, with my customer-managed policy attached:
image

@lorengordon
Copy link

lorengordon commented Mar 9, 2022

I don't follow, do you have an example?

Yes, if you create a resource and also in the same config/state attempt to reference an attribute of that resource in the key of the for_each expression, then you will encounter the error mentioned in the OP:

The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.

Note the "key" of the for_each expression is what must be known. That is important. The problem in this specific case is that the policy is being created in the same state, and so the ARN is unknown in a first apply, and the ARN is used as the for_each key.

My solution is to avoid referencing attributes of resources that are likely to be created in the same config/state. Instead of this:

resource "aws_iam_role_policy_attachment" "custom" {
  for_each = toset([for arn in var.role_policy_arns : arn if var.create_role])

  role       = aws_iam_role.this[0].name
  policy_arn = each.key
}

I would use either a map, or an object variable, and construct the for_each key from values that may be known in advance. Using a map(string), the key would be anything the user sets, presumably a "name" of the policy, and the value would be the ARN of the policy:

variable "role_policy_arns" {
  type    = map(string)
  default = {}
}

resource "aws_iam_role_policy_attachment" "custom" {
  for_each = var.create_role ? var.role_policy_arns : {}

  role       = aws_iam_role.this[0].name
  policy_arn = each.value
}

Using an object, you have a lot of flexibility in the data structure. I like lists of objects because it works with the splat operator, but a map of objects would be fine also. Here is a list of objects:

variable "role_policy_arns" {
  type = list(object({
    name = string  # <-- used as for_each key, so cannot reference attribute of resource in same state
    arn  = string  # <-- only in for_each value, so it is fine to reference attribute of resource in same state
  }))
  default = []
}

resource "aws_iam_role_policy_attachment" "custom" {
  for_each = {for policy in var.role_policy_arns : policy.name => policy if var.create_role}

  role       = aws_iam_role.this[0].name
  policy_arn = each.value.arn
}

And here is a map of objects, where the map key is used directly as the for_each key expression, and is again an arbitrary value set by the user that presumably represents the policy name...

variable "role_policy_arns" {
  type = map(object({
    arn  = string  # <-- only in for_each value, so it is fine to reference attribute of resource in same state
  }))
  default = {}
}

resource "aws_iam_role_policy_attachment" "custom" {
  for_each = var.create_role ? var.role_policy_arns : {}

  role       = aws_iam_role.this[0].name
  policy_arn = each.value.arn
}

@bryantbiggs
Copy link
Member

thank you for sharing this @lorengordon - I will dive in tomorrow and check it out

@zachfeld
Copy link

@bryantbiggs were you ever able to figure out a solution?

@lrstanley
Copy link

lrstanley commented Apr 12, 2022

Running into this as well, as I create a policy that this module doesn't have. I believe, and I'm not 100% sure so someone might want to test this, you can probably use vars in the depends_on field, in the resource. This essentially would tell Terraform that it has to wait on whatever is behind that variable (i.e. Terraform can't provide the variable until the resource is created).

If the above is the case, then I'd assume this would be a very easy fix. This stackoverflow post is a bit outdated, but I'm curious if this still applies today. It would make sense that it does.

EDIT: looks like the above isn't the case, from my testing. I believe there is a way around it by using a more defined input, I'm just not sure what that would be.

EDIT (again): @lorengordon's solution seems to be what I was thinking of. Not sure why my brain completely skipped over your comment. 😄

It would also be nice to simply add a policy_inline field, where you can provide your own JSON, and it will inline the policy into the created role. Most of our policies were previously inlined anyway, and it has the benefit that the module has full control of its creation/destruction.

@bryantbiggs
Copy link
Member

PR is up to resolve this if anyone wants to give it a try #250

@bryantbiggs
Copy link
Member

And big thank you to @lorengordon for pointing out the workaround, this will help a lot as well as in other modules so thank you

@antonbabenko
Copy link
Member

This issue has been resolved in version 5.0.0 🎉

@github-actions
Copy link

github-actions bot commented Nov 8, 2022

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 8, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.