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

Feat: Support Allowing Actions from Specific Principal ARNs in Bucket Policy. #95

Merged
merged 29 commits into from
Jul 2, 2021
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
eb7d975
Support allowing actions from specific principal ARNs in bucket policy.
korenyoni Jun 29, 2021
0b98826
Auto Format
cloudpossebot Jun 29, 2021
f65e03f
Run go fmt.
korenyoni Jun 29, 2021
ee2de01
Fix indentation in examples/complete module block.
korenyoni Jun 30, 2021
6eda726
Auto Format
cloudpossebot Jun 30, 2021
c4aa8cc
Make use of account ID in examples/complete/privileged-principals.tf …
korenyoni Jun 30, 2021
830e3de
Rename bucket prefixes used by examples/complete such that the exampl…
korenyoni Jun 30, 2021
1d19af0
Merge branch 'feat/deployment-arns' of github.com:cloudposse/terrafor…
korenyoni Jun 30, 2021
cba9b9a
Auto Format
cloudpossebot Jun 30, 2021
8b979b5
Move privileged_principals_arn declaration to locals block to avoid t…
korenyoni Jun 30, 2021
5795882
Merge branch 'feat/deployment-arns' of github.com:cloudposse/terrafor…
korenyoni Jun 30, 2021
a739cfd
Fix formatting in README.yaml.
korenyoni Jun 30, 2021
ecf8354
Auto Format
cloudpossebot Jun 30, 2021
c47582e
Use aws_iam_policy_document data source instead of jsonencode(HCL) fo…
korenyoni Jun 30, 2021
88783de
Merge branch 'feat/deployment-arns' of github.com:cloudposse/terrafor…
korenyoni Jun 30, 2021
3f2eb04
Auto Format
cloudpossebot Jun 30, 2021
38c16d4
Ensure var.user_enabled is acknowledged when examples/complete invoke…
korenyoni Jun 30, 2021
35d9db5
Use var.enabled in null_label module invocations instead of count; en…
korenyoni Jun 30, 2021
bca9bf6
Merge branch 'feat/deployment-arns' of github.com:cloudposse/terrafor…
korenyoni Jun 30, 2021
3b0ba44
Use consistent end-of-file newlines.
korenyoni Jun 30, 2021
8c446d0
Ensure all tfvars instantiate lists formatted line-by-line.
korenyoni Jun 30, 2021
0b306d2
Use unique seeds for each test's random ID.
korenyoni Jun 30, 2021
c3cc259
Do not re-iterate through all array indices with every statement iter…
korenyoni Jun 30, 2021
c39b5c0
Ensure example fixtures with user_enabled=false do not later have the…
korenyoni Jun 30, 2021
a85309d
Change order of prefixes in expected bucket policy (to match actual p…
korenyoni Jun 30, 2021
d874aaf
Fix variable description; fix README snippet formatting.
korenyoni Jul 2, 2021
bf8b21c
Auto Format
cloudpossebot Jul 2, 2021
f202dbf
Remove allowed_bucket_actions when user_enabled=false.
korenyoni Jul 2, 2021
a4d9adc
Fix formatting in privileged-principals.tf.
korenyoni Jul 2, 2021
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
36 changes: 32 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ Using a [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overvie
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
# version = "x.x.x"
acl = "private"
enabled = true
user_enabled = true
Expand All @@ -129,7 +129,7 @@ Using [grants](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
# version = "x.x.x"
acl = ""
enabled = true
user_enabled = true
Expand All @@ -156,6 +156,30 @@ module "s3_bucket" {
}
```

Allowing specific principal ARNs to perform actions on the bucket:

```hcl
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
acl = "private"
enabled = true
user_enabled = true
versioning_enabled = false
allowed_bucket_actions = ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"]
name = "app"
stage = "test"
namespace = "eg"

privileged_principal_arns = {
"arn:aws:iam::123456789012:role/principal1" = ["prefix1/", "prefix2/"]
"arn:aws:iam::123456789012:role/principal2" = [""]
}
privileged_principal_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
}
```




Expand Down Expand Up @@ -244,6 +268,8 @@ Available targets:
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
| <a name="input_object_lock_configuration"></a> [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. | <pre>object({<br> mode = string # Valid values are GOVERNANCE and COMPLIANCE.<br> days = number<br> years = number<br> })</pre> | `null` | no |
| <a name="input_policy"></a> [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no |
| <a name="input_privileged_principal_actions"></a> [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no |
| <a name="input_privileged_principal_arns"></a> [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.<br>Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_replication_rules"></a> [replication\_rules](#input\_replication\_rules) | DEPRECATED: Use s3\_replication\_rules instead. | `list(any)` | `null` | no |
| <a name="input_restrict_public_buckets"></a> [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no |
Expand Down Expand Up @@ -427,8 +453,8 @@ Check out [our other projects][github], [follow us on twitter][twitter], [apply
### Contributors

<!-- markdownlint-disable -->
| [![Erik Osterman][osterman_avatar]][osterman_homepage]<br/>[Erik Osterman][osterman_homepage] | [![Andriy Knysh][aknysh_avatar]][aknysh_homepage]<br/>[Andriy Knysh][aknysh_homepage] | [![Maxim Mironenko][maximmi_avatar]][maximmi_homepage]<br/>[Maxim Mironenko][maximmi_homepage] | [![Josh Myers][joshmyers_avatar]][joshmyers_homepage]<br/>[Josh Myers][joshmyers_homepage] |
|---|---|---|---|
| [![Erik Osterman][osterman_avatar]][osterman_homepage]<br/>[Erik Osterman][osterman_homepage] | [![Andriy Knysh][aknysh_avatar]][aknysh_homepage]<br/>[Andriy Knysh][aknysh_homepage] | [![Maxim Mironenko][maximmi_avatar]][maximmi_homepage]<br/>[Maxim Mironenko][maximmi_homepage] | [![Josh Myers][joshmyers_avatar]][joshmyers_homepage]<br/>[Josh Myers][joshmyers_homepage] | [![Yonatan Koren][korenyoni_avatar]][korenyoni_homepage]<br/>[Yonatan Koren][korenyoni_homepage] |
|---|---|---|---|---|
<!-- markdownlint-restore -->

[osterman_homepage]: https://github.com/osterman
Expand All @@ -439,6 +465,8 @@ Check out [our other projects][github], [follow us on twitter][twitter], [apply
[maximmi_avatar]: https://img.cloudposse.com/150x150/https://github.com/maximmi.png
[joshmyers_homepage]: https://github.com/joshmyers
[joshmyers_avatar]: https://img.cloudposse.com/150x150/https://github.com/joshmyers.png
[korenyoni_homepage]: https://github.com/korenyoni
[korenyoni_avatar]: https://img.cloudposse.com/150x150/https://github.com/korenyoni.png

[![README Footer][readme_footer_img]][readme_footer_link]
[![Beacon][beacon]][website]
Expand Down
30 changes: 28 additions & 2 deletions README.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ usage: |-
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
# version = "x.x.x"
acl = "private"
enabled = true
user_enabled = true
Expand All @@ -106,7 +106,7 @@ usage: |-
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
# version = "x.x.x"
acl = ""
enabled = true
user_enabled = true
Expand All @@ -133,6 +133,30 @@ usage: |-
}
```

Allowing specific principal ARNs to perform actions on the bucket:

```hcl
module "s3_bucket" {
source = "cloudposse/s3-bucket/aws"
# Cloud Posse recommends pinning every module to a specific version
# version = "x.x.x"
acl = "private"
enabled = true
user_enabled = true
versioning_enabled = false
allowed_bucket_actions = ["s3:GetObject", "s3:ListBucket", "s3:GetBucketLocation"]
name = "app"
stage = "test"
namespace = "eg"

privileged_principal_arns = {
"arn:aws:iam::123456789012:role/principal1" = ["prefix1/", "prefix2/"]
"arn:aws:iam::123456789012:role/principal2" = [""]
}
privileged_principal_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
korenyoni marked this conversation as resolved.
Show resolved Hide resolved
}
```

include:
- "docs/targets.md"
- "docs/terraform.md"
Expand All @@ -147,3 +171,5 @@ contributors:
github: "maximmi"
- name: "Josh Myers"
github: "joshmyers"
- name: "Yonatan Koren"
github: "korenyoni"
2 changes: 2 additions & 0 deletions docs/terraform.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
| <a name="input_namespace"></a> [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
| <a name="input_object_lock_configuration"></a> [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. | <pre>object({<br> mode = string # Valid values are GOVERNANCE and COMPLIANCE.<br> days = number<br> years = number<br> })</pre> | `null` | no |
| <a name="input_policy"></a> [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no |
| <a name="input_privileged_principal_actions"></a> [privileged\_principal\_actions](#input\_privileged\_principal\_actions) | List of actions to permit `privileged_principal_arns` to perform on bucket and bucket prefixes (see `privileged_principal_arns`) | `list(string)` | `[]` | no |
| <a name="input_privileged_principal_arns"></a> [privileged\_principal\_arns](#input\_privileged\_principal\_arns) | (Optional) Map of IAM Principal ARNs to lists of S3 path prefixes to grant `privileged_principal_actions` permissions.<br>Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no |
| <a name="input_regex_replace_chars"></a> [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.<br>If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| <a name="input_replication_rules"></a> [replication\_rules](#input\_replication\_rules) | DEPRECATED: Use s3\_replication\_rules instead. | `list(any)` | `null` | no |
| <a name="input_restrict_public_buckets"></a> [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no |
Expand Down
13 changes: 12 additions & 1 deletion examples/complete/fixtures.us-east-2.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,19 @@ acl = "private"

force_destroy = true

user_enabled = true

versioning_enabled = false

allow_encrypted_uploads_only = true

allowed_bucket_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
allowed_bucket_actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload"
]
11 changes: 10 additions & 1 deletion examples/complete/grants.us-east-2.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,13 @@ versioning_enabled = false

allow_encrypted_uploads_only = true

allowed_bucket_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
allowed_bucket_actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload"
]
11 changes: 10 additions & 1 deletion examples/complete/lifecycle.us-east-2.tfvars
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,13 @@ versioning_enabled = false

allow_encrypted_uploads_only = true

allowed_bucket_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
allowed_bucket_actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload"
]
46 changes: 3 additions & 43 deletions examples/complete/main.tf
Original file line number Diff line number Diff line change
@@ -1,23 +1,11 @@
locals {
replication_enabled = length(var.s3_replication_rules) > 0
extra_rule = local.replication_enabled ? {
id = "replication-test-explicit-bucket"
status = "Enabled"
prefix = "/extra"
priority = 5
destination_bucket = module.s3_bucket_replication_target_extra[0].bucket_arn
} : null
s3_replication_rules = local.replication_enabled ? concat(var.s3_replication_rules, [local.extra_rule]) : null
}

provider "aws" {
region = var.region
}

module "s3_bucket" {
source = "../../"

user_enabled = true
user_enabled = var.user_enabled
acl = var.acl
force_destroy = var.force_destroy
grants = var.grants
Expand All @@ -30,36 +18,8 @@ module "s3_bucket" {
s3_replication_enabled = local.replication_enabled
s3_replica_bucket_arn = join("", module.s3_bucket_replication_target.*.bucket_arn)
s3_replication_rules = local.s3_replication_rules
privileged_principal_actions = var.privileged_principal_actions
privileged_principal_arns = local.privileged_principal_arns

context = module.this.context
}

module "s3_bucket_replication_target" {
count = local.replication_enabled ? 1 : 0

source = "../../"

user_enabled = true
acl = "private"
force_destroy = true
versioning_enabled = true
s3_replication_source_roles = [module.s3_bucket.replication_role_arn]

attributes = ["target"]
context = module.this.context
}

module "s3_bucket_replication_target_extra" {
count = local.replication_enabled ? 1 : 0

source = "../../"

user_enabled = true
acl = "private"
force_destroy = true
versioning_enabled = true
s3_replication_source_roles = [module.s3_bucket.replication_role_arn]

attributes = ["target", "extra"]
context = module.this.context
}
90 changes: 90 additions & 0 deletions examples/complete/privileged-principals.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
locals {
account_id = data.aws_caller_identity.current.account_id
principal_names = [
"arn:aws:iam::${local.account_id}:role/${join("", module.deployment_principal_label.*.id)}",
"arn:aws:iam::${local.account_id}:role/${join("", module.additional_deployment_principal_label.*.id)}"
]
privileged_principal_arns = var.privileged_principal_enabled ? {
(local.principal_names[0]) = [""]
(local.principal_names[1]) = ["prefix1/", "prefix2/"]
} : {}
}

data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "deployment_assume_role" {
count = var.privileged_principal_enabled ? 1 : 0

statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
identifiers = ["ec2.amazonaws.com"] # example: this role can be used in an IAM Instance Profile
type = "Service"
}
}
}

data "aws_iam_policy_document" "deployment_iam_policy" {
count = var.privileged_principal_enabled ? 1 : 0

statement {
actions = var.privileged_principal_actions
effect = "Allow"
resources = ["arn:aws:s3:::${module.this.id}*"]
}
}

resource "aws_iam_policy" "deployment_iam_policy" {
count = var.privileged_principal_enabled ? 1 : 0

policy = join("", data.aws_iam_policy_document.deployment_iam_policy.*.json)
}

module "deployment_principal_label" {
source = "cloudposse/label/null"
version = "0.24.1"
enabled = var.privileged_principal_enabled

attributes = ["deployment"]

context = module.this.context
}

resource "aws_iam_role" "deployment_iam_role" {
count = var.privileged_principal_enabled ? 1 : 0

name = join("", module.deployment_principal_label.*.id)

korenyoni marked this conversation as resolved.
Show resolved Hide resolved
assume_role_policy = join("", data.aws_iam_policy_document.deployment_assume_role.*.json)

tags = module.deployment_principal_label.tags
}


module "additional_deployment_principal_label" {
source = "cloudposse/label/null"
version = "0.24.1"
enabled = var.privileged_principal_enabled

attributes = ["deployment", "additional"]

context = module.this.context
}

resource "aws_iam_role" "additional_deployment_iam_role" {
count = var.privileged_principal_enabled ? 1 : 0

name = join("", module.additional_deployment_principal_label.*.id)

korenyoni marked this conversation as resolved.
Show resolved Hide resolved
assume_role_policy = join("", data.aws_iam_policy_document.deployment_assume_role.*.json)

tags = module.additional_deployment_principal_label.tags
}

resource "aws_iam_role_policy_attachment" "additional_deployment_role_attachment" {
count = var.privileged_principal_enabled ? 1 : 0

policy_arn = join("", aws_iam_policy.deployment_iam_policy.*.arn)
role = join("", aws_iam_role.deployment_iam_role.*.name)
}
43 changes: 43 additions & 0 deletions examples/complete/privileged-principals.us-east-2.tfvars
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
enabled = true

region = "us-east-2"

namespace = "eg"

stage = "test"

name = "s3-principals-test" # s3-privileged-principals-test will hit the name length limit

acl = "private"

force_destroy = true

allow_encrypted_uploads_only = true

allowed_bucket_actions = [
korenyoni marked this conversation as resolved.
Show resolved Hide resolved
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload"
]

privileged_principal_actions = [
"s3:PutObject",
"s3:PutObjectAcl",
"s3:GetObject",
"s3:DeleteObject",
"s3:ListBucket",
"s3:ListBucketMultipartUploads",
"s3:GetBucketLocation",
"s3:AbortMultipartUpload"
]

privileged_principal_enabled = true

versioning_enabled = false

user_enabled = false
Loading