Can an instance profile resource in Terraform have multiple inline policies? - terraform

I'm trying to convert a Cloud Formation script to Terraform. One of the issues I'm running into in the script
Cloud Formation:
"Resources": {
"*******InstanceProfile": {
"Condition": "IsInstanceProfile",
"Type": "AWS::IAM::Role",
"Properties": {
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
},
"Policies": [
{
"PolicyName": "********-*******-instance",
"PolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Resource": [
{
"Fn::Sub": "arn:aws:iam::*:role/${ReadOnlyRole}"
},
{
"Fn::Sub": "arn:aws:iam::${AWSOrganizationsAccountID}:role/${OrganizationsRole}"
}
]
}
]
}
}
],
"RoleName": {
"Ref": "AccessName"
}
}
}
I am ASSUMING I can use an inline_policy within a resource like in aws_iam_role. Here is my Terraform snippet that I've produced but get errors:
resource "aws_iam_role" "*****_instance_profile" {
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
policy = jsonencode({
Version = "2012-10-17"
Statement = [
Action = ["sts:AssumeRole"]
Effect = "Allow"
Resource = "arn:aws:iam::*:role/${ReadOnlyRole"
]
})
}
I guess I'm stuck with adding the resource within the policy. How would anyone handle this conversion? Do you folks think I'm going down the correct path with aws_iam_role? And, if so, how would you handle multiple resources in an inline_policy?

I would just use the official documentation when adding multiple resources to a policy.
data "aws_iam_policy_document" "example" {
statement {
actions = [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
]
resources = [
"arn:aws:s3:::*",
]
}
statement {
actions = [
"s3:*",
]
resources = [
"arn:aws:s3:::${var.s3_bucket_name}/home/&{aws:username}",
"arn:aws:s3:::${var.s3_bucket_name}/home/&{aws:username}/*",
]
}
}
resource "aws_iam_user" "user" {
name = "test-user"
}
resource "aws_iam_policy" "policy" {
name = "test-policy"
description = "A test policy"
policy = data.aws_iam_policy_document.example.json
}
resource "aws_iam_user_policy_attachment" "test-attach" {
user = aws_iam_user.user.name
policy_arn = aws_iam_policy.policy.arn
}

Related

Task role defined by Terraform not working correctly for ECS scheduled task

Our team has a bunch of cron jobs running as an ECS scheduled task. Lately I'm adding a new job that requires the use of dynamodb, so I added the permissions in our terraform files, but keep on getting permission failure:
com.amazonaws.services.dynamodbv2.model.AmazonDynamoDBException:
User: arn:aws:sts::87********23:assumed-role/tcoe-tableau/74a408106bf543ee95dbe4841d00b0f7 is not authorized to perform: dynamodb:GetItem on resource: arn:aws:dynamodb:us-east-1:87********23:table/tcoe-candyjar-metrics (Service: AmazonDynamoDBv2;
Status Code: 400; Error Code: AccessDeniedException; Request ID: H52U8GCS1JAB74OJ6VSSEFLCQNVV4KQNSO5AEMVJF66Q9ASUAAJG; Proxy: null)
My related terraform are as follows:
First, here are the ecs cluster and task definition:
resource "aws_ecs_cluster" "ecs-cluster" {
name = "${var.stack_id}"
tags {
StackId = "${var.stack_id}"
}
lifecycle {
ignore_changes = [
"tags"
]
}
}
resource "aws_ecs_task_definition" "task-definition" {
family = "${var.stack_id}"
network_mode = "awsvpc"
requires_compatibilities = [
"FARGATE"
]
cpu = "${var.cpu}"
memory = "${var.task_memory}"
task_role_arn = "${aws_iam_role.task_role.arn}"
execution_role_arn = "${aws_iam_role.ecs_task_execution_role.arn}"
container_definitions = <<EOF
[
{
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "${var.log_group}",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "${var.stack_id}"
}
},
"ulimits": [
{
"name": "nofile",
"softLimit": 4096,
"hardLimit": 8192
}
],
"image": "${var.ecr_account}.dkr.ecr.us-east-1.amazonaws.com/${var.ecr_namespace}/${var.stack_id}:latest",
"environment": [
{"name": "ENV", "value": "${var.environment}" }
],
"essential": true,
"privileged": false,
"name": "${var.stack_id}",
"memory": ${var.memory}
}
]
EOF
tags {
StackId = "${var.stack_id}"
}
}
Then here's the task role for the task definition:
resource "aws_iam_role" "task_role" {
name = "${var.stack_id}"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "",
"Effect": "Allow",
"Principal": {
${data.aws_caller_identity.current.account_id == var.dev_account ? "\"AWS\": [\"arn:aws:iam::61********19:role/${var.dev_role_name}\"]," : ""}
"Service": ["ecs-tasks.amazonaws.com"]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_instance_profile" "task_role_profile" {
name = "${var.stack_id}"
role = "${aws_iam_role.task_role.name}"
}
Finally here I'm adding the dynamodb-related policy to the task role:
resource "aws_iam_role_policy" "main" {
name = "${var.stack_id}-extra-policy"
role = "${aws_iam_role.task_role.id}"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:List*",
"dynamodb:Get*",
"dynamodb:Describe*",
"dynamodb:DeleteItem",
"dynamodb:Put*",
"dynamodb:UpdateItem",
"dynamodb:BatchWriteItem"
],
"Resource": [
"arn:aws:dynamodb:us-east-1:87********23:table/tcoe-candyjar-metrics",
"arn:aws:dynamodb:us-east-1:87********23:table/tcoe-candyjar-metrics/index/*"
]
}
]
}
EOF
}
Am I doing something wrong here or missing anything?
I thought my failure was due to using role.id instead of role.name, and I wanted to figure out the differences between id and name, so I posted this question aws iam role id vs role name in terraform, when to use which?, then the answer/comment indicated that that are exactly the same, which prompted me to go back and carefully check my commit history and build history, and I realized that the reason role.id didn't work was due to some human error I made. My new codes worked not because I used role.name, but because i unknowingly fixed the other error at the same time.
To summarize, role.id and role.name are exactly the same.

