Terraform AWS IAM Iterate Over Rendered JSON Policies - terraform

How can I iterate over the JSON rendered data.aws_iam_policy_document documents within an aws_iam_policy?
data "aws_iam_policy_document" "role_1" {
statement {
sid = "CloudFront1"
actions = [
"cloudfront:ListDistributions",
"cloudfront:ListStreamingDistributions"
]
resources = ["*"]
}
}
data "aws_iam_policy_document" "role_2" {
statement {
sid = "CloudFront2"
actions = [
"cloudfront:CreateInvalidation",
"cloudfront:GetDistribution",
"cloudfront:GetInvalidation",
"cloudfront:ListInvalidations"
]
resources = ["*"]
}
}
variable "role_policy_docs" {
type = list(string)
description = "Policies associated with Role"
default = [
"data.aws_iam_policy_document.role_1.json",
"data.aws_iam_policy_document.role_2.json",
]
}
locals {
role_policy_docs = { for s in var.role_policy_docs: index(var.role_policy_docs, s) => s}
}
resource "aws_iam_policy" "role" {
for_each = local.role_policy_docs
name = format("RolePolicy-%02d", each.key)
description = "Custom Policies for Role"
policy = each.value
}
resource "aws_iam_role_policy_attachment" "role" {
for_each = { for p in aws_iam_policy.role : p.name => p.arn }
role = aws_iam_role.role.name
policy_arn = each.value
}
This example has been reduced down to the very basics. The policy documents are dynamically generated with the source_json and override_json conventions. I cannot simply combine the statements into a single policy document.
Terraform Error:
Error: "policy" contains an invalid JSON policy
on role.tf line 35, in resource "aws_iam_policy" "role":
35: policy = each.value

This:
variable "role_policy_docs" {
type = list(string)
description = "Policies associated with Role"
default = [
"data.aws_iam_policy_document.role_1.json",
"data.aws_iam_policy_document.role_2.json",
]
}
Is literally defining those default values as strings, so what you're getting is this:
+ role_policy_docs = {
+ 0 = "data.aws_iam_policy_document.role_1.json"
+ 1 = "data.aws_iam_policy_document.role_2.json"
}
If you tried removing the quotations around the data blocks, it will not be valid because you cannot use variables in default definitions. Instead, assign your policy documents to a new local, and use that local in your for loop instead:
locals {
role_policies = [
data.aws_iam_policy_document.role_1.json,
data.aws_iam_policy_document.role_2.json,
]
role_policy_docs = {
for s in local.role_policies :
index(local.role_policies, s) => s
}
}

Related

Make terraform module creation fail based on a condition

I am trying to leverage precondition hook to check for an input to a module creation in terraform.
module "groups" {
source = "../path/to/groups"
for_each = var.groups.groups
name = each.key
type = each.value.type
policies = each.value.policies
depends_on = [
module.policies
]
lifecycle {
precondition {
condition = alltrue([ for item in self.policies :
alltrue([ for p in item : contains(locals.policies_list, p) ]) ] )
error_message = format("Attempt to create a group with a non existing policy")
}
}
}
However, although:
terraform --version --json
{
"terraform_version": "1.3.7",
"platform": "linux_amd64",
"provider_selections": {},
"terraform_outdated": false
}
This fails:
The block type name "lifecycle" is reserved for use by Terraform in a future version.
Is this because the specific functionality is not available in terraform for module creation? Is there a way around making my module creation fail based on the above condition?
Unfortunately the lifecycle block is not available for a module.
A way around this is the following
module "groups" {
source = "../path/to/groups"
for_each = var.groups.groups
name = each.key
type = each.value.type
policies = each.value.policies
depends_on = [
null_resource.group_check,
module.policies
]
}
resource "null_resource" "group_check" {
for_each = var.groups.groups
lifecycle {
precondition {
condition = alltrue([for p in each.value.policies : contains(local.policies_list, p)])
error_message = format("Attempt to create a group with a non existing policy")
}
}
depends_on = [
module.policies
]
}

Terraform - Reference a for_each resource from another for_each resource

