diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..7cf5f6d --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,161 @@ +version: 2.1 + +jobs: + # + # Test 1 & Test 2 + # + test-static-code-and-linting: + machine: + image: ubuntu-1604:201903-01 + # This job has been blocked because Docker Layer Caching is not available on your plan. + # Please upgrade to continue building. + # Note: you will need to push a new commit or call the API to rerun the pipeline. + docker_layer_caching: false + + steps: + - checkout + + - run: pwd + - run: ls -ltra + - run: git branch + + - run: + name: test1-terraform-format + command: | + if [[ $(make format| tail -n +2) ]]; then + echo "===================================================================================================" + echo " NOT PASSED - There are Terraform conf files that needs a canonical format and styleto be formated " + echo "===================================================================================================" + exit 1 + else + echo "===================================================================================================" + echo " PASSED - All Terraform conf files already have canonical format and are correcyly styled " + echo "===================================================================================================" + fi + + - run: + name: test2-terraform-linting + command: | + if make lint | grep 'Awesome! Your code is following the best practices'; then + echo "===============================================================================================" + echo " PASSED - Awesome! Your code is following the best practices " + echo "===============================================================================================" + else + echo "===============================================================================================" + echo " NOT PASSED - Terraform lint needed " + echo "===============================================================================================" + fi + # + # Test 3 + # + test-e2e-terratests: + machine: + image: ubuntu-1604:201903-01 + docker_layer_caching: false + + steps: + - checkout + + - run: pwd + - run: ls -ltra + - run: git branch + + - run: + name: Install awscli + command: sudo pip install awscli + + - run: + name: Configure awscli + command: | + echo "AWS_ACCESS_KEY_ID - $AWS_ACCESS_KEY_ID" + echo "AWS_PROFILE_NAME - $AWS_PROFILE_NAME" + + # AWS defautl awscli profile + aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID + aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY + aws configure set region us-east-1 + aws configure set output json + + # AWS dev awscli profile + aws configure set role_arn arn:aws:iam::$AWS_ACCOUNT_ID_DEV:role/DeployMaster --profile $AWS_PROFILE_NAME + aws configure set source_profile default --profile $AWS_PROFILE_NAME + +# - run: cat ~/.aws/credentials +# - run: cat ~/.aws/config + + - run: + name: Test AWS permissions + command: aws budgets describe-budgets --account-id $AWS_ACCOUNT_ID_DEV --profile $AWS_PROFILE_NAME + + - run: + name: test3-terratests-dep-init + command: cd modules/waf-global && make terratest-dep-init + + - run: + name: test3-terratests-go-test + command: cd modules/waf-global && make terratest-go-test + + - run: + name: test4-terratests-dep-init + command: cd modules/waf-regional && make terratest-dep-init + + - run: + name: test4-terratests-go-test + command: cd modules/waf-regional && make terratest-go-test + # + # Release + # + release-patch-with-changelog: + machine: + image: ubuntu-1604:201903-01 + docker_layer_caching: false + + steps: + - checkout + + - run: pwd + - run: ls -ltra + - run: git branch + + - run: + name: release-patch-with-changelog-circleci + command: | + if git status | grep 'nothing to commit, working tree clean'; then + echo "===============================================================================================" + echo "release-patch-with-changelog-circleci" + echo "===============================================================================================" + git config --global user.email "$GIT_USER_EMAIL" + git config --global user.name "$GIT_USER_NAME" + make release-patch-with-changelog-circleci + else + echo "===============================================================================================" + echo "Changes in working directory pending to be pushed - please check 'git status' cmd output below " + echo "===============================================================================================" + echo "$(git status)" + echo "===============================================================================================" + fi +# +# Jobs workflow +# +workflows: + version: 2 + changelog_and_release: + jobs: + - test-static-code-and-linting: + context: binbashar-org-global-context + filters: + branches: + ignore: # only branches matching the below regex filters will run + - master + - test-e2e-terratests: + context: binbashar-org-global-context + filters: + branches: + ignore: # only branches matching the below regex filters will run + - master + - release-patch-with-changelog: + context: binbashar-org-global-context + filters: + branches: + only: # only branches matching the below regex filters will run + - master \ No newline at end of file diff --git a/.gitignore b/.gitignore index bf3bf91..7f58769 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ # Except # !*.env.example +!*.circleci !*.dockeringnore !*.hosts !/.gitignore @@ -65,6 +66,8 @@ Thumbs.db *.iml *.ipr .vscode +/src +/venv # Custom Project tools files # ######################## @@ -88,4 +91,5 @@ Thumbs.db # # Terratest # -tests/vendor/ \ No newline at end of file +modules/waf-global/tests/vendor/ +modules/waf-regional/tests/vendor/ \ No newline at end of file diff --git a/Makefile b/Makefile index 1e4c551..9014eda 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LOCAL_OS_GIT_CONF_DIR := ~/.gitconfig LOCAL_OS_AWS_CONF_DIR := ~/.aws TF_PWD_DIR := $(shell pwd) -TF_VER := 0.11.14 +TF_VER := 0.12.12 TF_PWD_CONT_DIR := "/go/src/project/" TF_DOCKER_ENTRYPOINT := /usr/local/go/bin/terraform TF_DOCKER_IMAGE := binbash/terraform-resources @@ -36,7 +36,7 @@ endef GIT_SEMTAG_VER_PATCH := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s patch -o) GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s minor -o) -GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s major -o) +GIT_SEMTAG_VER_MAJOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s major -o) help: @echo 'Available Commands:' @@ -54,8 +54,20 @@ version: ## Show terraform version format: ## The terraform fmt is used to rewrite tf conf files to a canonical format and style. ${TF_CMD_PREFIX} fmt ${TF_PWD_CONT_DIR} -lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. - docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint --deep +doc-tf-eleven: ## A utility to generate documentation from Terraform 0.11 modules in various output formats. + docker run --rm -v ${TF_PWD_DIR}:/data -t binbash/terraform-docs markdown table /data + +doc-tf-twelve: ## A utility to generate documentation from Terraform 0.12 modules in various output formats. + bash terraform-docs.sh markdown ${TF_PWD_DIR} + +lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan (tf0.11 --> < 0.9.2. + docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint:0.9.2 --deep + +#==============================================================# +# CIRCLECI # +#==============================================================# +circleci-validate-config: ## Validate A CircleCI Config (https://circleci.com/docs/2.0/local-cli/) + circleci config validate .circleci/config.yml #==============================================================# # GIT RELEASE # @@ -66,24 +78,80 @@ release-patch: ## releasing patch (eg: 0.0.1 -> 0.0.2) based on semantic tagging sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git ${GIT_SEMTAG_CMD_PREFIX} final -s patch +release-patch-with-changelog: ## make changelog-patch && git add && git commit && make release-patch + @if git status | grep 'nothing to commit, working directory clean'; then\ + make changelog-patch;\ + git status;\ + git add CHANGELOG.md;\ + git commit -m "Updating CHANGELOG.md via make changelog-patch for ${GIT_SEMTAG_VER_PATCH} [ci skip]";\ + git push origin master;\ + make release-patch;\ + else\ + echo "===============================================================================================";\ + echo "Changes in working directory pending to be pushed - please check 'git status' cmd output below ";\ + echo "===============================================================================================";\ + echo "$$(git status)";\ + echo "===============================================================================================";\ + fi + +release-patch-with-changelog-circleci: ## make changelog-patch && git add && git commit && make release-patch + make changelog-patch + git status + git add CHANGELOG.md + git commit -m "Updating CHANGELOG.md via make changelog-patch for ${GIT_SEMTAG_VER_PATCH} [ci skip]" + git push origin master + make release-patch + release-minor: ## releasing minor (eg: 0.0.2 -> 0.1.0) based on semantic tagging script for Git # pre-req -> https://github.com/pnikosis/semtag ${GIT_SEMTAG_CMD_PREFIX} get sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git ${GIT_SEMTAG_CMD_PREFIX} final -s minor +release-minor-with-changelog: ## make changelog-minor && git add && git commit && make release-minor + @if git status |grep 'nothing to commit, working directory clean'; then\ + make changelog-minor;\ + git status;\ + git add CHANGELOG.md;\ + git commit -m "Updating CHANGELOG.md via make changelog-minorfor ${GIT_SEMTAG_VER_PATCH} [ci skip]";\ + git push origin master;\ + make release-minor;\ + else\ + echo "===============================================================================================";\ + echo "Changes in working directory pending to be pushed - please check 'git status' cmd output below ";\ + echo "===============================================================================================";\ + echo "$$(git status)";\ + echo "===============================================================================================";\ + fi + release-major: ## releasing major (eg: 0.1.0 -> 1.0.0) based on semantic tagging script for Git # pre-req -> https://github.com/pnikosis/semtag ${GIT_SEMTAG_CMD_PREFIX} get sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git ${GIT_SEMTAG_CMD_PREFIX} final -s major +release-major-with-changelog: ## make changelog-major && git add && git commit && make release-major + @if git status |grep 'nothing to commit, working directory clean'; then\ + make changelog-major;\ + git status;\ + git add CHANGELOG.md;\ + git commit -m "Updating CHANGELOG.md via make changelog-major for ${GIT_SEMTAG_VER_PATCH} [ci skip]";\ + git push origin master;\ + make release-major;\ + else\ + echo "===============================================================================================";\ + echo "Changes in working directory pending to be pushed - please check 'git status' cmd output below ";\ + echo "===============================================================================================";\ + echo "$$(git status)";\ + echo "===============================================================================================";\ + fi + changelog-init: ## git-chglog (https://github.com/git-chglog/git-chglog) config initialization -> ./.chglog @if [ ! -d ./.chglog ]; then\ docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release --init;\ sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog;\ else\ - echo "==============================";\ + echo "==============================";\ echo "git-chglog already initialized";\ echo "==============================";\ echo "$$(ls ./.chglog)";\ diff --git a/README.md b/README.md index edbd8f5..993d78d 100644 --- a/README.md +++ b/README.md @@ -154,4 +154,29 @@ module "waf_regional_test" { rule_csrf_action_type = "COUNT" rule_blacklisted_ips_action_type = "COUNT" } -``` \ No newline at end of file +``` + +# Release Management + +## Docker based makefile commands +- https://cloud.docker.com/u/binbash/repository/docker/binbash/git-release +- https://github.com/binbashar/terraform-aws-waf-owasp/blob/master/Makefile + +Root directory `Makefile` has the automated steps (to be integrated with **CircleCI jobs** []() ) + +### CircleCi PR auto-release job +
+ leverage-circleci +
+ +- https://circleci.com/gh/binbashar/terraform-aws-waf-owasp +- **NOTE:** Will only run after merged PR. + +### Manual execution from workstation +``` +$ make +Available Commands: + - release-major-with-changelog make changelog-major && git add && git commit && make release-major + - release-minor-with-changelog make changelog-minor && git add && git commit && make release-minor + - release-patch-with-changelog make changelog-patch && git add && git commit && make release-patch + ``` \ No newline at end of file diff --git a/figures/circleci.png b/figures/circleci.png new file mode 100644 index 0000000..6e3a37b Binary files /dev/null and b/figures/circleci.png differ diff --git a/modules/waf-global/Makefile b/modules/waf-global/Makefile index b5adbe8..f0f57a4 100644 --- a/modules/waf-global/Makefile +++ b/modules/waf-global/Makefile @@ -6,7 +6,7 @@ LOCAL_OS_GIT_CONF_DIR := ~/.gitconfig LOCAL_OS_AWS_CONF_DIR := ~/.aws TF_PWD_DIR := $(shell pwd) -TF_VER := 0.11.14 +TF_VER := 0.12.12 TF_PWD_CONT_DIR := "/go/src/project/" TF_DOCKER_ENTRYPOINT := /usr/local/go/bin/terraform TF_DOCKER_IMAGE := binbash/terraform-resources @@ -46,22 +46,6 @@ docker run --rm \ -it ${TF_DOCKER_IMAGE}:${TF_VER} endef -# -# GIT-RELEASE -# -# pre-req -> https://github.com/pnikosis/semtag -define GIT_SEMTAG_CMD_PREFIX -docker run --rm \ --v ${TF_PWD_DIR}:/data:rw \ --v ${LOCAL_OS_SSH_DIR}:/root/.ssh \ --v ${LOCAL_OS_GIT_CONF_DIR}:/etc/gitconfig \ ---entrypoint=/opt/semtag/semtag/semtag \ --it binbash/git-release -endef - -GIT_SEMTAG_VER_PATCH := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s patch -o) -GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s minor -o) -GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s major -o) help: @echo 'Available Commands:' @@ -79,12 +63,14 @@ version: ## Show terraform version format: ## The terraform fmt is used to rewrite tf conf files to a canonical format and style. ${TF_CMD_PREFIX} fmt ${TF_PWD_CONT_DIR} -doc: ## A utility to generate documentation from Terraform modules in various output formats. +doc-tf-eleven: ## A utility to generate documentation from Terraform 0.11 modules in various output formats. docker run --rm -v ${TF_PWD_DIR}:/data -t binbash/terraform-docs markdown table /data -lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. - docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint --deep +doc-tf-twelve: ## A utility to generate documentation from Terraform 0.12 modules in various output formats. + bash terraform-docs.sh markdown ${TF_PWD_DIR} +lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan (tf0.11 --> < 0.9.2. + docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint:0.9.2 --deep #==============================================================# # TERRATEST # @@ -98,55 +84,4 @@ terratest-dep-init: ## dep is a dependency management tool for Go. (https://gith terratest-go-test: ## lint: TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. ${TERRATEST_GO_CMD_PREFIX} test - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} . - -#==============================================================# -# GIT RELEASE # -#==============================================================# -release-patch: ## releasing patch (eg: 0.0.1 -> 0.0.2) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s patch - -release-minor: ## releasing minor (eg: 0.0.2 -> 0.1.0) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s minor - -release-major: ## releasing major (eg: 0.1.0 -> 1.0.0) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s major - -changelog-init: ## git-chglog (https://github.com/git-chglog/git-chglog) config initialization -> ./.chglog - @if [ ! -d ./.chglog ]; then\ - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release --init;\ - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog;\ - else\ - echo "==============================";\ - echo "git-chglog already initialized";\ - echo "==============================";\ - echo "$$(ls ./.chglog)";\ - echo "==============================";\ - fi - -changelog-patch: ## git-chglog generation for path release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_PATCH} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md - -changelog-minor: ## git-chglog generation for minor release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_MINOR} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md - -changelog-major: ## git-chglog generation for major release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_MAJOR} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md \ No newline at end of file + sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} . \ No newline at end of file diff --git a/modules/waf-global/README.md b/modules/waf-global/README.md index 0e72b70..7ee21d0 100644 --- a/modules/waf-global/README.md +++ b/modules/waf-global/README.md @@ -43,26 +43,29 @@ References | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| admin\_remote\_ipset | List of IPs allowed to access admin pages | list | n/a | yes | -| blacklisted\_ips | List of IPs to blacklist | list | n/a | yes | -| cloudfront\_arn | List of CloudFront Distributions ARNs | list | n/a | yes | -| rule\_admin\_access\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_auth\_tokens\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_blacklisted\_ips\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_csrf\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_lfi\_rfi\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_php\_insecurities\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_size\_restriction\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_sqli\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_ssi\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_xss\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | +| admin\_remote\_ipset | List of IPs allowed to access admin pages, ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32'] | list(string) | `` | no | +| blacklisted\_ips | List of IPs to blacklist, eg ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32'] | list(string) | `` | no | +| cloudfront\_arn | List of CloudFront Distributions ARNs | list(string) | `` | no | +| rule\_admin\_access\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_auth\_tokens\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_blacklisted\_ips\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_csrf\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_lfi\_rfi\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_php\_insecurities\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_size\_restriction\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_sqli\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_ssi\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_xss\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| tags | A mapping of tags to assign to all resources | map | `` | no | | waf\_prefix | Prefix to use when naming resources | string | n/a | yes | ## Outputs | Name | Description | |------|-------------| -| web\_acl\_id | | +| web\_acl\_id | AWS WAF web acl id. | +| web\_acl\_metric\_name | The name or description for the Amazon CloudWatch metric of this web ACL. | +| web\_acl\_name | The name or description of the web ACL. | ## Examples ### waf-global diff --git a/modules/waf-global/main.tf b/modules/waf-global/main.tf index dc49f83..be2a28d 100644 --- a/modules/waf-global/main.tf +++ b/modules/waf-global/main.tf @@ -3,7 +3,7 @@ # resource "aws_waf_web_acl" "waf_acl" { name = "${var.waf_prefix}-generic-owasp-acl" - metric_name = "${var.waf_prefix}genericowaspacl" + metric_name = replace("${var.waf_prefix}genericowaspacl", "/[^0-9A-Za-z]/", "") default_action { type = "ALLOW" @@ -15,11 +15,11 @@ resource "aws_waf_web_acl" "waf_acl" { # rules { action { - type = "${var.rule_size_restriction_action_type}" + type = var.rule_size_restriction_action_type } priority = 10 - rule_id = "${aws_waf_rule.restrict_sizes.id}" + rule_id = aws_waf_rule.restrict_sizes.id type = "REGULAR" } @@ -29,54 +29,53 @@ resource "aws_waf_web_acl" "waf_acl" { # rules { action { - type = "${var.rule_blacklisted_ips_action_type}" + type = var.rule_blacklisted_ips_action_type } - + priority = 20 - rule_id = "${aws_waf_rule.detect_blacklisted_ips.id}" + rule_id = aws_waf_rule.detect_blacklisted_ips.id type = "REGULAR" } - # # Reason: the apps do not use auth tokens yet. # So COMMENT rule block below to deactivate this rule # rules { action { - type = "${var.rule_auth_tokens_action}" + type = var.rule_auth_tokens_action } - + priority = 30 - rule_id = "${aws_waf_rule.detect_bad_auth_tokens.id}" + rule_id = aws_waf_rule.detect_bad_auth_tokens.id type = "REGULAR" } rules { action { - type = "${var.rule_sqli_action}" + type = var.rule_sqli_action } priority = 40 - rule_id = "${aws_waf_rule.mitigate_sqli.id}" + rule_id = aws_waf_rule.mitigate_sqli.id type = "REGULAR" } rules { action { - type = "${var.rule_xss_action}" + type = var.rule_xss_action } priority = 50 - rule_id = "${aws_waf_rule.mitigate_xss.id}" + rule_id = aws_waf_rule.mitigate_xss.id type = "REGULAR" } rules { action { - type = "${var.rule_lfi_rfi_action}" + type = var.rule_lfi_rfi_action } priority = 60 - rule_id = "${aws_waf_rule.detect_rfi_lfi_traversal.id}" + rule_id = aws_waf_rule.detect_rfi_lfi_traversal.id type = "REGULAR" } @@ -86,26 +85,25 @@ resource "aws_waf_web_acl" "waf_acl" { # rules { action { - type = "${var.rule_php_insecurities_action_type}" + type = var.rule_php_insecurities_action_type } - + priority = 70 - rule_id = "${aws_waf_rule.detect_php_insecure.id}" + rule_id = aws_waf_rule.detect_php_insecure.id type = "REGULAR" } - # # Reason: the apps do not use CSRF tokens. # So COMMENT rule block below to deactivate this rule # rules { action { - type = "${var.rule_csrf_action_type}" + type = var.rule_csrf_action_type } - + priority = 80 - rule_id = "${aws_waf_rule.enforce_csrf.id}" + rule_id = aws_waf_rule.enforce_csrf.id type = "REGULAR" } @@ -114,11 +112,11 @@ resource "aws_waf_web_acl" "waf_acl" { # rules { action { - type = "${var.rule_ssi_action_type}" + type = var.rule_ssi_action_type } priority = 90 - rule_id = "${aws_waf_rule.detect_ssi.id}" + rule_id = aws_waf_rule.detect_ssi.id type = "REGULAR" } @@ -128,21 +126,24 @@ resource "aws_waf_web_acl" "waf_acl" { # rules { action { - type = "${var.rule_admin_access_action_type}" + type = var.rule_admin_access_action_type } - + priority = 100 - rule_id = "${aws_waf_rule.detect_admin_access.id}" + rule_id = aws_waf_rule.detect_admin_access.id type = "REGULAR" } + + tags = "${var.tags}" } # # This is how we link the WAF ACL to one or many CloudFront distributions. # resource "aws_wafregional_web_acl_association" "acl_cloudfront_association" { - depends_on = ["aws_waf_web_acl.waf_acl"] - count = "${length(var.cloudfront_arn)}" - resource_arn = "${element(var.cloudfront_arn, count.index)}" - web_acl_id = "${aws_waf_web_acl.waf_acl.id}" + depends_on = [aws_waf_web_acl.waf_acl] + count = length(var.cloudfront_arn) + resource_arn = element(var.cloudfront_arn, count.index) + web_acl_id = aws_waf_web_acl.waf_acl.id } + diff --git a/modules/waf-global/outputs.tf b/modules/waf-global/outputs.tf index 4458698..a363e5a 100644 --- a/modules/waf-global/outputs.tf +++ b/modules/waf-global/outputs.tf @@ -1,3 +1,14 @@ output "web_acl_id" { - value = "${aws_waf_web_acl.waf_acl.id}" + description = "AWS WAF web acl id." + value = aws_waf_web_acl.waf_acl.id } + +output "web_acl_name" { + description = "The name or description of the web ACL." + value = aws_waf_web_acl.waf_acl.name +} + +output "web_acl_metric_name" { + description = "The name or description for the Amazon CloudWatch metric of this web ACL." + value = aws_waf_web_acl.waf_acl.metric_name +} \ No newline at end of file diff --git a/modules/waf-global/terraform-docs.awk b/modules/waf-global/terraform-docs.awk new file mode 100644 index 0000000..bd6b2b7 --- /dev/null +++ b/modules/waf-global/terraform-docs.awk @@ -0,0 +1,90 @@ +# This script converts Terraform 0.12 variables/outputs to something suitable for `terraform-docs` +# As of terraform-docs v0.6.0, HCL2 is not supported. This script is a *dirty hack* to get around it. +# https://github.com/segmentio/terraform-docs/ +# https://github.com/segmentio/terraform-docs/issues/62 + +{ + if ( $0 ~ /\{/ ) { + braceCnt++ + } + + if ( $0 ~ /\}/ ) { + braceCnt-- + } + + # [START] variable or output block started + if ($0 ~ /^[[:space:]]*(variable|output)[[:space:]][[:space:]]*"(.*?)"/) { + # Normalize the braceCnt (should be 1 now) + braceCnt = 1 + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + blockCnt++ + print $0 + } + + # [START] multiline default statement started + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*(default)[[:space:]][[:space:]]*=/) { + if ($3 ~ "null") { + print " default = \"null\"" + } else { + print $0 + blockDefCnt++ + blockDefStart=1 + } + } + } + + # [PRINT] single line "description" + if (blockCnt > 0) { + if (blockDefCnt == 0) { + if ($0 ~ /^[[:space:]][[:space:]]*description[[:space:]][[:space:]]*=/) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + print $0 + } + } + } + + # [PRINT] single line "type" + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*type[[:space:]][[:space:]]*=/ ) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + type=$3 + if (type ~ "object") { + print " type = \"object\"" + } else { + # legacy quoted types: "string", "list", and "map" + if ($3 ~ /^[[:space:]]*"(.*?)"[[:space:]]*$/) { + print " type = " $3 + } else { + print " type = \"" $3 "\"" + } + } + } + } + + # [CLOSE] variable/output block + if (blockCnt > 0) { + if (braceCnt == 0 && blockCnt > 0) { + blockCnt-- + print $0 + } + } + + # [PRINT] Multiline "default" statement + if (blockCnt > 0 && blockDefCnt > 0) { + if (blockDefStart == 1) { + blockDefStart = 0 + } else { + print $0 + } + } +} diff --git a/modules/waf-global/terraform-docs.sh b/modules/waf-global/terraform-docs.sh new file mode 100755 index 0000000..603576e --- /dev/null +++ b/modules/waf-global/terraform-docs.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +PWD=$(pwd) + +which awk 2>&1 >/dev/null || ( echo "awk not available"; exit 1) +which terraform 2>&1 >/dev/null || ( echo "terraform not available"; exit 1) +which terraform-docs 2>&1 >/dev/null || ( echo "terraform-docs not available"; exit 1) + +if [[ "`terraform version | head -1`" =~ 0\.12 ]]; then + TMP_FILE="$(mktemp /tmp/terraform-docs-XXXXXXXXXX)" + awk -f ${PWD}/terraform-docs.awk $2/*.tf > ${TMP_FILE} + terraform-docs $1 ${TMP_FILE} + rm -f ${TMP_FILE} +else + terraform-docs $1 $2 +fi diff --git a/modules/waf-global/tests/Gopkg.lock b/modules/waf-global/tests/Gopkg.lock new file mode 100644 index 0000000..ec43917 --- /dev/null +++ b/modules/waf-global/tests/Gopkg.lock @@ -0,0 +1,97 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:f44d09e27cefd53e739b4469bc4e797164b86b9f4bece82dae2e1b17d52ec53d" + name = "github.com/gruntwork-io/terratest" + packages = [ + "modules/collections", + "modules/customerrors", + "modules/files", + "modules/logger", + "modules/retry", + "modules/shell", + "modules/ssh", + "modules/terraform", + ] + pruneopts = "UT" + revision = "9f4f4f69d6521e2cb71c872297e8e04a890a5ec7" + version = "v0.22.2" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:99d32780e5238c2621fff621123997c3e3cca96db8be13179013aea77dfab551" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "UT" + revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" + version = "v1.4.0" + +[[projects]] + branch = "master" + digest = "1:4b049aae7a44fb6fb4d8e84355b145e591f686111817c0998fe31a9f8bf226dc" + name = "golang.org/x/crypto" + packages = [ + "curve25519", + "ed25519", + "ed25519/internal/edwards25519", + "internal/chacha20", + "internal/subtle", + "poly1305", + "ssh", + "ssh/agent", + ] + pruneopts = "UT" + revision = "8986dd9e96cf0a6f74da406c005ba3df38527c04" + +[[projects]] + branch = "master" + digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70" + name = "golang.org/x/net" + packages = ["context"] + pruneopts = "UT" + revision = "fe3aa8a4527195a6057b3fad46619d7d090e99b5" + +[[projects]] + branch = "master" + digest = "1:95abab3e07422980e85e253e49eb75c05c1dee6e84b50cb0d3353747ef8099eb" + name = "golang.org/x/sys" + packages = ["cpu"] + pruneopts = "UT" + revision = "f43be2a4598cf3a47be9f94f0c28197ed9eae611" + +[[projects]] + digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" + version = "v2.2.4" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/gruntwork-io/terratest/modules/terraform", + "github.com/stretchr/testify/assert", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/modules/waf-global/tests/Gopkg.toml b/modules/waf-global/tests/Gopkg.toml new file mode 100644 index 0000000..e0b60dd --- /dev/null +++ b/modules/waf-global/tests/Gopkg.toml @@ -0,0 +1,38 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/gruntwork-io/terratest" + version = "0.22.2" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.4.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/modules/waf-global/tests/README.md b/modules/waf-global/tests/README.md new file mode 100644 index 0000000..69ec723 --- /dev/null +++ b/modules/waf-global/tests/README.md @@ -0,0 +1,147 @@ +# Terraform Module Tests: Terratests + +## Overview +Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. +It provides a variety of helper functions and patterns for common infrastructure testing tasks, including: +- Testing Terraform code +- Working with AWS APIs +- And much more + +Official **terratest** [documentation available on GitHub project repo](https://github.com/gruntwork-io/terratest). +Ref Article [available on Medium maintainers blog](https://blog.gruntwork.io/open-sourcing-terratest-a-swiss-army-knife-for-testing-infrastructure-code-5d883336fcd5). + +### Install requirements + +Terratest uses the Go testing framework. To use terratest, you need to install: + +- [Go](https://golang.org/) (requires version >=1.10) +- [dep](https://github.com/golang/dep) (requires version >=0.5.1) + +## Files Organization +* Terraform files are located at the root of this directory. +* Tests can be found under tests/ directory. + +## Testing +### Key Points +* We use `terratest` for testing this module. +* Keep in mind that `terratest` is not a binary but a Go library with helpers that make it easier to work with Terraform and other tools. +* Test files use `_test` suffix. E.g.: `create_file_with_default_values_test.go` +* Test classes use `Test` prefix. E.g.: `func TestCreateFileWithDefaultValues(t *testing.T) {` +* Our tests make use of a fixture/ dir that resembles how the module will be used. + +### Set Up + +#### Dokerized Makefile +``` +$ make +Available Commands: +... + - terratest-dep-init dep is a dependency management tool for Go. (https://github.com/golang/dep) + - terratest-go-test lint: TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. +... +``` + +1. `make terratest-dep-init` +``` +$ make terratest-dep-init +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-ec2-jenkins-vault:"/go/src/project/":rw -v ~/.ssh:/root/.ssh -v ~/.gitconfig:/etc/gitconfig --entrypoint=dep -it binbash/terraform-resources:0.11.14 init + Locking in master (da137c7) for transitive dep golang.org/x/net + Using ^1.3.0 as constraint for direct dep github.com/stretchr/testify + Locking in v1.3.0 (ffdc059) for direct dep github.com/stretchr/testify + Locking in v1.0.0 (792786c) for transitive dep github.com/pmezard/go-difflib + Using ^0.17.5 as constraint for direct dep github.com/gruntwork-io/terratest + Locking in v0.17.5 (03959c9) for direct dep github.com/gruntwork-io/terratest + Locking in v1.1.1 (8991bc2) for transitive dep github.com/davecgh/go-spew + Locking in master (4def268) for transitive dep golang.org/x/crypto + Locking in master (04f50cd) for transitive dep golang.org/x/sys +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-ec2-jenkins-vault:"/go/src/project/":rw -v ~/.ssh:/root/.ssh -v ~/.gitconfig:/etc/gitconfig --entrypoint=dep -it binbash/terraform-resources:0.11.14 ensure +sudo chown -R delivery:delivery . +cp -r ./vendor ./tests/ && rm -rf ./vendor +cp -r ./Gopkg* ./tests/ && rm -rf ./Gopkg* +``` + +2. `terratest-go-test` +``` +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: Destroy complete! Resources: 23 destroyed. +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +... +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: Warning: output "aws_s3_bucket_ssl_certificates_bucket_domain_name": must use splat syntax to access aws_s3_bucket.ssl_certificates_bucket attribute "bucket_domain_name", because it has "count" set; use aws_s3_bucket.ssl_certificates_bucket.*.bucket_domain_name to obtain a list of the attributes across all instances +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +PASS +ok project/tests 227.167s +sudo chown -R delivery:delivery . + +``` + +#### Local installed deps execution +* Make sure this module is within the **GOPATH directory**. + * Default GOPATH is usually set to `$HOME/go` but you can override that permanently or temporarily. + * For instance, you could place all your modules under `/home/john.doe/project_name/tf-modules/src/` + * Then you would use `export GOPATH=/home/john.doe/project_name/tf-modules/` + * Or you could simply place all your modules under `$HOME/go/src/` +* Go to the `tests/` dir and run `dep ensure` to resolve all dependencies. + * This should create a `vendor/` dir under `tests/` dir and also a `pkg/` dir under the GOPATH dir. +* Now you can run `go test` + + +### Tests Result: Passing +``` +TestAwsWaf 2019-10-31T18:46:44Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_ssi: Still destroying... [id=5768e332-3203-41f2-9985-01c04c2f1c84, 10s elapsed] +TestAwsWaf 2019-10-31T18:46:44Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_auth_tokens: Still destroying... [id=64932756-fd35-4858-b347-287a0d3e6b37, 30s elapsed] +TestAwsWaf 2019-10-31T18:46:45Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_rfi_lfi_traversal: Destruction complete after 43s +TestAwsWaf 2019-10-31T18:46:46Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_php_insecure_var_refs: Still destroying... [id=3ade79c3-3eb1-4202-9673-63603aa62d02, 40s elapsed] +TestAwsWaf 2019-10-31T18:46:47Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_php_insecure_uri: Destruction complete after 43s +TestAwsWaf 2019-10-31T18:46:48Z command.go:158: module.waf_global_test.aws_waf_sql_injection_match_set.sql_injection_match_set: Still destroying... [id=827e5d19-c5ae-4a33-b85a-43aa7a993cf6, 40s elapsed] +TestAwsWaf 2019-10-31T18:46:49Z command.go:158: module.waf_global_test.aws_waf_xss_match_set.xss_match_set: Still destroying... [id=12dfaa5e-b175-4625-88a1-769794255b66, 10s elapsed] +TestAwsWaf 2019-10-31T18:46:49Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_php_insecure_var_refs: Destruction complete after 44s +TestAwsWaf 2019-10-31T18:46:50Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_admin_url: Still destroying... [id=5b34eb2c-61b9-414a-9517-d112e27e16e9, 40s elapsed] +TestAwsWaf 2019-10-31T18:46:51Z command.go:158: module.waf_global_test.aws_waf_sql_injection_match_set.sql_injection_match_set: Destruction complete after 44s +TestAwsWaf 2019-10-31T18:46:53Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_admin_url: Destruction complete after 44s +TestAwsWaf 2019-10-31T18:46:54Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_ssi: Still destroying... [id=5768e332-3203-41f2-9985-01c04c2f1c84, 20s elapsed] +TestAwsWaf 2019-10-31T18:46:54Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_auth_tokens: Still destroying... [id=64932756-fd35-4858-b347-287a0d3e6b37, 40s elapsed] +TestAwsWaf 2019-10-31T18:46:57Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_auth_tokens: Destruction complete after 43s +TestAwsWaf 2019-10-31T18:46:59Z command.go:158: module.waf_global_test.aws_waf_xss_match_set.xss_match_set: Still destroying... [id=12dfaa5e-b175-4625-88a1-769794255b66, 20s elapsed] +TestAwsWaf 2019-10-31T18:47:02Z command.go:158: module.waf_global_test.aws_waf_byte_match_set.match_ssi: Destruction complete after 28s +TestAwsWaf 2019-10-31T18:47:04Z command.go:158: module.waf_global_test.aws_waf_xss_match_set.xss_match_set: Destruction complete after 26s +TestAwsWaf 2019-10-31T18:47:04Z command.go:158: +TestAwsWaf 2019-10-31T18:47:04Z command.go:158: Destroy complete! Resources: 24 destroyed. +PASS +ok project/tests 271.951s +sudo chown -R delivery:delivery . +``` + + +## Code Linting & Static Code Analysis + +* The `terraform fmt` command is used to rewrite Terraform configuration files to a canonical format and style. + This command applies a subset of the Terraform language style conventions, along with other minor adjustments for + readability. (https://www.terraform.io/docs/commands/fmt.html) +* TFLint is a Terraform linter focused on possible errors, best practices, etc. (https://github.com/wata727/tflint) + +``` +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy x [15:32:08] +$ make +Available Commands: + ... + - format The terraform fmt is used to rewrite tf conf files to a canonical format and style. + - lint TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. + ... + +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy o [15:31:47] +$ make format +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget:"/go/src/project/":rw --entrypoint=/usr/local/go/bin/terraform -it binbash/terraform-resources:0.11.14 fmt "/go/src/project/" +/go/src/project/outputs.tf +/go/src/project/sns.tf +/go/src/project/tests/fixture/main.tf +/go/src/project/tests/fixture/outputs.tf +/go/src/project/tests/fixture/variables.tf + + + +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy x [15:34:45] +$ make lint +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget:/data -t wata727/tflint --deep +Awesome! Your code is following the best practices :) +``` \ No newline at end of file diff --git a/modules/waf-global/tests/fixture/.gitkeep b/modules/waf-global/tests/fixture/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/modules/waf-global/tests/fixture/locals.tf b/modules/waf-global/tests/fixture/locals.tf new file mode 100644 index 0000000..0e8c6dd --- /dev/null +++ b/modules/waf-global/tests/fixture/locals.tf @@ -0,0 +1,7 @@ +locals { + tags = { + Name = "infra-waf-global-test" + Terraform = "true" + Environment = "${var.environment}" + } +} diff --git a/modules/waf-global/tests/fixture/main.tf b/modules/waf-global/tests/fixture/main.tf new file mode 100644 index 0000000..86337b9 --- /dev/null +++ b/modules/waf-global/tests/fixture/main.tf @@ -0,0 +1,56 @@ +# +# Define some input just to show how variables can be passed from the test. +# +#variable "countries" { +# description = "Countries" +# default = "AR,BR,CH" +#} + +# +# Instantiate the module. +# +#module "backend" { +# source = "../../" +# +# countries = "${var.countries}" +#} + +# +# Output the module's output for verification. +# +#output "countries" { +# value = "${module.sample.countries}" +#} + +#=================# +# WAF GLOBAL # +#=================# +module "waf_global_test" { + source = "../../" + + # Just a prefix to add some level of organization + waf_prefix = "${var.project}-${var.environment}" + + # List of IPs that are blacklisted + blacklisted_ips = var.blacklisted_ips + + # List of IPs that are allowed to access admin pages + admin_remote_ipset = var.admin_remote_ipset + + # Pass the list of CloudFront distribution ARNs that the WAF ACL will be connected to + cloudfront_arn = var.cloudfront_arn + + # By default seted to COUNT for testing in order to avoid service affection; when ready, set it to BLOCK + rule_size_restriction_action_type = "COUNT" + rule_sqli_action = "COUNT" + rule_xss_action = "COUNT" + rule_lfi_rfi_action = "COUNT" + rule_ssi_action_type = "COUNT" + rule_auth_tokens_action = "COUNT" + rule_admin_access_action_type = "COUNT" + rule_php_insecurities_action_type = "COUNT" + rule_csrf_action_type = "COUNT" + rule_blacklisted_ips_action_type = "COUNT" + + tags = "${local.tags}" +} \ No newline at end of file diff --git a/modules/waf-global/tests/fixture/outputs.tf b/modules/waf-global/tests/fixture/outputs.tf new file mode 100644 index 0000000..2b91d72 --- /dev/null +++ b/modules/waf-global/tests/fixture/outputs.tf @@ -0,0 +1,14 @@ +# +# WAF +# +output "web_acl_id" { + value = module.waf_global_test.web_acl_id +} + +output "web_acl_name" { + value = module.waf_global_test.web_acl_name +} + +output "web_acl_metric_name" { + value = module.waf_global_test.web_acl_metric_name +} \ No newline at end of file diff --git a/modules/waf-global/tests/fixture/variables.tf b/modules/waf-global/tests/fixture/variables.tf new file mode 100644 index 0000000..39ebb5f --- /dev/null +++ b/modules/waf-global/tests/fixture/variables.tf @@ -0,0 +1,58 @@ +#=============================# +# AWS Provider Settings # +#=============================# +provider "aws" { + region = "${var.region}" + profile = "${var.profile}" +} + +variable "region" { + description = "AWS Region" + default = "us-east-1" +} + +variable "profile" { + description = "AWS Profile" + default = "bb-dev-deploymaster" +} + +#=============================# +# Backend Config (partial) # +#=============================# +terraform { + required_version = ">= 0.12.12" +} + +#==============================# +# Project Variables # +#==============================# +variable "project" { + description = "Project id" + default = "bb" +} + +variable "environment" { + description = "Environment Name" + default = "dev-test" +} + +#==============================# +# WAF # +#==============================# +variable "blacklisted_ips" { + default = [] + type = list(string) + description = "List of IPs to blacklist" +} + +variable "admin_remote_ipset" { + default = [] + type = list(string) + description = "List of IPs allowed to access admin pages" +} + +variable "cloudfront_arn" { + default = [] + type = list(string) + description = "List of CloudFront Distributions ARNs" +} diff --git a/modules/waf-global/tests/verify_output_test.go b/modules/waf-global/tests/verify_output_test.go new file mode 100644 index 0000000..98bd72c --- /dev/null +++ b/modules/waf-global/tests/verify_output_test.go @@ -0,0 +1,35 @@ +package tests + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestAwsWaf(t *testing.T) { + expectedValue1 := "bb-dev-test-generic-owasp-acl" + expectedValue2 := "bbdevtestgenericowaspacl" + + terraformOptions := &terraform.Options { + // The path to where our Terraform code is located + TerraformDir: "fixture/", + + // Disable colors in Terraform commands so its easier to parse stdout/stderr + NoColor: true, + } + + // At the end of the test, run `terraform destroy` to clean up any resources that were created + defer terraform.Destroy(t, terraformOptions) + + // This will run `terraform init` and `terraform apply` and fail the test if there are any errors + terraform.InitAndApply(t, terraformOptions) + + // Run `terraform output` to get the values of output variables + actualOutput1 := terraform.Output(t, terraformOptions, "web_acl_name") + actualOutput2 := terraform.Output(t, terraformOptions, "web_acl_metric_name") + + // Verify we're getting back the outputs we expect + assert.Equal(t, expectedValue1, actualOutput1) + assert.Equal(t, expectedValue2, actualOutput2) +} diff --git a/modules/waf-global/variables.tf b/modules/waf-global/variables.tf index 7e041ab..3094a57 100644 --- a/modules/waf-global/variables.tf +++ b/modules/waf-global/variables.tf @@ -3,17 +3,20 @@ variable "waf_prefix" { } variable "blacklisted_ips" { - type = "list" - description = "List of IPs to blacklist" + default = [] + type = list(string) + description = "List of IPs to blacklist, eg ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32']" } variable "admin_remote_ipset" { - type = "list" - description = "List of IPs allowed to access admin pages" + default = [] + type = list(string) + description = "List of IPs allowed to access admin pages, ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32']" } variable "cloudfront_arn" { - type = "list" + default = [] + type = list(string) description = "List of CloudFront Distributions ARNs" } @@ -66,3 +69,10 @@ variable "rule_blacklisted_ips_action_type" { default = "COUNT" description = "Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing)" } + +variable "tags" { + type = "map" + description = "A mapping of tags to assign to all resources" + default = {} +} + diff --git a/modules/waf-global/versions.tf b/modules/waf-global/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/modules/waf-global/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/modules/waf-global/waf_ruleset10_blacklisted_ips.tf b/modules/waf-global/waf_ruleset10_blacklisted_ips.tf index 73a6fd2..06555cb 100644 --- a/modules/waf-global/waf_ruleset10_blacklisted_ips.tf +++ b/modules/waf-global/waf_ruleset10_blacklisted_ips.tf @@ -5,16 +5,42 @@ resource "aws_waf_rule" "detect_blacklisted_ips" { name = "${var.waf_prefix}-generic-detect-blacklisted-ips" - metric_name = "${var.waf_prefix}genericdetectblacklistedips" + metric_name = replace("${var.waf_prefix}genericdetectblacklistedips", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_ipset.blacklisted_ips.id}" + data_id = aws_waf_ipset.blacklisted_ips.id negated = false type = "IPMatch" } } resource "aws_waf_ipset" "blacklisted_ips" { - name = "${var.waf_prefix}-generic-match-blacklisted-ips" - ip_set_descriptors = "${var.blacklisted_ips}" -} + name = "${var.waf_prefix}-generic-match-blacklisted-ips" + dynamic "ip_set_descriptors" { + for_each = var.blacklisted_ips + content { + //resource "aws_waf_ipset" "test" { + // name = "test" + // + // dynamic "ip_set_descriptors" { + // # The for_each argument is a hardcoded list in this illustrative example, + // # however it can be sourced from a variable or local value as well as + // # support multiple argument values as a map. + // for_each = ["1.1.1.1/32", "2.2.2.2/32", "3.3.3.3/32"] + // + // content { + // type = "IPV4" + // value = ip_set_descriptors.value + // } + // } + //} + // + //output "test" { + // value = aws_waf_ipset.test.ip_set_descriptors[*].value + //} + + type = "IPV4" + value = ip_set_descriptors.value + } + } +} \ No newline at end of file diff --git a/modules/waf-global/waf_ruleset1_sqli.tf b/modules/waf-global/waf_ruleset1_sqli.tf index d82b6e2..348c455 100644 --- a/modules/waf-global/waf_ruleset1_sqli.tf +++ b/modules/waf-global/waf_ruleset1_sqli.tf @@ -5,10 +5,10 @@ resource "aws_waf_rule" "mitigate_sqli" { name = "${var.waf_prefix}-generic-mitigate-sqli" - metric_name = "${var.waf_prefix}genericmitigatesqli" + metric_name = replace("${var.waf_prefix}genericmitigatesqli", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_sql_injection_match_set.sql_injection_match_set.id}" + data_id = aws_waf_sql_injection_match_set.sql_injection_match_set.id negated = false type = "SqlInjectionMatch" } @@ -101,3 +101,4 @@ resource "aws_waf_sql_injection_match_set" "sql_injection_match_set" { } } } + diff --git a/modules/waf-global/waf_ruleset2_auth_tokens.tf b/modules/waf-global/waf_ruleset2_auth_tokens.tf index d9d803c..be453bc 100644 --- a/modules/waf-global/waf_ruleset2_auth_tokens.tf +++ b/modules/waf-global/waf_ruleset2_auth_tokens.tf @@ -6,10 +6,10 @@ resource "aws_waf_rule" "detect_bad_auth_tokens" { name = "${var.waf_prefix}-generic-detect-bad-auth-tokens" - metric_name = "${var.waf_prefix}genericdetectbadauthtokens" + metric_name = replace("${var.waf_prefix}genericdetectbadauthtokens", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_byte_match_set.match_auth_tokens.id}" + data_id = aws_waf_byte_match_set.match_auth_tokens.id negated = false type = "ByteMatch" } @@ -40,3 +40,4 @@ resource "aws_waf_byte_match_set" "match_auth_tokens" { } } } + diff --git a/modules/waf-global/waf_ruleset3_xss.tf b/modules/waf-global/waf_ruleset3_xss.tf index 0a099ff..211c628 100644 --- a/modules/waf-global/waf_ruleset3_xss.tf +++ b/modules/waf-global/waf_ruleset3_xss.tf @@ -5,10 +5,10 @@ resource "aws_waf_rule" "mitigate_xss" { name = "${var.waf_prefix}-generic-mitigate-xss" - metric_name = "${var.waf_prefix}genericmitigatexss" + metric_name = replace("${var.waf_prefix}genericmitigatexss", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_xss_match_set.xss_match_set.id}" + data_id = aws_waf_xss_match_set.xss_match_set.id negated = false type = "XssMatch" } @@ -83,3 +83,4 @@ resource "aws_waf_xss_match_set" "xss_match_set" { } } } + diff --git a/modules/waf-global/waf_ruleset4_lfi_rfi.tf b/modules/waf-global/waf_ruleset4_lfi_rfi.tf index 248dfd7..ad4196b 100644 --- a/modules/waf-global/waf_ruleset4_lfi_rfi.tf +++ b/modules/waf-global/waf_ruleset4_lfi_rfi.tf @@ -6,10 +6,10 @@ resource "aws_waf_rule" "detect_rfi_lfi_traversal" { name = "${var.waf_prefix}-generic-detect-rfi-lfi-traversal" - metric_name = "${var.waf_prefix}genericdetectrfilfitraversal" + metric_name = replace("${var.waf_prefix}genericdetectrfilfitraversal", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_byte_match_set.match_rfi_lfi_traversal.id}" + data_id = aws_waf_byte_match_set.match_rfi_lfi_traversal.id negated = false type = "ByteMatch" } @@ -98,3 +98,4 @@ resource "aws_waf_byte_match_set" "match_rfi_lfi_traversal" { } } } + diff --git a/modules/waf-global/waf_ruleset5_admin_access.tf b/modules/waf-global/waf_ruleset5_admin_access.tf index 377dbdc..5ecc18b 100644 --- a/modules/waf-global/waf_ruleset5_admin_access.tf +++ b/modules/waf-global/waf_ruleset5_admin_access.tf @@ -6,24 +6,50 @@ resource "aws_waf_rule" "detect_admin_access" { name = "${var.waf_prefix}-generic-detect-admin-access" - metric_name = "${var.waf_prefix}genericdetectadminaccess" + metric_name = replace("${var.waf_prefix}genericdetectadminaccess", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_ipset.admin_remote_ipset.id}" + data_id = aws_waf_ipset.admin_remote_ipset.id negated = true type = "IPMatch" } predicates { - data_id = "${aws_waf_byte_match_set.match_admin_url.id}" + data_id = aws_waf_byte_match_set.match_admin_url.id negated = false type = "ByteMatch" } } resource "aws_waf_ipset" "admin_remote_ipset" { - name = "${var.waf_prefix}-generic-match-admin-remote-ip" - ip_set_descriptors = "${var.admin_remote_ipset}" + name = "${var.waf_prefix}-generic-match-admin-remote-ip" + dynamic "ip_set_descriptors" { + for_each = var.admin_remote_ipset + content { + //resource "aws_waf_ipset" "test" { + // name = "test" + // + // dynamic "ip_set_descriptors" { + // # The for_each argument is a hardcoded list in this illustrative example, + // # however it can be sourced from a variable or local value as well as + // # support multiple argument values as a map. + // for_each = ["1.1.1.1/32", "2.2.2.2/32", "3.3.3.3/32"] + // + // content { + // type = "IPV4" + // value = ip_set_descriptors.value + // } + // } + //} + // + //output "test" { + // value = aws_waf_ipset.test.ip_set_descriptors[*].value + //} + + type = "IPV4" + value = ip_set_descriptors.value + } + } } resource "aws_waf_byte_match_set" "match_admin_url" { @@ -39,3 +65,4 @@ resource "aws_waf_byte_match_set" "match_admin_url" { } } } + diff --git a/modules/waf-global/waf_ruleset6_php_insecurities.tf b/modules/waf-global/waf_ruleset6_php_insecurities.tf index 3d77f2b..f0712fd 100644 --- a/modules/waf-global/waf_ruleset6_php_insecurities.tf +++ b/modules/waf-global/waf_ruleset6_php_insecurities.tf @@ -5,16 +5,16 @@ resource "aws_waf_rule" "detect_php_insecure" { name = "${var.waf_prefix}-generic-detect-php-insecure" - metric_name = "${var.waf_prefix}genericdetectphpinsecure" + metric_name = replace("${var.waf_prefix}genericdetectphpinsecure", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_byte_match_set.match_php_insecure_uri.id}" + data_id = aws_waf_byte_match_set.match_php_insecure_uri.id negated = false type = "ByteMatch" } predicates { - data_id = "${aws_waf_byte_match_set.match_php_insecure_var_refs.id}" + data_id = aws_waf_byte_match_set.match_php_insecure_var_refs.id negated = false type = "ByteMatch" } @@ -127,3 +127,4 @@ resource "aws_waf_byte_match_set" "match_php_insecure_var_refs" { } } } + diff --git a/modules/waf-global/waf_ruleset7_size_restriction.tf b/modules/waf-global/waf_ruleset7_size_restriction.tf index 4918b58..faea6ba 100644 --- a/modules/waf-global/waf_ruleset7_size_restriction.tf +++ b/modules/waf-global/waf_ruleset7_size_restriction.tf @@ -5,10 +5,10 @@ resource "aws_waf_rule" "restrict_sizes" { name = "${var.waf_prefix}-generic-restrict-sizes" - metric_name = "${var.waf_prefix}genericrestrictsizes" + metric_name = replace("${var.waf_prefix}genericrestrictsizes", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_size_constraint_set.size_restrictions.id}" + data_id = aws_waf_size_constraint_set.size_restrictions.id negated = false type = "SizeConstraint" } @@ -59,3 +59,4 @@ resource "aws_waf_size_constraint_set" "size_restrictions" { } } } + diff --git a/modules/waf-global/waf_ruleset8_csrf.tf b/modules/waf-global/waf_ruleset8_csrf.tf index 985c16b..df3722e 100644 --- a/modules/waf-global/waf_ruleset8_csrf.tf +++ b/modules/waf-global/waf_ruleset8_csrf.tf @@ -5,16 +5,16 @@ resource "aws_waf_rule" "enforce_csrf" { name = "${var.waf_prefix}-generic-enforce-csrf" - metric_name = "${var.waf_prefix}genericenforcecsrf" + metric_name = replace("${var.waf_prefix}genericenforcecsrf", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_byte_match_set.match_csrf_method.id}" + data_id = aws_waf_byte_match_set.match_csrf_method.id negated = false type = "ByteMatch" } predicates { - data_id = "${aws_waf_size_constraint_set.csrf_token_set.id}" + data_id = aws_waf_size_constraint_set.csrf_token_set.id negated = true type = "SizeConstraint" } @@ -48,3 +48,4 @@ resource "aws_waf_size_constraint_set" "csrf_token_set" { } } } + diff --git a/modules/waf-global/waf_ruleset9_ssi.tf b/modules/waf-global/waf_ruleset9_ssi.tf index 39c09b4..f16ef92 100644 --- a/modules/waf-global/waf_ruleset9_ssi.tf +++ b/modules/waf-global/waf_ruleset9_ssi.tf @@ -5,10 +5,10 @@ resource "aws_waf_rule" "detect_ssi" { name = "${var.waf_prefix}-generic-detect-ssi" - metric_name = "${var.waf_prefix}genericdetectssi" + metric_name = replace("${var.waf_prefix}genericdetectssi", "/[^0-9A-Za-z]/", "") predicates { - data_id = "${aws_waf_byte_match_set.match_ssi.id}" + data_id = aws_waf_byte_match_set.match_ssi.id negated = false type = "ByteMatch" } @@ -97,3 +97,4 @@ resource "aws_waf_byte_match_set" "match_ssi" { } } } + diff --git a/modules/waf-regional/Makefile b/modules/waf-regional/Makefile index a7729d2..f0f57a4 100644 --- a/modules/waf-regional/Makefile +++ b/modules/waf-regional/Makefile @@ -6,7 +6,7 @@ LOCAL_OS_GIT_CONF_DIR := ~/.gitconfig LOCAL_OS_AWS_CONF_DIR := ~/.aws TF_PWD_DIR := $(shell pwd) -TF_VER := 0.11.14 +TF_VER := 0.12.12 TF_PWD_CONT_DIR := "/go/src/project/" TF_DOCKER_ENTRYPOINT := /usr/local/go/bin/terraform TF_DOCKER_IMAGE := binbash/terraform-resources @@ -46,22 +46,6 @@ docker run --rm \ -it ${TF_DOCKER_IMAGE}:${TF_VER} endef -# -# GIT-RELEASE -# -# pre-req -> https://github.com/pnikosis/semtag -define GIT_SEMTAG_CMD_PREFIX -docker run --rm \ --v ${TF_PWD_DIR}:/data:rw \ --v ${LOCAL_OS_SSH_DIR}:/root/.ssh \ --v ${LOCAL_OS_GIT_CONF_DIR}:/etc/gitconfig \ ---entrypoint=/opt/semtag/semtag/semtag \ --it binbash/git-release -endef - -GIT_SEMTAG_VER_PATCH := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s patch -o) -GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s minor -o) -GIT_SEMTAG_VER_MINOR := $(shell ${GIT_SEMTAG_CMD_PREFIX} final -s major -o) help: @echo 'Available Commands:' @@ -79,12 +63,14 @@ version: ## Show terraform version format: ## The terraform fmt is used to rewrite tf conf files to a canonical format and style. ${TF_CMD_PREFIX} fmt ${TF_PWD_CONT_DIR} -doc: ## A utility to generate documentation from Terraform modules in various output formats. +doc-tf-eleven: ## A utility to generate documentation from Terraform 0.11 modules in various output formats. docker run --rm -v ${TF_PWD_DIR}:/data -t binbash/terraform-docs markdown table /data -lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. - docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint --deep +doc-tf-twelve: ## A utility to generate documentation from Terraform 0.12 modules in various output formats. + bash terraform-docs.sh markdown ${TF_PWD_DIR} +lint: ## TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan (tf0.11 --> < 0.9.2. + docker run --rm -v ${TF_PWD_DIR}:/data -t wata727/tflint:0.9.2 --deep #==============================================================# # TERRATEST # @@ -98,52 +84,4 @@ terratest-dep-init: ## dep is a dependency management tool for Go. (https://gith terratest-go-test: ## lint: TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. ${TERRATEST_GO_CMD_PREFIX} test - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} . - -#==============================================================# -# GIT RELEASE # -#==============================================================# -release-patch: ## releasing patch (eg: 0.0.1 -> 0.0.2) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s patch - -release-minor: ## releasing minor (eg: 0.0.2 -> 0.1.0) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s minor - -release-major: ## releasing major (eg: 0.1.0 -> 1.0.0) based on semantic tagging script for Git - # pre-req -> https://github.com/pnikosis/semtag - ${GIT_SEMTAG_CMD_PREFIX} get - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.git - ${GIT_SEMTAG_CMD_PREFIX} final -s major - -changelog-init: ## git-chglog (https://github.com/git-chglog/git-chglog) config initialization -> ./.chglog - @if [ ! -d ./.chglog ]; then\ - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release --init;\ - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog;\ - else\ - echo "==============================";\ - echo "git-chglog already initialized";\ - echo "==============================";\ - echo "$$(ls ./.chglog)";\ - echo "==============================";\ - fi - -changelog-patch: ## git-chglog generation for path release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_PATCH} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md - -changelog-minor: ## git-chglog generation for minor release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_MINOR} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md - -changelog-major: ## git-chglog generation for major release - docker run --rm -v ${TF_PWD_DIR}:/data -it binbash/git-release -o CHANGELOG.md --next-tag ${GIT_SEMTAG_VER_MAJOR} - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./.chglog - sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} ./CHANGELOG.md \ No newline at end of file + sudo chown -R ${LOCAL_OS_USER}:${LOCAL_OS_USER} . \ No newline at end of file diff --git a/modules/waf-regional/README.md b/modules/waf-regional/README.md index b254b02..f214311 100644 --- a/modules/waf-regional/README.md +++ b/modules/waf-regional/README.md @@ -43,27 +43,28 @@ References | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| -| admin\_remote\_ipset | List of IPs allowed to access admin pages | list | n/a | yes | -| alb\_arn | List of ALB ARNs | list | n/a | yes | -| blacklisted\_ips | List of IPs to blacklist | list | n/a | yes | -| rule\_admin\_access\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_auth\_tokens\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_blacklisted\_ips\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_csrf\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_lfi\_rfi\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_php\_insecurities\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_size\_restriction\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_size\_restriction\_action\_type\_enable | Enable rule\_size\_restriction\_action\_type if set to true, otherwise don't use attach this rule to the waf web acl | string | `"false"` | no | -| rule\_sqli\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_ssi\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | -| rule\_xss\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"BLOCK"` | no | +| admin\_remote\_ipset | List of IPs allowed to access admin pages, ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32'] | list(string) | `` | no | +| alb\_arn | List of ALB ARNs | list(string) | `` | no | +| blacklisted\_ips | List of IPs to blacklist, eg ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32'] | list(string) | `` | no | +| rule\_admin\_access\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_auth\_tokens\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_blacklisted\_ips\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_csrf\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_lfi\_rfi\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_php\_insecurities\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_size\_restriction\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_sqli\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_ssi\_action\_type | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | +| rule\_xss\_action | Rule action type. Either BLOCK, ALLOW, or COUNT (useful for testing) | string | `"COUNT"` | no | | waf\_prefix | Prefix to use when naming resources | string | n/a | yes | ## Outputs | Name | Description | |------|-------------| -| web\_acl\_id | | +| web\_acl\_id | AWS WAF web acl id. | +| web\_acl\_metric\_name | The name or description for the Amazon CloudWatch metric of this web ACL. | +| web\_acl\_name | The name or description of the web ACL. | ## Examples ### waf-regional diff --git a/modules/waf-regional/main.tf b/modules/waf-regional/main.tf index 81f99a5..4f06457 100644 --- a/modules/waf-regional/main.tf +++ b/modules/waf-regional/main.tf @@ -3,7 +3,7 @@ # resource "aws_wafregional_web_acl" "wafregional_acl" { name = "${var.waf_prefix}-generic-owasp-acl" - metric_name = "${var.waf_prefix}genericowaspacl" + metric_name = replace("${var.waf_prefix}genericowaspacl", "/[^0-9A-Za-z]/", "") default_action { type = "ALLOW" @@ -15,11 +15,11 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # rule { action { - type = "${var.rule_size_restriction_action_type}" + type = var.rule_size_restriction_action_type } priority = 10 - rule_id = "${aws_wafregional_rule.restrict_sizes.id}" + rule_id = aws_wafregional_rule.restrict_sizes.id type = "REGULAR" } @@ -29,54 +29,53 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # rule { action { - type = "${var.rule_blacklisted_ips_action_type}" + type = var.rule_blacklisted_ips_action_type } - + priority = 20 - rule_id = "${aws_wafregional_rule.detect_blacklisted_ips.id}" + rule_id = aws_wafregional_rule.detect_blacklisted_ips.id type = "REGULAR" } - # # Reason: the apps do not use auth tokens yet. # So COMMENT rule block below to deactivate this rule # rule { action { - type = "${var.rule_auth_tokens_action}" + type = var.rule_auth_tokens_action } - + priority = 30 - rule_id = "${aws_wafregional_rule.detect_bad_auth_tokens.id}" + rule_id = aws_wafregional_rule.detect_bad_auth_tokens.id type = "REGULAR" } rule { action { - type = "${var.rule_sqli_action}" + type = var.rule_sqli_action } priority = 40 - rule_id = "${aws_wafregional_rule.mitigate_sqli.id}" + rule_id = aws_wafregional_rule.mitigate_sqli.id type = "REGULAR" } rule { action { - type = "${var.rule_xss_action}" + type = var.rule_xss_action } priority = 50 - rule_id = "${aws_wafregional_rule.mitigate_xss.id}" + rule_id = aws_wafregional_rule.mitigate_xss.id type = "REGULAR" } rule { action { - type = "${var.rule_lfi_rfi_action}" + type = var.rule_lfi_rfi_action } priority = 60 - rule_id = "${aws_wafregional_rule.detect_rfi_lfi_traversal.id}" + rule_id = aws_wafregional_rule.detect_rfi_lfi_traversal.id type = "REGULAR" } @@ -86,26 +85,25 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # rule { action { - type = "${var.rule_php_insecurities_action_type}" + type = var.rule_php_insecurities_action_type } - + priority = 70 - rule_id = "${aws_wafregional_rule.detect_php_insecure.id}" + rule_id = aws_wafregional_rule.detect_php_insecure.id type = "REGULAR" } - # # Reason: the apps do not use CSRF tokens. # So COMMENT rule block below to deactivate this rule # rule { action { - type = "${var.rule_csrf_action_type}" + type = var.rule_csrf_action_type } - + priority = 80 - rule_id = "${aws_wafregional_rule.enforce_csrf.id}" + rule_id = aws_wafregional_rule.enforce_csrf.id type = "REGULAR" } @@ -114,11 +112,11 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # rule { action { - type = "${var.rule_ssi_action_type}" + type = var.rule_ssi_action_type } priority = 90 - rule_id = "${aws_wafregional_rule.detect_ssi.id}" + rule_id = aws_wafregional_rule.detect_ssi.id type = "REGULAR" } @@ -128,11 +126,11 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # rule { action { - type = "${var.rule_admin_access_action_type}" + type = var.rule_admin_access_action_type } - + priority = 100 - rule_id = "${aws_wafregional_rule.detect_admin_access.id}" + rule_id = aws_wafregional_rule.detect_admin_access.id type = "REGULAR" } } @@ -141,8 +139,9 @@ resource "aws_wafregional_web_acl" "wafregional_acl" { # This is how we link the WAF ACL to one or many ALBs. # resource "aws_wafregional_web_acl_association" "acl_alb_association" { - depends_on = ["aws_wafregional_web_acl.wafregional_acl"] - count = "${length(var.alb_arn)}" - resource_arn = "${element(var.alb_arn, count.index)}" - web_acl_id = "${aws_wafregional_web_acl.wafregional_acl.id}" + depends_on = [aws_wafregional_web_acl.wafregional_acl] + count = length(var.alb_arn) + resource_arn = element(var.alb_arn, count.index) + web_acl_id = aws_wafregional_web_acl.wafregional_acl.id } + diff --git a/modules/waf-regional/outputs.tf b/modules/waf-regional/outputs.tf index be62934..d4c550c 100644 --- a/modules/waf-regional/outputs.tf +++ b/modules/waf-regional/outputs.tf @@ -1,3 +1,14 @@ output "web_acl_id" { - value = "${aws_wafregional_web_acl.wafregional_acl.id}" + description = "AWS WAF web acl id." + value = aws_wafregional_web_acl.wafregional_acl.id } + +output "web_acl_name" { + description = "The name or description of the web ACL." + value = aws_wafregional_web_acl.wafregional_acl.name +} + +output "web_acl_metric_name" { + description = "The name or description for the Amazon CloudWatch metric of this web ACL." + value = aws_wafregional_web_acl.wafregional_acl.metric_name +} \ No newline at end of file diff --git a/modules/waf-regional/terraform-docs.awk b/modules/waf-regional/terraform-docs.awk new file mode 100644 index 0000000..bd6b2b7 --- /dev/null +++ b/modules/waf-regional/terraform-docs.awk @@ -0,0 +1,90 @@ +# This script converts Terraform 0.12 variables/outputs to something suitable for `terraform-docs` +# As of terraform-docs v0.6.0, HCL2 is not supported. This script is a *dirty hack* to get around it. +# https://github.com/segmentio/terraform-docs/ +# https://github.com/segmentio/terraform-docs/issues/62 + +{ + if ( $0 ~ /\{/ ) { + braceCnt++ + } + + if ( $0 ~ /\}/ ) { + braceCnt-- + } + + # [START] variable or output block started + if ($0 ~ /^[[:space:]]*(variable|output)[[:space:]][[:space:]]*"(.*?)"/) { + # Normalize the braceCnt (should be 1 now) + braceCnt = 1 + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + blockCnt++ + print $0 + } + + # [START] multiline default statement started + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*(default)[[:space:]][[:space:]]*=/) { + if ($3 ~ "null") { + print " default = \"null\"" + } else { + print $0 + blockDefCnt++ + blockDefStart=1 + } + } + } + + # [PRINT] single line "description" + if (blockCnt > 0) { + if (blockDefCnt == 0) { + if ($0 ~ /^[[:space:]][[:space:]]*description[[:space:]][[:space:]]*=/) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + print $0 + } + } + } + + # [PRINT] single line "type" + if (blockCnt > 0) { + if ($0 ~ /^[[:space:]][[:space:]]*type[[:space:]][[:space:]]*=/ ) { + # [CLOSE] "default" block + if (blockDefCnt > 0) { + blockDefCnt = 0 + } + type=$3 + if (type ~ "object") { + print " type = \"object\"" + } else { + # legacy quoted types: "string", "list", and "map" + if ($3 ~ /^[[:space:]]*"(.*?)"[[:space:]]*$/) { + print " type = " $3 + } else { + print " type = \"" $3 "\"" + } + } + } + } + + # [CLOSE] variable/output block + if (blockCnt > 0) { + if (braceCnt == 0 && blockCnt > 0) { + blockCnt-- + print $0 + } + } + + # [PRINT] Multiline "default" statement + if (blockCnt > 0 && blockDefCnt > 0) { + if (blockDefStart == 1) { + blockDefStart = 0 + } else { + print $0 + } + } +} diff --git a/modules/waf-regional/terraform-docs.sh b/modules/waf-regional/terraform-docs.sh new file mode 100755 index 0000000..603576e --- /dev/null +++ b/modules/waf-regional/terraform-docs.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +PWD=$(pwd) + +which awk 2>&1 >/dev/null || ( echo "awk not available"; exit 1) +which terraform 2>&1 >/dev/null || ( echo "terraform not available"; exit 1) +which terraform-docs 2>&1 >/dev/null || ( echo "terraform-docs not available"; exit 1) + +if [[ "`terraform version | head -1`" =~ 0\.12 ]]; then + TMP_FILE="$(mktemp /tmp/terraform-docs-XXXXXXXXXX)" + awk -f ${PWD}/terraform-docs.awk $2/*.tf > ${TMP_FILE} + terraform-docs $1 ${TMP_FILE} + rm -f ${TMP_FILE} +else + terraform-docs $1 $2 +fi diff --git a/modules/waf-regional/tests/Gopkg.lock b/modules/waf-regional/tests/Gopkg.lock new file mode 100644 index 0000000..ec43917 --- /dev/null +++ b/modules/waf-regional/tests/Gopkg.lock @@ -0,0 +1,97 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:ffe9824d294da03b391f44e1ae8281281b4afc1bdaa9588c9097785e3af10cec" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "UT" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" + +[[projects]] + digest = "1:f44d09e27cefd53e739b4469bc4e797164b86b9f4bece82dae2e1b17d52ec53d" + name = "github.com/gruntwork-io/terratest" + packages = [ + "modules/collections", + "modules/customerrors", + "modules/files", + "modules/logger", + "modules/retry", + "modules/shell", + "modules/ssh", + "modules/terraform", + ] + pruneopts = "UT" + revision = "9f4f4f69d6521e2cb71c872297e8e04a890a5ec7" + version = "v0.22.2" + +[[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "UT" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + digest = "1:99d32780e5238c2621fff621123997c3e3cca96db8be13179013aea77dfab551" + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require", + ] + pruneopts = "UT" + revision = "221dbe5ed46703ee255b1da0dec05086f5035f62" + version = "v1.4.0" + +[[projects]] + branch = "master" + digest = "1:4b049aae7a44fb6fb4d8e84355b145e591f686111817c0998fe31a9f8bf226dc" + name = "golang.org/x/crypto" + packages = [ + "curve25519", + "ed25519", + "ed25519/internal/edwards25519", + "internal/chacha20", + "internal/subtle", + "poly1305", + "ssh", + "ssh/agent", + ] + pruneopts = "UT" + revision = "8986dd9e96cf0a6f74da406c005ba3df38527c04" + +[[projects]] + branch = "master" + digest = "1:76ee51c3f468493aff39dbacc401e8831fbb765104cbf613b89bef01cf4bad70" + name = "golang.org/x/net" + packages = ["context"] + pruneopts = "UT" + revision = "fe3aa8a4527195a6057b3fad46619d7d090e99b5" + +[[projects]] + branch = "master" + digest = "1:95abab3e07422980e85e253e49eb75c05c1dee6e84b50cb0d3353747ef8099eb" + name = "golang.org/x/sys" + packages = ["cpu"] + pruneopts = "UT" + revision = "f43be2a4598cf3a47be9f94f0c28197ed9eae611" + +[[projects]] + digest = "1:59f10c1537d2199d9115d946927fe31165959a95190849c82ff11e05803528b0" + name = "gopkg.in/yaml.v2" + packages = ["."] + pruneopts = "UT" + revision = "f221b8435cfb71e54062f6c6e99e9ade30b124d5" + version = "v2.2.4" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/gruntwork-io/terratest/modules/terraform", + "github.com/stretchr/testify/assert", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/modules/waf-regional/tests/Gopkg.toml b/modules/waf-regional/tests/Gopkg.toml new file mode 100644 index 0000000..e0b60dd --- /dev/null +++ b/modules/waf-regional/tests/Gopkg.toml @@ -0,0 +1,38 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/gruntwork-io/terratest" + version = "0.22.2" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.4.0" + +[prune] + go-tests = true + unused-packages = true diff --git a/modules/waf-regional/tests/README.md b/modules/waf-regional/tests/README.md new file mode 100644 index 0000000..7b343a2 --- /dev/null +++ b/modules/waf-regional/tests/README.md @@ -0,0 +1,151 @@ +# Terraform Module Tests: Terratests + +## Overview +Terratest is a Go library that makes it easier to write automated tests for your infrastructure code. +It provides a variety of helper functions and patterns for common infrastructure testing tasks, including: +- Testing Terraform code +- Working with AWS APIs +- And much more + +Official **terratest** [documentation available on GitHub project repo](https://github.com/gruntwork-io/terratest). +Ref Article [available on Medium maintainers blog](https://blog.gruntwork.io/open-sourcing-terratest-a-swiss-army-knife-for-testing-infrastructure-code-5d883336fcd5). + +### Install requirements + +Terratest uses the Go testing framework. To use terratest, you need to install: + +- [Go](https://golang.org/) (requires version >=1.10) +- [dep](https://github.com/golang/dep) (requires version >=0.5.1) + +## Files Organization +* Terraform files are located at the root of this directory. +* Tests can be found under tests/ directory. + +## Testing +### Key Points +* We use `terratest` for testing this module. +* Keep in mind that `terratest` is not a binary but a Go library with helpers that make it easier to work with Terraform and other tools. +* Test files use `_test` suffix. E.g.: `create_file_with_default_values_test.go` +* Test classes use `Test` prefix. E.g.: `func TestCreateFileWithDefaultValues(t *testing.T) {` +* Our tests make use of a fixture/ dir that resembles how the module will be used. + +### Set Up + +#### Dokerized Makefile +``` +$ make +Available Commands: +... + - terratest-dep-init dep is a dependency management tool for Go. (https://github.com/golang/dep) + - terratest-go-test lint: TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. +... +``` + +1. `make terratest-dep-init` +``` +$ make terratest-dep-init +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-ec2-jenkins-vault:"/go/src/project/":rw -v ~/.ssh:/root/.ssh -v ~/.gitconfig:/etc/gitconfig --entrypoint=dep -it binbash/terraform-resources:0.11.14 init + Locking in master (da137c7) for transitive dep golang.org/x/net + Using ^1.3.0 as constraint for direct dep github.com/stretchr/testify + Locking in v1.3.0 (ffdc059) for direct dep github.com/stretchr/testify + Locking in v1.0.0 (792786c) for transitive dep github.com/pmezard/go-difflib + Using ^0.17.5 as constraint for direct dep github.com/gruntwork-io/terratest + Locking in v0.17.5 (03959c9) for direct dep github.com/gruntwork-io/terratest + Locking in v1.1.1 (8991bc2) for transitive dep github.com/davecgh/go-spew + Locking in master (4def268) for transitive dep golang.org/x/crypto + Locking in master (04f50cd) for transitive dep golang.org/x/sys +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-ec2-jenkins-vault:"/go/src/project/":rw -v ~/.ssh:/root/.ssh -v ~/.gitconfig:/etc/gitconfig --entrypoint=dep -it binbash/terraform-resources:0.11.14 ensure +sudo chown -R delivery:delivery . +cp -r ./vendor ./tests/ && rm -rf ./vendor +cp -r ./Gopkg* ./tests/ && rm -rf ./Gopkg* +``` + +2. `terratest-go-test` +``` +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: Destroy complete! Resources: 23 destroyed. +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +... +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: Warning: output "aws_s3_bucket_ssl_certificates_bucket_domain_name": must use splat syntax to access aws_s3_bucket.ssl_certificates_bucket attribute "bucket_domain_name", because it has "count" set; use aws_s3_bucket.ssl_certificates_bucket.*.bucket_domain_name to obtain a list of the attributes across all instances +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +TestInstanceJenkinsVault 2019-07-08T02:42:42Z command.go:158: +PASS +ok project/tests 227.167s +sudo chown -R delivery:delivery . + +``` + +#### Local installed deps execution +* Make sure this module is within the **GOPATH directory**. + * Default GOPATH is usually set to `$HOME/go` but you can override that permanently or temporarily. + * For instance, you could place all your modules under `/home/john.doe/project_name/tf-modules/src/` + * Then you would use `export GOPATH=/home/john.doe/project_name/tf-modules/` + * Or you could simply place all your modules under `$HOME/go/src/` +* Go to the `tests/` dir and run `dep ensure` to resolve all dependencies. + * This should create a `vendor/` dir under `tests/` dir and also a `pkg/` dir under the GOPATH dir. +* Now you can run `go test` + + +### Tests Result: Passing +``` +TestAwsWaf 2019-10-31T19:02:59Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_csrf_method: Still destroying... [id=34acdfb9-a02a-4073-8954-ab7f69e374b9, 30s elapsed] +TestAwsWaf 2019-10-31T19:03:00Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_admin_url: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:01Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_rfi_lfi_traversal: Still destroying... [id=9d212894-799e-4291-a61e-7b1d463e021f, 30s elapsed] +TestAwsWaf 2019-10-31T19:03:01Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_uri: Still destroying... [id=836f53b9-c11d-4d0a-9112-e04ccccf64b7, 20s elapsed] +TestAwsWaf 2019-10-31T19:03:02Z command.go:158: module.waf_regional_test.aws_wafregional_xss_match_set.xss_match_set: Still destroying... [id=307f4208-9669-4f31-8dcd-926fe7e74bf5, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:03Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_auth_tokens: Still destroying... [id=805ef3dc-cdb2-426e-bea0-a5383dafe8b7, 30s elapsed] +TestAwsWaf 2019-10-31T19:03:05Z command.go:158: module.waf_regional_test.aws_wafregional_xss_match_set.xss_match_set: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:05Z command.go:158: module.waf_regional_test.aws_wafregional_size_constraint_set.size_restrictions: Still destroying... [id=0bad6fdc-862d-465c-8fc1-66fc1252a35d, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:06Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_var_refs: Still destroying... [id=699f7f9a-c50b-401c-8dd4-f6682a98a76d, 10s elapsed] +TestAwsWaf 2019-10-31T19:03:07Z command.go:158: module.waf_regional_test.aws_wafregional_size_constraint_set.size_restrictions: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:07Z command.go:158: module.waf_regional_test.aws_wafregional_size_constraint_set.csrf_token_set: Still destroying... [id=23fea397-6c08-4fe1-ae99-15ae0578196b, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:08Z command.go:158: module.waf_regional_test.aws_wafregional_size_constraint_set.csrf_token_set: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:09Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_csrf_method: Still destroying... [id=34acdfb9-a02a-4073-8954-ab7f69e374b9, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:11Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_csrf_method: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:11Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_rfi_lfi_traversal: Still destroying... [id=9d212894-799e-4291-a61e-7b1d463e021f, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:11Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_uri: Still destroying... [id=836f53b9-c11d-4d0a-9112-e04ccccf64b7, 30s elapsed] +TestAwsWaf 2019-10-31T19:03:12Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_rfi_lfi_traversal: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:13Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_auth_tokens: Still destroying... [id=805ef3dc-cdb2-426e-bea0-a5383dafe8b7, 40s elapsed] +TestAwsWaf 2019-10-31T19:03:14Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_auth_tokens: Destruction complete after 42s +TestAwsWaf 2019-10-31T19:03:16Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_var_refs: Still destroying... [id=699f7f9a-c50b-401c-8dd4-f6682a98a76d, 20s elapsed] +TestAwsWaf 2019-10-31T19:03:19Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_uri: Destruction complete after 38s +TestAwsWaf 2019-10-31T19:03:21Z command.go:158: module.waf_regional_test.aws_wafregional_byte_match_set.match_php_insecure_var_refs: Destruction complete after 24s +TestAwsWaf 2019-10-31T19:03:21Z command.go:158: +TestAwsWaf 2019-10-31T19:03:21Z command.go:158: Destroy complete! Resources: 24 destroyed. +PASS +ok project/tests 234.458s +``` + + +## Code Linting & Static Code Analysis + +* The `terraform fmt` command is used to rewrite Terraform configuration files to a canonical format and style. + This command applies a subset of the Terraform language style conventions, along with other minor adjustments for + readability. (https://www.terraform.io/docs/commands/fmt.html) +* TFLint is a Terraform linter focused on possible errors, best practices, etc. (https://github.com/wata727/tflint) + +``` +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy x [15:32:08] +$ make +Available Commands: + ... + - format The terraform fmt is used to rewrite tf conf files to a canonical format and style. + - lint TFLint is a Terraform linter for detecting errors that can not be detected by terraform plan. + ... + +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy o [15:31:47] +$ make format +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget:"/go/src/project/":rw --entrypoint=/usr/local/go/bin/terraform -it binbash/terraform-resources:0.11.14 fmt "/go/src/project/" +/go/src/project/outputs.tf +/go/src/project/sns.tf +/go/src/project/tests/fixture/main.tf +/go/src/project/tests/fixture/outputs.tf +/go/src/project/tests/fixture/variables.tf + + + +# delivery @ delivery-I7567 in ~/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget on git:BBL-121-fix-budget-sns-policy x [15:34:45] +$ make lint +docker run --rm -v /home/delivery/Binbash/repos/BB-Leverage/terraform/terraform-aws-cost-budget:/data -t wata727/tflint --deep +Awesome! Your code is following the best practices :) +``` \ No newline at end of file diff --git a/modules/waf-regional/tests/fixture/.gitkeep b/modules/waf-regional/tests/fixture/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/modules/waf-regional/tests/fixture/main.tf b/modules/waf-regional/tests/fixture/main.tf new file mode 100644 index 0000000..5aa85a6 --- /dev/null +++ b/modules/waf-regional/tests/fixture/main.tf @@ -0,0 +1,54 @@ +# +# Define some input just to show how variables can be passed from the test. +# +#variable "countries" { +# description = "Countries" +# default = "AR,BR,CH" +#} + +# +# Instantiate the module. +# +#module "backend" { +# source = "../../" +# +# countries = "${var.countries}" +#} + +# +# Output the module's output for verification. +# +#output "countries" { +# value = "${module.sample.countries}" +#} + +#=================# +# WAF REGIONAL # +#=================# +module "waf_regional_test" { + source = "../../" + + # Just a prefix to add some level of organization + waf_prefix = "${var.project}-${var.environment}" + + # List of IPs that are blacklisted + blacklisted_ips = var.blacklisted_ips + + # List of IPs that are allowed to access admin pages + admin_remote_ipset = var.admin_remote_ipset + + # Pass the list of ALB ARNs that the WAF ACL will be connected to + alb_arn = var.alb_arn + + # By default seted to COUNT for testing in order to avoid service affection; when ready, set it to BLOCK + rule_size_restriction_action_type = "COUNT" + rule_sqli_action = "COUNT" + rule_xss_action = "COUNT" + rule_lfi_rfi_action = "COUNT" + rule_ssi_action_type = "COUNT" + rule_auth_tokens_action = "COUNT" + rule_admin_access_action_type = "COUNT" + rule_php_insecurities_action_type = "COUNT" + rule_csrf_action_type = "COUNT" + rule_blacklisted_ips_action_type = "COUNT" +} \ No newline at end of file diff --git a/modules/waf-regional/tests/fixture/outputs.tf b/modules/waf-regional/tests/fixture/outputs.tf new file mode 100644 index 0000000..6779c42 --- /dev/null +++ b/modules/waf-regional/tests/fixture/outputs.tf @@ -0,0 +1,14 @@ +# +# WAF +# +output "web_acl_id" { + value = module.waf_regional_test.web_acl_id +} + +output "web_acl_name" { + value = module.waf_regional_test.web_acl_name +} + +output "web_acl_metric_name" { + value = module.waf_regional_test.web_acl_metric_name +} \ No newline at end of file diff --git a/modules/waf-regional/tests/fixture/variables.tf b/modules/waf-regional/tests/fixture/variables.tf new file mode 100644 index 0000000..95e118f --- /dev/null +++ b/modules/waf-regional/tests/fixture/variables.tf @@ -0,0 +1,58 @@ +#=============================# +# AWS Provider Settings # +#=============================# +provider "aws" { + region = "${var.region}" + profile = "${var.profile}" +} + +variable "region" { + description = "AWS Region" + default = "us-east-1" +} + +variable "profile" { + description = "AWS Profile" + default = "bb-dev-deploymaster" +} + +#=============================# +# Backend Config (partial) # +#=============================# +terraform { + required_version = ">= 0.12.12" +} + +#==============================# +# Project Variables # +#==============================# +variable "project" { + description = "Project id" + default = "bb" +} + +variable "environment" { + description = "Environment Name" + default = "dev-test" +} + +#==============================# +# WAF # +#==============================# +variable "blacklisted_ips" { + default = [] + type = list(string) + description = "List of IPs to blacklist" +} + +variable "admin_remote_ipset" { + default = [] + type = list(string) + description = "List of IPs allowed to access admin pages" +} + +variable "alb_arn" { + default = [] + type = list(string) + description = "List of CloudFront Distributions ARNs" +} diff --git a/modules/waf-regional/tests/verify_output_test.go b/modules/waf-regional/tests/verify_output_test.go new file mode 100644 index 0000000..98bd72c --- /dev/null +++ b/modules/waf-regional/tests/verify_output_test.go @@ -0,0 +1,35 @@ +package tests + +import ( + "testing" + + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestAwsWaf(t *testing.T) { + expectedValue1 := "bb-dev-test-generic-owasp-acl" + expectedValue2 := "bbdevtestgenericowaspacl" + + terraformOptions := &terraform.Options { + // The path to where our Terraform code is located + TerraformDir: "fixture/", + + // Disable colors in Terraform commands so its easier to parse stdout/stderr + NoColor: true, + } + + // At the end of the test, run `terraform destroy` to clean up any resources that were created + defer terraform.Destroy(t, terraformOptions) + + // This will run `terraform init` and `terraform apply` and fail the test if there are any errors + terraform.InitAndApply(t, terraformOptions) + + // Run `terraform output` to get the values of output variables + actualOutput1 := terraform.Output(t, terraformOptions, "web_acl_name") + actualOutput2 := terraform.Output(t, terraformOptions, "web_acl_metric_name") + + // Verify we're getting back the outputs we expect + assert.Equal(t, expectedValue1, actualOutput1) + assert.Equal(t, expectedValue2, actualOutput2) +} diff --git a/modules/waf-regional/variables.tf b/modules/waf-regional/variables.tf index d4f5995..26adce7 100644 --- a/modules/waf-regional/variables.tf +++ b/modules/waf-regional/variables.tf @@ -3,17 +3,20 @@ variable "waf_prefix" { } variable "blacklisted_ips" { - type = "list" - description = "List of IPs to blacklist" + default = [] + type = list(string) + description = "List of IPs to blacklist, eg ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32']" } variable "admin_remote_ipset" { - type = "list" - description = "List of IPs allowed to access admin pages" + default = [] + type = list(string) + description = "List of IPs allowed to access admin pages, ['1.1.1.1/32', '2.2.2.2/32', '3.3.3.3/32']" } variable "alb_arn" { - type = "list" + default = [] + type = list(string) description = "List of ALB ARNs" } diff --git a/modules/waf-regional/versions.tf b/modules/waf-regional/versions.tf new file mode 100644 index 0000000..ac97c6a --- /dev/null +++ b/modules/waf-regional/versions.tf @@ -0,0 +1,4 @@ + +terraform { + required_version = ">= 0.12" +} diff --git a/modules/waf-regional/wafregional_ruleset10_blacklisted_ips.tf b/modules/waf-regional/wafregional_ruleset10_blacklisted_ips.tf index 7198ad3..c3e77fb 100644 --- a/modules/waf-regional/wafregional_ruleset10_blacklisted_ips.tf +++ b/modules/waf-regional/wafregional_ruleset10_blacklisted_ips.tf @@ -5,16 +5,43 @@ resource "aws_wafregional_rule" "detect_blacklisted_ips" { name = "${var.waf_prefix}-generic-detect-blacklisted-ips" - metric_name = "${var.waf_prefix}genericdetectblacklistedips" + metric_name = replace("${var.waf_prefix}genericdetectblacklistedips", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_ipset.blacklisted_ips.id}" + data_id = aws_wafregional_ipset.blacklisted_ips.id negated = false type = "IPMatch" } } resource "aws_wafregional_ipset" "blacklisted_ips" { - name = "${var.waf_prefix}-generic-match-blacklisted-ips" - ip_set_descriptor = "${var.blacklisted_ips}" + name = "${var.waf_prefix}-generic-match-blacklisted-ips" + dynamic "ip_set_descriptor" { + for_each = var.blacklisted_ips + content { + //resource "aws_waf_ipset" "test" { + // name = "test" + // + // dynamic "ip_set_descriptors" { + // # The for_each argument is a hardcoded list in this illustrative example, + // # however it can be sourced from a variable or local value as well as + // # support multiple argument values as a map. + // for_each = ["1.1.1.1/32", "2.2.2.2/32", "3.3.3.3/32"] + // + // content { + // type = "IPV4" + // value = ip_set_descriptors.value + // } + // } + //} + // + //output "test" { + // value = aws_waf_ipset.test.ip_set_descriptors[*].value + //} + + type = "IPV4" + value = ip_set_descriptors.value + } + } } + diff --git a/modules/waf-regional/wafregional_ruleset1_sqli.tf b/modules/waf-regional/wafregional_ruleset1_sqli.tf index f168874..5bc5405 100644 --- a/modules/waf-regional/wafregional_ruleset1_sqli.tf +++ b/modules/waf-regional/wafregional_ruleset1_sqli.tf @@ -5,10 +5,10 @@ resource "aws_wafregional_rule" "mitigate_sqli" { name = "${var.waf_prefix}-generic-mitigate-sqli" - metric_name = "${var.waf_prefix}genericmitigatesqli" + metric_name = replace("${var.waf_prefix}genericmitigatesqli", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_sql_injection_match_set.sql_injection_match_set.id}" + data_id = aws_wafregional_sql_injection_match_set.sql_injection_match_set.id negated = false type = "SqlInjectionMatch" } @@ -101,3 +101,4 @@ resource "aws_wafregional_sql_injection_match_set" "sql_injection_match_set" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset2_auth_tokens.tf b/modules/waf-regional/wafregional_ruleset2_auth_tokens.tf index 5c8283e..25b849e 100644 --- a/modules/waf-regional/wafregional_ruleset2_auth_tokens.tf +++ b/modules/waf-regional/wafregional_ruleset2_auth_tokens.tf @@ -6,10 +6,10 @@ resource "aws_wafregional_rule" "detect_bad_auth_tokens" { name = "${var.waf_prefix}-generic-detect-bad-auth-tokens" - metric_name = "${var.waf_prefix}genericdetectbadauthtokens" + metric_name = replace("${var.waf_prefix}genericdetectbadauthtokens", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_byte_match_set.match_auth_tokens.id}" + data_id = aws_wafregional_byte_match_set.match_auth_tokens.id negated = false type = "ByteMatch" } @@ -40,3 +40,4 @@ resource "aws_wafregional_byte_match_set" "match_auth_tokens" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset3_xss.tf b/modules/waf-regional/wafregional_ruleset3_xss.tf index 0288c19..cf9a00d 100644 --- a/modules/waf-regional/wafregional_ruleset3_xss.tf +++ b/modules/waf-regional/wafregional_ruleset3_xss.tf @@ -5,10 +5,10 @@ resource "aws_wafregional_rule" "mitigate_xss" { name = "${var.waf_prefix}-generic-mitigate-xss" - metric_name = "${var.waf_prefix}genericmitigatexss" + metric_name = replace("${var.waf_prefix}genericmitigatexss", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_xss_match_set.xss_match_set.id}" + data_id = aws_wafregional_xss_match_set.xss_match_set.id negated = false type = "XssMatch" } @@ -83,3 +83,4 @@ resource "aws_wafregional_xss_match_set" "xss_match_set" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset4_lfi_rfi.tf b/modules/waf-regional/wafregional_ruleset4_lfi_rfi.tf index fc95e3f..9ea5dc9 100644 --- a/modules/waf-regional/wafregional_ruleset4_lfi_rfi.tf +++ b/modules/waf-regional/wafregional_ruleset4_lfi_rfi.tf @@ -6,10 +6,10 @@ resource "aws_wafregional_rule" "detect_rfi_lfi_traversal" { name = "${var.waf_prefix}-generic-detect-rfi-lfi-traversal" - metric_name = "${var.waf_prefix}genericdetectrfilfitraversal" + metric_name = replace("${var.waf_prefix}genericdetectrfilfitraversal", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_byte_match_set.match_rfi_lfi_traversal.id}" + data_id = aws_wafregional_byte_match_set.match_rfi_lfi_traversal.id negated = false type = "ByteMatch" } @@ -98,3 +98,4 @@ resource "aws_wafregional_byte_match_set" "match_rfi_lfi_traversal" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset5_admin_access.tf b/modules/waf-regional/wafregional_ruleset5_admin_access.tf index 3ec57bf..d2abd27 100644 --- a/modules/waf-regional/wafregional_ruleset5_admin_access.tf +++ b/modules/waf-regional/wafregional_ruleset5_admin_access.tf @@ -6,24 +6,50 @@ resource "aws_wafregional_rule" "detect_admin_access" { name = "${var.waf_prefix}-generic-detect-admin-access" - metric_name = "${var.waf_prefix}genericdetectadminaccess" + metric_name = replace("${var.waf_prefix}genericdetectadminaccess", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_ipset.admin_remote_ipset.id}" + data_id = aws_wafregional_ipset.admin_remote_ipset.id negated = true type = "IPMatch" } predicate { - data_id = "${aws_wafregional_byte_match_set.match_admin_url.id}" + data_id = aws_wafregional_byte_match_set.match_admin_url.id negated = false type = "ByteMatch" } } resource "aws_wafregional_ipset" "admin_remote_ipset" { - name = "${var.waf_prefix}-generic-match-admin-remote-ip" - ip_set_descriptor = "${var.admin_remote_ipset}" + name = "${var.waf_prefix}-generic-match-admin-remote-ip" + dynamic "ip_set_descriptor" { + for_each = var.admin_remote_ipset + content { + //resource "aws_waf_ipset" "test" { + // name = "test" + // + // dynamic "ip_set_descriptors" { + // # The for_each argument is a hardcoded list in this illustrative example, + // # however it can be sourced from a variable or local value as well as + // # support multiple argument values as a map. + // for_each = ["1.1.1.1/32", "2.2.2.2/32", "3.3.3.3/32"] + // + // content { + // type = "IPV4" + // value = ip_set_descriptors.value + // } + // } + //} + // + //output "test" { + // value = aws_waf_ipset.test.ip_set_descriptors[*].value + //} + + type = "IPV4" + value = ip_set_descriptors.value + } + } } resource "aws_wafregional_byte_match_set" "match_admin_url" { @@ -39,3 +65,4 @@ resource "aws_wafregional_byte_match_set" "match_admin_url" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset6_php_insecurities.tf b/modules/waf-regional/wafregional_ruleset6_php_insecurities.tf index b7243d8..c4493a2 100644 --- a/modules/waf-regional/wafregional_ruleset6_php_insecurities.tf +++ b/modules/waf-regional/wafregional_ruleset6_php_insecurities.tf @@ -5,16 +5,16 @@ resource "aws_wafregional_rule" "detect_php_insecure" { name = "${var.waf_prefix}-generic-detect-php-insecure" - metric_name = "${var.waf_prefix}genericdetectphpinsecure" + metric_name = replace("${var.waf_prefix}genericdetectphpinsecure", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_byte_match_set.match_php_insecure_uri.id}" + data_id = aws_wafregional_byte_match_set.match_php_insecure_uri.id negated = false type = "ByteMatch" } predicate { - data_id = "${aws_wafregional_byte_match_set.match_php_insecure_var_refs.id}" + data_id = aws_wafregional_byte_match_set.match_php_insecure_var_refs.id negated = false type = "ByteMatch" } @@ -127,3 +127,4 @@ resource "aws_wafregional_byte_match_set" "match_php_insecure_var_refs" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset7_size_restriction.tf b/modules/waf-regional/wafregional_ruleset7_size_restriction.tf index e1c8d0b..37575f9 100644 --- a/modules/waf-regional/wafregional_ruleset7_size_restriction.tf +++ b/modules/waf-regional/wafregional_ruleset7_size_restriction.tf @@ -5,17 +5,17 @@ resource "aws_wafregional_rule" "restrict_sizes" { name = "${var.waf_prefix}-generic-restrict-sizes" - metric_name = "${var.waf_prefix}genericrestrictsizes" + metric_name = replace("${var.waf_prefix}genericrestrictsizes", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_size_constraint_set.size_restrictions.id}" + data_id = aws_wafregional_size_constraint_set.size_restrictions.id negated = false type = "SizeConstraint" } } resource "aws_wafregional_size_constraint_set" "size_restrictions" { - name = "${var.waf_prefix}-generic-size-restrictions" + name = "${var.waf_prefix}-generic-size-restrictions" # # Note: we are disabling this constraint because uploads will be affected. @@ -59,3 +59,4 @@ resource "aws_wafregional_size_constraint_set" "size_restrictions" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset8_csrf.tf b/modules/waf-regional/wafregional_ruleset8_csrf.tf index 72f252f..f949362 100644 --- a/modules/waf-regional/wafregional_ruleset8_csrf.tf +++ b/modules/waf-regional/wafregional_ruleset8_csrf.tf @@ -5,16 +5,16 @@ resource "aws_wafregional_rule" "enforce_csrf" { name = "${var.waf_prefix}-generic-enforce-csrf" - metric_name = "${var.waf_prefix}genericenforcecsrf" + metric_name = replace("${var.waf_prefix}genericenforcecsrf", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_byte_match_set.match_csrf_method.id}" + data_id = aws_wafregional_byte_match_set.match_csrf_method.id negated = false type = "ByteMatch" } predicate { - data_id = "${aws_wafregional_size_constraint_set.csrf_token_set.id}" + data_id = aws_wafregional_size_constraint_set.csrf_token_set.id negated = true type = "SizeConstraint" } @@ -48,3 +48,4 @@ resource "aws_wafregional_size_constraint_set" "csrf_token_set" { } } } + diff --git a/modules/waf-regional/wafregional_ruleset9_ssi.tf b/modules/waf-regional/wafregional_ruleset9_ssi.tf index 5f021e3..e951c43 100644 --- a/modules/waf-regional/wafregional_ruleset9_ssi.tf +++ b/modules/waf-regional/wafregional_ruleset9_ssi.tf @@ -5,10 +5,10 @@ resource "aws_wafregional_rule" "detect_ssi" { name = "${var.waf_prefix}-generic-detect-ssi" - metric_name = "${var.waf_prefix}genericdetectssi" + metric_name = replace("${var.waf_prefix}genericdetectssi", "/[^0-9A-Za-z]/", "") predicate { - data_id = "${aws_wafregional_byte_match_set.match_ssi.id}" + data_id = aws_wafregional_byte_match_set.match_ssi.id negated = false type = "ByteMatch" } @@ -97,3 +97,4 @@ resource "aws_wafregional_byte_match_set" "match_ssi" { } } } +