Terraform give IAM access to specific folder only in S3 - amazon-web-services

How can I create an IAM user using terraform with access to one folder in s3
I found a lot of answers that give access to the entire bucket but I want access to one folder only
resource "aws_iam_policy" "username-s3-access" {
name = "username-s3-access"
path = "/"
policy = data.aws_iam_policy_document.username-s3-access.json
}
resource "aws_iam_user" "username-s3-access" {
name = "username-s3-access"
path = "/machine/"
}
resource "aws_iam_user_policy_attachment" "username-s3-access" {
user = aws_iam_user.username-s3-access.name
policy_arn = aws_iam_policy.username-s3-access.arn
}

I found the solution based on this official documentation in AWS site https://aws.amazon.com/premiumsupport/knowledge-center/s3-folder-user-access/
This code will give full assess to one folder in the bucket
scroll down to find the code for readonly access to one folder
Note: change the bucket_arn and the folder_name
variable "bucket_arn" {
type = string
default = "bucket_arn"
}
variable "folder_name" {
type = string
default = "s3_folder_name"
}
data "aws_iam_policy_document" "username-s3-access" {
statement {
sid = "AllowUserToSeeBuckets"
effect = "Allow"
resources = ["arn:aws:s3:::*"]
actions = [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
]
}
statement {
sid = "AllowBucketListing"
effect = "Allow"
resources = [ var.bucket_arn ]
actions = ["s3:ListBucket"]
}
statement {
sid = "AllowFolderListing"
effect = "Allow"
resources = [ var.bucket_arn ]
actions = ["s3:ListBucket"]
condition {
test = "StringEquals"
variable = "s3:prefix"
values = [
"",
"${var.folder_name}"
]
}
condition {
test = "StringEquals"
variable = "s3:delimiter"
values = ["/"]
}
}
statement {
sid = "AllowAllS3ActionsInUserFolder"
effect = "Allow"
resources = ["${var.bucket_arn}/${var.folder_name}/*"]
actions = ["s3:*"]
}
}
resource "aws_iam_policy" "username-s3-access" {
name = "username-s3-access"
path = "/"
policy = data.aws_iam_policy_document.username-s3-access.json
}
resource "aws_iam_user" "username-s3-access" {
name = "username-s3-access"
path = "/machine/"
}
resource "aws_iam_user_policy_attachment" "username-s3-access" {
user = aws_iam_user.username-s3-access.name
policy_arn = aws_iam_policy.username-s3-access.arn
}
if you want to give read only access to the folder then replace the last statement
statement {
sid = "AllowReadOnlyInUserFolder"
effect = "Allow"
resources = ["${var.bucket_name}/${var.folder_name}/*"]
actions = [
"s3:GetObject",
"s3:ListBucket",
]
}

Related

Adding S3 bucket policy to multiple buckets with for_each Terraform module

