Terraform depends_on aws_iam_policy - amazon-web-services

I have a module that create some aws policy from json files.
Terraform plan return an error when it try to attach the new resources (policies) to the role it is creating.
The "for_each" value depends on resource attributes that cannot be determined until apply
This is ok, so I tried to use depends_on on the module that create the new resources (policies), but I still have the same error.
here my module:
module "admin" {
source = "./my_repo/admin"
depends_on = [
aws_iam_policy.common,
aws_iam_policy.ses_sending,
aws_iam_policy.athena_readonly,
]
policies = [
aws_iam_policy.common.arn,
aws_iam_policy.ses_sending.arn,
aws_iam_policy.athena_readonly.arn,
]
In the module ./my_repo/admin I have a file with this code (here I have the error)
resource "aws_iam_role_policy_attachment" "me" {
for_each = toset(var.policies)
role = aws_iam_role.me.name
policy_arn = each.value
}
What's wrong?
Thank you

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

Related

Terraform resolving loop

I want AWS instance that is allowed to read its own tags, but not of any other resources? Normally, idea of instance being allowed to do something is expressed by iam_role and aws_profile_instance, but when writing policy for the role, I can't refer to ARN of instance, since it creates loop.
It makes sense: normally, Terraform creates resources in order, and once created it never revisits them. What I want requires creating instance without iam role, and attach role to instance after instance is created.
Is it possible with Terraform?
EDIT: (minimal example):
+; cat problem.tf
resource "aws_instance" "problem" {
instance_type = "t2.medium"
ami = "ami-08d489468314a58df"
iam_instance_profile = aws_iam_instance_profile.problem.name
}
resource "aws_iam_policy" "problem" {
name = "problem"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{ Effect = "Allow"
Action = ["ssm:GetParameters"]
Resource = [aws_instance.problem.arn]
}
]
})
}
resource "aws_iam_role" "problem" {
name = "problem"
managed_policy_arns = [aws_iam_policy.problem.id]
# Copy-pasted from aws provider documentation. AWS is overcomplicated.
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
resource "aws_iam_instance_profile" "problem" {
name = "problem"
role = aws_iam_role.problem.name
}
+; terraform apply -refresh=false
Acquiring state lock. This may take a few moments...
Releasing state lock. This may take a few moments...
╷
│ Error: Cycle: aws_iam_instance_profile.problem, aws_instance.problem, aws_iam_policy.problem, aws_iam_role.problem
│
│
╵
The problem here arises because you've used the managed_policy_arns shorthand to attach the policy to the role in the same resource that declares the role. That shorthand can be convenient in simple cases, but it can also create cycle problems as you've seen here because it causes the role to refer to the policy, rather than the policy to refer to the role.
The good news is that you can avoid a cycle here by declaring that relationship in the opposite direction, either by using the separate aws_iam_policy_attachment resource type -- which only declares the connection between the role and the policy -- or by using aws_iam_role_policy to declare a policy that's directly attached to the role. You only really need the separate attachment if you intend to attach the same policy to multiple principals, so I'm going to show the simpler approach with aws_iam_role_policy here:
resource "aws_instance" "example" {
instance_type = "t2.medium"
ami = "ami-08d489468314a58df"
iam_instance_profile = aws_iam_instance_profile.example.name
}
resource "aws_iam_instance_profile" "example" {
name = "example"
role = aws_iam_role.example.name
}
resource "aws_iam_role" "example" {
name = "example"
# Allow the EC2 service to assume this role, so
# that the EC2 instance can act as it through its
# profile.
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
resource "aws_iam_role_policy" "example" {
name = "example"
role = aws_iam_role.example.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = ["ssm:GetParameters"]
Resource = [aws_instance.example.arn]
},
]
})
}
Now all of the dependency edges go in the correct order to avoid a cycle.
The policy won't be connected to the role until both the role and the instance are both created, so it's important to consider here that the software running in the instance might start up before the role's policy is assigned, and so it should be prepared to encounter access violation errors for some time after boot and keep trying periodically until it succeeds, rather than aborting at the first error.
If this is part of a shared module that's using the functionality of the EC2 instance as part of the abstraction it's creating, it can help the caller of the module to be explicit about that hidden dependency on the aws_iam_role_policy by including it in any output values that refer to behavior of the EC2 instance that won't work until the role policy is ready. For example, if the EC2 instance is providing an HTTPS service on port 443 that won't work until the policy is active:
output "service_url" {
value = "https://${aws_instance.example.private_ip}/"
# Anything which refers to this output value
# should also wait until the role policy is
# created before taking any of its actions,
# even though Terraform can't "see" that
# dependency automatically.
depends_on = [aws_iam_role_policy.example]
}

Terraform: Attaching pre-existing aws policies to a pre-existing aws role

