Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to grant hub users extra IAM permissions #1488

Merged
merged 2 commits into from
Jul 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions docs/howto/features/cloud-access.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,86 @@ on why users want this!

4. Get this change deployed, and users should now be able to use the buckets!
Currently running users might have to restart their pods for the change to take effect.


## Granting access to cloud buckets in other cloud accounts / projects

Sometimes, users on a hub we manage need access to a storage bucket
managed by an external third party - often a different research
group. This can help with access to raw data, collaboration, etc.

This section outlines how to grant this access. Currently, this
functionality is implemented only on AWS - but we can add it for
other cloud providers when needed.

### AWS

On AWS, we would need to set up [cross account S3 access](https://aws.amazon.com/premiumsupport/knowledge-center/cross-account-access-s3/).

1. Find the ARN of the service account used by the *users* on the hub. You can
find this under `userServiceAccount.annotations.eks.amazon.com/role-arn` in
the `values.yaml` file for your hub. It should look something like
`arn:aws:iam::<account-id>:role/<hub-name>`.
2. In the AWS account *with the S3 bucket*, [create an IAM
policy](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create-console.html)
that grants appropriate access to the S3 bucket from the hub. For example, the
following policy grants readonly access to the bucket for users of the hub

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "<arn-of-service-account-from-step-1>"
},
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<name-of-bucket>",
"arn:aws:s3:::<name-of-bucket>/*"
]
}
]
}
```

You can add additional permissions to the bucket if needed here.

```{note}
You can list as many buckets as you want, but each bucket needs two entries -
one with the `/*` and one without so both listing the bucket as well as fetching
data from it can work
```

3. In the `.tfvars` file for the cluster hosting the hub, add `extra_iam_policy`
as a key to the hub under `hub_cloud_permissions`. This is used to set any additional
IAM permissions granted to the users of the hub. In this case, you should copy the
exact policy that was applied to the bucket in step 2, but remove the "Principal" key.
So it would look something like:

```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::<name-of-bucket>",
"arn:aws:s3:::<name-of-bucket>/*"
]
}
]
}
```

4. Apply the terraform config, and test out if s3 bucket access works on the hub!
13 changes: 6 additions & 7 deletions terraform/aws/buckets.tf
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
resource "aws_s3_bucket" "user_buckets" {
for_each = var.user_buckets
bucket = "${var.cluster_name}-${each.key}"
bucket = "${var.cluster_name}-${each.key}"

}

resource "aws_s3_bucket_lifecycle_configuration" "user_bucket_expiry" {
for_each = var.user_buckets
bucket = "${var.cluster_name}-${each.key}"
bucket = "${var.cluster_name}-${each.key}"

dynamic "rule" {
for_each = each.value.delete_after != null ? [1] : []

content {
id = "delete-after-expiry"
id = "delete-after-expiry"
status = "Enabled"

expiration {
Expand All @@ -34,11 +34,10 @@ locals {
]))
}


data "aws_iam_policy_document" "bucket_access" {
for_each = { for bp in local.bucket_permissions : "${bp.hub_name}.${bp.bucket_name}" => bp }
statement {
effect = "Allow"
effect = "Allow"
actions = ["s3:*"]
principals {
type = "AWS"
Expand All @@ -57,6 +56,6 @@ data "aws_iam_policy_document" "bucket_access" {
resource "aws_s3_bucket_policy" "user_bucket_access" {

for_each = { for bp in local.bucket_permissions : "${bp.hub_name}.${bp.bucket_name}" => bp }
bucket = aws_s3_bucket.user_buckets[each.value.bucket_name].id
policy = data.aws_iam_policy_document.bucket_access[each.key].json
bucket = aws_s3_bucket.user_buckets[each.value.bucket_name].id
policy = data.aws_iam_policy_document.bucket_access[each.key].json
}
35 changes: 24 additions & 11 deletions terraform/aws/irsa.tf
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ data "aws_partition" "current" {}

resource "aws_iam_role" "irsa_role" {
for_each = var.hub_cloud_permissions
name = "${var.cluster_name}-${each.key}"
name = "${var.cluster_name}-${each.key}"

assume_role_policy = data.aws_iam_policy_document.irsa_role_assume[each.key].json
}
Expand All @@ -13,29 +13,42 @@ data "aws_iam_policy_document" "irsa_role_assume" {
for_each = var.hub_cloud_permissions
statement {

effect = "Allow"
effect = "Allow"

actions = ["sts:AssumeRoleWithWebIdentity"]
actions = ["sts:AssumeRoleWithWebIdentity"]

principals {
type = "Federated"
principals {
type = "Federated"

identifiers = [
"arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer, "https://", "")}"
]
}
identifiers = [
"arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer, "https://", "")}"
]
}
condition {
test = "StringEquals"
variable = "${replace(data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer, "https://", "")}:sub"
values = [
values = [
"system:serviceaccount:${each.key}:user-sa"
]
}
}
}

resource "aws_iam_policy" "extra_user_policy" {
for_each = { for hub_name, value in var.hub_cloud_permissions : hub_name => value if value.extra_iam_policy != "" }
name = "${var.cluster_name}-${each.key}-extra-user-policy"
description = "Extra permissions granted to users on hub ${each.key} on ${var.cluster_name}"
policy = each.value.extra_iam_policy
}

resource "aws_iam_role_policy_attachment" "extra_user_policy" {
for_each = { for hub_name, value in var.hub_cloud_permissions : hub_name => value if value.extra_iam_policy != "" }
role = aws_iam_role.irsa_role[each.key].name
policy_arn = aws_iam_policy.extra_user_policy[each.key].arn
}

output "kubernetes_sa_annotations" {
value = {
value = {
for k, v in var.hub_cloud_permissions :
k => "eks.amazonaws.com/role-arn: ${aws_iam_role.irsa_role[k].arn}"
}
Expand Down
33 changes: 32 additions & 1 deletion terraform/aws/projects/uwhackweeks.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,44 @@ hub_cloud_permissions = {
"staging" : {
requestor_pays: true,
bucket_admin_access: ["scratch-staging"],
extra_iam_policy: ""
},
"prod" : {
requestor_pays: true,
bucket_admin_access: ["scratch"],
extra_iam_policy: ""
},
"snowex" : {
requestor_pays: true,
bucket_admin_access: ["snowex-scratch"],
# Grant S3 access to S3 buckets in other accounts
# See https://github.com/2i2c-org/infrastructure/issues/1455
extra_iam_policy: <<-EOT
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::dinosar",
"arn:aws:s3:::eis-dh-hydro"
]
},
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": [
"arn:aws:s3:::dinosar/*",
"arn:aws:s3:::eis-dh-hydro/*"
]
}
]
}
EOT
}
}
}
18 changes: 15 additions & 3 deletions terraform/aws/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ variable "user_buckets" {
type = map(object({ delete_after : number }))
default = {}
description = <<-EOT
GCS Buckets to be created.
S3 Buckets to be created.

The key for each entry will be prefixed with {var.prefix}- to form
the name of the bucket.
Expand All @@ -35,7 +35,7 @@ variable "user_buckets" {
}

variable "hub_cloud_permissions" {
type = map(object({ requestor_pays : bool, bucket_admin_access : set(string) }))
type = map(object({ requestor_pays : bool, bucket_admin_access : set(string), extra_iam_policy : string }))
default = {}
description = <<-EOT
Map of cloud permissions given to a particular hub
Expand All @@ -46,7 +46,19 @@ variable "hub_cloud_permissions" {
1. requestor_pays: Identify as coming from the google cloud project when accessing
storage buckets marked as https://cloud.google.com/storage/docs/requester-pays.
This *potentially* incurs cost for us, the originating project, so opt-in.
2. bucket_admin_access: List of GCS storage buckets that users on this hub should have read
2. bucket_admin_access: List of S3 storage buckets that users on this hub should have read
and write permissions for.
3. extra_iam_policy: An AWS IAM Policy document that grants additional rights to the users
on this hub when talking to AWS services.
EOT
}

variable "extra_user_iam_policy" {
default = {}
description = <<-EOT
Policy JSON to attach to the IAM role assumed by users of the hub.

Used to grant additional permissions to the IAM role that is assumed by
user pods when making requests to AWS services (such as S3)
EOT
}