I am creating policy based on tags. The environment value differs for each resource, so I have to add them as multiple values. Unable to pass as list. Tried join,split and for loop. None of them works. Pls help. Below code just add the value as "beta,test" which will not work as expected
main.tf
locals{
workspaceValues = terraform.workspace == "dev" ? ["alpha", "dev"] : terraform.workspace == "test" ? ["beta", "test"] : ["prod", "staging"]
}
resource "aws_iam_group_policy" "inline_policy" {
name = "${terraform.workspace}_policy"
group = aws_iam_group.backend_admin.name
policy = templatefile("policy.tpl", { env = join(",", local.workspaceValues), region = "${data.aws_region.current.name}", account_id = "${data.aws_caller_identity.current.account_id}" })
}
policy.tpl:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"ForAllValues:StringLikeIfExists": {
"aws:ResourceTag/Environment": "${env}"
}
}
}
]
}
You can use jsonencode to properly format TF list as a list in json in your template:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:*",
"Resource": "*",
"Condition": {
"ForAllValues:StringLikeIfExists": {
"aws:ResourceTag/Environment": ${jsonencode(env)}
}
}
}
]
}
For that you would call the template as follows:
resource "aws_iam_group_policy" "inline_policy" {
name = "${terraform.workspace}_policy"
group = aws_iam_group.backend_admin.name
policy = templatefile("policy.tpl", {
env = local.workspaceValues
})
}
You are not using region nor account_id in your template, so there is no reason to pass them in.
An alternative solution to avoid this all together is to recreate the policy using the data component "aws_iam_policy_document" and then pass it to the aws_iam_policy resource like the following,
data "aws_iam_policy_document" "example" {
statement {
effect = "Allow"
actions = [
"ec2:*"
]
resources = [
"*"
]
condition {
test = "ForAllValues:StringLikeIfExists"
variable = "aws:ResourceTag/Environment"
values = local.workspaceValues
}
}
}
resource "aws_iam_policy" "example" {
name = "example_policy"
path = "/"
policy = data.aws_iam_policy_document.example.json
}
Related
I want to create a resource policy for a Secrets Manager secret.
I am following the official example on the docs
resource "aws_secretsmanager_secret_policy" "this" {
count = var.create_resource_policy ? 1 : 0
secret_arn = aws_secretsmanager_secret.mysecret.arn
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableAnotherAWSAccountToReadTheSecret",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:root"
},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}
]
}
POLICY
}
Is there a way to pass the following as variables in the policy document things as the principal(s), the action the resources etc?
I want to be able to pass those things as terraform variables.
Yes, you can use the built-in functions in terraform for that with interpolation syntax. For example, if you had a data source to get the account ID, you could do the following:
data "aws_caller_identity" "current" {}
resource "aws_secretsmanager_secret_policy" "this" {
count = var.create_resource_policy ? 1 : 0
secret_arn = aws_secretsmanager_secret.mysecret.arn
policy = <<POLICY
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "EnableAnotherAWSAccountToReadTheSecret",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
},
"Action": "secretsmanager:GetSecretValue",
"Resource": "*"
}
]
}
POLICY
}
You could then do the same for any other property you want/need. However, I find it easier to use the built-in data source [1] for creating policies. So for example, the policy you have could be written in the following way:
data "aws_iam_policy_document" "secrets_manager_policy" {
statement {
sid = "EnableAnotherAWSAccountToReadTheSecret"
effect = "Allow"
principals {
identifiers = ["arn:aws:iam::${data.aws_caller_identity.current.id}:root"]
type = "AWS"
}
actions = [
"secretsmanager:GetSecretValue"
]
resources = ["*"]
}
}
You could then tell actions argument to use a variable which would most preferably be a list(string) which would list all the actions necessary:
data "aws_iam_policy_document" "secrets_manager_policy" {
statement {
sid = "EnableAnotherAWSAccountToReadTheSecret"
effect = "Allow"
principals {
identifiers = "arn:aws:iam::${data.aws_caller_identity.current.id}:root"
type = "AWS"
}
actions = var.secrets_actions
resources = [ "*" ]
}
}
Then, you would only have to reference the output of the data source in the original resource:
resource "aws_secretsmanager_secret_policy" "this" {
count = var.create_resource_policy ? 1 : 0
secret_arn = aws_secretsmanager_secret.mysecret.arn
policy = data.aws_iam_policy_document.secrets_manager_policy.json
}
[1] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
Im new at Terraform and im trying to create ecsTaskExcecutionRoles for each service i have, i create a module that allows to send a list of secrets, i want to make the inline policy that allows the access optional.
i tried putting inside the inline_policy something like:
count = length(var.secrets_arn_list) > 0 ? 1 : 0
but it's not possible use count in that place
data "aws_iam_policy_document" "ecs_tasks_execution_role" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
resource "aws_iam_role" "ecs_tasks_execution_role" {
name = "TaskExecutionRole-${var.environment}-${var.project}"
assume_role_policy = "${data.aws_iam_policy_document.ecs_tasks_execution_role.json}"
inline_policy {
name = "SecretsManagerAccess-${var.project}-${var.environment}"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": var.secrets_arn_list
}
]
})
}
tags = var.tags
}
resource "aws_iam_role_policy_attachment" "ecs_tasks_execution_role" {
role = "${aws_iam_role.ecs_tasks_execution_role.name}"
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
Someone knows how to solve it?
Yes, there is a way using dynamic [1] and for_each meta-argument [2]:
dynamic "inline_policy" {
for_each = length(var.secrets_arn_list) > 0 ? [1] : []
content {
name = "SecretsManagerAccess-${var.project}-${var.environment}"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:DescribeSecret",
"secretsmanager:ListSecretVersionIds"
],
"Resource": var.secrets_arn_list
}
]
})
}
}
[1] https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks
[2] https://developer.hashicorp.com/terraform/language/meta-arguments/for_each
Either use a dynamic block, instead of count, or move the policy into a separate Terraform aws_iam_role_policy resource and put the count on that resource.
When trying to use the eventbridge input_transformer i do not receive the transformed object, but instead i get the original object sent directly to SQS.
I currently have the following setup running
locals {
rule-arn = "arn:aws:events:${data.aws_arn.event-rule.region}:${data.aws_arn.event-rule.account}:rule/${aws_cloudwatch_event_rule.notification.name}"
}
resource "aws_sqs_queue" "test-queue" {
name = "test-queue"
}
resource "aws_cloudwatch_event_rule" "notification" {
name = "test-notification"
event_bus_name = aws_cloudwatch_event_bus.events.name
description = "Listens to all events in the TEST.Notification namespace"
event_pattern = jsonencode({
source = [{ "prefix" : "TEST.Notification" }],
})
}
resource "aws_cloudwatch_event_target" "developer-notification" {
rule = aws_cloudwatch_event_rule.notification.name
target_id = "SendToSQS"
arn = aws_sqs_queue.test-queue.arn
input_transformer {
input_paths = {
"detailType" = "$.detail-type",
}
input_template = jsonencode(
{
"detailType" : "<detailType>"
}
)
}
}
resource "aws_sqs_queue_policy" "test-queue" {
queue_url = aws_sqs_queue.test-queue.id
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "",
"Statement": [
{
"Sid": "Allow EventBridge to SQS",
"Effect": "Allow",
"Principal": {
"Service": "events.amazonaws.com"
},
"Action": "*",
"Resource": "${aws_sqs_queue.test-queue.arn}",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "${aws_cloudwatch_event_rule.notification.arn}"
}
}
}
]
}
POLICY
}
I am running Terraform version:
Terraform v1.2.3
on darwin_arm64
I have seen some talk about having to do stuff like
"\"<detailType>\""
in order to have it work, but i've had no luck with that either, so for brevity/readability i've removed all the weird tricks i've seen people use. My thinking is there's something more basic i am missing here.
Does someone know what i am doing wrong?
The below code works when i create the resources but i would like to tie each SQS created with a different s3 bucket.for example.I want CloudTrail_SQS_Management_Event/CloudTrail_DLQ_Management_Event to use a bucket called "management_sqs_bucket and CloudTrail_SQS_Data_Event/CloudTrail_DLQ_Data_Event to use bucket called "data_sqs_bucket and for the bucket names to reflect accordingly on the queue policies.
SQS/variables.tf
variable "sqs_queue_name"{
description = "The name of different SQS to be created"
type = string
}
variable "dead_queue_name"{
description = "The name of different Dead Queues to be created"
type = string
}
variable "max_receive_count" {
type = number
}
SQS/iam.tf
data "aws_iam_policy_document" "policy_document"{
statement{
actions = [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:ReceiveMessage",
"sqs:SendMessage",
"sqs:SetQueueAttributes"
]
effect = "Allow"
resources = values(aws_sqs_queue.sqs)[*].arn
}
resource "aws_sqs_queue_policy" "Cloudtrail_SQS_Policy" {
queue_url = aws_sqs_queue.CloudTrail_SQS.id
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "sqspolicy",
"Statement": [
{
"Sid": "AllowSQSInvocation",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "sqs:*",
"Resource": "${aws_sqs_queue.CloudTrail_SQS.arn}",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:s3:::${var.cloudtrail_event_log_bucket_name}"
}
}
}
]
}
POLICY
}
resource "aws_sqs_queue_policy" "CloudTrail_SQS_DLQ"{
queue_url = aws_sqs_queue.CloudTrail_SQS_DLQ.id
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "sqspolicy",
"Statement": [
{
"Sid": "DLQ Policy",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "sqs:*",
"Resource": "${aws_sqs_queue.CloudTrail_SQS_DLQ.arn}",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:s3:::${var.cloudtrail_event_log_bucket_name}"
}
}
}
]
}
POLICY
}
SQS/main.tf
resource "aws_sqs_queue" "sqs" {
name = var.sqs_queue_name
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.dlq.arn
maxReceiveCount = var.max_receive_count
})
}
resource "aws_sqs_queue" "dlq" {
name = var.dead_queue_name
}
SQS/output.tf
output "sqs_queue_id"{
value = values(aws_sqs_queue.sqs)[*].id
description = "The URL for the created Amazon SQS queue."
}
output "sqs_queue_arn" {
value = values(aws_sqs_queue.sqs)[*].arn
description = "The ARN of the SQS queue."
}
variable.tf
variable "queue_names" {
default = [
{
sqs_name = "CloudTrail_SQS_Management_Event"
dlq_name = "CloudTrail_DLQ_Management_Event"
},
{
sqs_name = "CloudTrail_SQS_Data_Event"
dlq_name = "CloudTrail_DLQ_Data_Event"
}
]
}
module "sqs_queue" {
source = "../SQS"
for_each = {
for sqs, dlq in var.queue_names : sqs => dlq
}
sqs_queue_name = each.value.sqs_name
dead_queue_name = each.value.dlq_name
max_receive_count = var.max_receive_count
}
From what I understand I believe this is what you would want to do:
variables.tf:
variable "queue_names" {
default = [
{
sqs_name = "CloudTrail_SQS_Management_Event"
dlq_name = "CloudTrail_DLQ_Management_Event"
bucket_name = "management_sqs_bucket"
},
{
sqs_name = "CloudTrail_SQS_Data_Event"
dlq_name = "CloudTrail_DLQ_Data_Event"
bucket_name = "data_sqs_bucket"
}
]
}
main.tf:
module "my_sqs" {
source = "./my_sqs"
for_each = {
for q in var.queue_names : q.sqs_name => q
}
sqs_queue_name = each.value.sqs_name
dead_queue_name = each.value.dlq_name
max_receive_count = 4
cloudtrail_event_log_bucket_name = each.value.bucket_name
}
Also, I see that some code duplication for aws_sqs_queue_policy because you have both the SQS queue and the DLQ. This can be refactored to something like this:
iam.tf:
data "aws_iam_policy_document" "policy_document" {
statement {
actions = [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:ReceiveMessage",
"sqs:SendMessage",
"sqs:SetQueueAttributes"
]
effect = "Allow"
resources = aws_sqs_queue.sqs[*].arn
}
}
locals {
queue_data = [
{
id = aws_sqs_queue.sqs.id
arn = aws_sqs_queue.sqs.arn
},
{
id = aws_sqs_queue.dlq.id
arn = aws_sqs_queue.dlq.arn
}
]
}
resource "aws_sqs_queue_policy" "sqs_policy" {
# This can be achieved with for_each similar to what we have in main.tf, but I did not want to complicate it
count = length(aws_sqs_queue.sqs[*])
queue_url = local.queue_data[count.index].id
policy = <<POLICY
{
"Version": "2012-10-17",
"Id": "sqspolicy",
"Statement": [
{
"Sid": "AllowSQSInvocation",
"Effect": "Allow",
"Principal": {"AWS":"*"},
"Action": "sqs:*",
"Resource": "${local.queue_data[count.index].arn}",
"Condition": {
"ArnEquals": {
"aws:SourceArn": "arn:aws:s3:::${var.cloudtrail_event_log_bucket_name}"
}
}
}
]
}
POLICY
}
I am having issues with my s3 bucket policy, it seems to add the policy correctly and even verified it in AWS and it shows the exact policy set in the policy.tpl but it keeps saying there are changes
I've tried changing the action and resource into arrays which I've heard may help.. tried removing the "Version" from the policy, the SID, keeps saying there are changes everytime i run it
policy.tf
resource "aws_s3_bucket_policy" "bucket" {
bucket = aws_s3_bucket.bucket.id
policy = local.policy
}
locals.tf
locals {
template_dir = "${path.module}/templates"
template_vars = {
encrypt = var.s3_require_encryption_enabled
bucket_arn = aws_s3_bucket.bucket.arn
extra_statements = var.s3_bucket_policy
}
policy = templatefile("${local.template_dir}/policy.tpl", local.template_vars)
}
templates/policy.tpl
{
"Version": "2008-10-17",
"Statement": [
{
"Sid" : "",
"Effect" : "Deny",
"Principal" : "*",
"Action" : "s3:*",
"Resource" : "${bucket_arn}/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
in AWS
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::test-bucket-us-east-1/*",
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}
says
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
#aws_s3_bucket_policy.bucket will be updated in-place
~ resource "aws_s3_bucket_policy" "bucket" {
bucket = "test-bucket-us-east-1"
id = "test-bucket-us-east-1"
+ policy = jsonencode(
{
+ Statement = [
+ {
+ Action = "s3:*"
+ Condition = {
+ Bool = {
+ aws:SecureTransport = "false"
}
}
+ Effect = "Deny"
+ Principal = "*"
+ Resource = "arn:aws:s3:::test-bucket-us-east-1/*"
+ Sid = ""
},
]
+ Version = "2008-10-17"
}
)
}
Plan: 0 to add, 1 to change, 0 to destroy.
Based on the comments, the underlying bucket policy had issues.
PutBucketPolicy has
Content-MD5
The MD5 hash of the request body.
For requests made using the AWS Command Line Interface (CLI) or AWS SDKs, this field is calculated automatically.)
So resource aws_s3_bucket_policy was trying to update the policy.