diff --git a/.github/workflows/auto-readme.yml b/.github/workflows/auto-readme.yml new file mode 100644 index 00000000..03232cd6 --- /dev/null +++ b/.github/workflows/auto-readme.yml @@ -0,0 +1,55 @@ +name: "auto-readme" +on: + schedule: + # Example of job definition: + # .---------------- minute (0 - 59) + # | .------------- hour (0 - 23) + # | | .---------- day of month (1 - 31) + # | | | .------- month (1 - 12) OR jan,feb,mar,apr ... + # | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat + # | | | | | + # * * * * * user-name command to be executed + + # Update README.md nightly at 4am UTC + - cron: '0 4 * * *' + +jobs: + update: + if: github.event_name == 'schedule' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Update readme + shell: bash + id: update + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + make init + make readme/build + # Ignore changes if they are only whitespace + git diff --ignore-all-space --ignore-blank-lines --quiet README.md && { git restore README.md; echo Ignoring whitespace-only changes in README; } + + - name: Create Pull Request + # This action will not create or change a pull request if there are no changes to make. + # If a PR of the auto-update/readme branch is open, this action will just update it, not create a new PR. + uses: cloudposse/actions/github/create-pull-request@0.30.0 + with: + token: ${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }} + commit-message: Update README.md and docs + title: Update README.md and docs + body: |- + ## what + This is an auto-generated PR that updates the README.md and docs + + ## why + To have most recent changes of README.md and doc from origin templates + + branch: auto-update/readme + base: main + delete-branch: true + labels: | + auto-update + no-release + readme diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml index c5193b62..70f829e3 100644 --- a/.github/workflows/validate-codeowners.yml +++ b/.github/workflows/validate-codeowners.yml @@ -10,7 +10,7 @@ jobs: steps: - name: "Checkout source code at current commit" uses: actions/checkout@v2 - - uses: mszostok/codeowners-validator@v0.5.0 + - uses: mszostok/codeowners-validator@v0.7.1 if: github.event.pull_request.head.repo.full_name == github.repository name: "Full check of CODEOWNERS" with: @@ -18,10 +18,12 @@ jobs: # files so we can use the same CODEOWNERS file for Terraform and non-Terraform repos # checks: "files,syntax,owners,duppatterns" checks: "syntax,owners,duppatterns" + owner_checker_allow_unowned_patterns: "false" # GitHub access token is required only if the `owners` check is enabled github_access_token: "${{ secrets.PUBLIC_REPO_ACCESS_TOKEN }}" - - uses: mszostok/codeowners-validator@v0.5.0 + - uses: mszostok/codeowners-validator@v0.7.1 if: github.event.pull_request.head.repo.full_name != github.repository name: "Syntax check of CODEOWNERS" with: checks: "syntax,duppatterns" + owner_checker_allow_unowned_patterns: "false" diff --git a/.gitignore b/.gitignore index ce573e9d..41a3239b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ # Build harness files .build-harness build-harness + +# Editor and merge files +*.orig +*.draft +*~ diff --git a/Makefile b/Makefile index 6002cb92..b9920609 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SHELL := /bin/bash -export TERRAFORM_VERSION = 0.12.3 +export TERRAFORM_VERSION = 1.1.6 # List of targets the `readme` target should call before generating the readme export README_DEPS ?= docs/targets.md docs/terraform.md diff --git a/README.md b/README.md index c3f40345..80bb3d2c 100644 --- a/README.md +++ b/README.md @@ -212,15 +212,15 @@ Available targets: | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 3.68.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.2.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.68.0 | +| [aws](#provider\_aws) | >= 4.2.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -238,10 +238,21 @@ Available targets: | [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_canonical_user_id.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -253,6 +264,7 @@ Available targets: | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [acl](#input\_acl) | The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`. | `string` | `"private"` | no | +| [acl\_grants](#input\_acl\_grants) | A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [allow\_encrypted\_uploads\_only](#input\_allow\_encrypted\_uploads\_only) | Set to `true` to prevent uploads of unencrypted objects to S3 bucket | `bool` | `false` | no | | [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `false` | no | @@ -269,7 +281,7 @@ Available targets: | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN used for the `SSE-KMS` encryption. This can only be used when you set the value of `sse_algorithm` as `aws:kms`. The default aws/s3 AWS KMS master key is used if this element is absent while the `sse_algorithm` is `aws:kms` | `string` | `""` | no | @@ -277,12 +289,13 @@ Available targets: | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
list(object({
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_configuration\_rules](#input\_lifecycle\_configuration\_rules) | A list of lifecycle rules |
list(object({
id = string
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | DEPRECATED: A list of lifecycle rules |
list(object({
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
| `null` | no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [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. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [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 | +| [policy](#input\_policy) | DEPRECATED: 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 | | [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 | | [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.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | @@ -293,6 +306,7 @@ Available targets: | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | diff --git a/docs/terraform.md b/docs/terraform.md index c688ee51..63d2ea47 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -3,15 +3,15 @@ | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | >= 0.13.0 | -| [aws](#requirement\_aws) | >= 3.68.0 | +| [terraform](#requirement\_terraform) | >= 1.0 | +| [aws](#requirement\_aws) | >= 4.2.0 | | [time](#requirement\_time) | >= 0.7 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | >= 3.68.0 | +| [aws](#provider\_aws) | >= 4.2.0 | | [time](#provider\_time) | >= 0.7 | ## Modules @@ -29,10 +29,21 @@ | [aws_iam_role.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role) | resource | | [aws_iam_role_policy_attachment.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | | [aws_s3_bucket.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket) | resource | +| [aws_s3_bucket_accelerate_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_accelerate_configuration) | resource | +| [aws_s3_bucket_acl.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_acl) | resource | +| [aws_s3_bucket_cors_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_cors_configuration) | resource | +| [aws_s3_bucket_lifecycle_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_lifecycle_configuration) | resource | +| [aws_s3_bucket_logging.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_logging) | resource | +| [aws_s3_bucket_object_lock_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_object_lock_configuration) | resource | | [aws_s3_bucket_ownership_controls.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_ownership_controls) | resource | | [aws_s3_bucket_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_policy) | resource | | [aws_s3_bucket_public_access_block.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_public_access_block) | resource | +| [aws_s3_bucket_replication_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_replication_configuration) | resource | +| [aws_s3_bucket_server_side_encryption_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_server_side_encryption_configuration) | resource | +| [aws_s3_bucket_versioning.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_versioning) | resource | +| [aws_s3_bucket_website_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket_website_configuration) | resource | | [time_sleep.wait_for_aws_s3_bucket_settings](https://registry.terraform.io/providers/hashicorp/time/latest/docs/resources/sleep) | resource | +| [aws_canonical_user_id.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/canonical_user_id) | data source | | [aws_iam_policy_document.aggregated_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.bucket_policy](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | | [aws_iam_policy_document.replication](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | @@ -44,6 +55,7 @@ | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| | [acl](#input\_acl) | The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`. | `string` | `"private"` | no | +| [acl\_grants](#input\_acl\_grants) | A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permission = string
uri = string
}))
| `null` | no | | [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | | [allow\_encrypted\_uploads\_only](#input\_allow\_encrypted\_uploads\_only) | Set to `true` to prevent uploads of unencrypted objects to S3 bucket | `bool` | `false` | no | | [allow\_ssl\_requests\_only](#input\_allow\_ssl\_requests\_only) | Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests | `bool` | `false` | no | @@ -60,7 +72,7 @@ | [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | | [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | | [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no | -| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | +| [grants](#input\_grants) | DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this. |
list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no | | [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | | [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no | | [kms\_master\_key\_arn](#input\_kms\_master\_key\_arn) | The AWS KMS master key ARN used for the `SSE-KMS` encryption. This can only be used when you set the value of `sse_algorithm` as `aws:kms`. The default aws/s3 AWS KMS master key is used if this element is absent while the `sse_algorithm` is `aws:kms` | `string` | `""` | no | @@ -68,12 +80,13 @@ | [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | | [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | | [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules |
list(object({
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_configuration\_rules](#input\_lifecycle\_configuration\_rules) | A list of lifecycle rules |
list(object({
id = string
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
|
[
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_noncurrent_version_expiration": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"id": "noop",
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no | +| [lifecycle\_rules](#input\_lifecycle\_rules) | DEPRECATED: A list of lifecycle rules |
list(object({
prefix = string
enabled = bool
tags = map(string)

enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
enable_noncurrent_version_expiration = bool

abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number

standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
| `null` | no | | [logging](#input\_logging) | Bucket access logging configuration. |
object({
bucket_name = string
prefix = string
})
| `null` | no | | [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | | [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | | [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. |
object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no | -| [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 | +| [policy](#input\_policy) | DEPRECATED: 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 | | [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 | | [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.
Resource list will include the bucket itself along with all the prefixes. Prefixes should not begin with '/'. | `map(list(string))` | `{}` | no | | [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | @@ -84,6 +97,7 @@ | [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replication_rules` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no | | [s3\_replication\_rules](#input\_s3\_replication\_rules) | Specifies the replication rules for S3 bucket replication if enabled. You must also set s3\_replication\_enabled to true. | `list(any)` | `null` | no | | [s3\_replication\_source\_roles](#input\_s3\_replication\_source\_roles) | Cross-account IAM Role ARNs that will be allowed to perform S3 replication to this bucket (for replication within the same AWS account, it's not necessary to adjust the bucket policy). | `list(string)` | `[]` | no | +| [source\_policy\_documents](#input\_source\_policy\_documents) | List of IAM policy documents that are merged together into the exported document. Statements defined in source\_policy\_documents or source\_json must have unique sids. Statements with the same sid from documents assigned to the override\_json and override\_policy\_documents arguments will override source statements. | `list(string)` | `[]` | no | | [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no | | [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | | [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf index f080d322..3520e45f 100644 --- a/examples/complete/variables.tf +++ b/examples/complete/variables.tf @@ -18,8 +18,8 @@ variable "grants" { variable "lifecycle_rules" { type = list(object({ - enabled = bool prefix = string + enabled = bool tags = map(string) enable_glacier_transition = bool @@ -39,8 +39,8 @@ variable "lifecycle_rules" { expiration_days = number })) default = [{ - enabled = false prefix = "" + enabled = false tags = {} enable_glacier_transition = true @@ -72,7 +72,13 @@ variable "s3_replication_enabled" { variable "policy" { type = string default = "" - description = "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" + description = "DEPRECATED: Use source_policy_documents instead. 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" +} + +variable "source_policy_documents" { + type = list(string) + default = null + description = "List of IAM policy documents that are merged together into the exported document. Statements defined in source_policy_documents or source_json must have unique sids. Statements with the same sid from documents assigned to the override_json and override_policy_documents arguments will override source statements." } variable "region" { @@ -256,4 +262,4 @@ variable "bucket_key_enabled" { For more information, see: https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-key.html EOT -} \ No newline at end of file +} diff --git a/examples/complete/versions.tf b/examples/complete/versions.tf new file mode 100644 index 00000000..a5c83197 --- /dev/null +++ b/examples/complete/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 0.13.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0.0" + } + time = { + source = "hashicorp/time" + version = ">= 0.7" + } + } +} diff --git a/main.tf b/main.tf index c0e6ce25..7c890fa6 100644 --- a/main.tf +++ b/main.tf @@ -1,8 +1,12 @@ locals { - enabled = module.this.enabled - bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id - replication_enabled = local.enabled && var.s3_replication_enabled - bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}" + enabled = module.this.enabled + + replication_enabled = local.enabled && var.s3_replication_enabled + versioning_enabled = local.enabled && var.versioning_enabled + transfer_acceleration_enabled = local.enabled && var.transfer_acceleration_enabled + + bucket_name = var.bucket_name != null && var.bucket_name != "" ? var.bucket_name : module.this.id + bucket_arn = "arn:${data.aws_partition.current.partition}:s3:::${join("", aws_s3_bucket.default.*.id)}" # Deprecate `replication_rules` in favor of `s3_replication_rules` to keep all the replication related # inputs grouped under s3_replica[tion] @@ -17,126 +21,205 @@ resource "aws_s3_bucket" "default" { #bridgecrew:skip=BC_AWS_S3_16:Skipping `Ensure S3 bucket versioning is enabled` because dynamic blocks are not supported by checkov #bridgecrew:skip=BC_AWS_S3_14:Skipping `Ensure all data stored in the S3 bucket is securely encrypted at rest` because variables are not understood #bridgecrew:skip=BC_AWS_GENERAL_56:Skipping `Ensure that S3 buckets are encrypted with KMS by default` because we do not have good defaults - count = local.enabled ? 1 : 0 - bucket = local.bucket_name - acl = try(length(var.grants), 0) == 0 ? var.acl : null - force_destroy = var.force_destroy - tags = module.this.tags - acceleration_status = var.transfer_acceleration_enabled ? "Enabled" : null - - dynamic "versioning" { - for_each = var.versioning_enabled ? [true] : [] + #bridgecrew:skip=BC_AWS_GENERAL_72:We do not agree that cross-region replication must be enabled + count = local.enabled ? 1 : 0 + bucket = local.bucket_name + force_destroy = var.force_destroy + + dynamic "object_lock_configuration" { + for_each = var.object_lock_configuration != null ? [1] : [] + content { - enabled = true + object_lock_enabled = "Enabled" } } - dynamic "lifecycle_rule" { - for_each = var.lifecycle_rules + tags = module.this.tags +} + +resource "aws_s3_bucket_accelerate_configuration" "default" { + count = local.transfer_acceleration_enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + status = "Enabled" +} + +resource "aws_s3_bucket_versioning" "default" { + count = local.versioning_enabled ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) + + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "default" { + count = local.enabled && length(local.lifecycle_configuration_rules) > 0 ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + dynamic "rule" { + for_each = local.lifecycle_configuration_rules + content { - enabled = lifecycle_rule.value.enabled - prefix = lifecycle_rule.value.prefix - tags = lifecycle_rule.value.tags - abort_incomplete_multipart_upload_days = lifecycle_rule.value.abort_incomplete_multipart_upload_days + id = rule.value.id + status = rule.value.enabled == true ? "Enabled" : "Disabled" + + # Filter is always required due to https://github.com/hashicorp/terraform-provider-aws/issues/23299 + filter { + dynamic "and" { + for_each = (try(length(rule.value.prefix), 0) + try(length(rule.value.tags), 0)) > 0 ? [1] : [] + content { + prefix = rule.value.prefix == null ? "" : rule.value.prefix + tags = try(length(rule.value.tags), 0) > 0 ? rule.value.tags : {} + } + } + } + + dynamic "abort_incomplete_multipart_upload" { + for_each = try(tonumber(rule.value.abort_incomplete_multipart_upload_days), null) != null ? [1] : [] + content { + days_after_initiation = rule.value.abort_incomplete_multipart_upload_days + } + } dynamic "noncurrent_version_expiration" { - for_each = lifecycle_rule.value.enable_noncurrent_version_expiration ? [1] : [] + for_each = rule.value.enable_noncurrent_version_expiration ? [1] : [] content { - days = lifecycle_rule.value.noncurrent_version_expiration_days + noncurrent_days = rule.value.noncurrent_version_expiration_days } } dynamic "noncurrent_version_transition" { - for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + for_each = rule.value.enable_glacier_transition ? [1] : [] content { - days = lifecycle_rule.value.noncurrent_version_glacier_transition_days - storage_class = "GLACIER" + noncurrent_days = rule.value.noncurrent_version_glacier_transition_days + storage_class = "GLACIER" } } dynamic "noncurrent_version_transition" { - for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + for_each = rule.value.enable_deeparchive_transition ? [1] : [] content { - days = lifecycle_rule.value.noncurrent_version_deeparchive_transition_days - storage_class = "DEEP_ARCHIVE" + noncurrent_days = rule.value.noncurrent_version_deeparchive_transition_days + storage_class = "DEEP_ARCHIVE" } } dynamic "transition" { - for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : [] + for_each = rule.value.enable_glacier_transition ? [1] : [] content { - days = lifecycle_rule.value.glacier_transition_days + days = rule.value.glacier_transition_days storage_class = "GLACIER" } } dynamic "transition" { - for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : [] + for_each = rule.value.enable_deeparchive_transition ? [1] : [] content { - days = lifecycle_rule.value.deeparchive_transition_days + days = rule.value.deeparchive_transition_days storage_class = "DEEP_ARCHIVE" } } - - dynamic "transition" { - for_each = lifecycle_rule.value.enable_standard_ia_transition ? [1] : [] + for_each = rule.value.enable_standard_ia_transition ? [1] : [] content { - days = lifecycle_rule.value.standard_transition_days + days = rule.value.standard_transition_days storage_class = "STANDARD_IA" } } dynamic "expiration" { - for_each = lifecycle_rule.value.enable_current_object_expiration ? [1] : [] + for_each = rule.value.enable_current_object_expiration ? [1] : [] content { - days = lifecycle_rule.value.expiration_days + days = rule.value.expiration_days } } } } - dynamic "logging" { - for_each = var.logging == null ? [] : [1] - content { - target_bucket = var.logging["bucket_name"] - target_prefix = var.logging["prefix"] + depends_on = [ + # versioning must be set before lifecycle configuration + aws_s3_bucket_versioning.default + ] +} + +resource "aws_s3_bucket_logging" "default" { + count = local.enabled && var.logging != null ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + target_bucket = var.logging["bucket_name"] + target_prefix = var.logging["prefix"] +} + +# https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html +# https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption +resource "aws_s3_bucket_server_side_encryption_configuration" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + rule { + bucket_key_enabled = var.bucket_key_enabled + + apply_server_side_encryption_by_default { + sse_algorithm = var.sse_algorithm + kms_master_key_id = var.kms_master_key_arn } } +} + +resource "aws_s3_bucket_website_configuration" "default" { + for_each = local.enabled && var.website_inputs != null ? toset(var.website_inputs) : toset([]) + bucket = join("", aws_s3_bucket.default.*.id) + + index_document { + suffix = each.value.index_document + } - # https://docs.aws.amazon.com/AmazonS3/latest/dev/bucket-encryption.html - # https://www.terraform.io/docs/providers/aws/r/s3_bucket.html#enable-default-server-side-encryption - server_side_encryption_configuration { - rule { - bucket_key_enabled = var.bucket_key_enabled + error_document { + key = each.value.error_document + } - apply_server_side_encryption_by_default { - sse_algorithm = var.sse_algorithm - kms_master_key_id = var.kms_master_key_arn - } - } + redirect_all_requests_to { + host_name = each.value.redirect_all_requests_to + protocol = each.value.protocol } - dynamic "website" { - for_each = var.website_inputs == null ? [] : var.website_inputs + dynamic "routing_rule" { + for_each = length(jsondecode(each.value.routing_rules)) > 0 ? jsondecode(each.value.routing_rules) : [] content { - index_document = website.value.index_document - error_document = website.value.error_document - redirect_all_requests_to = website.value.redirect_all_requests_to - routing_rules = website.value.routing_rules + dynamic "condition" { + for_each = routing_rule.value["Condition"] + + content { + key_prefix_equals = lookup(condition.value, "KeyPrefixEquals") + } + } + + dynamic "redirect" { + for_each = routing_rule.value["Redirect"] + content { + replace_key_prefix_with = lookup(redirect.value, "ReplaceKeyPrefixWith") + } + } } } +} + +resource "aws_s3_bucket_cors_configuration" "default" { + count = local.enabled && var.cors_rule_inputs != null ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) dynamic "cors_rule" { - for_each = var.cors_rule_inputs == null ? [] : var.cors_rule_inputs + for_each = var.cors_rule_inputs content { allowed_headers = cors_rule.value.allowed_headers @@ -146,96 +229,142 @@ resource "aws_s3_bucket" "default" { max_age_seconds = cors_rule.value.max_age_seconds } } +} - dynamic "grant" { - for_each = try(length(var.grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : var.grants +data "aws_canonical_user_id" "default" { + count = local.enabled ? 1 : 0 +} + +resource "aws_s3_bucket_acl" "default" { + count = local.enabled ? 1 : 0 + bucket = join("", aws_s3_bucket.default.*.id) + + # Conflicts with access_control_policy so this is enabled if no grants + acl = try(length(local.acl_grants), 0) == 0 ? var.acl : null + + dynamic "access_control_policy" { + for_each = try(length(local.acl_grants), 0) == 0 || try(length(var.acl), 0) > 0 ? [] : [1] content { - id = grant.value.id - type = grant.value.type - permissions = grant.value.permissions - uri = grant.value.uri + dynamic "grant" { + for_each = local.acl_grants + + content { + grantee { + id = grant.value.id + type = grant.value.type + uri = grant.value.uri + } + permission = grant.value.permission + } + } + + owner { + id = join("", data.aws_canonical_user_id.default.*.id) + } } } +} - dynamic "replication_configuration" { - for_each = local.replication_enabled ? [1] : [] +resource "aws_s3_bucket_replication_configuration" "default" { + count = local.replication_enabled ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) + role = aws_iam_role.replication[0].arn + + dynamic "rule" { + for_each = local.s3_replication_rules == null ? [] : local.s3_replication_rules content { - role = aws_iam_role.replication[0].arn + id = rule.value.id + priority = try(rule.value.priority, 0) + + # `prefix` at this level is a V1 feature, replaced in V2 with the filter block. + # `prefix` conflicts with `filter`, and for multiple destinations, a filter block + # is required even if it empty, so we always implement `prefix` as a filter. + # OBSOLETE: prefix = try(rule.value.prefix, null) + status = try(rule.value.status, null) + + # This is only relevant when "filter" is used + delete_marker_replication { + status = try(rule.value.delete_marker_replication_status, "Disabled") + } - dynamic "rules" { - for_each = local.s3_replication_rules == null ? [] : local.s3_replication_rules + destination { + # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with + # s3_replica_bucket_arn to specify single destination for all rules + bucket = try(length(rule.value.destination_bucket), 0) > 0 ? rule.value.destination_bucket : var.s3_replica_bucket_arn + storage_class = try(rule.value.destination.storage_class, "STANDARD") - content { - id = rules.value.id - priority = try(rules.value.priority, 0) - # `prefix` at this level is a V1 feature, replaced in V2 with the filter block. - # `prefix` conflicts with `filter`, and for multiple destinations, a filter block - # is required even if it empty, so we always implement `prefix` as a filter. - # OBSOLETE: prefix = try(rules.value.prefix, null) - status = try(rules.value.status, null) - # The `Delete marker replication` was disabled by default since empty filter created in Line 210, this needed to be "Enabled" to turn it on - delete_marker_replication_status = try(rules.value.delete_marker_replication_status, null) - - destination { - # Prefer newer system of specifying bucket in rule, but maintain backward compatibility with - # s3_replica_bucket_arn to specify single destination for all rules - bucket = try(length(rules.value.destination_bucket), 0) > 0 ? rules.value.destination_bucket : var.s3_replica_bucket_arn - storage_class = try(rules.value.destination.storage_class, "STANDARD") - replica_kms_key_id = try(rules.value.destination.replica_kms_key_id, null) - account_id = try(rules.value.destination.account_id, null) - - # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-walkthrough-5.html - dynamic "metrics" { - for_each = try(rules.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - - content { - status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 - } - } + dynamic "encryption_configuration" { + for_each = try(rule.value.destination.replica_kms_key_id, null) != null ? [1] : [] - # This block is required when replication metrics are enabled. - dynamic "replication_time" { - for_each = try(rules.value.destination.metrics.status, "") == "Enabled" ? [1] : [] + content { + replica_kms_key_id = try(rule.value.destination.replica_kms_key_id, null) + } + } - content { - status = "Enabled" - # Minutes can only have 15 as a valid value. - minutes = 15 - } - } + account = try(rule.value.destination.account_id, null) - dynamic "access_control_translation" { - for_each = try(rules.value.destination.access_control_translation.owner, null) == null ? [] : [rules.value.destination.access_control_translation.owner] + # https://docs.aws.amazon.com/AmazonS3/latest/userguide/replication-walkthrough-5.html + dynamic "metrics" { + for_each = try(rule.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - content { - owner = access_control_translation.value - } + content { + status = "Enabled" + event_threshold { + # Minutes can only have 15 as a valid value. + minutes = 15 } } + } - dynamic "source_selection_criteria" { - for_each = try(rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled, null) == null ? [] : [rules.value.source_selection_criteria.sse_kms_encrypted_objects.enabled] + # This block is required when replication metrics are enabled. + dynamic "replication_time" { + for_each = try(rule.value.destination.metrics.status, "") == "Enabled" ? [1] : [] - content { - sse_kms_encrypted_objects { - enabled = source_selection_criteria.value - } + content { + status = "Enabled" + time { + # Minutes can only have 15 as a valid value. + minutes = 15 } } + } + + dynamic "access_control_translation" { + for_each = try(rule.value.destination.access_control_translation.owner, null) == null ? [] : [rule.value.destination.access_control_translation.owner] + + content { + owner = access_control_translation.value + } + } + } - # Replication to multiple destination buckets requires that priority is specified in the rules object. - # If the corresponding rule requires no filter, an empty configuration block filter {} must be specified. - # See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket - dynamic "filter" { - for_each = try(rules.value.filter, null) == null ? [{ prefix = null, tags = {} }] : [rules.value.filter] + dynamic "source_selection_criteria" { + for_each = try(rule.value.source_selection_criteria.sse_kms_encrypted_objects.enabled, null) == null ? [] : [rule.value.source_selection_criteria.sse_kms_encrypted_objects.enabled] + + content { + sse_kms_encrypted_objects { + status = source_selection_criteria.value + } + } + } + + # Replication to multiple destination buckets requires that priority is specified in the rules object. + # If the corresponding rule requires no filter, an empty configuration block filter {} must be specified. + # See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket + dynamic "filter" { + for_each = try(rule.value.filter, null) == null ? [{ prefix = null, tags = {} }] : [rule.value.filter] + + content { + prefix = try(filter.value.prefix, try(rule.value.prefix, null)) + dynamic "tag" { + for_each = try(filter.value.tags, {}) content { - prefix = try(filter.value.prefix, try(rules.value.prefix, null)) - tags = try(filter.value.tags, {}) + key = tag.key + value = tag.value } } } @@ -243,17 +372,24 @@ resource "aws_s3_bucket" "default" { } } - dynamic "object_lock_configuration" { - for_each = var.object_lock_configuration != null ? [1] : [] - content { - object_lock_enabled = "Enabled" - rule { - default_retention { - mode = var.object_lock_configuration.mode - days = var.object_lock_configuration.days - years = var.object_lock_configuration.years - } - } + depends_on = [ + # versioning must be set before replication + aws_s3_bucket_versioning.default + ] +} + +resource "aws_s3_bucket_object_lock_configuration" "default" { + count = local.enabled && var.object_lock_configuration != null ? 1 : 0 + + bucket = join("", aws_s3_bucket.default.*.id) + + object_lock_enabled = "Enabled" + + rule { + default_retention { + mode = var.object_lock_configuration.mode + days = var.object_lock_configuration.days + years = var.object_lock_configuration.years } } } @@ -393,9 +529,10 @@ data "aws_iam_policy_document" "bucket_policy" { } data "aws_iam_policy_document" "aggregated_policy" { - count = local.enabled ? 1 : 0 - source_json = var.policy - override_json = join("", data.aws_iam_policy_document.bucket_policy.*.json) + count = local.enabled ? 1 : 0 + + source_policy_documents = compact(concat([var.policy], var.source_policy_documents)) + override_policy_documents = data.aws_iam_policy_document.bucket_policy.*.json } resource "aws_s3_bucket_policy" "default" { diff --git a/test/Makefile b/test/Makefile index 17b2fe74..095a5b7c 100644 --- a/test/Makefile +++ b/test/Makefile @@ -33,11 +33,11 @@ clean: all: module examples/complete ## Run basic sanity checks against the module itself -module: export TESTS ?= installed lint get-modules module-pinning get-plugins provider-pinning validate terraform-docs input-descriptions output-descriptions +module: export TESTS ?= installed lint module-pinning provider-pinning validate terraform-docs input-descriptions output-descriptions module: deps $(call RUN_TESTS, ../) ## Run tests against example -examples/complete: export TESTS ?= installed lint get-modules get-plugins validate +examples/complete: export TESTS ?= installed lint validate examples/complete: deps $(call RUN_TESTS, ../$@) diff --git a/test/src/Makefile b/test/src/Makefile index dd458292..d7ae5975 100644 --- a/test/src/Makefile +++ b/test/src/Makefile @@ -1,4 +1,4 @@ -export TF_CLI_ARGS_init ?= -get-plugins=true +export TERRAFORM_VERSION ?= $(shell curl -s https://checkpoint-api.hashicorp.com/v1/check/terraform | jq -r -M '.current_version' | cut -d. -f1) .DEFAULT_GOAL : all @@ -14,16 +14,13 @@ init: .PHONY : test ## Run tests test: init - # Verify we have valid AWS credential - if command -v aws >/dev/null; then \ - (unset AWS_PROFILE; aws sts get-caller-identity >/dev/null); \ - fi - go test -v -timeout 10m -run TestExamplesComplete + go mod download + go test -v -timeout 15m -run TestExamplesComplete ## Run tests in docker container docker/test: docker run --name terratest --rm -it -e AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e GITHUB_TOKEN \ - -e PATH="/usr/local/terraform/0.13/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ + -e PATH="/usr/local/terraform/$(TERRAFORM_VERSION)/bin:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" \ -v $(CURDIR)/../../:/module/ cloudposse/test-harness:latest -C /module/test/src test .PHONY : clean diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go index fc8d354f..0d843d90 100644 --- a/test/src/examples_complete_test.go +++ b/test/src/examples_complete_test.go @@ -3,24 +3,21 @@ package test import ( "bytes" "encoding/json" - "math/rand" - "strconv" - "strings" - "testing" - "time" - "github.com/gruntwork-io/terratest/modules/aws" + "github.com/gruntwork-io/terratest/modules/random" "github.com/gruntwork-io/terratest/modules/terraform" test_structure "github.com/gruntwork-io/terratest/modules/test-structure" "github.com/stretchr/testify/assert" + "strings" + "testing" ) // Test the Terraform module in examples/complete using Terratest. func TestExamplesComplete(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano()) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"fixtures.us-east-2.tfvars"} @@ -62,9 +59,9 @@ func TestExamplesComplete(t *testing.T) { // Test the Terraform module in examples/complete using Terratest for grants. func TestExamplesCompleteWithGrants(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 1) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"grants.us-east-2.tfvars"} @@ -99,9 +96,9 @@ func TestExamplesCompleteWithGrants(t *testing.T) { // Test the Terraform module in examples/complete using Terratest for grants. func TestExamplesCompleteWithObjectLock(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 2) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"object-lock.us-east-2.tfvars"} @@ -134,9 +131,9 @@ func TestExamplesCompleteWithObjectLock(t *testing.T) { func TestExamplesCompleteWithLifecycleRules(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 3) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"lifecycle.us-east-2.tfvars"} @@ -170,9 +167,9 @@ func TestExamplesCompleteWithLifecycleRules(t *testing.T) { func TestExamplesCompleteWithReplication(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 4) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"replication.us-east-2.tfvars"} @@ -220,10 +217,10 @@ func TestExamplesCompleteWithReplication(t *testing.T) { func TestExamplesCompleteWithPrivilegedPrincipals(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 5) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} awsRegion := "us-east-2" - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"privileged-principals.us-east-2.tfvars"} @@ -328,9 +325,9 @@ func TestExamplesCompleteWithPrivilegedPrincipals(t *testing.T) { func TestExamplesCompleteDisabled(t *testing.T) { t.Parallel() - rand.Seed(time.Now().UnixNano() + 6) + randID := strings.ToLower(random.UniqueId()) + attributes := []string{randID} - attributes := []string{strconv.Itoa(rand.Intn(100000))} rootFolder := "../../" terraformFolderRelativeToRoot := "examples/complete" varFiles := []string{"replication.us-east-2.tfvars"} diff --git a/variables-deprecated.tf b/variables-deprecated.tf new file mode 100644 index 00000000..71a0772a --- /dev/null +++ b/variables-deprecated.tf @@ -0,0 +1,78 @@ +variable "grants" { + type = list(object({ + id = string + type = string + permissions = list(string) + uri = string + })) + default = null + + description = "DEPRECATED (replaced by `acl_grants`): A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this." +} + +locals { + acl_grants = var.grants == null ? var.acl_grants : flatten( + [ + for g in var.grants : [ + for p in g.permissions : { + id = g.id + type = g.type + permission = p + uri = g.uri + } + ] + ]) +} + +variable "lifecycle_rules" { + type = list(object({ + prefix = string + enabled = bool + tags = map(string) + + enable_glacier_transition = bool + enable_deeparchive_transition = bool + enable_standard_ia_transition = bool + enable_current_object_expiration = bool + enable_noncurrent_version_expiration = bool + + abort_incomplete_multipart_upload_days = number + noncurrent_version_glacier_transition_days = number + noncurrent_version_deeparchive_transition_days = number + noncurrent_version_expiration_days = number + + standard_transition_days = number + glacier_transition_days = number + deeparchive_transition_days = number + expiration_days = number + })) + default = null + description = "DEPRECATED: A list of lifecycle rules" +} + +locals { + lifecycle_configuration_rules = var.lifecycle_rules == null ? var.lifecycle_configuration_rules : ( + [for i, v in var.lifecycle_rules : { + id = "rule-${i + 1}" + prefix = v.prefix + enabled = v.enabled + tags = v.tags + + enable_glacier_transition = v.enable_glacier_transition + enable_deeparchive_transition = v.enable_deeparchive_transition + enable_standard_ia_transition = v.enable_standard_ia_transition + enable_current_object_expiration = v.enable_current_object_expiration + enable_noncurrent_version_expiration = v.enable_noncurrent_version_expiration + + abort_incomplete_multipart_upload_days = v.abort_incomplete_multipart_upload_days + noncurrent_version_glacier_transition_days = v.noncurrent_version_glacier_transition_days + noncurrent_version_deeparchive_transition_days = v.noncurrent_version_deeparchive_transition_days + noncurrent_version_expiration_days = v.noncurrent_version_expiration_days + + standard_transition_days = v.standard_transition_days + glacier_transition_days = v.glacier_transition_days + deeparchive_transition_days = v.deeparchive_transition_days + expiration_days = v.expiration_days + }] + ) +} \ No newline at end of file diff --git a/variables.tf b/variables.tf index 29b2e87d..4a6fde88 100644 --- a/variables.tf +++ b/variables.tf @@ -4,22 +4,28 @@ variable "acl" { description = "The [canned ACL](https://docs.aws.amazon.com/AmazonS3/latest/dev/acl-overview.html#canned-acl) to apply. We recommend `private` to avoid exposing sensitive information. Conflicts with `grants`." } -variable "grants" { +variable "acl_grants" { type = list(object({ - id = string - type = string - permissions = list(string) - uri = string + id = string + type = string + permission = string + uri = string })) default = null - description = "An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this." + description = "A list of policy grants for the bucket. Conflicts with `acl`. Set `acl` to `null` to use this." } variable "policy" { type = string default = "" - description = "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" + description = "DEPRECATED: 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" +} + +variable "source_policy_documents" { + type = list(string) + default = [] + description = "List of IAM policy documents that are merged together into the exported document. Statements defined in source_policy_documents or source_json must have unique sids. Statements with the same sid from documents assigned to the override_json and override_policy_documents arguments will override source statements." } variable "force_destroy" { @@ -79,8 +85,9 @@ variable "allow_ssl_requests_only" { description = "Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests" } -variable "lifecycle_rules" { +variable "lifecycle_configuration_rules" { type = list(object({ + id = string prefix = string enabled = bool tags = map(string) @@ -102,6 +109,7 @@ variable "lifecycle_rules" { expiration_days = number })) default = [{ + id = "noop" enabled = false prefix = "" tags = {} diff --git a/versions.tf b/versions.tf index 749b0eb1..87da5770 100644 --- a/versions.tf +++ b/versions.tf @@ -1,10 +1,10 @@ terraform { - required_version = ">= 0.13.0" + required_version = ">= 1.0" required_providers { aws = { source = "hashicorp/aws" - version = ">= 3.68.0" + version = ">= 4.2.0" } time = { source = "hashicorp/time"