I have the following module which works fine.
Module
resource "aws_s3_bucket" "buckets" {
bucket = var.s3_buckets
}
resource "aws_s3_bucket_acl" "buckets" {
bucket = var.s3_buckets
acl = "private"
}
Root module
module "s3_buckets" {
source = "./modules/s3"
for_each = toset([
"bucket-test1-${var.my_env}",
"bucket-test2-${var.my_env}",
])
s3_buckets = each.value
}
I would like to add get the following policy to all the buckets in the list. Obviously the count option below does not work.
data "aws_iam_policy_document" "buckets" {
count = length(var.s3_buckets)
statement {
sid = "AllowSSlRequestsOnly"
actions = ["s3:*"]
effect = "Deny"
condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
principals {
type = "*"
identifiers = ["*"]
}
resources = ["arn:aws:s3:::${var.s3_buckets}"]
}
}
resource "aws_s3_bucket_policy" "buckets" {
bucket = var.s3_buckets
policy = data.aws_iam_policy_document.buckets[count.index].json
}
I'm thinking that I need another for_each and use a resource for the IAM policy, I have seen an example such as below, but in the current form I'm providing a string instead a set of strings. Any ideas?
resource "aws_s3_bucket_policy" "buckets" {
for_each = var.s3_buckets
bucket = each.key
policy = jsonencode({
Version = "2012-10-17"
Id = "AllowSSlRequestsOnly",
Statement = [
{
Sid = "AllowSSlRequestsOnly"
Effect = "Deny"
Principal = "*"
Action = "s3:*"
Resource = each.value.arn
Condition = {
Bool = {
"aws:SecureTransport": "false"
}
}
}
]
})
}
If you are adding the policy inside the module (which you probably are, otherwise it doesn't make much sense to attach the policies outside, since you have full control) - then why do you need to mingle with count() at all?
Create the policy and attach to the bucket like:
data "aws_iam_policy_document" "buckets" {
statement {
sid = "AllowSSlRequestsOnly"
actions = ["s3:*"]
effect = "Deny"
condition {
test = "Bool"
variable = "aws:SecureTransport"
values = ["false"]
}
principals {
type = "*"
identifiers = ["*"]
}
resources = ["arn:aws:s3:::${var.s3_buckets}"]
}
}
resource "aws_s3_bucket_policy" "buckets" {
bucket = var.s3_buckets
policy = data.aws_iam_policy_document.buckets.json
}
Couple more points:
It's a singular bucket that you are passing to the module, yet the variable is named s3_buckets, it is confusing.
Using var.s3_buckets for all dependent resources is not the best practice. Create the bucket with var.s3.buckets, after which use the outputs of the resource. This and examples of policies reside here: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket
good luck ☺️

Apply same data for two resources in loop terraform

I want to create two Amazon SNS topics with the same aws_iam_policy_document, aws_sns_topic_policy & time_sleep configs.
This is my terraform, my_sns_topic.tf:
resource "aws_sns_topic" "topic_a" {
name = "topic-a"
}
resource "aws_sns_topic" "topic_b" {
name = "topic-b"
}
data "aws_iam_policy_document" "topic_notification" {
version = "2008-10-17"
statement {
sid = "__default_statement_ID"
actions = [
"SNS:Publish"
]
# Cut off some lines for simplification.
## NEW LINE ADDED
statement {
sid = "allow_snowflake_subscription"
principals {
type = "AWS"
identifiers = [var.storage_aws_iam_user_arn]
}
actions = ["SNS:Subscribe"]
resources = [aws_sns_topic.topic_a.arn] # Troubles with this line
}
}
resource "aws_sns_topic_policy" "topic_policy_notification" {
arn = aws_sns_topic.topic_a.arn
policy = data.aws_iam_policy_document.topic_policy_notification.json
}
resource "time_sleep" "topic_wait_10s" {
depends_on = [aws_sns_topic.topic_a]
create_duration = "10s"
}
As you can see here, I set up the configuration only for topic-a. I want to loop this over to apply for topic-b as well.
It would be better to use map and for_each, instead of separately creating "a" and "b" topics:
variable "topics" {
default = ["a", "b"]
}
resource "aws_sns_topic" "topic" {
for_each = toset(var.topics)
name = "topic-${each.key}"
}
data "aws_iam_policy_document" "topic_notification" {
version = "2008-10-17"
statement {
sid = "__default_statement_ID"
actions = [
"SNS:Publish"
]
# Cut off some lines for simplification.
}
resource "aws_sns_topic_policy" "topic_policy_notification" {
for_each = toset(var.topics)
arn = aws_sns_topic.topic[each.key].arn
policy = data.aws_iam_policy_document.topic_policy_notification.json
}
resource "time_sleep" "topic_wait_10s" {
for_each = toset(var.topics)
depends_on = [aws_sns_topic.topic[each.key]]
create_duration = "10s"
}

AWS S3 replication fails without explicit error

I'm trying to setup one-way replication between two accounts using this guide. Here's the relevant code for the source account:
data "aws_iam_policy_document" "s3-replication-trust" {
statement {
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
effect = "Allow"
}
}
resource "aws_iam_role" "s3-replication-prod" {
count = var.env == "prod" ? 1 : 0 # only apply in prod account
name = "s3-replication-role-prod"
path = "/"
assume_role_policy = data.aws_iam_policy_document.s3-replication-trust.json
}
data "aws_iam_policy_document" "s3-replication-prod" {
count = var.env == "prod" ? 1 : 0 # only apply in prod account
statement {
actions = [
"s3:GetReplicationConfiguration",
"s3:ListBucket"
]
effect = "Allow"
resources = [ aws_s3_bucket.source.arn ]
}
statement {
actions = [
"s3:GetObjectVersionForReplication",
"s3:GetObjectVersion",
"s3:GetObjectVersionAcl",
"s3:GetObjectVersionTagging"
]
effect = "Allow"
resources = [ "${aws_s3_bucket.source.arn}/*" ]
}
statement {
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete",
"s3:ReplicateTags"
]
effect = "Allow"
resources = [ "${aws_s3_bucket.destination.arn}/*" ]
}
}
resource "aws_iam_policy" "s3-replication-prod" {
count = var.env == "prod" ? 1 : 0 # only apply in prod account
name = "s3-replication"
path = "/"
policy = data.aws_iam_policy_document.s3-replication-prod[0].json
}
resource "aws_iam_role_policy_attachment" "s3-replication-prod" {
count = var.env == "prod" ? 1 : 0 # only apply in prod account
role = aws_iam_role.s3-replication-prod[0].name
policy_arn = aws_iam_policy.s3-replication-prod[0].arn
}
resource "aws_s3_bucket_replication_configuration" "replication" {
count = var.env == "prod" ? 1 : 0 # only apply in prod account
# Must have bucket versioning enabled first
depends_on = [aws_s3_bucket_versioning.source]
role = aws_iam_role.s3-replication-prod[0].arn
bucket = aws_s3_bucket.source.id
rule {
id = "ReplicateToDev"
status = "Enabled"
destination {
bucket = aws_s3_bucket.destination.arn
storage_class = "ONEZONE_IA"
access_control_translation {
owner = "Destination"
}
account = var.destination_account_id
}
}
}
And here's the code relevant to the destination account:
data "aws_iam_policy_document" "destination_bucket_policy" {
statement {
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${var.prod_account_id}:role/s3-replication-role-prod"
]
}
actions = [
"s3:ReplicateDelete",
"s3:ReplicateObject"
]
resources = ["${aws_s3_bucket.destination.arn}/*"]
}
statement {
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${var.prod_account_id}:role/s3-replication-role-prod"
]
}
actions = [
"s3:List*",
"s3:GetBucketVersioning",
"s3:PutBucketVersioning"
]
resources = [aws_s3_bucket.destination.arn]
}
}
resource "aws_s3_bucket_policy" "s3-replication-dev" {
count = var.env == "dev" ? 1 : 0 # only apply in dev account
bucket = "${var.app}-dev"
policy = data.aws_iam_policy_document.destination_bucket_policy.json
}
When I try to add any new object to the source bucket, it is unable to replicate. When I navigate to the object's listing in the console, it shows replication status "FAILED".
There's no obvious errors showing up in CloudTrail.
What am I doing wrong here?

