diff --git a/.circleci/config.yml b/.circleci/config.yml index d42f04d..29a6773 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,69 +1,175 @@ version: 2 +# TODO: centralize full configuration. Figure out how +# ?? Each step as a separate script that is downloaded and run ?? +# ?? CircleCI feature request to supoort include from remote sources +# More Markdown terraform_testing +# Python testing. Add doc and test that too +# circleci/python: Both 2 and 3? +# if src/requirements.txt get version from *.tf and test +# Style+: flake8 + hacking?, prospector? +# Security: bandit, RATS, + +# This file uses YAML anchors to deduplicate steps +# see https://circleci.com/blog/circleci-hacks-reuse-yaml-in-your-circleci-config-with-yaml/ +# and https://learnxinyminutes.com/docs/yaml/ + +.steps_template: &steps_terraform_static_analysis + steps: + - checkout + - run: + name: "Check: Validate tf files (terraform validate)" + command: | + find . -type f -name "*.tf" -exec dirname {} \;|sort -u | while read m; do (terraform validate -check-variables=false "$m" && echo "√ $m") || exit 1 ; done + - run: + name: "Check: Terraform formatting (terraform fmt)" + command: | + if [ `terraform fmt --list=true -diff=true -write=false | tee format-issues | wc -c` -ne 0 ]; then + echo "Some terraform files need be formatted, run 'terraform fmt' to fix" + echo "Formatting issues:" + cat format-issues + exit 1 + fi + - run: + name: "Install: tflint" + command: | + apk update + apk add jq wget + # Get latest version of tflint (v0.7.0 test if still need to exclude modules. Any other changes) + pkg_arch=linux_amd64 + dl_url=$(curl -s https://api.github.com/repos/wata727/tflint/releases/latest | jq -r ".assets[] | select(.name | test(\"${pkg_arch}\")) | .browser_download_url") + wget ${dl_url} + unzip tflint_linux_amd64.zip + mkdir -p /usr/local/tflint/bin + # Setup PATH for later run steps - ONLY for Bash and not in Bash + #echo 'export PATH=/usr/local/tflint/bin:$PATH' >> $BASH_ENV + echo "Installing tflint..." + install tflint /usr/local/tflint/bin + echo "Configuring tflint..." + tf_ver=$(terraform version | awk 'FNR <= 1' | cut -dv -f2) + echo -e "\tConfig for terraform version: ${tf_ver}" + if [ -f '.tflint.hcl' ]; then + sed -i "/terraform_version =/s/\".*\"/\"${tf_ver}\"/" .tflint.hcl + else + { + echo -e "config {\nterraform_version = \"${tf_ver}\"\ndeep_check = true\nignore_module = {" + for module in $(grep -h '[^a-zA-Z]source[ =]' *.tf | sed -r 's/.*=\s+//' | sort -u); do + # if not ^"../ + echo "${module} = true" + done + echo -e "}\n}\n" + } > .tflint.hcl + fi + echo "tflint configuration:" + cat .tflint.hcl + - run: + # Not supporting modules from registry ?? v0.5.4 + # For now, must ignore in config file + name: "Check: tflint" + command: | + #echo "Initializing terraform..." + #terraform init -input=false + echo "Running tflint..." + /usr/local/tflint/bin/tflint --version + /usr/local/tflint/bin/tflint + jobs: - build: + ### + ### Documentation testing: Markdown + ### + # Markdown Lint https://github.com/DavidAnson/markdownlint + # CLI https://github.com/igorshubovych/markdownlint-cli + # https://hub.docker.com/r/circleci/node/tags/ + markdown_lint_node: docker: - - image: hashicorp/terraform:0.11.3 - entrypoint: /bin/sh + - image: circleci/node:10.5.0 steps: - checkout - run: - name: "Validate tf files (terraform validate)" + name: "Install: markdown lint (node.js)" command: | - find . -type f -name "*.tf" -exec dirname {} \;|sort -u | while read m; do (terraform validate -check-variables=false "$m" && echo "√ $m") || exit 1 ; done + sudo npm install -g markdownlint-cli - run: - name: "Check: Terraform formatting (terraform fmt)" + name: "Check: markdown lint (node.js)" command: | - if [ `terraform fmt --list=true -diff=true -write=false | tee format-issues | wc -c` -ne 0 ]; then - echo "Some terraform files need be formatted, run 'terraform fmt' to fix" - echo "Formatting issues:" - cat format-issues - exit 1 - fi + #markdownlint --help + echo -n "markdownlint version: " + markdownlint --version + markdownlint ./ + # Markdown Lint https://github.com/markdownlint/markdownlint + # https://hub.docker.com/r/circleci/ruby/tags/ + markdown_lint_ruby: + docker: + - image: circleci/ruby:2.5.1 + steps: + - checkout + - run: + name: "Install: markdown lint (ruby)" + command: | + gem install mdl + - run: + name: "Check: markdown lint (ruby)" + command: | + #mdl --help + echo -n "mdl version: " + mdl --version + mdl . + markdown_proofer: + docker: + - image: circleci/golang:1.10 + entrypoint: /bin/sh + steps: + - checkout - run: - name: "Install: tflint" + name: "Install: markdown proofer" command: | - apk add jq wget - # Get latest version of tflint + # Get latest version pkg_arch=linux_amd64 - dl_url=$(curl -s https://api.github.com/repos/wata727/tflint/releases/latest | jq -r ".assets[] | select(.name | test(\"${pkg_arch}\")) | .browser_download_url") + # Prerelease, so latest doesn't work yet + #dl_url=$(curl -s https://api.github.com/repos/felicianotech/md-proofer/releases/latest | jq -r ".assets[] | select(.name | test(\"${pkg_arch}\")) | .browser_download_url") + dl_url='https://github.com/felicianotech/md-proofer/releases/download/v0.2.0/md-proofer--v0.2.0--linux-amd64.tar.gz' wget ${dl_url} - unzip tflint_linux_amd64.zip - mkdir -p /usr/local/tflint/bin - # Setup PATH for later run steps - ONLY for Bash and not in Bash - #echo 'export PATH=/usr/local/tflint/bin:$PATH' >> $BASH_ENV - echo "Installing tflint..." - install tflint /usr/local/tflint/bin - echo "Configuring tflint..." - tf_ver=$(terraform version | awk 'FNR <= 1' | cut -dv -f2) - echo -e "\tConfig for terraform version: ${tf_ver}" - if [ -f '.tflint.hcl' ]; then - sed -i "/terraform_version =/s/\".*\"/\"${tf_ver}\"/" .tflint.hcl - else - { - echo -e "config {\nterraform_version = \"${tf_ver}\"\ndeep_check = true\nignore_module = {" - for module in $(grep -h '[^a-zA-Z]source[ =]' *.tf | sed -r 's/.*=\s+//' | sort -u); do - # if not ^"../ - echo "${module} = true" - done - echo "}}" - } > .tflint.hcl - fi - echo "tflint configuration:" - cat .tflint.hcl + tar xzf md-proofer--v0.2.0--linux-amd64.tar.gz - run: - # Not supporting modules from registry ?? v0.5.4 - # For now, must ignore in config file - name: "Check: tflint" + name: "Check: markdown proofer" command: | - #echo "Initializing terraform..." - #terraform init -input=false - echo "Running tflint..." - /usr/local/tflint/bin/tflint --version - /usr/local/tflint/bin/tflint + ./md-proofer version + #./md-proofer lint --help + # Will this find all *.md in directory structure or need to run in each directory ? + if ./md-proofer lint ./; then + echo "md-proofer passed" + else + echo "md-proofer failed" + fi + ### + ### Terraform testing + ### + terraform_0_11_3: + docker: + - image: hashicorp/terraform:0.11.3 + entrypoint: /bin/sh + <<: *steps_terraform_static_analysis + + terraform_0_11_7: + docker: + - image: hashicorp/terraform:0.11.7 + entrypoint: /bin/sh + <<: *steps_terraform_static_analysis + + terraform_latest: + docker: + - image: hashicorp/terraform:latest + entrypoint: /bin/sh + <<: *steps_terraform_static_analysis workflows: version: 2 - build: + terraform_testing: jobs: - - build + - markdown_lint_node + - markdown_lint_ruby + # Currently doesn't do anything that markdownlint node doesn't do + #- markdown_proofer + - terraform_0_11_3 + - terraform_0_11_7 + - terraform_latest diff --git a/.gitignore b/.gitignore index f4400be..bc0aaa2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -.terraform* -!terraform.tfstate* -terraform.* +*.tfstate +*.tfstate.backup +*.tfvars +.terraform diff --git a/.markdownlintrc b/.markdownlintrc new file mode 100644 index 0000000..cd4c495 --- /dev/null +++ b/.markdownlintrc @@ -0,0 +1,4 @@ +{ +"default": true, +"MD013": { "code_blocks": false, "tables": false }, +} diff --git a/.mdlrc b/.mdlrc new file mode 100644 index 0000000..b7e0825 --- /dev/null +++ b/.mdlrc @@ -0,0 +1 @@ +rules "~MD013" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..7d32999 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,47 @@ +# See http://pre-commit.com for more information +# See http://pre-commit.com/hooks.html for more hooks +# To update to all latest tagged versions run: +# pre-commit autoupdate +# TODO: write dependencies install instructions and put in each of +# my pre-commit repos. Decide where to put for others +repos: + - repo: https://github.com/devops-workflow/pre-commit-terraform + rev: v1.13.3 + hooks: + - id: terraform_tools + #- id: terraform_template + # args: [--owner=appzen-oss, --repo=terraform-template] + - id: terraform_fmt + - id: terraform_docs + - id: terraform_graph + #- id: tflint + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v1.4.0 + hooks: + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: check-yaml + - id: detect-aws-credentials + - id: detect-private-key + - id: mixed-line-ending + args: [--fix=lf] + - id: trailing-whitespace + # TODO: test these + # check-json + # pretty-format-json + #- repo: https://github.com/jumanjihouse/pre-commit-hooks + # # Requires: shellcheck, shfmt + # rev: 1.8.0 + # hooks: + # - id: shellcheck + # - id: shfmt + #- repo: git://github.com/detailyang/pre-commit-shell + # # Requires: shellcheck + # rev: 1.0.2 + # hooks: + # - id: shell-lint +# TODO: +# add bashate shell code style https://github.com/openstack-dev/bashate +# gitlint https://github.com/jorisroovers/gitlint +# Create new repo and hook for markdown linters diff --git a/README.md b/README.md index 88c3df9..b1e1414 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,82 @@ -[![CircleCI](https://circleci.com/gh/devops-workflow/terraform-aws-s3-buckets?style=svg)](https://circleci.com/gh/devops-workflow/terraform-aws-s3-buckets) +# AWS S3 Buckets Terraform module -AWS S3 Buckets Terraform module -======================== +[![CircleCI](https://circleci.com/gh/appzen-oss/terraform-aws-s3-buckets.svg?style=svg)](https://circleci.com/gh/appzen-oss/terraform-aws-s3-buckets) +[![Github release](https://img.shields.io/github/release/appzen-oss/terraform-aws-s3-buckets.svg)](https://github.com/appzen-oss/terraform-aws-s3-buckets/releases) -Terraform module which creates S3 buckets on AWS. +Terraform module which creates multiple AWS S3 buckets -Terraform Registry: https://registry.terraform.io/modules/devops-workflow/s3-buckets/aws +[Terraform Registry](https://registry.terraform.io/modules/devops-workflow/s3-buckets/aws) -Usage ------ +## Usage ```hcl module "s3-buckets" { - source = "devops-workflow/s3-buckets/aws" - names = ["bucket1", "bucket2", "bucket3"] - environment = "dev" - org = "corp" + source = "devops-workflow/s3-buckets/aws" + names = ["bucket1", "bucket2", "bucket3"] + environment = "dev" + organization = "corp" } ``` -This would create/manage 3 S3 buckets: `corp-dev-bucket1`, `corp-dev-bucket2`, and `corp-dev-bucket3` +This would create/manage 3 S3 buckets: `corp-dev-bucket1`, `corp-dev-bucket2`, +and `corp-dev-bucket3` If a S3 bucket already exists, you will need to import it. Like this: ```Shell terraform import module.s3-buckets.aws_s3_bucket.this[0] corp-dev-bucket1 ``` + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| allow_encrypted_uploads_only | Set to `true` to prevent uploads of unencrypted objects to S3 bucket | string | `false` | no | +| attributes | Suffix name with additional attributes (policy, role, etc.) | list | `` | no | +| block\_public\_acls | Whether Amazon S3 should block public ACLs for this bucket | string | `"true"` | no | +| block\_public\_policy | Whether Amazon S3 should block public bucket policies for this bucket | string | `"true"` | no | +| component | TAG: Underlying, dedicated piece of service (Cache, DB, ...) | string | `"UNDEF-S3-Buckets"` | no | +| delimiter | Delimiter to be used between `name`, `namespaces`, `attributes`, etc. | string | `"-"` | no | +| enabled | Set to false to prevent the module from creating anything | string | `"true"` | no | +| environment | Environment (ex: `dev`, `qa`, `stage`, `prod`). (Second or top level namespace. Depending on namespacing options) | string | n/a | yes | +| encryption | If encryption is true, create an S3 bucket with default encryption i.e. `AES256` | string | false | no | +| force\_destroy | Delete all objects in bucket on destroy | string | `"false"` | no | +| ignore\_public\_acls | Whether Amazon S3 should ignore public ACLs for this bucket | string | `"true"` | no | +| 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 `encryption` as `true`. The default aws/s3 AWS KMS master key is used if this element is absent | string | `` | no | +| monitor | TAG: Should resource be monitored | string | `"UNDEF-S3-Buckets"` | no | +| names | List of S3 bucket names | list | n/a | yes | +| namespace-env | Prefix name with the environment. If true, format is: - | string | `"true"` | no | +| namespace-org | Prefix name with the organization. If true, format is: -. If both env and org namespaces are used, format will be -- | string | `"true"` | no | +| organization | Organization name (Top level namespace) | string | `""` | no | +| owner | TAG: Owner of the service | string | `"UNDEF-S3-Buckets"` | no | +| principal | principal | string | `"*"` | no | +| product | TAG: Company/business product | string | `"UNDEF-S3-Buckets"` | no | +| public | Allow public read access to bucket | string | `"false"` | no | +| restrict\_public\_buckets | Whether Amazon S3 should restrict public bucket policies for this bucket | string | `"true"` | no | +| service | TAG: Application (microservice) name | string | `"UNDEF-S3-Buckets"` | no | +| sse_algorithm | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | string | `AES256` | no | +| tags | A map of additional tags | map | `` | no | +| team | TAG: Department/team of people responsible for service | string | `"UNDEF-S3-Buckets"` | no | +| versioned | Version the bucket | string | `"false"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| regions | List of AWS S3 Bucket Regions | + + + + + +## Resource Graph of plan + +![Terraform Graph](resource-plan-graph.png) + diff --git a/examples/disabled/README.md b/examples/disabled/README.md index 0fddb95..c068c8f 100644 --- a/examples/disabled/README.md +++ b/examples/disabled/README.md @@ -1 +1,24 @@ # Example: Disabled by enabled variable + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| environment | | string | `"dev"` | no | +| organization | | string | `"testorg"` | no | +| region | | string | `"us-west-2"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| regions | List of AWS S3 Bucket Regions | + + diff --git a/examples/disabled/outputs.tf b/examples/disabled/outputs.tf index 70b5e47..2de9f73 100644 --- a/examples/disabled/outputs.tf +++ b/examples/disabled/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${module.s3.ids}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${module.s3.name_bases}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${module.s3.names}" diff --git a/examples/multiple/README.md b/examples/multiple/README.md index 740400b..dfae508 100644 --- a/examples/multiple/README.md +++ b/examples/multiple/README.md @@ -1 +1,24 @@ # Example: Managing multiple S3 buckets + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| environment | | string | `"dev"` | no | +| organization | | string | `"testorg"` | no | +| region | | string | `"us-west-2"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| regions | List of AWS S3 Bucket Regions | + + diff --git a/examples/multiple/outputs.tf b/examples/multiple/outputs.tf index 70b5e47..2de9f73 100644 --- a/examples/multiple/outputs.tf +++ b/examples/multiple/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${module.s3.ids}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${module.s3.name_bases}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${module.s3.names}" diff --git a/examples/none/README.md b/examples/none/README.md index e760978..49bc3ea 100644 --- a/examples/none/README.md +++ b/examples/none/README.md @@ -1 +1,24 @@ # Example: Disabled by providing an empty list + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| environment | | string | `"dev"` | no | +| organization | | string | `"testorg"` | no | +| region | | string | `"us-west-2"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| regions | List of AWS S3 Bucket Regions | + + diff --git a/examples/none/outputs.tf b/examples/none/outputs.tf index 70b5e47..2de9f73 100644 --- a/examples/none/outputs.tf +++ b/examples/none/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${module.s3.ids}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${module.s3.name_bases}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${module.s3.names}" diff --git a/examples/other-modules/README.md b/examples/other-modules/README.md new file mode 100644 index 0000000..4c661b8 --- /dev/null +++ b/examples/other-modules/README.md @@ -0,0 +1,14 @@ +# Other Terraform modules using this + +List of other Terraform modules using this one or that have examples (test cases) +that use this module. + +These can also serve as more examples + +| Name | GitHub Repo | Terraform Registry | +|-----|-----|-----| +| #MODULE# | [Repo](https://github.com/#ORG#/terraform-#PROVIDER#-#MODULE#) | [Registry](https://registry.terraform.io/modules/#ORG#/#MODULE#/#PROVIDER#) | + + + + diff --git a/examples/other-modules/main.tf b/examples/other-modules/main.tf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/examples/other-modules/main.tf @@ -0,0 +1 @@ + diff --git a/examples/policy/README.md b/examples/policy/README.md index 603bfb3..6e7fa6c 100644 --- a/examples/policy/README.md +++ b/examples/policy/README.md @@ -1 +1,25 @@ # Example: Managing multiple S3 buckets and create policy for them + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| environment | | string | `"dev"` | no | +| organization | | string | `"testorg"` | no | +| region | | string | `"us-west-2"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| policy | Unique to this example | +| regions | List of AWS S3 Bucket Regions | + + diff --git a/examples/policy/outputs.tf b/examples/policy/outputs.tf index e09abaa..cf3330c 100644 --- a/examples/policy/outputs.tf +++ b/examples/policy/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${module.s3.ids}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${module.s3.name_bases}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${module.s3.names}" diff --git a/examples/single/README.md b/examples/single/README.md index 445a0eb..fe0cbd9 100644 --- a/examples/single/README.md +++ b/examples/single/README.md @@ -1 +1,24 @@ # Example: Managing one S3 buckets + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|:----:|:-----:|:-----:| +| environment | | string | `"dev"` | no | +| organization | | string | `"testingorg"` | no | +| region | | string | `"us-west-2"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| arns | List of AWS S3 Bucket ARNs | +| domain\_names | List of AWS S3 Bucket Domain Names | +| hosted\_zone\_ids | List of AWS S3 Bucket Hosted Zone IDs | +| ids | List of AWS S3 Bucket IDs | +| name\_bases | List of base names used to generate S3 bucket names | +| names | List of AWS S3 Bucket Names | +| regions | List of AWS S3 Bucket Regions | + + diff --git a/examples/single/outputs.tf b/examples/single/outputs.tf index 70b5e47..2de9f73 100644 --- a/examples/single/outputs.tf +++ b/examples/single/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${module.s3.ids}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${module.s3.name_bases}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${module.s3.names}" diff --git a/examples/single/variables.tf b/examples/single/variables.tf index 1433c5b..9c42d1a 100644 --- a/examples/single/variables.tf +++ b/examples/single/variables.tf @@ -3,7 +3,7 @@ variable "environment" { } variable "organization" { - default = "testorg" + default = "testingorg" } variable "region" { diff --git a/main.tf b/main.tf index 9120cb4..5f41fb9 100644 --- a/main.tf +++ b/main.tf @@ -1,24 +1,12 @@ -/** - * AWS S3 Bucket Terraform Module - * ===================== - * - * Create multiple AWS S3 buckets and set policies - * - * Usage: - * ------ - * '''hcl - * module "s3-bucket" { - * source = "../s3-bucket" - * names = ["images","thumbnails"] - * environment = "dev" - * org = "corp" - * } - * ''' -**/ +## +## terraform-aws-s3-buckets +## # TODO: Allow pass policy via variable. Default empty policy. If can be done, otherwise 2 modules # create s3 bucket and set policy -# TODO: setup encryption +# TODO: +# support w. or w/o encryption (AES|KMS) +# use boolean for var.public # https://www.terraform.io/docs/providers/aws/r/aws_s3_bucket.html # https://www.terraform.io/docs/providers/aws/r/aws_s3_bucket_policy.html @@ -27,13 +15,13 @@ module "enabled" { source = "devops-workflow/boolean/local" - version = "0.1.1" + version = "0.1.2" value = "${var.enabled}" } module "labels" { - source = "devops-workflow/labels/null" - version = "0.1.0" + source = "appzen-oss/labels/null" + version = "0.2.0" attributes = "${var.attributes}" component = "${var.component}" delimiter = "${var.delimiter}" @@ -51,8 +39,26 @@ module "labels" { team = "${var.team}" } +locals { + encryption_def = { + "true" = [{ + rule = [{ + apply_server_side_encryption_by_default = [{ + sse_algorithm = "${var.kms_master_key_arn != "" ? "aws:kms" : "AES256"}" + kms_master_key_id = "${var.kms_master_key_arn}" + }] + }] + }] + + "false" = [] + } + + server_side_encryption_configuration = "${local.encryption_def[var.encryption]}" +} + +## if encryption is false then create bucket without encryption resource "aws_s3_bucket" "this" { - count = "${module.enabled.value ? length(var.names) : 0}" + count = "${module.enabled.value ? length(var.names) : 0 }" bucket = "${module.labels.id[count.index]}" acl = "${var.public ? "public-read" : "private"}" @@ -62,36 +68,125 @@ resource "aws_s3_bucket" "this" { enabled = "${var.versioned}" } + server_side_encryption_configuration = "${local.server_side_encryption_configuration}" + #acceleration_status #lifecycle_rule {} #logging { # target_bucket # target_prefix #} - #region + #region = "${var.region}" #request_payer #replication_configuration {} - #server_side_encryption_configuration + + + # server_side_encryption_configuration { + # rule { + # apply_server_side_encryption_by_default { + # sse_algorithm = "${var.sse_algorithm}" + # kms_master_key_id = "${var.kms_master_key_arn}" + # } + # } + # } + tags = "${module.labels.tags[count.index]}" } -/* -data "template_file" "policy_s3_bucket" { - # TODO: add condition to select public or private template - # or 2 data and condition in policy for which data to use - template = "${file("${path.module}/files/policy_s3_bucket.json")}" - vars = { - name = "${aws_s3_bucket.this.bucket}" - principal = "${var.principal}" - } +resource "aws_s3_bucket_public_access_block" "this" { + depends_on = ["aws_s3_bucket.this"] + count = "${module.enabled.value ? length(var.names) : 0}" + bucket = "${module.labels.id[count.index]}" + block_public_acls = "${var.block_public_acls}" + block_public_policy = "${var.block_public_policy}" + ignore_public_acls = "${var.ignore_public_acls}" + restrict_public_buckets = "${var.restrict_public_buckets}" } -resource "aws_s3_bucket_policy" "bucket_policy" { - bucket = "${aws_s3_bucket.this.id}" - policy = "${data.template_file.policy_s3_bucket.rendered}" +# data "template_file" "policy_s3_bucket" { +# # TODO: add condition to select public or private template +# # or 2 data and condition in policy for which data to use +# template = "${file("${path.module}/files/policy_s3_bucket.json")}" +# vars = { +# name = "${aws_s3_bucket.this.bucket}" +# principal = "${var.principal}" +# } +# } + +# resource "aws_s3_bucket_policy" "bucket_policy" { +# count = "${module.enabled.value ? length(var.names) : 0}" +# bucket = "${module.labels.id[count.index]}" +# #bucket = "${aws_s3_bucket.this.id}" +# policy = "${data.template_file.policy_s3_bucket.rendered}" +# } + +# https://aws.amazon.com/blogs/security/how-to-prevent-uploads-of-unencrypted-objects-to-amazon-s3/ +data "aws_iam_policy_document" "bucket_policy" { + count = "${module.enabled.value && var.allow_encrypted_uploads_only == "true" ? length(var.names) : 0}" + + statement { + sid = "DenyIncorrectEncryptionHeader" + effect = "Deny" + actions = ["s3:PutObject"] + resources = ["arn:aws:s3:::${module.labels.id[count.index]}/*"] + + principals { + identifiers = ["*"] + type = "*" + } + + condition { + test = "StringNotEquals" + values = ["${var.kms_master_key_arn != "" ? "aws:kms" : "AES256"}"] + variable = "s3:x-amz-server-side-encryption" + } + } + + statement { + sid = "DenyUnEncryptedObjectUploads" + effect = "Deny" + actions = ["s3:PutObject"] + resources = ["arn:aws:s3:::${module.labels.id[count.index]}/*"] + + principals { + identifiers = ["*"] + type = "*" + } + + condition { + test = "Null" + values = ["true"] + variable = "s3:x-amz-server-side-encryption" + } + } + + statement { + sid = "BucketOwnerFullAccess" + effect = "Deny" + actions = ["s3:PutObject"] + resources = ["arn:aws:s3:::${module.labels.id[count.index]}/*"] + + principals { + identifiers = ["*"] + type = "*" + } + + condition { + test = "StringNotEquals" + values = ["bucket-owner-full-control"] + variable = "s3:x-amz-acl" + } + } } -*/ +resource "aws_s3_bucket_policy" "default" { + depends_on = ["aws_s3_bucket.this"] + count = "${module.enabled.value && var.allow_encrypted_uploads_only == "true" ? length(var.names) : 0}" + bucket = "${module.labels.id[count.index]}" + policy = "${element(data.aws_iam_policy_document.bucket_policy.*.json, count.index)}" + + #policy = "${join("", data.aws_iam_policy_document.bucket_policy.*.json, count.index)}" +} #resource "aws_s3_bucket_notification" @@ -101,7 +196,7 @@ resource "aws_s3_bucket_object" "this" { count = "${length(var.files)}" bucket = "${aws_s3_bucket.this.id}" key = "${element(keys(var.files), count.index)}" - source = "${lookup(var.files, element(keys(var.files), count.index))}" + #source = "${lookup(var.files, element(keys(var.files), count.index))}" etag = "${md5(file("${lookup(var.files, element(keys(var.files), count.index))}"))}" } */ diff --git a/outputs.tf b/outputs.tf index 9af35df..0d615f4 100644 --- a/outputs.tf +++ b/outputs.tf @@ -18,6 +18,11 @@ output "ids" { value = "${aws_s3_bucket.this.*.id}" } +output "name_bases" { + description = "List of base names used to generate S3 bucket names" + value = "${var.names}" +} + output "names" { description = "List of AWS S3 Bucket Names" value = "${aws_s3_bucket.this.*.id}" diff --git a/resource-plan-graph.png b/resource-plan-graph.png new file mode 100644 index 0000000..8a9b9f4 Binary files /dev/null and b/resource-plan-graph.png differ diff --git a/variables.tf b/variables.tf index 807a8fc..568df1b 100644 --- a/variables.tf +++ b/variables.tf @@ -81,7 +81,9 @@ variable "team" { default = "UNDEF-S3-Buckets" } +// // Module specific Variables +// variable "enabled" { description = "Set to false to prevent the module from creating anything" default = true @@ -92,6 +94,24 @@ variable "force_destroy" { default = false } +variable "encryption" { + type = "string" + default = "false" + description = "If encryption is true, create an S3 bucket with default encryption i.e. `AES256`" +} + +variable "kms_master_key_arn" { + type = "string" + default = "" + description = "The AWS KMS master key ARN used for the SSE-KMS encryption. This can only be used when you set the value of encryption as true. The default aws/s3 AWS KMS master key is used if this element is absent" +} + +variable "allow_encrypted_uploads_only" { + type = "string" + default = "false" + description = "Set to `true` to prevent uploads of unencrypted objects to S3 bucket" +} + variable "principal" { description = "principal" default = "*" @@ -106,3 +126,26 @@ variable "versioned" { description = "Version the bucket" default = false } + +// +// S3 Public restriction block +// +variable "block_public_acls" { + description = "Whether Amazon S3 should block public ACLs for this bucket" + default = true +} + +variable "block_public_policy" { + description = "Whether Amazon S3 should block public bucket policies for this bucket" + default = true +} + +variable "ignore_public_acls" { + description = "Whether Amazon S3 should ignore public ACLs for this bucket" + default = true +} + +variable "restrict_public_buckets" { + description = "Whether Amazon S3 should restrict public bucket policies for this bucket" + default = true +}