add cloud_watch_logs_role_arn to aws_cloudtrail resource terraform

Hi I am following the guide for https://d1.awsstatic.com/whitepapers/compliance/AWS_CIS_Foundations_Benchmark.pdf. I would like to complete the infrastructure changes for section 3. To start this process I need to create an aws_cloudtrail resource with SSE-KMS encryption enabled. I'm currently reviewing the documentation for this resource. How do I automate the process of specifying the role for the CloudWatch Logs endpoint to assume to write to a user’s log group?
resource aws_cloudtrail "cisbenchmark" {
name = "cis-benchmark"
enable_logging = true
s3_bucket_name = aws_s3_bucket.cisbenchmark.bucket
enable_log_file_validation = true
is_multi_region_trail = true
include_global_service_events = true
cloud_watch_logs_role_arn = ????
cloud_watch_logs_group_arn = aws_cloudwatch_log_group.cisbenchmark.arn
kms_key_id = var.kms_key_arn
is_organization_trail = true
}
I've added ???? to specify where I'm missing the attribute.
If anyone is still looking for the answer:
resource aws_cloudtrail "cisbenchmark" {
name = "cis-benchmark"
enable_logging = true
s3_bucket_name = aws_s3_bucket.cisbenchmark.bucket
enable_log_file_validation = true
is_multi_region_trail = true
include_global_service_events = true
cloud_watch_logs_role_arn = aws_iam_role.cloud_trail.arn
cloud_watch_logs_group_arn = aws_cloudwatch_log_group.cisbenchmark.arn
kms_key_id = var.kms_key_arn
is_organization_trail = true
}
resource "aws_iam_role" "cloud_trail" {
name = "cloudTrail-cloudWatch-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy" "aws_iam_role_policy_cloudTrail_cloudWatch" {
name = "cloudTrail-cloudWatch-policy"
role = aws_iam_role.cloud_trail.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailCreateLogStream2014110",
"Effect": "Allow",
"Action": [
"logs:CreateLogStream"
],
"Resource": [
"${aws_cloudwatch_log_group.cisbenchmark.arn}:*"
]
},
{
"Sid": "AWSCloudTrailPutLogEvents20141101",
"Effect": "Allow",
"Action": [
"logs:PutLogEvents"
],
"Resource": [
"${aws_cloudwatch_log_group.cisbenchmark.arn}:*"
]
}
]
}
EOF
}

Count Not Working Correctly in Terraform Resource aws_iam_policy

