Create parameterized resource policy on terraform - amazon-web-services

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

Related

Conditional inline_policy in aws_iam_role

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.

How to append multiple AWS SQS policies using Terraform

I do have the default policy for the SQS like below. Referred the documentation - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue_policy
And if there is sns-subscription required, I would like to append the policy on top of the default policy.
Default policy is like below
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sqs:SendMessage*"
],
"Resource": [
"${aws_sqs_queue.queue.arn}"
]
}
]
}
Additional policy like below
{
"Sid": "topic-subscription-arn-test",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "SQS:SendMessage",
"Resource": ["${aws_sqs_queue.queue.arn}"],
"Condition": {
"ArnLike": {
"aws:SourceArn": "arn-test"
}
}
}
I just tried, but the policy getting override. Any thoughts how policy can be appended using Terraform? Thanks in advance.
Looking for somekind of thought for this approach? It may be more than 2 policies, so I am trying to append these policies.
I would strongly suggest using the aws_iam_policy_document data source [1] for building policies in Terraform instead of JSON. Since the SQS queue has an argument policy [2], the resource aws_sqs_queue_policy does not have to be used, but it can also be combined with the data source mentioned above. So there are two options:
Create a policy using data source and attach it by using the policy argument
Create a policy using data source and attach it to the aws_sqs_queue_policy
If you decide for the first option, here is how the code should look like:
data "aws_iam_policy_document" "sqs_policy" {
statement {
sid = "FirstSQSPolicy"
effect = "Allow"
actions = [
"sqs:SendMessage*"
]
resources = [
aws_sqs_queue.queue.arn
]
}
statement {
sid = "topic-subscription-arn-test"
effect = "Allow"
actions = [
"sqs:SendMessage"
]
resources = [
aws_sqs_queue.queue.arn
]
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = [
"arn-test"
]
}
}
}
resource "aws_sqs_queue" "terraform_queue" {
...
policy = data.aws_iam_policy_document.sqs_policy.json
}
For the second option, you can use the same data source and attach the JSON to the aws_sqs_queue_policy resource:
data "aws_iam_policy_document" "sqs_policy" {
statement {
sid = "FirstSQSPolicy"
effect = "Allow"
actions = [
"sqs:SendMessage*"
]
resources = [
aws_sqs_queue.queue.arn
]
}
statement {
sid = "topic-subscription-arn-test"
effect = "Allow"
actions = [
"sqs:SendMessage"
]
resources = [
aws_sqs_queue.queue.arn
]
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = [
"arn-test"
]
}
}
}
resource "aws_sqs_queue_policy" "sqs_queue_policy" {
queue_url = aws_sqs_queue.queue.id
policy = data.aws_iam_policy_document.sqs_policy.json
}
Using the data source for IAM policies you can add statements as you need them.
[1] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
[2] https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sqs_queue#policy

Passing List to IAM Group Policy Template file

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
}

AWS KMS and IAM association using terraform version 0.12