I have a terraform file with the following contents:
resource "aws_iam_group" "developers" {
name = each.value
for_each = toset(var.groups)
}
resource "aws_iam_group_membership" "developers_team" {
name = "Developers Team"
users = [each.value]
for_each = toset(var.group_users)
group = aws_iam_group.developers.name
}
I would like to reference aws_iam_group from aws_iam_group_membership. How would I do that? The current terraform file is not working.
I tried this:
group = aws_iam_group.developers[each.value] //This will not work since it uses the for_each of
its own code block
The variable file is as below:
variable "groups" {
type = list(string)
default = [
"terraform_group1",
"terraform_group2",
"terraform_group3",
]
}
variable "group_users" {
type = list(string)
default = [
"terraform_test_user1",
"terraform_test_user2"
]
}
Edit:
I tried the below, but it is not working
resource "aws_iam_group_membership" "developers_team" {
name = "Developers Team"
users = [for group_user in var.group_users : group_user]
for_each = toset(var.groups)
group = aws_iam_group.developers[each.key]
}
Apparently, this is working:
resource "aws_iam_group" "developer" {
name = "truedeveloper"
}
resource "aws_iam_group_membership" "developers_team" {
name = "Developers_Team"
users = [for group_user in var.group_users : group_user]
for_each = toset(var.groups)
group = aws_iam_group.developer.name
}

Terraform access map

I am trying to access all groups and create groups in the below terraform code. But I am facing error This object does not have an attribute named "groups". Is there any logic I am missing here in the resource "og" "example"
for_each=toset(flatten(local.instances[*].groups)). Thanks
locals {
instances = {
test1 = {
baseUrl = "url1"
subDomain = "sd1"
groups = [
"app1",
"app2",
],
}
test2 = {
baseUrl = "url2"
subDomain = "sd2"
groups = [
"t1",
"t2",
],
}
}
}
resource "og" "example" {
for_each = toset(flatten(local.instances[*].groups))
name = each.value
description = "${each.value}-access"
}
Your local variable is a map, not a list. So it should be:
for_each = toset(flatten(values(local.instances)[*].groups))

Unable to pass variable from .tf file to .json policy template

I'm a newbe to terraform world and experiencing some tough time around passing variables from .tf file to .json
My sample tf lambda function is as follows
data "template_file" "task" {
template = file("./iam/grange_rest_dynlambda_policy.json")
vars = {
resource="${var.stage}_grange_dynamodb"
}
}
resource "aws_lambda_function" "grange_rest_dynlambda" {
function_name = "${var.stage}_grange_rest_dynlambda"
handler = "lambda/src/index.handler"
memory_size = "256"
timeout = 10
reserved_concurrent_executions = "-1"
filename = "${path.module}/../dist/lambda.zip"
role = aws_iam_role.grange_rest_dynlambda_iam_role.arn
runtime = "nodejs14.x"
publish = true
}
resource "aws_lambda_alias" "grange_rest_dynlambda_alias" {
depends_on = ["aws_lambda_function.grange_rest_dynlambda"]
name = var.stage
description = var.stage
function_name = aws_lambda_function.grange_rest_dynlambda.arn
function_version = aws_lambda_function.grange_rest_dynlambda.version
}
// Enable cloudwatch for lambda
resource "aws_cloudwatch_log_group" "example" {
name = "/aws/lambda/${var.stage}_grange_rest_dynlambda"
retention_in_days = 14
}
# See also the following AWS managed policy: AWSLambdaBasicExecutionRole
resource "aws_iam_policy" "lambda_logging" {
name = "lambda_logging"
path = "/"
description = "IAM policy for logging from a lambda"
policy = file("./iam/grange_rest_dynlambda_logging_policy.json")
}
// Lambda + DynamoDB
resource "aws_iam_role" "grange_rest_dynlambda_iam_role" {
name = "grange_rest_dynlambda_iam_role"
assume_role_policy = file("./iam/grange_rest_dynlambda_assume_policy.json")
}
resource "aws_iam_role_policy" "grange_rest_dynlambda_iam_policy" {
policy = file("./iam/grange_rest_dynlambda_policy.json")
role = aws_iam_role.grange_rest_dynlambda_iam_role.id
}
resource "aws_iam_role_policy_attachment" "lambda_logs" {
role = aws_iam_role.grange_rest_dynlambda_iam_role.name
policy_arn = aws_iam_policy.lambda_logging.arn
}
// API Gateway + Lambda
resource "aws_api_gateway_resource" "grange_rest_dynlambda_api" {
parent_id = aws_api_gateway_rest_api.grange_rest_api_gateway.root_resource_id
path_part = "grange_rest_dynlambda_api"
rest_api_id = aws_api_gateway_rest_api.grange_rest_api_gateway.id
}
resource "aws_api_gateway_method" "grange_rest_dynlambda_api_get" {
authorization = "NONE"
http_method = "GET"
resource_id = aws_api_gateway_resource.grange_rest_dynlambda_api.id
rest_api_id = aws_api_gateway_rest_api.grange_rest_api_gateway.id
}
resource "aws_api_gateway_method" "grange_rest_dynlambda_api_post" {
authorization = "NONE"
http_method = "POST"
resource_id = aws_api_gateway_resource.grange_rest_dynlambda_api.id
rest_api_id = aws_api_gateway_rest_api.grange_rest_api_gateway.id
}
resource "aws_lambda_permission" "apigw" {
action = "lambda:InvokeFunction"
statement_id = "AllowExecutionFromAPIGateway"
function_name = aws_lambda_function.grange_rest_dynlambda.function_name
principal = "apigateway.amazonaws.com"
source_arn = "${aws_api_gateway_rest_api.grange_rest_api_gateway.execution_arn}/*/*"
}
output "base_url" {
value = aws_api_gateway_deployment.apigwdeployment.invoke_url
}
I inject policy from a JSON file and expect "resource" variable to be passed into JSON. But, that's not how it works
{
"Version": "2012-10-17",
"Statement":[{
"Effect": "Allow",
"Action": [
"dynamodb:BatchGetItem",
"dynamodb:GetItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:BatchWriteItem",
"dynamodb:PutItem",
"dynamodb:UpdateItem"
],
"Resource": "arn:aws:dynamodb:us-east-2:741573820784:table/${resource}"
}
]
}
What am I missing?
The template_file data source does not replace the variables in the actual file. It just reads the file and provides the "rendered" output directly to your Terraform.
Therefore, you need to change your Terraform where you want to consume the "rendered" output:
Before:
resource "aws_iam_role_policy" "grange_rest_dynlambda_iam_policy" {
policy = file("./iam/grange_rest_dynlambda_policy.json")
role = aws_iam_role.grange_rest_dynlambda_iam_role.id
}
After:
resource "aws_iam_role_policy" "grange_rest_dynlambda_iam_policy" {
policy = data.template_file.task.rendered
role = aws_iam_role.grange_rest_dynlambda_iam_role.id
}
You need to access the rendered property of the template_file data source:
data.template_file.task.rendered
This will replace ${resource} with the value of "${var.stage}_grange_dynamodb".
Please note, that the documentation recommends to use the templatefile function instead of this data source.