Instead of using the aws console to simply attach a couple of pre-existing policies to a pre-existing role, I need to do it via Terraform within a module for a specific system that requires the perms.
I am not having much luck doing it though?
variables.tf
variable "masterrole" {
description = "role already present within the cn-tio-tooling-acc"
default = "arn:aws-cn:iam::12345678910:role/Master"
}
variable "policies" {
description = "policies already present within the cn-tio-tooling-acc"
default = "arn:aws-cn:iam::12345678910:policy/Source-1,arn:aws-cn:iam::351767606935:policy/Source-2"
}
data.tf <-- Referencing the role and policy data that's already present within the account
data "aws_iam_role" "masterrole" {
name = "Master"
}
data "aws_iam_policy" "policies" {
arn = var.policies
}
IAM.tf
resource "aws_iam_role_policy_attachment" "Sources" {
role = aws_iam_role.masterrole.name
policy_arn = aws_iam_policy.policies.arn
}
Probably something really simple here, but why do I get the following from a 'plan' result?
Error: Reference to undeclared resource
on cn_cpm_iam.tf line 3, in resource "aws_iam_role_policy_attachment" "Sources":
3: role = aws_iam_role.masterrole.name
A managed resource "aws_iam_role" "masterrole" has not been declared in the
root module.
Error: Reference to undeclared resource
on cn_cpm_iam.tf line 4, in resource "aws_iam_role_policy_attachment" "Sources":
4: policy_arn = aws_iam_policy.cpmpolicies.arn
A managed resource "aws_iam_policy" "policies" has not been declared in the
root module.
When referencing data sources in terraform you need to prefix them with data.. So try using
resource "aws_iam_role_policy_attachment" "Sources" {
role = data.aws_iam_role.masterrole.name
policy_arn = data.aws_iam_policy.policies.arn
}
But as you already know the name and the ARN you can just use them without querying the data sources:
resource "aws_iam_role_policy_attachment" "Sources" {
role = "Master"
policy_arn = var.policies
}
Let me know if i am missing something here ;)

Terraform: Attaching an unmanaged IAM role

Terraform version: 12
We have a legacy, unmanaged by Terraform IAM role that I'd like to reference from an aws_iam_policy_attachment block and I attempted the following:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
"arn:aws:iam::1234567890:role/unmanaged-role"
]
policy_arn = aws_iam_policy.example-policy.arn
}
Dry-run works fine but when applying TF says:
– ValidationError: The specified value for roleName is invalid. It must contain only alphanumeric characters and/or the following: +=,.#_-
Is there a way I can just reference the unmanaged role without defining it in TF? Or is there some non-destructive way of declaring it that doesn't change anything to do with the unmanaged role?
In your roles, you are providing role ARN, not role name.
Therefore, instead of ARN, you should use its name:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
"unmanaged-role"
]
policy_arn = aws_iam_policy.example-policy.arn
}
You can also use data_source
data "aws_iam_role" "example" {
name = "unmanaged-role"
}
and the reference it in your resource:
resource "aws_iam_policy_attachment" "example-attach" {
name = "example-attach"
roles = [
aws_iam_role.managed-role.name,
data.aws_iam_role.example.name
]
policy_arn = aws_iam_policy.example-policy.arn
}

Terraform: depends_on for module not working as expected in AWS

I am new to terraform. I was working with terraform v0.12 previously and since I wanted to bring in a dependency between modules, I started using terraform v0.13 recently. I am trying to create an IAM role and attach a few policies to the created role. But the issue arises in policy attachment to the role. Few policies are getting attached to the role but some policies throw an error saying no such role exists while the other policies are attached to the role properly. Is there anything wrong in my implementation?
module.tf
provider "aws" {
region = "ap-southeast-1"
}
#Control Plane role and policies
module "ControlPlane_Role" {
source = "../../templates/IAM/roles"
role_name = var.EKS-master-role
}
module "ControlPlane_Policy1" {
source = "../../templates/IAM/aws_policy"
role_name = var.EKS-master-role
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
depends_on = [module.ControlPlane_Role.role_create]
}
module "ControlPlane_Policy2" {
source = "../../templates/IAM/aws_policy"
role_name = var.EKS-master-role
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy"
depends_on = [module.ControlPlane_Role.role_create]
}
templates/IAM/roles/role.tf
resource "aws_iam_role" "role_create" {
assume_role_policy = data.aws_iam_policy_document.trusted_entity.json
name = var.role_name
}
aws_policy.tf
resource "aws_iam_role_policy_attachment" "aws_policy" {
role = var.role_name
policy_arn = var.policy_arn
}
I'll be passing the variable files separately and there are no issues with that.
Error:
Error: Error attaching policy arn:aws:iam::aws:policy/AmazonEKSClusterPolicy to IAM Role EKS-master: NoSuchEntity: The role with name EKS-master cannot be found.
Error attaching policy arn:aws:iam::aws:policy/AmazonEKSServicePolicy to IAM Role EKS-master: NoSuchEntity: The role with name EKS-master cannot be found.
If I re-run the command terraform apply again on the same resources without any change, the policies are getting attached.
You shouldn't use depends_on except for some exceptional cases. From the templates/IAM/roles, define an output that is the name of the role and in the other modules pass this output (role_name = module.ControlPlane_Role.output_role_name). With this setup, the dependency graph is clear (create the role, create the things that depend on the role) instead of having to manually define dependencies with depends_on.