how to create an iam role with policy that grants access to the SQS created

I created 2 SQS and the DeadLetterQueue with the code in my main.tf calling the SQS/main.tf module.I would like to destroy and create them again but this time,I want to call IAM/iam_role.tf as well to create one IAM role together with the policy documents.I don't know how to specify that in my main.tf so that the resources section of the data policy document has both CloudTrail_SQS created ,meaning "CloudTrail_SQS_Data_Event" and "cloudTrail_SQS_Management_Event" and the resources arn of the S3 give the role access to the 2 different buckets used for the SQS,meaning "cloudtrail-management-event-logs" and "aws-cloudtrail143-sqs-logs"
SQS/main.tf
resource "aws_sqs_queue" "CloudTrail_SQS"{
name = var.sqs_queue_name
redrive_policy = jsonencode({
deadLetterTargetArn = aws_sqs_queue.CloudTrail_SQS_DLQ.arn
maxReceiveCount = 4
})
}
resource "aws_sqs_queue" "CloudTrail_SQS_DLQ"{
name = var.dead_queue_name
IAM/iam_role.tf
resource "aws_iam_role" "access_role" {
name = var.role_name
description = var.description
assume_role_policy = data.aws_iam_policy_document.trust_relationship.json
}
trust policy
data "aws_iam_policy_document" "trust_relationship" {
statement {
sid = "AllowAssumeRole"
actions = ["sts:AssumeRole"]
principals {
type = "AWS"
identifiers = [var.account_id]
}
condition {
test = "StringEquals"
variable = "sts:ExternalId"
values = [var.external_id]
}
}
}
data "aws_iam_policy_document" "policy_document"{
statement{
actions = [
"sqs:GetQueueUrl",
"sqs:ReceiveMessage",
"sqs:SendMessage"
]
effect = "Allow"
resources = aws_sqs_queue.CloudTrail_SQS.arn
}
statement {
actions = ["sqs:ListQueues"]
effect = "Allow"
resources = ["*"]
}
statement {
actions = ["s3:GetObject", "s3:GetBucketLocation"]
resources = [
"arn:aws:s3:::${var.cloudtrail_event_log_bucket_name}/*"
]
effect = "Allow"
}
statement {
actions = ["s3:ListBucket"]
resources = [
"arn:aws:s3:::${var.cloudtrail_event_log_bucket_name}"
]
effect = "Allow"
}
statement {
actions = ["kms:Decrypt", "kms:GenerateDataKey","kms:DescribeKey" ]
effect = "Allow"
resources = [var.kms_key_arn]
}
}
main.tf
module "data_events"{
source = "../SQS"
cloudtrail_event_log_bucket_name = "aws-cloudtrail143-sqs-logs"
sqs_queue_name = "CloudTrail_SQS_Data_Event"
dead_queue_name = "CloudTrail_DLQ_Data_Event"
}
module "management_events"{
source = "../SQS"
cloudtrail_event_log_bucket_name = "cloudtrail-management-event-logs"
sqs_queue_name = "cloudTrail_SQS_Management_Event"
dead_queue_name = "cloudTrail_DLQ_Management_Event"
}
The role would be created as shown below. But your question has so many mistakes and missing information, that its impossible to provide full, working code. So the below code should be treated as a template which you need to adjust for your use.
resource "aws_iam_role" "access_role" {
name = var.role_name
description = var.description
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Sid = ""
Principal = {
Service = "ec2.amazonaws.com"
}
},
]
})
inline_policy {
name = "allow-access-to-s3-sqs"
policy = data.aws_iam_policy_document.policy_document.json
}
}
data "aws_iam_policy_document" "policy_document"{
statement{
actions = [
"sqs:GetQueueUrl",
"sqs:ReceiveMessage",
"sqs:SendMessage"
]
effect = "Allow"
resources = [
module.data_events.sqs.arn,
module.management_events.sqs.arn,
]
}
statement {
actions = ["sqs:ListQueues"]
effect = "Allow"
resources = ["*"]
}
statement {
actions = ["s3:GetObject", "s3:GetBucketLocation"]
resources = [
"arn:aws:s3:::aws-cloudtrail143-sqs-logs/*"
"arn:aws:s3:::cloudtrail-management-event-logs/*"
]
effect = "Allow"
}
statement {
actions = ["s3:ListBucket"]
resources = [
"arn:aws:s3:::aws-cloudtrail143-sqs-logs",
"arn:aws:s3:::cloudtrail-management-event-logs"
]
effect = "Allow"
}
statement {
actions = ["kms:Decrypt", "kms:GenerateDataKey","kms:DescribeKey" ]
effect = "Allow"
resources = [var.kms_key_arn]
}
}
You can use the data sources of terraform.
At this time, you should write the output for SQS folder, write them as data in IAM folder and use it

