JSON Syntax error in Terraform aws_iam_role_policy - amazon-web-services

So with Terraform, I'm creating an IAM policy and attaching it to a role. I am currently running:
Terraform v0.12.16
provider.aws v2.40.0
provider.template v2.1.2
When executing the code, I am able to initialize terraform with no issue. When running terraform plan, I am being presented with the following error:
Error: "policy" contains an invalid JSON: invalid character '}' looking for beginning of value
on ec2-iam.tf line 8, in resource "aws_iam_role_policy" "s3_ec2_policy":
8: resource "aws_iam_role_policy" "s3_ec2_policy" {
I am getting stuck with this error. Any advice would be helpful. Below is my code:
data "template_file" "s3_web_policy" {
template = file("scripts/iam/web-ec2-policy.json")
vars = {
s3_bucket_arn = "arn:aws:s3:::${var.my_app_s3_bucket}/*"
}
}
resource "aws_iam_role_policy" "s3_ec2_policy" {
name = "s3_ec2_policy"
role = aws_iam_role.s3_ec2_role.id
policy = data.template_file.s3_web_policy.rendered
}
resource "aws_iam_role" "s3_ec2_role" {
name = "s3_ec2_role"
assume_role_policy = file("scripts/iam/web-ec2-assume-role.json")
}

It's common to encounter syntax errors when generating JSON from string templates, because the template language isn't aware of JSON syntax and so you as the tempate author must take care to ensure that brackets are all nested correctly, there are no missing or extra commas, etc.
You can usually avoid problems of this type by generating JSON with Terraform's jsonencode function instead:
resource "aws_iam_role_policy" "s3_ec2_policy" {
name = "s3_ec2_policy"
role = aws_iam_role.s3_ec2_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
# etc, etc
]
})
}
If the policy definition seems too large to include inline in your resource block, you can still factor it out into a separate template file if desired:
resource "aws_iam_role_policy" "s3_ec2_policy" {
name = "s3_ec2_policy"
role = aws_iam_role.s3_ec2_role.id
policy = templatefile("${path.module}/scripts/iam/web-ec2-policy.json.tmpl", {
s3_bucket_arn = "arn:aws:s3:::${var.my_app_s3_bucket}/*"
})
}
...but inside the template, rather than using individual template interpolations, just write the entire template as a single call to jsonencode, like this:
${jsonencode({
Version = "2012-10-17"
Statement = [
{
# ...
Resource = s3_bucket_arn
# ...
},
# etc, etc
]
})}
Note that the template_file data source is for Terraform 0.11 and earlier, and is in Terraform 0.12 only for backward compatibility. You should use the templatefile function instead, which serves the same purpose but is integrated directly into the Terraform language.

Related

Reference several aws_iam_policy_document data sources created with for_each in Terraform

I want to create multiple aws_iam_policy_document resources with for_each, to be later assumed by several roles, as follows:
# Policy to allow services to assume the role
data "aws_iam_policy_document" "this" {
for_each = var.lambda_configuration
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = [
"lambda.amazonaws.com",
"apigateway.amazonaws.com",
]
}
}
}
# IAM role for executing the Lambda function
resource "aws_iam_role" "this" {
for_each = var.lambda_configuration
name = "my_lambda_${each.key}_Executor_Role"
description = "Role for executing my_lambda-${each.key} function"
assume_role_policy = data.aws_iam_policy_document.assume_role_policy_[each.key].json
}
How should I interpolate this
assume_role_policy = data.aws_iam_policy_document.assume_role_policy_[each.key].json
to make the correct matching with the roles?
The correct syntax:
data.aws_iam_policy_document.this[each.key].json
Note that this is not interpolation, as you mentioned in your question. It is just a value lookup. And I have no idea where you got assume_role_policy_ from, but that is not valid HCL syntax.
This seems like a good situation for Chaining for_each Between Resources, which (subjectively) tends to lead to a configuration where it's easier for an unfamiliar reader to follow the way data is flowing between the resources.
data "aws_iam_policy_document" "this" {
for_each = var.lambda_configuration
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = [
"lambda.amazonaws.com",
"apigateway.amazonaws.com",
]
}
}
}
# IAM role for executing the Lambda function
resource "aws_iam_role" "this" {
for_each = data.aws_iam_policy_document.this
name = "my_lambda_${each.key}_Executor_Role"
description = "Role for executing my_lambda-${each.key} function"
assume_role_policy = each.value.json
}
Using the first resource itself as the for_each for the second one means that the two will always have exactly the same keys (because a resource with for_each appears in expressions as a map of objects using the keys from the for_each map), and that each.value inside the second block refers to the corresponding instance of the first block.
This has essentially the same behavior as using the index operator to select an element from data.aws_iam_policy_document.this using each.key, but makes the relationship between these two resources a little more direct and so (again, subjectively) easier to read and maintain in future.

gcp google_project_iam_member gives invalid argument message on terraform