Hi AWS and Terraform experts, I was kinda generating the KMS and IAM association that was built manually by our former colleague, I'm getting an issue to completing the copy of kms policy stated below:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::12345678912345:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "ALSKDJFHGNVBCMXJDH0987"
},
"Action": "kms:Decrypt",
"Resource": "*"
}
]
}
This ALSKDJFHGNVBCMXJDH0987 is an IAM Role which was I believe transformed by AWS console (not yet sure why)
{
"Effect": "Allow",
"Principal": {
"AWS": "ALSKDJFHGNVBCMXJDH0987"
},
"Action": "kms:Decrypt",
"Resource": "*"
}
I'm getting this error using the created terraform script I made:
Error: MalformedPolicyDocumentException: Policy contains a statement with one or more invalid principals.
status code: 400, request id: alsknldkj2-assd-3333-0sdc-askdjaksdjn2
on main.tf line 84, in resource "aws_kms_key" "secrets":
84: resource "aws_kms_key" "secrets" {
Is there something wrong with the sequence? or I'm missing anything?. Attached here is the terraform code I used:
data "template_file" "my-lambda-policy" {
template = "${file("policy/lambda.json")}"
vars = {
SWAG = var.AWS-SWAG
STUDENT-BELONGS = var.STUDENT
STUDENT-TEACHER = var.TEACHER
ROOM = var.CLASSROOM
}
}
resource "aws_iam_policy" "my-lambda-pol" {
name = "my-lambda-policy"
policy = data.template_file.my-lambda-policy.rendered
}
data "template_file" "my-my-lambda-pol2" {
template = "${file("policy/lambda2.json")}"
}
resource "aws_iam_policy" "my-lambda-pol2" {
name = "my-my-lambda-pol2"
policy = data.template_file.my-my-lambda-pol2.rendered
}
data "template_file" "my-lambda-to-my-kms-policy" {
template = "${file("policy/kms-lambda.json")}"
vars = {
SWAG = var.AWS-SWAG
KMS_KEY_ID = aws_kms_key.mysecret.id
}
}
resource "aws_iam_policy" "lambda-to-kms" {
name = "my-lambda-to-my-kms-policy"
policy = data.template_file.my-lambda-to-my-kms-policy.rendered
}
resource "aws_iam_role" "the-lambda-role" {
name = "{STUD_CHAIR}-${STU_SEAG}-${STUDENT-BELONGS}-${STUDENT-TEACHER}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "my-lambda-policy_attachment" {
policy_arn = aws_iam_policy.my-lambda-pol.arn
role = aws_iam_role.the-lambda-role.name
}
resource "aws_iam_role_policy_attachment" "my-lambda-pol2_attachment" {
policy_arn = aws_iam_policy.my-lambda-pol2.arn
role = aws_iam_role.the-lambda-role.name
}
resource "aws_iam_role_policy_attachment" "kms-attachment" {
depends_on = [aws_kms_key.mysecret]
policy_arn = aws_iam_policy.lambda-to-kms.arn
role = aws_iam_role.the-lambda-role.name
}
data "template_file" "my-kms-policy" {
template = "${file("policy/my-kms-policy.json")}"
vars = {
STUD_CHAIR= "${var.CHAIR}"
STU_SWAG = "${l{var.SWAG}}"
STUDENT-BELONGS = "${var.STUDENT}"
STUDENT-TEACHER = "${var.TEACHER}"
ROOM = "${var.CLASSROOM}"
}
}
resource "aws_kms_key" "mysecret" {
description = "KMS Key for ${var.STUDENT}-${var.TEACHER}-key-${var.CLASSROOM}"
policy = data.template_file.my-kms-policy.rendered
depends_on = [aws_iam_role.the-lambda-role]
}
resource "aws_kms_alias" "mysecret" {
name = "alias/${var.STUDENT}-${var.TEACHER}-key-${var.CLASSROOM}"
depends_on = [aws_iam_role.the-lambda-role]
target_key_id = aws_kms_key.mysecret.key_id
}
this is what inside of my-kms-policy.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${ROOM}:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::${ROOM}:role/${STUD_CHAIR}-${STU_SEAG}-${STUDENT-BELONGS}-${STUDENT-TEACHER}"
},
"Action": "kms:Decrypt",
"Resource": "*"
}
]
}
Workaround
A workaround is to run terraform apply twice.
Reason
When recreating an IAM role the policy referencing this role needs to be updated for reasons described here:
https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-user.html
If your Principal element contains the ARN for a specific IAM role or user, then that ARN is transformed to a unique principal ID when the policy is saved. This helps mitigate the risk of someone escalating their permissions by removing and recreating the role or user. You don't normally see this ID in the console because there is also a reverse transformation back to the ARN when the trust policy is displayed. However, if you delete the role or user, then the principal ID appears in the console because AWS can no longer map it back to an ARN. Therefore, if you delete and recreate a user or role referenced in a trust policy's Principal element, you must edit the role to replace the ARN.
Running Terraform the first time will recreate the IAM role and that way break the policy. Running it the second time will correct the policy, by adding the newly created reference to the IAM role.