How to get Subnet list from VPC with terraform

I've tried to get all subnet ids to add aws batch with terraform with following code:
data "aws_subnet_ids" "test_subnet_ids" {
vpc_id = "default"
}
data "aws_subnet" "test_subnet" {
count = "${length(data.aws_subnet_ids.test_subnet_ids.ids)}"
id = "${tolist(data.aws_subnet_ids.test_subnet_ids.ids)[count.index]}"
}
output "subnet_cidr_blocks" {
value = ["${data.aws_subnet.test_subnet.*.id}"]
}
Fortunately, it was working fine when I've tested like that. But when I tried to integrate with batch terraform like:
resource "aws_batch_compute_environment" "test-qr-processor" {
compute_environment_name = "test-qr-processor-test"
compute_resources {
instance_role = "${aws_iam_instance_profile.test-ec2-role.arn}"
instance_type = [
"optimal"
]
max_vcpus = 256
min_vcpus = 0
security_group_ids = [
"${aws_security_group.test-processor-batch.id}"
]
subnets = ["${data.aws_subnet.test_subnet.*.id}"]
type = "EC2"
}
service_role = "${aws_iam_role.test-batch-service-role.arn}"
type = "MANAGED"
depends_on = [ "aws_iam_role_policy_attachment.test-batch-service-role" ]
}
I've encountered following error message,
Error: Incorrect attribute value type
on terraform.tf line 142, in resource
"aws_batch_compute_environment" "test-processor": 142: subnets =
["${data.aws_subnet.test_subnet.*.id}"]
Inappropriate value for attribute "subnets": element 0: string
required.
Please let me know why, thanks.
"${data.aws_subnet.test_subnet.*.id}" is already string array type.
you should input value without [ ]
write code like :
subnets = "${data.aws_subnet.test_subnet.*.id}"
See :
Here's A document about Resource: aws_batch_compute_environment

Resources