I have created resources for log_group for list of given job names
resource "aws_cloudwatch_log_group" "logGroups" {
count = length(var.jobnames)
name = format("/aws/lambda/%s", format(local.function_name_format, var.jobnames[count.index]))
retention_in_days = 7
}
and now for the each log resource i am creating an iam policy
resource "aws_iam_policy" "base_iam_policy" {
count = length(var.jobnames)
name = format(local.base_iam_policy_name_format, var.jobnames[count.index])
path = "/"
description = "Base IAM policy for creating a lambda"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"${element(aws_cloudwatch_log_group.logGroups.*.arn, count.index)}*"
]
},
{
"Action": [
"cloudwatch:PutMetricData",
"cloudwatch:GetMetricData",
"cloudwatch:GetMetricStatistics",
"cloudwatch:ListMetrics"
],
"Resource": "*",
"Effect": "Allow"
},
{
"Effect": "Allow",
"Action": [
"ec2:CreateNetworkInterface",
"ec2:DescribeNetworkInterfaces",
"ec2:DeleteNetworkInterface"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:RequestedRegion": "${var.region}"
}
}
}
]
}
EOF
}
The issue is that for each BASE_IAM_POLICY, the resource for CreateLogStream is same. Looks like
in this "${element(aws_cloudwatch_log_group.logGroups.*.arn, count.index)}*" count is not getting incremented ?
Honestly, this seems like a bug in terraform. In the meantime I'd recommend indexing the elements directly, like the following
"${aws_cloudwatch_log_group.logGroups[count.index].arn}*"

Terraform: Setting up logging from AWS LoadBalancer to S3 bucket