I'm using terraform to deploy the following
resource "google_project_iam_custom_role" "brw-user-function-item-registered-role" {
role_id = "brw_user_function_item_registered_role"
title = "brw-user-function-item-registered-role"
description = "Role used by the brw-user-function item-registered"
permissions = [
"storage.objects.create",
"storage.objects.get",
"storage.objects.list"
]
}
resource "google_service_account" "brw-user-function-item-registered-service-account" {
account_id = "brw-user-function-item-reg-svc"
display_name = "brw-user-function-item-registered-service_account"
}
resource "google_project_iam_member" "brw-user-function-item-registered-service-account-binding" {
project = local.project
role = "roles/${google_project_iam_custom_role.brw-user-function-item-registered-role.role_id}"
member = "serviceAccount:${google_service_account.brw-user-function-item-registered-service-account.email}"
depends_on = [
google_project_iam_custom_role.brw-user-function-item-registered-role,
google_service_account.brw-user-function-item-registered-service-account
]
}
However when I try to deploy this through terraform, I get the following error
Request "Create IAM Members roles/brw_user_function_item_registered_role serviceAccount:brw-user-function-item-reg-svc#brw-user.iam.gserviceaccount.com for \"project \\\"BRW-User\\\"\"" returned error: Error retrieving IAM policy for project "BRW-User": googleapi: Error 400: Request contains an invalid argument., badRequest
I'm not sure what is wrong here, I have added the depends_on as well just to make sure it is created in the correct order. Could the member attribute be wrong, I tried giving account_id as well and I still get the same error.
Only predefined roles have the string roles/ in front of the name.
You are using the string:
role = "roles/${google_project_iam_custom_role.brw-user-function-item-registered-role.role_id}"
Change it to:
role = google_project_iam_custom_role.brw-user-function-item-registered-role.name
Note the removal of roles/, changing role_id to name, and removing string interpolation.
In the resource google_project_iam_member, if you are passing a custom role it must be of the format:
[projects|organizations]/{parent-name}/roles/{role-name}
Here is an example:
resource "google_project_iam_member" "access" {
project = var.project_name
role = "projects/${var.project_name}/roles/${google_project_iam_custom_role.customer_access.role_id}"
member = "serviceAccount:${google_service_account.service_account.email}"
}
Also, as a best practice avoid using dashes in the resources name (better underscore) and try not make it too long. I've run into issues with long names.

Use an existing lambda function as datasource in Terraform

I'm trying to use an existing Lambda function as a data source and create an EC2 instance. This Lambda function essentially provides the latest AMI.
I'm looking at this doc:
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/lambda_invocation
Source Block:
data "aws_lambda_invocation" "example" {
function_name = aws_lambda_function.resource_selector.ResourceSelector
input = <<JSON
{
"key1": "AMIRegexPattern"
}
JSON
}
output "result_entry" {
value = jsondecode(data.aws_lambda_invocation.example.result)["key1"]
}
It throws this error and I'm a little lost:
Error: Reference to undeclared resource
on create-ec2.tf line 26, in data "aws_lambda_invocation" "example":
26: function_name = aws_lambda_function.resource_selector.ResourceSelector
A managed resource "aws_lambda_function" "resource_selector" has not been
declared in the root module.
Here is the function details:
Function Name - ResourceSelector
Function ARN : arn:aws:lambda:us-east-1:xx50:function:ResourceSelector
Any help on what I am missing? Also curious on this line esp and if this is correct:
function_name = aws_lambda_function.resource_selector.ResourceSelector
Thanks
If the lambda is created outside terraform you have to hardcode or pass via parameter, like so:
function_name = "ResourceSelector"
aws_lambda_function.resource_selector doesn't exist, or you can manage the lambda defininng the aws_lambda_function resource.
Also if you just want to get the latest AMI you don't need a lambda. Terraform actually has a data source that can pull that for you: aws_ami
Example:
data "aws_ami" "example" {
most_recent = true
owners = ["self"]
filter {
name = "name"
values = ["myami-*"]
}
}

terraform module - how to put policy in a variable

I have this snipit of code im using as my module.
What I am wondering is how can I make the policy
resource "aws_iam_role_policy" "role" {
name = var.name
role = var.role
policy = file("${path.module}/mypolicy.json")
}
here is my code I create my TF from:
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
}
What i want to know is the best way to reference 'policy' in my module, and the code I run to actually create the policy based off my module. I do not want to hard code the actual json in my module. How can I make this more reusable for later use for other policies?
What about passing the path to policy as a variable to your module?
In module:
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)
}
And then in the parent module you just provide new path if needed?
module "aws_iam_role_policy" {
source = "../modules/mypolicypolicy/"
name = "mypolicy"
role = module.myrole.myroleout
iam_policy_path = "new_policy_path.json"
}

Ignore certain tags from terraform locals

How can I ignore a certain tag defined in the locals variable?
For example: I would want to ignore the email tag for this dynamodb table resource.
Local definition
locals {
global_tags = {
email = "xxx.com"
owner = "xxx"
}
common_tags = {
Name = "live"
}
}
lifecycle {
ignore_changes = [
read_capacity,
write_capacity,
local.global_tags.email
]
}
tags = merge(local.global_tags,local.common_tags,var.received_nexgen_events_tags)
}
Details:
Terraform v0.12.0
+ provider.aws v2.30.0
I tried this but got an error
Error: Unsupported attribute
on ../../../../tf_module_dynamodb/events.tf line 22, in resource "aws_dynamodb_table" "events":
22: local.global_tags.email
This object has no argument, nested block, or exported attribute named
"local".
2: I also tried like this, got static variable reference is required , what is static variable reference?
lifecycle {
ignore_changes = [
read_capacity,
write_capacity,
local.global_tags["xxx.com"]
]
}
error :
22: local.global_tags["xxx.com"]
A static variable reference is required.
Well, there is a bug in terraform > 0.12 version
The terraform plan and terraform apply will say that its going to add tag.email , however it will ignore the tag.email when terraform apply command has run.
I tested using terraform state rm --target=resource-name and did an import and then did terraform state show resource-name, the tag.email was not imported(was ignored) !
More details : https://github.com/hashicorp/terraform-plugin-sdk/issues/167M
lifecycle {
ignore_changes = [
read_capacity,
write_capacity,
tags.email
]
}