Skip to content

Commit

Permalink
terraform, aws: fix implementation no longer hardcoding user-sa role
Browse files Browse the repository at this point in the history
  • Loading branch information
consideRatio committed Apr 11, 2024
1 parent 62964d5 commit fab3f08
Showing 1 changed file with 114 additions and 28 deletions.
142 changes: 114 additions & 28 deletions terraform/aws/bucket-access.tf
Original file line number Diff line number Diff line change
@@ -1,59 +1,145 @@
/*
Creates one aws_s3_bucket_policy per bucket - there can't be more than one as
they otherwise replace each other when applied.
The bucket policies grant bucket specific permissions to specific IAM Roles
based on them having `bucket_admin_access` or `bucket_readonly_access`
referencing the bucket via `var.hub_cloud_permissions`.
*/

locals {
# Nested for loop, thanks to https://www.daveperrett.com/articles/2021/08/19/nested-for-each-with-terraform/
hub_role_bucket = flatten([
/*
The bucket_role_actions local variable defined below is a list of objects
generated from `var.hub_cloud_permissions` roles and their respective
bucket_admin_access and bucket_readonly_access lists.
If for example `var.hub_cloud_permissions` is:
hub_cloud_permissions:
staging:
user-sa:
bucket_admin_access: [scratch-staging]
sciencecore:
user-sa:
bucket_admin_access: [scratch-sciencecore]
bucket_readonly_access: [persistent-sciencecore]
admin-sa:
bucket_admin_access: [scratch-sciencecore, persistent-sciencecore]
Then, the `local.bucket_role_actions` will look like below, with one list
item for each element in all `bucket_admin/readonly_access` lists:
bucket_role_actions:
- bucket: scratch-staging
role: staging
actions: ["s3:*"]
- bucket: scratch-sciencecore
role: sciencecore
actions: ["s3:*"]
- bucket: scratch-sciencecore
role: sciencecore-admin-sa
actions: ["s3:*"]
- bucket: persistent-sciencecore
role: sciencecore
actions: ["s3:ListBucket", "s3:GetObject", "s3:GetObjectVersion"]
- bucket: persistent-sciencecore
role: sciencecore
actions: ["s3:*"]
*/
bucket_role_actions = flatten([
for hub, hub_value in var.hub_cloud_permissions : [
for role, role_value in hub_value : flatten([
[
for bucket in role_value.bucket_admin_access : {
// id can be simplified, it was set to not change anything
id = role == "user-sa" ? "${hub}.${bucket}" : "${hub}.${role}.${bucket}.admin"
bucket = bucket
// role should match the id set in irsa.tf
role = role == "user-sa" ? hub : "${hub}-${role}"
bucket = bucket
actions = ["s3:*"]
}
],
[
for bucket in role_value.bucket_readonly_access : {
id = "${hub}.${role}.${bucket}.readonly"
// role should match the id set in irsa.tf
role = role == "user-sa" ? hub : "${hub}-${role}"
bucket = bucket
// role should match the id set in irsa.tf
role = role == "user-sa" ? hub : "${hub}-${role}"
actions = [
"s3:ListBucket",
"s3:GetObject",
"s3:GetObjectVersion",
]
}
]
],
])
]
])
}

// FIXME: there can only be one declared per bucket, so if we have multiple
// roles that has permissions, we need to merge them
data "aws_iam_policy_document" "bucket_admin_access" {
for_each = { for index, hrb in local.hub_role_bucket : hrb.id => hrb }
statement {
effect = "Allow"
actions = each.value.actions
principals {
type = "AWS"
identifiers = [
aws_iam_role.irsa_role[each.value.role].arn
locals {
/*
The `local.bucket_role_actions_lists` variable defined below is reprocessing
`local.bucket_role_actions` to a dictionary with one key per bucket with
associated permissions.
bucket_role_actions_lists:
scratch-staging:
- bucket: scratch-staging
role: staging
actions: ["s3:*"]
scratch-sciencecore:
- bucket: scratch-sciencecore
role: sciencecore
actions: ["s3:*"]
- bucket: scratch-sciencecore
role: sciencecore-admin-sa
actions: ["s3:*"]
persistent-sciencecore:
- bucket: persistent-sciencecore
role: sciencecore
actions: ["s3:ListBucket", "s3:GetObject", "s3:GetObjectVersion"]
- bucket: persistent-sciencecore
role: sciencecore
actions: ["s3:*"]
*/
bucket_role_actions_lists = {
for bucket, _ in var.user_buckets :
bucket => [for bra in local.bucket_role_actions : bra if bra.bucket == bucket]
// Filter out user_buckets not mentioned in hub_cloud_permissions
if[for bra in local.bucket_role_actions : bra if bra.bucket == bucket] != []
}
}



data "aws_iam_policy_document" "bucket_policy" {
for_each = local.bucket_role_actions_lists

// Only one policy document can be declared per bucket, so we provide multiple
// "statement" in this policy.
dynamic "statement" {
for_each = { for index, bra in each.value : "${bra.bucket}.${bra.role}" => bra }

content {
effect = "Allow"
actions = statement.value.actions
principals {
type = "AWS"
identifiers = [
aws_iam_role.irsa_role[statement.value.role].arn
]
}
resources = [
# Grant access only to the bucket and its contents
aws_s3_bucket.user_buckets[statement.value.bucket].arn,
"${aws_s3_bucket.user_buckets[statement.value.bucket].arn}/*",
]
}
resources = [
# Grant access only to the bucket and its contents
aws_s3_bucket.user_buckets[each.value.bucket].arn,
"${aws_s3_bucket.user_buckets[each.value.bucket].arn}/*",
]
}
}

// There can only be one of these per bucket, if more are defined they will end
// up replacing each other without terraform indicating there is trouble.
resource "aws_s3_bucket_policy" "user_bucket_access" {
for_each = { for index, hrb in local.hub_role_bucket : hrb.id => hrb }
bucket = aws_s3_bucket.user_buckets[each.value.bucket].id
policy = data.aws_iam_policy_document.bucket_admin_access[each.key].json
for_each = local.bucket_role_actions_lists
bucket = aws_s3_bucket.user_buckets[each.key].id
policy = data.aws_iam_policy_document.bucket_policy[each.key].json
}

0 comments on commit fab3f08

Please sign in to comment.