Terraform: Error creating IAM Role. MalformedPolicyDocument: Has prohibited field Resource

My TF code is giving me an error:
/*
* Policy: AmazonEC2ReadOnlyAccess
*/
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ec2:Describe*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "elasticloadbalancing:Describe*",
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"cloudwatch:ListMetrics",
"cloudwatch:GetMetricStatistics",
"cloudwatch:Describe*"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": "autoscaling:Describe*",
"Resource": "*"
}
]
}
EOF
I copied and pasted the Policy from https://console.aws.amazon.com/iam/home?region=us-west-2#/policies/arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess$jsonEditor
* aws_iam_role.<role name>: Error creating IAM Role <role name>: MalformedPolicyDocument: Has prohibited field Resource
status code: 400, request id: <request id>
Not sure why it's saying Resource is prohibited.
Need to define assume_role_policy with sts:AssumeRole (Who can assume this role, ex: EC2 service).
Policy can be directly attached using aws_iam_role_policy_attachment instead of duplicating existing policy.
resource "aws_iam_role" "ec2_iam_role" {
name = "ec2_iam_role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
"Service": [
"ec2.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "ec2-read-only-policy-attachment" {
role = "${aws_iam_role.ec2_iam_role.name}"
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess"
}
I had faced similar issue when using role arn. When I tried using aws_iam_role_policy_attachment - I was getting error for role name having unsupported characters.
What worked for me for to create a aws_iam_role_policy as below:
resource "aws_iam_role_policy" "api-invoker" {
provider = <some provider>
role = aws_iam_role.api-invoker.id
policy = data.aws_iam_policy_document.execute-api.json
}
data "aws_iam_policy_document" "execute-api" {
statement {
sid = "all"
actions = [
"execute-api:*",
]
resources = [
"*"
]
}
}
I have faced the same issue while i am creating a policy to assume role from another AWS account. So, I have added another AWS account Id in the trusted entities then the problem is resolved.
#create i am user for account-1
resource "aws_iam_user" "user-1" {
name = "my-user"
tags = {
"Name" = "my-user"
}
}
# create policy for 2nd account
resource "aws_iam_policy" "prod_s3" {
provider = aws.aws02
name = "prod_s3"
description = "allow assuming prod_s3 role"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = "sts:AssumeRole",
Resource = "arn:aws:iam::940883708906:user/my-user"
}]
})
}
# Attach the policy
resource "aws_iam_user_policy_attachment" "prod_s3" {
provider = aws.aws02
user = aws_iam_user.user-1.name
policy_arn = aws_iam_policy.prod_s3.arn
}
# create assume role
resource "aws_iam_role" "prod_list_s3" {
provider = aws.aws02
name = "role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = "sts:AssumeRole",
Principal = { "AWS" : "arn:aws:iam::${data.aws_caller_identity.utils.account_id}:root" }
}]
})
}
# output arn
output "role-arn" {
value = aws_iam_role.prod_list_s3.arn
}
# create caller identity
data "aws_caller_identity" "utils" {
provider = aws.aws02
}
# create s3 full access for 2nd account and attach the file
resource "aws_iam_policy" "s3_all" {
provider = aws.aws02
name = "s3_all"
description = "allows listing all s3 buckets"
policy = file("role_permissions_policy.json")
}
# inside the file
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "*"
}
]
}
**strong text**
# Attach the assume role
resource "aws_iam_policy_attachment" "s3-all-att" {
name = "list s3 buckets policy to role"
roles = ["${aws_iam_role.prod_list_s3.name}"]
policy_arn = aws_iam_policy.s3_all.arn
provider = aws.aws02