How to attach multiple IAM policies to IAM roles using Terraform?

I want to attach multiple IAM Policy ARNs to a single IAM Role.
One method is to create a new policy with privileges of all the policies (multiple policies).
But in AWS, we have some predefined IAM policies like AmazonEC2FullAccess, AmazomS3FullAccess, etc. I want to use a combination of these for my role.
I could not find a way to do so in the Terraform documentation.
As per documentation we can use aws_iam_role_policy_attachment to attach a policy to a role, but not multiple policies to a role as this is available via AWS console.
Please let me know if there is a method to do the same or is it still a feature to be added.
The Terraform version I use is v0.9.5
For Terraform versions >= 0.12 the cleanest way to add multiple policies is probably something like this:
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
for_each = toset([
"arn:aws:iam::aws:policy/AmazonEC2FullAccess",
"arn:aws:iam::aws:policy/AmazonS3FullAccess"
])
role = var.iam_role_name
policy_arn = each.value
}
As described in Pranshu Verma's answer, the list of policies can also be put into a variable.
Using for_each in favor of count has the advantage, that insertions to the list are properly recognized by terraform so that it would really only add one policy, while with count all policies after the insertion would be changed (this is described in detail in this blog post)
Thanks Krishna Kumar R for the hint.
A little more polished answer I reached from your answer.
# Define policy ARNs as list
variable "iam_policy_arn" {
description = "IAM Policy to be attached to role"
type = "list"
}
# Then parse through the list using count
resource "aws_iam_role_policy_attachment" "role-policy-attachment" {
role = "${var.iam_role_name}"
count = "${length(var.iam_policy_arn)}"
policy_arn = "${var.iam_policy_arn[count.index]}"
}
And finally the list of policies should be specified in *.tfvars file or in command line using -var, for example:
iam_policy_arn = [
"arn:aws:iam::aws:policy/AmazonEC2FullAccess", "arn:aws:iam::aws:policy/AmazonS3FullAccess"]
Did you try something like this:
resource "aws_iam_role" "iam_role_name" {
name = "iam_role_name"
}
resource "aws_iam_role_policy_attachment" "mgd_pol_1" {
name = "mgd_pol_attach_name"
role = "${aws_iam_role.iam_role_name.name}"
policy_arn = "${aws_iam_policy.mgd_pol_1.arn}"
}
resource "aws_iam_role_policy_attachment" "mgd_pol_2" {
name = "mgd_pol_attach_name"
role = "${aws_iam_role.iam_role_name.name}"
policy_arn = "${aws_iam_policy.mgd_pol_2.arn}"
}
Adding another option, which is similar to the excepted answer but instead of:
policy_arn = "${var.iam_policy_arn[count.index]}"
You can use the element function:
policy_arn = "${element(var.iam_policy_arn,count.index)}"
I think that in some cases (like a project with a large amount of code) this could be more readable.
In my case I added multiple statements in one policy document:
data "aws_iam_policy_document" "sns-and-sqs-policy" {
statement {
sid = "AllowToPublishToSns"
effect = "Allow"
actions = [
"sns:Publish",
]
resources = [
data.resource.arn,
]
}
statement {
sid = "AllowToSubscribeFromSqs"
effect = "Allow"
actions = [
"sqs:changeMessageVisibility*",
"sqs:SendMessage",
"sqs:ReceiveMessage",
"sqs:GetQueue*",
"sqs:DeleteMessage",
]
resources = [
data.resource.arn,
]
}
}
resource "aws_iam_policy" "sns-and-sqs" {
name = "sns-and-sqs-policy"
policy = data.aws_iam_policy_document.sns-and-sqs-policy.json
}
resource "aws_iam_role_policy_attachment" "sns-and-sqs-role" {
role = "role_name"
policy_arn = aws_iam_policy.sns-and-sqs.arn
}
simply combine your policies in one policy
1.Use a datasource with for loop to get all the policies
data "aws_iam_policy" "management_group_policy" {
for_each = toset(["Billing", "AmazonS3ReadOnlyAccess"])
name = each.value
}
2.Attach to role as so;
resource "aws_iam_role_policy_attachment" "dev_role_policy_attachment" {
for_each = data.aws_iam_policy.management_group_policy
role = aws_iam_role.role.name
policy_arn = each.value.arn
}
This is an example how i did it:
resource "aws_iam_group_policy_attachment" "policy_attach_example" {
for_each = aws_iam_policy.example
group = aws_iam_group.example.name
policy_arn = each.value["arn"]
}
So basically "aws_iam_policy.example" is a list of policies that i have made in the same way, with for_each
Hope that this help you, i know i come late but i had this simillar issue