How to loop through a list of s3 buckets and create and attach a number of policies for each bucket?

I am learning about terraform modules and my objective is to build module which takes in a collection of s3 Buckets, and then creates and applies to them some iam policies.
What I have tried so far was to have some sort of a for loop, where I generate the policies and attach them to the buckets. For reference, my code looks something like this:
data "aws_iam_policy_document" "foo_iam_policy" {
statement {
sid = ""
effect = "Allow"
resources = [
for arn in var.s3_buckets_arn :
"${arn}/*"
]
actions = [
"s3:GetObject",
"s3:GetObjectVersion",
]
}
statement {
sid = ""
effect = "Allow"
resources = var.s3_buckets_arn
actions = ["s3:*"]
}
}
resource "aws_iam_policy" "foo_iam_policy" {
name = "foo-iam-policy"
path = "/"
description = "IAM policy for foo to access S3"
policy = data.aws_iam_policy_document.foo_iam_policy.json
}
data "aws_iam_policy_document" "foo_assume_rule_policy" {
statement {
effect = "Allow"
actions = [
"sts:AssumeRole"]
principals {
type = "AWS"
identifiers = [
var.foo_iam_user_arn]
}
condition {
test = "StringEquals"
values = var.foo_external_ids
variable = "sts:ExternalId"
}
}
}
resource "aws_iam_role" "foo_role" {
name = "foo-role"
assume_role_policy = data.aws_iam_policy_document.foo_assume_rule_policy.json
}
resource "aws_iam_role_policy_attachment" "foo_attach_s3_policy" {
role = aws_iam_role.foo_role.name
policy_arn = aws_iam_policy.foo_iam_policy.arn
}
data "aws_iam_policy_document" "foo_policy_source" {
for_each = toset(var.s3_buckets_arn)
// arn = each.key
statement {
sid = "VPCAllow"
effect = "Allow"
resources = [
each.key,
"${each.key}/*",
]
actions = [
"s3:*"]
condition {
test = "StringEquals"
variable = "aws:SourceVpc"
values = [
"vpc-01010101"]
}
principals {
type = "*"
identifiers = [
"*"]
}
}
}
I don't know if what I have tried makes much sense, or if there is a better way to loop through buckets and generate policies. My question is: what is the best practice for such cases where one wants to provide a list of buckets and loop through them to attach policies?
On a side note, I have encountered an error with my approach:
The “for_each” value depends on resource attributes that cannot be
determined (Terraform)
To attach a bucket policy to a bucket you should use aws_s3_bucket_policy, not aws_iam_policy_document. Also if the buckets already exist, probably it would be better to fetch their data first using data source aws_s3_bucket:
data "aws_s3_bucket" "selected" {
# s3_buckets_names easier to use then s3_buckets_arns
for_each = toset(var.s3_buckets_names)
bucket = each.value
}
Then, you can iterate over the selected buckets and add your policy to it:
resource "aws_s3_bucket_policy" "bucket_policie" {
for_each = data.aws_s3_bucket.selected
bucket = each.key
policy = "your policy document"
}