The terraform structure of my project is:
iam/policies/policy1.tf
iam/roles/roles1.tf
policy1.tf includes several policies, etc.:
resource "aws_iam_policy" "policy1A" {
name = "..."
policy = "${data.template_file.policy1ATempl.rendered}"
}
roles1.tf includes several roles, etc.:
resource "aws_iam_role" "role1" {
name = ....
assume_role_policy = ....
}
Now I want to attach policy1A to role1. Since policy1 and role1 are not in the same folder, how do I attach them?
resource "aws_iam_policy_attachment" "attach1" {
name = "attachment1"
roles = ??? (sth. like ["${aws_iam_role.role1.name}"])
policy_arn = ??? (sth. like "${aws_iam_policy.Policy1.arn}")
}
You can't make references like this across directories because Terraform only works in one directory at a time. If you really do need to separate these into different folders (which I would recommend against here) then normally you would use data sources to retrieve information about resources that have already been created.
In this case though the aws_iam_policy data source is currently pretty much useless because it requires the ARN of the policy instead of the path or name. Instead you can construct it if you know the policy name as it fits a well known pattern. You obviously know the name of role(s) you want to attach the policy to and you know the name of the policy so that covers those things.
data "aws_caller_identity" "current" {}
resource "aws_iam_policy_attachment" "attach1" {
name = "attachment1"
roles = "role1"
policy_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/policy1A"
}
This above example does briefly show off how to use data sources as it fetches the AWS account ID, using the aws_caller_identity data source, and dynamically uses that to build the ARN of the policy you are trying to attach.
I would echo the warning in the docs against using the resource aws_iam_policy_attachment unless you are absolutely certain with what you're doing because it will detach the policy from anything else it happens to be attached to. If you want to take a managed policy and attach it to N roles I would instead recommend using aws_iam_role_policy_attachment and it's like instead.
Related
I need to create several iam policies from json files.
So, I've a file called iam_policies.tf with many of these code:
resource "aws_iam_policy" "name" {
name = "policy-name"
description = "Policy desc xxx"
path = "/"
policy = file("${path.module}/_/iam_policies/policy.json")
}
In a module I would like to use these policies as argument of var, but when I try to attach the policy...
resource "aws_iam_role_policy_attachment" "me" {
for_each = toset(var.policies)
role = aws_iam_role.me.name
policy_arn = each.value
}
I get the error: 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.
This is the module that create policies resources and other resources:
module "admin" {
source = "./repo/module_name"
policies = [
aws_iam_policy.common.arn,
aws_iam_policy.ses_sending.arn,
aws_iam_policy.athena_readonly.arn,
aws_iam_policy.s3_deploy.arn,
]
...
}
I've tried with depends_on but It doesn't works.
I'm using terraform cloud, so I can't use apply -target
How can I do? What's wrong?
Thank you
If you can't use target, you have to separate your deployments into two deployments. First you deploy your policies, and then they will become inputs of the main deployment.
Could you help me with the problem of getting AWS IAM role name via terraform?
Briefly, I have a previously created role with the name: test-platform-testenv-eks2020041915272704860006.
Different roles have different names with variables (platform/stack names) in their names.
Now I have a Terraform file that must get the name of this IAM role (to attach a policy to this role).
I can do it using the exact name of the role.
But how to do it without hard code (knowing tags and some other parameters of this role)?
Many thanks in advance.
You could store the role name in an AWS Systems Manager Parameter Store resource as part of the original stack.
The subsequent stack can then pick the role name from the Parameter Store using the aws_ssm_parameter data source like in the following snippet:
data "aws_ssm_parameter" "role" {
name = "/path/to/role/name"
}
resource "aws_iam_policy_attachment" "policy-attachment" {
roles = ["${data.aws_ssm_parameter.role.value}"] # pull value from parameter store
...
}
I've got a solution. Maybe not the best, but it works for me. So,
Get iam_instance_profile of the created EKS node using data "aws_instance", for example:
data "aws_instance" "eks_node" {
filter {...}
}
Get role name of the previously created EKS node using data "aws_iam_instance_profile" (from defined in step 1 aws_instance), for example:
data "aws_iam_instance_profile" "eks_node_profile" {
name = data.aws_instance.eks_node.iam_instance_profile
}
Attach the newly created policy to the previously defined EKS role, for example:
resource "aws_iam_role_policy_attachment" "attach_policy_to_eks" {
policy_arn = aws_iam_policy.created_policy_eks.arn
role = data.aws_iam_instance_profile.eks_node_profile.role_name
}
Thanks all for help.
Here is the terraform code I have used to create a service account and bind a role to it:
resource "google_service_account" "sa-name" {
account_id = "sa-name"
display_name = "SA"
}
resource "google_project_iam_binding" "firestore_owner_binding" {
role = "roles/datastore.owner"
members = [
"serviceAccount:sa-name#${var.project}.iam.gserviceaccount.com",
]
depends_on = [google_service_account.sa-name]
}
Above code worked great... except it removed the datastore.owner from any other service account in the project that this role was previously assigned to. We have a single project that many teams use and there are service accounts managed by different teams. My terraform code would only have our team's service accounts and we could end up breaking other teams service accounts.
Is there another way to do this in terraform?
This of course can be done via GCP UI or gcloud cli without any issue or affecting other SAs.
From terraform docs, "google_project_iam_binding" is Authoritative. Sets the IAM policy for the project and replaces any existing policy already attached. That means that it replaces completely members for a given role inside it.
To just add a role to a new service account, without editing everybody else from that role, you should use the resource "google_project_iam_member":
resource "google_service_account" "sa-name" {
account_id = "sa-name"
display_name = "SA"
}
resource "google_project_iam_member" "firestore_owner_binding" {
project = <your_gcp_project_id_here>
role = "roles/datastore.owner"
member = "serviceAccount:${google_service_account.sa-name.email}"
}
Extra change from your sample: the use of service account resource email generated attribute to remove the explicit depends_on. You don't need the depends_on if you do it like this and you avoid errors with bad configuration.
Terraform can infer the dependency from the use of a variable from another resource. Check the docs here to understand this behavior better.
It's an usual problem with Terraform. Either you do all with it, or nothing. If you are between, unexpected things can happen!!
If you want to use terraform, you have to import the existing into the tfstate. Here the doc for the bindind, and, of course, you have to add all the account in the Terraform file. If not, the binding will be removed, but this time, you will see the deletion in the tf plan.
I have an infrastructure I'm deploying using Terraform in AWS. This infrastructure can be deployed to different environments, for which I'm using workspaces.
Most of the components in the deployment should be created separately for each workspace, but I have several key components that I wish to be shared between them, primarily:
IAM roles and permissions
They should use the same API Gateway, but each workspace should deploy to different paths and methods
For example:
resource "aws_iam_role" "lambda_iam_role" {
name = "LambdaGeneralRole"
policy = <...>
}
resource "aws_lambda_function" "my_lambda" {
function_name = "lambda-${terraform.workspace}"
role = "${aws_iam_role.lambda_iam_role.arn}"
}
The first resource is a IAM role that should be shared across all instances of that Lambda, and shouldn't be recreated more than once.
The second resource is a Lambda function whose name depends on the current workspace, so each workspace will deploy and keep track of the state of a different Lambda.
How can I share resources, and their state, between different Terraform workspaces?
For the shared resources, I create them in a separate template and then refer to them using terraform_remote_state in the template where I need information about them.
What follows is how I implement this, there are probably other ways to implement it. YMMV
In the shared services template (where you would put your IAM role) I use Terraform backend to store the output data for the shared services template in Consul. You also need to output any information you want to use in other templates.
shared_services template
terraform {
backend "consul" {
address = "consul.aa.example.com:8500"
path = "terraform/shared_services"
}
}
resource "aws_iam_role" "lambda_iam_role" {
name = "LambdaGeneralRole"
policy = <...>
}
output "lambda_iam_role_arn" {
value = "${aws_iam_role.lambda_iam_role.arn}"
}
A "backend" in Terraform determines how state is loaded and how an operation such as apply is executed. This abstraction enables non-local file state storage, remote execution, etc.
In the individual template you invoke the backend as a data source using terraform_remote_state and can use the data in that template.
terraform_remote_state:
Retrieves state meta data from a remote backend
individual template
data "terraform_remote_state" "shared_services" {
backend = "consul"
config {
address = "consul.aa.example.com:8500"
path = "terraform/shared_services"
}
}
# This is where you use the terraform_remote_state data source
resource "aws_lambda_function" "my_lambda" {
function_name = "lambda-${terraform.workspace}"
role = "${data.terraform_remote_state.shared_services.lambda_iam_role_arn}"
}
References:
https://www.terraform.io/docs/state/remote.html
https://www.terraform.io/docs/backends/
https://www.terraform.io/docs/providers/terraform/d/remote_state.html
Resources like aws_iam_role having a name attribute will not create a new instance if the name value matches an already provisioned resource.
So, the following will create a single aws_iam_role named LambdaGeneralRole.
resource "aws_iam_role" "lambda_iam_role" {
name = "LambdaGeneralRole"
policy = <...>
}
...
resource "aws_iam_role" "lambda_iam_role_reuse_existing_if_name_is_LambdaGeneralRole" {
name = "LambdaGeneralRole"
policy = <...>
}
Similarly, the aws provider will effectively creat one S3 bucket name my-store given the following:
resource "aws_s3_bucket" "store-1" {
bucket = "my-store"
acl = "public-read"
force_destroy = true
}
...
resource "aws_s3_bucket" "store-2" {
bucket = "my-store"
acl = "public-read"
force_destroy = true
}
This behaviour holds even if the resources were defined different workspaces with their respective separate Terraform state.
To get the best of this approach, define the shared resources as separate configuration. That way, you don't risk destroying a shared resource after running terraform destroy.
I want to attach one of the pre-existing AWS managed roles to a policy, here's my current code:
resource "aws_iam_role_policy_attachment" "sto-readonly-role-policy-attach" {
role = "${aws_iam_role.sto-test-role.name}"
policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}
Is there a better way to model the managed policy and then reference it instead of hardcoding the ARN? It just seems like whenever I hardcode ARNs / paths or other stuff like this, I usually find out later there was a better way.
Is there something already existing in Terraform that models managed policies? Or is hardcoding the ARN the "right" way to do it?
The IAM Policy data source is great for this. A data resource is used to describe data or resources that are not actively managed by Terraform, but are referenced by Terraform.
For your example, you would create a data resource for the managed policy as follows:
data "aws_iam_policy" "ReadOnlyAccess" {
arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}
The name of the data source, ReadOnlyAccess in this case, is entirely up to you. For managed policies I use the same name as the policy name for the sake of consistency, but you could just as easily name it readonly if that suits you.
You would then attach the IAM policy to your role as follows:
resource "aws_iam_role_policy_attachment" "sto-readonly-role-policy-attach" {
role = "${aws_iam_role.sto-test-role.name}"
policy_arn = "${data.aws_iam_policy.ReadOnlyAccess.arn}"
}
When using values that Terraform itself doesn't directly manage, you have a few options.
The first, simplest option is to just hard-code the value as you did here. This is a straightforward answer if you expect that the value will never change. Given that these "canned policies" are documented, built-in AWS features they likely fit this criteria.
The second option is to create a Terraform module and hard-code the value into that, and then reference this module from several other modules. This allows you to manage the value centrally and use it many times. A module that contains only outputs is a common pattern for this sort of thing, although you could also choose to make a module that contains an aws_iam_role_policy_attachment resource with the role set from a variable.
The third option is to place the value in some location that Terraform can retrieve values from, such as Consul, and then retrieve it from there using a data source. With only Terraform in play, this ends up being largely equivalent to the second option, though it means Terraform will re-read it on each refresh rather than only when you update the module using terraform init -upgrade, and thus this could be a better option for values that change often.
The fourth option is to use a specialized data source that can read the value directly from the source of truth. Terraform does not currently have a data source for fetching information on AWS managed policies, so this is not an option for your current situation, but can be used to fetch other AWS-defined data such as the AWS IP address ranges, service ARNs, etc.
Which of these is appropriate for a given situation will depend on how commonly the value changes, who manages changes to it, and on the availability of specialized Terraform data sources.
I got a similar situation, and I don't want to use the arn in my terraform script for two reasons,
If we do it on the Web console, we don't really look at the arn, we search the role name, then attach it to the role
The arn is not easy to remember, looks like is not for human
I would rather use the policy name, but not the arn, here is my example
# Get the policy by name
data "aws_iam_policy" "required-policy" {
name = "AmazonS3FullAccess"
}
# Create the role
resource "aws_iam_role" "system-role" {
name = "data-stream-system-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
}
# Attach the policy to the role
resource "aws_iam_role_policy_attachment" "attach-s3" {
role = aws_iam_role.system-role.name
policy_arn = data.aws_iam_policy.required-policy.arn
}