I have an aws_lb that I want to log to an S3 bucket.
What I have unsuccessfully tried to do:
data "aws_elb_service_account" "main" {}
data "aws_iam_policy_document" "bucket_policy" {
statement {
sid = ""
actions = ["s3:PutObject"]
resources = ["arn:aws:s3:::my-bucket/*"]
principals {
type = "AWS"
identifiers = ["${data.aws_elb_service_account.main.arn}"]
}
}
}
I also tried this:
resource "aws_iam_role" "lb-logs-role" {
name = "lb-logs-role"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "elasticloadbalancing.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
tags = {
Name = "lb-logs-role"
Environment = terraform.workspace
Management = "Managed by Terraform"
}
}
resource "aws_iam_role_policy" "s3-logs-access" {
name = "s3-logs-access"
role = aws_iam_role.lb-logs-role.id
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"s3:PutObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
EOF
}
This is the error I am seeing:
Error: Failure configuring LB attributes: InvalidConfigurationRequest: Access Denied for bucket: my-bucket. Please check S3bucket permission
status code: 400, request id: 5b629210-9738-11e9-bcc6-6f3b4f22bf28
on modules/tableau-linux/lb.tf line 1, in resource "aws_lb" "main":
1: resource "aws_lb" "main" {
Any ideas?
It looks like the API will request the ACL of the bucket to see if it has permission, and populate the initial folder structure, therefore the even though the aws_elb_service_account has permissions to putObject in the bucket the api call will fail. This policy is what the AWS web console creates when it creates the S3 bucket for you, and it solved it for me.
data "aws_region" "current" {}
data "aws_caller_identity" "current" {}
data "aws_elb_service_account" "main" {}
resource "aws_s3_bucket_policy" "lb-bucket-policy" {
bucket = aws_s3_bucket.lb-log-storage-s3.id
policy = <<POLICY
{
"Id": "Policy",
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"AWS": [
"${data.aws_elb_service_account.main.arn}"
]
},
"Action": [
"s3:PutObject"
],
"Resource": "${aws_s3_bucket.lb-log-storage-s3.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"
},
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": [
"s3:PutObject"
],
"Resource": "${aws_s3_bucket.lb-log-storage-s3.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control"
}
}
},
{
"Effect": "Allow",
"Principal": {
"Service": "delivery.logs.amazonaws.com"
},
"Action": [
"s3:GetBucketAcl"
],
"Resource": "${aws_s3_bucket.lb-log-storage-s3.arn}"
}
]
}
POLICY
}
It seems the issue is with your policy but you can try my code using aws_lb, Here is the complete configuration to launch to LB in default VPC and create bucket named test-bucket-1-unique-name, policy and LB named test-http-lb. Along with SG and Route53 entry that is commented.
# Creating Load Balancer
resource "aws_lb" "httplb" {
name = "test-http-lb"
internal = false
load_balancer_type = "application"
security_groups = ["${aws_security_group.lbsg.id}"]
subnets = ["subnet-99fdf8e0", "subnet-902b0ddb"]
enable_deletion_protection = false
access_logs {
bucket = "${aws_s3_bucket.bucket.bucket}"
prefix = "http-lb"
enabled = true
}
tags = {
Environment = "test-http"
}
}
# Creating Security Groups for Load Balancer
resource "aws_security_group" "lbsg" {
name = "test-loadbalancer-sg"
description = "test-Allow LB traffic"
tags = {
Name = "test-SG-Balancer"
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
description = "HTTP"
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
#uncomment this if you want to add route53 record
# resource "aws_route53_record" "web" {
# zone_id = "${data.aws_route53_zone.primary.zone_id}"
# name = "${var.env_prefix_name}.ironman.co
# type = "A"
# alias {
# name = "${aws_lb.httplb.dns_name}"
# zone_id = "${aws_lb.httplb.zone_id}"
# evaluate_target_health = true
# }
# }
data "aws_elb_service_account" "main" {}
# Creating policy on S3, for lb to write
resource "aws_s3_bucket_policy" "lb-bucket-policy" {
bucket = "${aws_s3_bucket.bucket.id}"
policy = <<POLICY
{
"Id": "testPolicy1561031527701",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "testStmt1561031516716",
"Action": [
"s3:PutObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::test-bucket-1-for-lb-logs/http-lb/*",
"Principal": {
"AWS": [
"${data.aws_elb_service_account.main.arn}"
]
}
}
]
}
POLICY
}
resource "aws_s3_bucket" "bucket" {
bucket = "test-bucket-1-for-lb-logs"
acl = "private"
region = "us-west-2"
versioning {
enabled = false
}
force_destroy = true
}
Then go to your S3 bucket and verify TestFile.
Here are the logs from terraform
This one stumped me also, but I got it working with this
{
Effect : "Allow",
Principal : {
"AWS" : "arn:aws:iam::127311923021:root"
},
Action : [
"s3:PutObject"
],
Resource : "${aws_s3_bucket.logging_bucket.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*",
}
Where does 127311923021 come from, you ask? Believe me, I did too! This AWS document says it is the ID of the AWS account for Elastic Load Balancing for your Region (us-east-1 in this case). It has a large table of these magic numbers!

Can I add an out of the box aws_iam_policy (SecurityAudit), an account id and an external id to an aws_iam_role using terraform?

I am setting up cloud security and I need to:
Select type of trusted entity > Another AWS account
Account ID: xxxxxxxxxx
External ID: xxxxxxxxxx
Attach the SecurityAudit Policy (which is already in AWS)
I'm not sure how to add an already existing policy or where to add the ids. I can't seem to work out a solution from the terraform documentation.
../Core/iam_roles.tf
# BEGIN 'foo'
resource "aws_iam_role" "foo" {
name = "${terraform.workspace}_Foo"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"automation.amazonaws.com",
"events.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "foo" {
policy_arn = "${aws_iam_policy.security_audit.arn}"
role = "${aws_iam_role.foo.name}"
}
Any help would be much appreciated!
If you're attaching a policy that already exists in the account, I would use a data source to query it. You have to know the ARN to use the IAM policy data source so it's not much different than specifying the ARN directly in the aws_iam_role_policy_attachment resource except it allows the terraform plan command to validate that the policy exists before running apply, it's an extra safeguard for you. The data source also gives you more information about the resource should you need it.
data "aws_iam_policy" "security_audit" {
arn = "arn:aws:iam::${var.target_account_id}:policy/SecurityAudit"
}
# BEGIN 'foo'
resource "aws_iam_role" "foo" {
name = "${terraform.workspace}_Foo"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"automation.amazonaws.com",
"events.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
},
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::${var.other_aws_account_id}:role/your_role_name_and_path_here"
]
},
"Action": "sts:AssumeRole"
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "foo" {
policy_arn = "${data.aws_iam_policy.security_audit.arn}"
role = "${aws_iam_role.foo.name}"
}
`# BEGIN 'Foo'
resource "aws_iam_role" "foo" {
name = "${terraform.workspace}_Foo"
path = "/"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::INSERT_ACCOUNT_NUMBER:root"
},
"Action": "sts:AssumeRole",
"Condition": {
"StringEquals": {
"sts:ExternalId": "INSERT_EXTERNAL_ID"
}
}
}
]
}
EOF
}
resource "aws_iam_role_policy_attachment" "foo" {
policy_arn = "arn:aws:iam::aws:policy/SecurityAudit"
role = "${aws_iam_role.foo.name}"
}
resource "aws_iam_instance_profile" "foo" {
name = "${terraform.workspace}_Foo"
role = "${aws_iam_role.foo.name}"
}
# END
`

Resources