I am creating AWS ECR repositories via terraform
resource "aws_ecr_repository" "repo1" {
name = "repo1"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
resource "aws_ecr_repository" "repo2" {
name = "repo2"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
Now I want to attach a policy to all ECR repositories.
Question is, is there a dynamic way to create a list of all the resources (of type ECR) created using the terraform script? If yes then we can have a for_each on that list and attach a policy.
Or is there any better way to do it?
P.S. I know I can attach policy by writing the following for each. I want to avoid duplication and avoid a case where policy is not attached if the block is missed by someone
resource "aws_ecr_lifecycle_policy" "insights_repository_policy" {
repository = aws_ecr_repository.insights_repository.name
policy = local.ecr_cleanup_policy
}
Edit: Question 2
There are some accounts I want to give access to. If I use list of repositories to create and then I want to assign policies for each account then it would make nested for loops. Is there a cleaner solution for that?
local {
accounts = {test=account_id_123, prod=account_id_456}
}
resource "aws_ecr_repository_policy" "access-permission" {
for_each = local.accounts
policy = <<POLICY
...
POLICY
repository = aws_ecr_repository.repo_template.name
}
Not in your form. It would be better if you used for_each or count. For example:
variable "repos" {
default = ["repo1", "repo2"]
}
resource "aws_ecr_repository" "repo" {
for_each = to_set(var.repos)
name = each.key
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
}
then you can do:
resource "aws_ecr_lifecycle_policy" "insights_repository_policy" {
for_each = aws_ecr_repository.repo
repository = each.value.name
policy = local.ecr_cleanup_policy
}
Related
I have the following code which works fine.
resource "aws_ses_email_identity" "main_from_email" {
email = "some#email.com"
}
data "aws_iam_policy_document" "main_from_email_policy_document" {
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = [aws_ses_email_identity.main_from_email.arn]
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
resource "aws_ses_identity_policy" "email_notif_policy" {
identity = aws_ses_email_identity.main_from_email.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document.json
}
The above code is working fine. But the email is hard coded. I would like to have the (email) resources to be created based on some configuration provided in terraform.tfvars as follows:
clientemails = {
"client1" = { "email" = "client1#gmail.com" }
"client2" = { "email" = "client2#gmail.com" }
}
I modified main resource as follows:
resource "aws_ses_email_identity" "main_from_email_map" {
for_each = var.clientemails
email = each.value.email
}
But, I don't know how I can modify "aws_iam_policy_document" and "aws_ses_identity_policy" to follow "aws_ses_email_identity".
How do I modify my terraform script to honor "clientemails" configuration?
This should be relatively straight forward to do:
resource "aws_ses_email_identity" "main_from_email_map" {
for_each = var.clientemails
email = each.value.email
}
data "aws_iam_policy_document" "main_from_email_policy_document" {
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = values(aws_ses_email_identity.main_from_email_map)[*].arn
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
resource "aws_ses_identity_policy" "email_notif_policy" {
for_each = aws_ses_email_identity.main_from_email_map
identity = each.value.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document.json
}
Here, there will be a single policy which will allow all the same actions for all the SES email identity ARNs. To achieve this, since the SES email identity resource was created using for_each meta-argument, the values built-in function [1] was used to fetch all the values of the ARN attribute for all the keys (hence the [*] part).
There could be a way to modify the data source with for_each if needed (i.e., to create a policy per client email):
data "aws_iam_policy_document" "main_from_email_policy_document" {
for_each = aws_ses_email_identity.main_from_email_map
statement {
actions = ["SES:SendEmail", "SES:SendRawEmail"]
resources = [each.value.arn]
principals {
identifiers = ["*"]
type = "AWS"
}
}
}
Then, you would also have to fix the SES identity policy:
resource "aws_ses_identity_policy" "email_notif_policy" {
for_each = aws_ses_email_identity.main_from_email_map
identity = each.value.arn
name = "${local.namespace}-ses_main_from_email_policy"
policy = data.aws_iam_policy_document.main_from_email_policy_document[each.key].json
}
The code where for_each meta-argument is used with the resources (e.g., aws_ses_email_identity.main_from_email_map) is called for_each chaining [2].
[1] https://developer.hashicorp.com/terraform/language/functions/values
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#chaining-for_each-between-resources
I want to create an object through which I will iterate using for_each to create some:
google_service_account
google_project_iam_member
resources
So my object is more or less like this
service_accounts = {
"gha_storage_admin_sa" = {
create = true
project = var.project_id
account_id = "id1"
display_name = "GHA service account 1"
role = "roles/storage.admin"
},
"gha_cluster_scaling_sa" = {
create = false
project = var.project_id
account_id = "id2"
display_name = "GHA service account 2"
role = google_organization_iam_custom_role.my_custom_role.id
},
}
resource "google_service_account" "service_account" {
for_each = {
for k, v in local.service_accounts: k => v
if v.create
}
project = each.value.project
account_id = each.value.account_id
display_name = each.value.display_name
}
resource "google_project_iam_member" "member" {
for_each = local.service_accounts
project = var.project_id
role = each.value.role
member = "serviceAccount:${google_service_account.service_account[each.key].email}"
}
This works fine if the above is a local variable.
I want however to expose it as a regular variable.
My question is whether the referenced resource (google_organization_iam_custom_role.my_custom_role.id) in the second element can be somehow exposed as a variable.
My question is whether the referenced resource (google_organization_iam_custom_role.my_custom_role.id) in the second element can be somehow exposed as a variable.
Sadly its not possible. TF does not support dynamic variables. All variables' values must be know at plan time. You have to pass the actual value of google_organization_iam_custom_role.my_custom_role.id as input variable to your code.
I am learning terraform and currently attempting to attach a policy to a created bucket. More specifically, the policy I want to attach has the same permissions/structure but the only difference is the resources section. I will illustrate with an example:
Let's say I create an s3 bucket like:
module "happy_bucket" {
source = "outer space"
name = "happy-bucket"
}
And another bucket like:
module "sad_bucket" {
source = "outer space"
name = "sad-bucket"
}
And now I have a policy that looks like:
data "aws_iam_policy_document" "some_policy" {
statement {
effect = "Allow"
resources = [module.some_bucket.bucket_arn]
actions = ["s3:GetObject", "s3:GetObjectVersion"]
}
}
And now I would like to attach "some_policy" to both "sad-bucket" and "happy-bucket". But I want to do that without having to repeat myself by creating the policy two times (because I need the .bucket_arn to be based on the bucket). In other words, I want to create one generic policy, and attach it to the 2 buckets I created (while picking up the arn dynamically).
Bucket policies require principals, so you need to add that to your some_policy. Having said that, if you want to keep using aws_iam_policy_document you use for_each to iterate over your buckets.
For example, if your module is:
variable "name" {}
resource "aws_s3_bucket" "b" {
bucket = var.name
}
output "bucket_arn" {
value = aws_s3_bucket.b.arn
}
output "bucket_name" {
value = aws_s3_bucket.b.id
}
then in parent module, you can:
module "happy_bucket" {
source = "./modules/buckets"
name = "happy-bucket-231123124ff"
}
module "sad_bucket" {
source = "./modules/buckets"
name = "sad-bucket-231123124ff"
}
locals {
my_buckets = {for bucket in [module.happy_bucket, module.sad_bucket]:
bucket.bucket_name => bucket}
}
data "aws_iam_policy_document" "some_policy" {
for_each = local.my_buckets
statement {
effect = "Allow"
resources = ["${each.value.bucket_arn}/*"]
actions = ["s3:GetObject", "s3:GetObjectVersion"]
principals {
type = "AWS"
identifiers = ["*"]
}
}
}
resource "aws_s3_bucket_policy" "bucket_policie" {
for_each = local.my_buckets
bucket = each.key
policy = data.aws_iam_policy_document.some_policy[each.key].json
}
I am wondering if there is an easy way to add multiple json file policies
mypolicy1.json,
mypolicy2.json,
mypolicy3.json
as of now this is my code.. works great for one policy
variable "iam_policy_path" {
default = "./mypolicy.json"
}
resource "aws_iam_role_policy" "role" {
name = var.name
role = var.role
policy = file(var.iam_policy_path)
}
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
iam_policy_path = "new_policy_path.json"
}
One way would be to use for_each and having your iam_policy_path being a list of paths.
For example:
variable "iam_policy_path" {
default = ["./mypolicy.json", "./mypolicy2.json"]
}
resource "aws_iam_role_policy" "role" {
for_each = toset(var.iam_policy_path) # for each requires set.
name = var.name
role = var.role
policy = file(each.key)
}
Then when using the module:
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
iam_policy_path = ["new_policy_path.json", "new_policy_path2.json"]
}
Based on the extra info. The complete solution may require also using aws_iam_role_policy_attachment which attaches a managed policy to a role.
Terraform version: 0.11
I am running multiple eks clusters and trying to enable IAM Roles-based service account in all cluster following this doc:
https://www.terraform.io/docs/providers/aws/r/eks_cluster.html#enabling-iam-roles-for-service-accounts
This works when I hardcode the cluster name in the policy statement and create multiple statements
data "aws_iam_policy_document" "example_assume_role_policy" {
# for cluster 1
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
effect = "Allow"
condition {
test = "StringEquals"
variable = "${replace(aws_iam_openid_connect_provider.example1.url, "https://", "")}:sub"
values = ["system:serviceaccount:kube-system:aws-node"]
}
principals {
identifiers = ["${aws_iam_openid_connect_provider.example1.arn}"]
type = "Federated"
}
}
}
Since I have multiple clusters, I want to be able to generate the statement dynamically
so I made the following changes:
I created a count variable and changed values in principals and and condition
count = "${length(var.my_eks_cluster)}"
condition {
test = "StringEquals"
variable = "${replace(element(aws_iam_openid_connect_provider.*.url, count.index), "https://", "")}:sub"
values = ["system:serviceaccount:kube-system:aws-node"]
}
principals {
identifiers = ["${element(aws_iam_openid_connect_provider.*.url, count.index)}"]
type = "Federated"
}
Terraform now is able to find the clusters BUT also generate multiple policies.
And this will not work, since in the following syntax, the assume_role_policy doesn't take the list
resource "aws_iam_role" "example" {
assume_role_policy = "${data.aws_iam_policy_document.example_assume_role_policy.*.json}"
name = "example"
}
It seems like instead of creating multiple policies, I need to generate multiple statements in one policy (so I can add to one iam_role). Has anyone done something similar before ? Thanks.
You only want one policy, so you should not use the count argument in your policy.
What you want to have instead is multiple statements, like this
data "aws_iam_policy_document" "example" {
statement {
# ...
}
statement {
# ...
}
}
Now you could hard-code this directly (maybe that would be a good start to test if it works). If you want to generate this dynamically from a variable you would need a dynamic-block as described here: https://www.terraform.io/docs/configuration/expressions.html
In your case that would probably be
data "aws_iam_policy_document" "example" {
dynamic "statement" {
for_each = aws_iam_openid_connect_provider
content {
actions = ["sts:AssumeRoleWithWebIdentity"]
effect = "Allow"
condition {
test = "StringEquals"
variable = "${replace(statement.value.url, "https://", "")}:sub"
values = ["system:serviceaccount:kube-system:aws-node"]
}
principals {
identifiers = ["${statement.value.arn}"]
type = "Federated"
}
}
}
}
I think that "dynamic" is only available since TF 0.12, though.