From fb597679928c7a3091fb9bc7699c5e46241b2606 Mon Sep 17 00:00:00 2001 From: Deepak verma <89906661+d4kverma@users.noreply.github.com> Date: Thu, 14 Sep 2023 22:55:13 +0530 Subject: [PATCH] fix: more rules added and missing workflow added (#11) * fix: more rules added and missing workflow added * fix: fix tflint warnings * fix: Uncomment module version in complete example * fix: new rules added --------- Co-authored-by: Anmol Nagpal Co-authored-by: mamrajyadav Co-authored-by: Archit Chopra --- .github/dependabot.yml | 150 ++++++- .github/workflows/auto_assignee.yml | 14 + .github/workflows/changelog.yml | 1 - .github/workflows/readme.yml | 17 +- .github/workflows/terraform.yml | 83 ---- .github/workflows/terratest.yml | 40 -- .github/workflows/tf-checks.yml | 51 +++ .github/workflows/tflint.yml | 11 + .github/workflows/tfsec.yml | 2 +- README.yaml | 319 +++++++++++++- .../byte_match_statement_rules/example.tf | 86 ++++ .../outputs.tf | 0 .../byte_match_statement_rules/versions.tf | 11 + _example/complete/example.tf | 400 +++++++++++++++++ _example/complete/outputs.tf | 14 + _example/{ => complete}/versions.tf | 4 +- .../geo_allowlist_statement_rules/example.tf | 79 ++++ .../geo_allowlist_statement_rules/outputs.tf | 14 + .../geo_allowlist_statement_rules/versions.tf | 11 + _example/geo_match_statement_rules/example.tf | 92 ++++ _example/geo_match_statement_rules/outputs.tf | 14 + .../geo_match_statement_rules/versions.tf | 11 + .../example.tf | 44 +- .../outputs.tf | 14 + .../versions.tf | 11 + .../example.tf | 178 ++++++++ .../outputs.tf | 14 + .../versions.tf | 11 + .../rate_based_statement_rules/example.tf | 82 ++++ .../rate_based_statement_rules/outputs.tf | 14 + .../rate_based_statement_rules/versions.tf | 11 + .../regex_match_statement_rules/example.tf | 88 ++++ .../regex_match_statement_rules/outputs.tf | 14 + .../regex_match_statement_rules/versions.tf | 11 + .../example.tf | 89 ++++ .../outputs.tf | 14 + .../versions.tf | 11 + .../sqli_match_statement_rules/example.tf | 97 ++++ .../sqli_match_statement_rules/outputs.tf | 14 + .../sqli_match_statement_rules/versions.tf | 11 + _example/xss_match_statement/example.tf | 95 ++++ _example/xss_match_statement/outputs.tf | 14 + _example/xss_match_statement/versions.tf | 11 + _test/waf.go | 35 -- main.tf | 415 +++++++++++++++--- outputs.tf | 6 +- variables.tf | 40 +- versions.tf | 11 + 48 files changed, 2472 insertions(+), 307 deletions(-) create mode 100644 .github/workflows/auto_assignee.yml delete mode 100644 .github/workflows/terraform.yml delete mode 100644 .github/workflows/terratest.yml create mode 100644 .github/workflows/tf-checks.yml create mode 100644 .github/workflows/tflint.yml create mode 100644 _example/byte_match_statement_rules/example.tf rename _example/{ => byte_match_statement_rules}/outputs.tf (100%) create mode 100644 _example/byte_match_statement_rules/versions.tf create mode 100644 _example/complete/example.tf create mode 100644 _example/complete/outputs.tf rename _example/{ => complete}/versions.tf (65%) create mode 100644 _example/geo_allowlist_statement_rules/example.tf create mode 100644 _example/geo_allowlist_statement_rules/outputs.tf create mode 100644 _example/geo_allowlist_statement_rules/versions.tf create mode 100644 _example/geo_match_statement_rules/example.tf create mode 100644 _example/geo_match_statement_rules/outputs.tf create mode 100644 _example/geo_match_statement_rules/versions.tf rename _example/{ => ip_set_reference_statement_rules}/example.tf (69%) create mode 100644 _example/ip_set_reference_statement_rules/outputs.tf create mode 100644 _example/ip_set_reference_statement_rules/versions.tf create mode 100644 _example/managed_rule_group_statement_rules/example.tf create mode 100644 _example/managed_rule_group_statement_rules/outputs.tf create mode 100644 _example/managed_rule_group_statement_rules/versions.tf create mode 100644 _example/rate_based_statement_rules/example.tf create mode 100644 _example/rate_based_statement_rules/outputs.tf create mode 100644 _example/rate_based_statement_rules/versions.tf create mode 100644 _example/regex_match_statement_rules/example.tf create mode 100644 _example/regex_match_statement_rules/outputs.tf create mode 100644 _example/regex_match_statement_rules/versions.tf create mode 100644 _example/size_constraint_statement_rules/example.tf create mode 100644 _example/size_constraint_statement_rules/outputs.tf create mode 100644 _example/size_constraint_statement_rules/versions.tf create mode 100644 _example/sqli_match_statement_rules/example.tf create mode 100644 _example/sqli_match_statement_rules/outputs.tf create mode 100644 _example/sqli_match_statement_rules/versions.tf create mode 100644 _example/xss_match_statement/example.tf create mode 100644 _example/xss_match_statement/outputs.tf create mode 100644 _example/xss_match_statement/versions.tf delete mode 100644 _test/waf.go create mode 100644 versions.tf diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 9067e3f..5d3c54c 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,6 +5,17 @@ version: 2 updates: + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 3 + assignees: + - "clouddrove-ci" + reviewers: + - "approvers" + - package-ecosystem: "terraform" # See documentation for possible values directory: "/" # Location of package manifests schedule: @@ -15,8 +26,115 @@ updates: # Add reviewer reviewers: - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/complete" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/byte_match_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/geo_allowlist_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/geo_match_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/ip_set_reference_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/managed_rule _group_statement" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/rate_based_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/regex_match_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + - package-ecosystem: "terraform" # See documentation for possible values - directory: "_example/" # Location of package manifests + directory: "/_example/size_constraint_statement_rules" # Location of package manifests schedule: interval: "weekly" # Add assignees @@ -24,4 +142,32 @@ updates: - "clouddrove-ci" # Add reviewer reviewers: - - "approvers" \ No newline at end of file + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/sqli_match_statement_rules" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 + + - package-ecosystem: "terraform" # See documentation for possible values + directory: "/_example/xss_match_statement" # Location of package manifests + schedule: + interval: "weekly" + # Add assignees + assignees: + - "clouddrove-ci" + # Add reviewer + reviewers: + - "approvers" + # Allow up to 3 open pull requests for pip dependencies + open-pull-requests-limit: 3 \ No newline at end of file diff --git a/.github/workflows/auto_assignee.yml b/.github/workflows/auto_assignee.yml new file mode 100644 index 0000000..f8b8bcd --- /dev/null +++ b/.github/workflows/auto_assignee.yml @@ -0,0 +1,14 @@ +name: Auto Assign PRs + +on: + pull_request: + types: [opened, reopened] + + workflow_dispatch: +jobs: + assignee: + uses: clouddrove/github-shared-workflows/.github/workflows/auto_assignee.yml@master + secrets: + GITHUB: ${{ secrets.GITHUB }} + with: + assignees: 'clouddrove-ci' diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index 4c3bb12..1ee6f78 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -11,4 +11,3 @@ jobs: secrets: inherit with: branch: 'master' - diff --git a/.github/workflows/readme.yml b/.github/workflows/readme.yml index 54f849a..d063f36 100644 --- a/.github/workflows/readme.yml +++ b/.github/workflows/readme.yml @@ -10,10 +10,10 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout' - uses: actions/checkout@v2.3.4 + uses: actions/checkout@master - name: 'Set up Python 3.7' - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' @@ -21,17 +21,16 @@ jobs: uses: 'clouddrove/github-actions@v9.0.2' with: actions_subcommand: 'readme' - github_token: '${{ secrets.GITHUB}}' + github_token: '${{ secrets.GITHUB }}' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}} - + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'pre-commit check errors' - uses: pre-commit/action@v2.0.0 + uses: pre-commit/action@v3.0.0 continue-on-error: true - - name: 'pre-commit fix errors' - uses: pre-commit/action@v2.0.0 + - name: 'pre-commit fix erros' + uses: pre-commit/action@v3.0.0 continue-on-error: true - name: 'push readme' @@ -40,7 +39,7 @@ jobs: with: actions_subcommand: 'push' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN}} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: 'Slack Notification' uses: clouddrove/action-slack@v2 diff --git a/.github/workflows/terraform.yml b/.github/workflows/terraform.yml deleted file mode 100644 index 10e9868..0000000 --- a/.github/workflows/terraform.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: static-checks - -on: - pull_request: - -jobs: - versionExtract: - name: Get min/max versions - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Terraform min/max versions - id: minMax - uses: clowdhaus/terraform-min-max@main - outputs: - minVersion: ${{ steps.minMax.outputs.minVersion }} - maxVersion: ${{ steps.minMax.outputs.maxVersion }} - - - versionEvaluate: - name: Evaluate Terraform versions - runs-on: ubuntu-latest - needs: versionExtract - strategy: - fail-fast: false - matrix: - version: - - ${{ needs.versionExtract.outputs.minVersion }} - - ${{ needs.versionExtract.outputs.maxVersion }} - directory: - - _example/ - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Terraform v${{ matrix.version }} - uses: hashicorp/setup-terraform@v1 - with: - terraform_version: ${{ matrix.version }} - - - name: 'Configure AWS Credentials' - uses: clouddrove/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.TEST_AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.TEST_AWS_ACCESS_SECRET_KEY }} - aws-region: us-east-2 - - - name: Init & validate v${{ matrix.version }} - run: | - cd ${{ matrix.directory }} - terraform init - terraform validate - - - name: tflint - uses: reviewdog/action-tflint@master - with: - tflint_version: v0.29.0 - github_token: ${{ secrets.GITHUB }} - working_directory: ${{ matrix.directory }} - fail_on_error: 'true' - filter_mode: 'nofilter' - flags: '--module' - - format: - name: Check code format - runs-on: ubuntu-latest - needs: versionExtract - - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Install Terraform v${{ needs.versionExtract.outputs.maxVersion }} - uses: hashicorp/setup-terraform@v1 - with: - terraform_version: ${{ needs.versionExtract.outputs.maxVersion }} - - - name: Check Terraform format changes - run: terraform fmt --recursive \ No newline at end of file diff --git a/.github/workflows/terratest.yml b/.github/workflows/terratest.yml deleted file mode 100644 index 00f2fe2..0000000 --- a/.github/workflows/terratest.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Terratest GitHub Actions' -on: - pull_request: - branches: - - master - types: [labeled] - -jobs: - Terratest: - name: 'Terratest' - runs-on: ubuntu-latest - steps: - - - name: 'Checkout' - uses: actions/checkout@v2.3.4 - - - name: 'Configure AWS Credentials' - uses: clouddrove/configure-aws-credentials@v1 - with: - aws-access-key-id: ${{ secrets.TEST_AWS_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.TEST_AWS_ACCESS_SECRET_KEY }} - aws-region: us-east-2 - - - name: 'Terratest' - uses: 'clouddrove/github-actions@v9.0.2' - with: - actions_subcommand: 'terratest' - if: ${{ github.event.label.name == 'terratest' }} - tf_actions_working_dir: '_test' - - - name: 'Slack Notification' - uses: clouddrove/action-slack@v2 - with: - status: ${{ job.status }} - fields: repo,author - author_name: 'CloudDrove' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # required - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_TERRAFORM }} # required - if: always() diff --git a/.github/workflows/tf-checks.yml b/.github/workflows/tf-checks.yml new file mode 100644 index 0000000..01f8fc2 --- /dev/null +++ b/.github/workflows/tf-checks.yml @@ -0,0 +1,51 @@ +name: tf-checks +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: +jobs: + complete: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/complete/' + byte_match_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/byte_match_statement_rules/' + geo_allowlist_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/geo_allowlist_statement_rules/' + geo_match_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/geo_match_statement_rules/' + ip_set_reference_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/ip_set_reference_statement_rules/' + managed_rule_group_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/managed_rule_group_statement_rules/' + rate_based_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/rate_based_statement_rules/' + regex_match_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/regex_match_statement_rules/' + size_constraint_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/size_constraint_statement_rules/' + sqli_match_statement_rules: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/sqli_match_statement_rules/' + xss_match_statement: + uses: clouddrove/github-shared-workflows/.github/workflows/tf-checks.yml@master + with: + working_directory: './_example/xss_match_statement/' diff --git a/.github/workflows/tflint.yml b/.github/workflows/tflint.yml new file mode 100644 index 0000000..5b8aa91 --- /dev/null +++ b/.github/workflows/tflint.yml @@ -0,0 +1,11 @@ +name: tf-lint +on: + push: + branches: [ master ] + pull_request: + workflow_dispatch: +jobs: + tflint: + uses: clouddrove/test-tfsec/.github/workflows/tflint.yaml@master + secrets: + GITHUB: ${{ secrets.GITHUB }} diff --git a/.github/workflows/tfsec.yml b/.github/workflows/tfsec.yml index 9aaf588..c203751 100644 --- a/.github/workflows/tfsec.yml +++ b/.github/workflows/tfsec.yml @@ -8,4 +8,4 @@ jobs: uses: clouddrove/github-shared-workflows/.github/workflows/tfsec.yml@master secrets: inherit with: - working_directory: '.' \ No newline at end of file + working_directory: '.' diff --git a/README.yaml b/README.yaml index 8f0e5a7..ab421d4 100644 --- a/README.yaml +++ b/README.yaml @@ -15,18 +15,19 @@ github_repo: clouddrove/terraform--aws-waf # Badges to display badges: - - name: "Terraform" - image: "https://img.shields.io/badge/Terraform-v1.1.7-green" - url: "https://www.terraform.io" - - name: "Licence" - image: "https://img.shields.io/badge/License-APACHE-blue.svg" - url: "LICENSE.md" + - name: "Latest Release" + image: "https://img.shields.io/github/release/clouddrove/terraform-aws-waf.svg" + url: "https://github.com/clouddrove/terraform-aws-waf/releases/latest" - name: "tfsec" image: "https://github.com/clouddrove/terraform-aws-waf/actions/workflows/tfsec.yml/badge.svg" url: "https://github.com/clouddrove/terraform-aws-waf/actions/workflows/tfsec.yml" - - name: "static-checks" - image: "https://github.com/clouddrove/terraform-aws-waf/actions/workflows/terraform.yml/badge.svg" - url: "https://github.com/clouddrove/terraform-aws-waf/actions/workflows/terraform.yml" + - name: "Licence" + image: "https://img.shields.io/badge/License-APACHE-blue.svg" + url: "LICENSE.md" + +prerequesties: + - name: Terraform 1.4.6 + url: https://learn.hashicorp.com/terraform/getting-started/install.html # description of this project description: |- @@ -44,16 +45,16 @@ usage: |- Here is an example of how you can use this module in your inventory structure: ```hcl module "waf" { - source = "clouddrove/waf/aws" - version = "1.3.0" - + source = "clouddrove/labels/aws" + version = "2.0.0" name = "waf" environment = "test" - allow_default_action = true + allow_default_action = false waf_enabled = true waf_scop = "REGIONAL" web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] visibility_config = { cloudwatch_metrics_enabled = true @@ -61,45 +62,284 @@ usage: |- } rules = [ + # ip set statement rules. 30 { - name = "AWSManagedRulesAmazonIpReputationList" + name = "whitelist-ip-set" priority = "0" + action = "allow" + + ip_set_reference_statement = { + arn = module.ip_set.ip_set_arn + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "test-waf-setup-waf-ip-set-block-metrics" + sampled_requests_enabled = true + } + }, + + ## Byte match statement rules. 30 + { + name = "byte-match-statement-rule-30" + priority = "30" + action = "allow" + + + byte_match_statement = { + positional_constraint = "EXACTLY" + search_string = "/cp-key" + priority = 30 + type = "COMPRESS_WHITE_SPACE" + field_to_match = { + uri_path = {} + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-30" + sampled_requests_enabled = false + } + }, + + ## geo_allowlist_statement_rules 90 + { + name = "geo-allowlist-statement-rule-90" + priority = "90" + action = "count" + + not_statement = { + geo_match_statement = { + country_codes = ["US"] + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-90" + sampled_requests_enabled = false + } + }, + + ## geo_match_statement_rules 60 + { + name = "geo-match-statement-rule-60" + priority = "60" + action = "count" + + geo_match_statement = { + country_codes = ["NL", "GB"] + } + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-60" + sampled_requests_enabled = false + } + }, + + # managed_rule_group_statement_rules 1 + { + name = "AWS-AWSManagedRulesAdminProtectionRuleSet" + priority = "1" override_action = "none" + managed_rule_group_statement = { + name = "AWSManagedRulesAdminProtectionRuleSet" + } + visibility_config = { cloudwatch_metrics_enabled = true - metric_name = "AWS-AWSManagedRulesAmazonIpReputationList" + metric_name = "AWSManagedRulesAdminProtectionRuleSet" sampled_requests_enabled = true } + }, + { + name = "AWS-AWSManagedRulesAmazonIpReputationList" + priority = "2" + override_action = "none" managed_rule_group_statement = { - name = "AWSManagedRulesAmazonIpReputationList" - vendor_name = "AWS" + name = "AWSManagedRulesAmazonIpReputationList" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWS-AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true } }, + { + name = "AWS-AWSManagedRulesCommonRuleSet" + priority = 3 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + } + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesCommonRuleSet" + } + }, { - name = "rate-limit" - priority = "1" - action = "block" + name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" + priority = 4 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } visibility_config = { cloudwatch_metrics_enabled = true - metric_name = "rate-limit" sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" } + }, + + #rate_based_statement_rules 40 + { + name = "rate-based-statement-rule-40" + priority = "40" + action = "block" + rate_based_statement = { - limit = 2000 + limit = 100 aggregate_key_type = "IP" } - } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-40" + sampled_requests_enabled = false + } + }, + + #regex_match_statement_rules 100 + { + name = "regex-match-statement-rule-100" + priority = "100" + action = "block" + + regex_match_statement = { + regex_string = "^/admin" + text_transformation = [ + { + priority = 90 + type = "COMPRESS_WHITE_SPACE" + } + ] + + field_to_match = { + uri_path = {} + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-100" + sampled_requests_enabled = false + } + }, + + #size_constraint_statement_rules 50 + { + name = "size-constraint-rule-50" + priority = "50" + action = "block" + + + size_constraint_statement = { + comparison_operator = "GT" + size = 15 + + field_to_match = { + all_query_arguments = {} + } + type = "COMPRESS_WHITE_SPACE" + priority = 1 + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-50" + sampled_requests_enabled = false + } + }, + + #sqli_match_statement_rules 70 + { + name = "sqli-match-statement-rule-70" + priority = "70" + action = "block" + + sqli_match_statement = { + + field_to_match = { + query_string = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-70" + sampled_requests_enabled = false + } + }, + + #xss_match_statement 80 + { + name = "xsss-match-statement-rule-80" + priority = "80" + action = "block" + + + xss_match_statement = { + field_to_match = { + uri_path = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-80" + sampled_requests_enabled = false + } + }, ] #logs - create_logging_configuration = false + create_logging_configuration = true redacted_fields = [ { single_header = { @@ -107,4 +347,35 @@ usage: |- } } ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } + } ``` diff --git a/_example/byte_match_statement_rules/example.tf b/_example/byte_match_statement_rules/example.tf new file mode 100644 index 0000000..876bf1b --- /dev/null +++ b/_example/byte_match_statement_rules/example.tf @@ -0,0 +1,86 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-30" + priority = "30" + action = "allow" + + byte_match_statement = { + positional_constraint = "EXACTLY" + search_string = "/cp-key" + priority = 30 + type = "COMPRESS_WHITE_SPACE" + field_to_match = { + uri_path = {} + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-30" + sampled_requests_enabled = false + } + }, + ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/outputs.tf b/_example/byte_match_statement_rules/outputs.tf similarity index 100% rename from _example/outputs.tf rename to _example/byte_match_statement_rules/outputs.tf diff --git a/_example/byte_match_statement_rules/versions.tf b/_example/byte_match_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/byte_match_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/complete/example.tf b/_example/complete/example.tf new file mode 100644 index 0000000..9c88ec2 --- /dev/null +++ b/_example/complete/example.tf @@ -0,0 +1,400 @@ +provider "aws" { + region = "eu-west-1" +} + +locals { + name = "waf" + environment = "test" + +} +module "ip_set" { + source = "../../" + name = local.name + environment = local.environment + ip_addresses = ["51.79.69.69/32"] +} + +module "waf" { + source = "../../" + name = local.name + environment = local.environment + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + # ip set statement rules. 30 + { + name = "WhitelistIpSetRule0" + priority = "0" + action = "allow" + + ip_set_reference_statement = { + arn = module.ip_set.ip_set_arn + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "test-waf-setup-waf-ip-set-block-metrics" + sampled_requests_enabled = true + } + }, + + # ## Byte match statement rules. 30 + { + name = "ByteMatchRule30" + priority = "30" + action = "allow" + + + byte_match_statement = { + positional_constraint = "EXACTLY" + search_string = "/cp-key" + priority = 30 + type = "COMPRESS_WHITE_SPACE" + field_to_match = { + uri_path = {} + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "ByteMatchRule30" + sampled_requests_enabled = false + } + }, + + # ## geo_allowlist_statement_rules 90 + { + name = "GeoAllowlistRule90" + priority = "90" + action = "count" + + not_statement = { + geo_match_statement = { + country_codes = ["US"] + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "GeoAllowlistRule90" + sampled_requests_enabled = false + } + }, + + # ## geo_match_statement_rules 60 + { + name = "GeoMatchRule60" + priority = "60" + action = "count" + + geo_match_statement = { + country_codes = ["NL", "GB"] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "GeoMatchRule60" + sampled_requests_enabled = false + } + }, + + # # managed_rule_group_statement_rules 1-7 + { + name = "AWS-AWSManagedRulesAdminProtectionRuleSet" + priority = "1" + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesAdminProtectionRuleSet" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAdminProtectionRuleSet" + sampled_requests_enabled = true + } + }, + { + name = "AWS-AWSManagedRulesAmazonIpReputationList" + priority = "2" + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesAmazonIpReputationList" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWS-AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true + } + }, + { + name = "AWS-AWSManagedRulesCommonRuleSet" + priority = 3 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesCommonRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" + priority = 4 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesSQLiRuleSet", + priority = 5 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesSQLiRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesSQLiRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesPHPRuleSet", + priority = 6 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesPHPRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesPHPRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesAnonymousIpList", + priority = 7 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesAnonymousIpList" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesAnonymousIpList" + } + }, + + # #rate_based_statement_rules 40 + { + name = "RateBasedRule40" + priority = "40" + action = "block" + + + rate_based_statement = { + limit = 100 + aggregate_key_type = "IP" + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "RateBasedRule40" + sampled_requests_enabled = false + } + }, + + # #regex_match_statement_rules 100 + { + name = "RegexMatchRule100" + priority = "100" + action = "block" + + regex_match_statement = { + regex_string = "^/admin" + text_transformation = [ + { + priority = 90 + type = "COMPRESS_WHITE_SPACE" + } + ] + + field_to_match = { + uri_path = {} + } + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "RegexMatchRule100" + sampled_requests_enabled = false + } + }, + + # #size_constraint_statement_rules 50 + { + name = "SizeConstraintRule50" + priority = "50" + action = "block" + + + size_constraint_statement = { + comparison_operator = "GT" + size = 15 + + field_to_match = { + all_query_arguments = {} + } + type = "COMPRESS_WHITE_SPACE" + priority = 1 + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "SizeConstraintRule50" + sampled_requests_enabled = false + } + }, + + # #sqli_match_statement_rules 70 + { + name = "SqliMatchRule70" + priority = "70" + action = "block" + + sqli_match_statement = { + + field_to_match = { + query_string = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "SqliMatchRule70" + sampled_requests_enabled = false + } + }, + + # #xss_match_statement 80 + { + name = "XsssMatchRule80" + priority = "80" + action = "block" + + + xss_match_statement = { + field_to_match = { + uri_path = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + } + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "XsssMatchRule80" + sampled_requests_enabled = false + } + }, + ] + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/complete/outputs.tf b/_example/complete/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/complete/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/versions.tf b/_example/complete/versions.tf similarity index 65% rename from _example/versions.tf rename to _example/complete/versions.tf index 9390f7c..f9190f8 100644 --- a/_example/versions.tf +++ b/_example/complete/versions.tf @@ -1,11 +1,11 @@ # Terraform version terraform { - required_version = ">= 1.3.6" + required_version = ">= 1.5.7" required_providers { aws = { source = "hashicorp/aws" - version = "~> 5.0" + version = "~> 5.13.1" } } } diff --git a/_example/geo_allowlist_statement_rules/example.tf b/_example/geo_allowlist_statement_rules/example.tf new file mode 100644 index 0000000..133b135 --- /dev/null +++ b/_example/geo_allowlist_statement_rules/example.tf @@ -0,0 +1,79 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-90" + priority = "80" + action = "count" + + not_statement = { + geo_match_statement = { + country_codes = ["US"] + } + } + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-80" + sampled_requests_enabled = false + } + }, ] + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/geo_allowlist_statement_rules/outputs.tf b/_example/geo_allowlist_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/geo_allowlist_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/geo_allowlist_statement_rules/versions.tf b/_example/geo_allowlist_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/geo_allowlist_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/geo_match_statement_rules/example.tf b/_example/geo_match_statement_rules/example.tf new file mode 100644 index 0000000..e6b798b --- /dev/null +++ b/_example/geo_match_statement_rules/example.tf @@ -0,0 +1,92 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-80" + priority = "80" + action = "count" + + geo_match_statement = { + country_codes = ["NL", "GB"] + } + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-60" + sampled_requests_enabled = false + } + }, + { + name = "rule-81" + priority = "81" + action = "allow" + + geo_match_statement = { + country_codes = ["US"] + } + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-60" + sampled_requests_enabled = false + } + }, ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/geo_match_statement_rules/outputs.tf b/_example/geo_match_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/geo_match_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/geo_match_statement_rules/versions.tf b/_example/geo_match_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/geo_match_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/example.tf b/_example/ip_set_reference_statement_rules/example.tf similarity index 69% rename from _example/example.tf rename to _example/ip_set_reference_statement_rules/example.tf index 3575699..a3b0396 100644 --- a/_example/example.tf +++ b/_example/ip_set_reference_statement_rules/example.tf @@ -2,24 +2,28 @@ provider "aws" { region = "eu-west-1" } +locals { + name = "waf" + environment = "test" +} + module "ip_set" { - source = "../" - name = "waf" - environment = "test" + source = "../../" + name = local.name + environment = local.environment ip_addresses = ["51.79.69.69/32"] } - module "waf" { - source = "../" - name = "waf" - environment = "test" - allow_default_action = true + source = "../../" + name = local.name + environment = local.environment + allow_default_action = false waf_enabled = true waf_scop = "REGIONAL" - web_acl_association = true + web_acl_association = false resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] visibility_config = { @@ -28,27 +32,10 @@ module "waf" { } rules = [ - { - name = "rate-limit" - priority = "1" - action = "block" - - visibility_config = { - cloudwatch_metrics_enabled = true - metric_name = "rate-limit" - sampled_requests_enabled = true - } - - rate_based_statement = { - limit = 2000 - aggregate_key_type = "IP" - } - }, - { - name = "allow-ip-set" + name = "whitelist-ip-set" priority = "0" - action = "allow" + action = "block" ip_set_reference_statement = { arn = module.ip_set.ip_set_arn @@ -62,7 +49,6 @@ module "waf" { } ] - #logs create_logging_configuration = false diff --git a/_example/ip_set_reference_statement_rules/outputs.tf b/_example/ip_set_reference_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/ip_set_reference_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/ip_set_reference_statement_rules/versions.tf b/_example/ip_set_reference_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/ip_set_reference_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/managed_rule_group_statement_rules/example.tf b/_example/managed_rule_group_statement_rules/example.tf new file mode 100644 index 0000000..693853a --- /dev/null +++ b/_example/managed_rule_group_statement_rules/example.tf @@ -0,0 +1,178 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = false + sampled_requests_enabled = true + } + + rules = [ + { + name = "AWS-AWSManagedRulesAdminProtectionRuleSet" + priority = "1" + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesAdminProtectionRuleSet" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWSManagedRulesAdminProtectionRuleSet" + sampled_requests_enabled = true + } + }, + { + name = "AWS-AWSManagedRulesAmazonIpReputationList" + priority = "2" + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesAmazonIpReputationList" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + metric_name = "AWS-AWSManagedRulesAmazonIpReputationList" + sampled_requests_enabled = true + } + }, + { + name = "AWS-AWSManagedRulesCommonRuleSet" + priority = 3 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesCommonRuleSet" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesCommonRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" + priority = 4 + override_action = "none" + + managed_rule_group_statement = { + name = "AWSManagedRulesKnownBadInputsRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWS-AWSManagedRulesKnownBadInputsRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesSQLiRuleSet", + priority = 5 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesSQLiRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesSQLiRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesPHPRuleSet", + priority = 6 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesPHPRuleSet" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesPHPRuleSet" + } + }, + { + name = "AWS-AWSManagedRulesAnonymousIpList", + priority = 7 + override_action = "none" + excluded_rules = [] + + managed_rule_group_statement = { + name = "AWSManagedRulesAnonymousIpList" + vendor_name = "AWS" + } + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + metric_name = "AWSManagedRulesAnonymousIpList" + } + }, + + ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/managed_rule_group_statement_rules/outputs.tf b/_example/managed_rule_group_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/managed_rule_group_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/managed_rule_group_statement_rules/versions.tf b/_example/managed_rule_group_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/managed_rule_group_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/rate_based_statement_rules/example.tf b/_example/rate_based_statement_rules/example.tf new file mode 100644 index 0000000..a7735aa --- /dev/null +++ b/_example/rate_based_statement_rules/example.tf @@ -0,0 +1,82 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-40" + priority = "40" + action = "block" + + + rate_based_statement = { + limit = 100 + aggregate_key_type = "IP" + } + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-40" + sampled_requests_enabled = false + } + }, ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/rate_based_statement_rules/outputs.tf b/_example/rate_based_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/rate_based_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/rate_based_statement_rules/versions.tf b/_example/rate_based_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/rate_based_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/regex_match_statement_rules/example.tf b/_example/regex_match_statement_rules/example.tf new file mode 100644 index 0000000..a872b8b --- /dev/null +++ b/_example/regex_match_statement_rules/example.tf @@ -0,0 +1,88 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-100" + priority = "100" + action = "block" + + regex_match_statement = { + regex_string = "^/admin" + + text_transformation = [ + { + priority = 90 + type = "COMPRESS_WHITE_SPACE" + } + ] + + field_to_match = { + uri_path = {} + } + } + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-100" + sampled_requests_enabled = false + } + }, ] + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/regex_match_statement_rules/outputs.tf b/_example/regex_match_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/regex_match_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/regex_match_statement_rules/versions.tf b/_example/regex_match_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/regex_match_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/size_constraint_statement_rules/example.tf b/_example/size_constraint_statement_rules/example.tf new file mode 100644 index 0000000..f7b972e --- /dev/null +++ b/_example/size_constraint_statement_rules/example.tf @@ -0,0 +1,89 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-50" + priority = "50" + action = "block" + + + size_constraint_statement = { + comparison_operator = "GT" + size = 15 + + field_to_match = { + all_query_arguments = {} + } + type = "COMPRESS_WHITE_SPACE" + priority = 1 + } + + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-40" + sampled_requests_enabled = false + } + }, ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/size_constraint_statement_rules/outputs.tf b/_example/size_constraint_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/size_constraint_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/size_constraint_statement_rules/versions.tf b/_example/size_constraint_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/size_constraint_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/sqli_match_statement_rules/example.tf b/_example/sqli_match_statement_rules/example.tf new file mode 100644 index 0000000..aa126ce --- /dev/null +++ b/_example/sqli_match_statement_rules/example.tf @@ -0,0 +1,97 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-70" + priority = "70" + action = "block" + + + sqli_match_statement = { + + field_to_match = { + query_string = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + + } + + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-60" + sampled_requests_enabled = false + } + }, ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/sqli_match_statement_rules/outputs.tf b/_example/sqli_match_statement_rules/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/sqli_match_statement_rules/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/sqli_match_statement_rules/versions.tf b/_example/sqli_match_statement_rules/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/sqli_match_statement_rules/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_example/xss_match_statement/example.tf b/_example/xss_match_statement/example.tf new file mode 100644 index 0000000..57e9a09 --- /dev/null +++ b/_example/xss_match_statement/example.tf @@ -0,0 +1,95 @@ +provider "aws" { + region = "eu-west-1" +} + +module "waf" { + source = "../../" + name = "waf" + environment = "test" + allow_default_action = false + waf_enabled = true + waf_scop = "REGIONAL" + + web_acl_association = false + resource_arn_list = ["arn:aws:elasticloadbalancing:eu-west-1:xxxxxxx:loadbalancer/app/alb-test/xxxxxxxxx"] + + visibility_config = { + cloudwatch_metrics_enabled = true + sampled_requests_enabled = true + } + + rules = [ + { + name = "rule-60" + priority = "60" + action = "block" + + + xss_match_statement = { + field_to_match = { + uri_path = {} + } + + text_transformation = [ + { + type = "URL_DECODE" + priority = 1 + }, + { + type = "HTML_ENTITY_DECODE" + priority = 2 + } + ] + } + + + + visibility_config = { + cloudwatch_metrics_enabled = false + metric_name = "rule-60" + sampled_requests_enabled = false + } + }, ] + + + #logs + + create_logging_configuration = false + redacted_fields = [ + { + single_header = { + name = "user-agent" + } + } + ] + + logging_filter = { + default_behavior = "DROP" + + filter = [ + { + behavior = "KEEP" + requirement = "MEETS_ANY" + condition = [ + { + action_condition = { + action = "ALLOW" + } + }, + ] + }, + { + behavior = "DROP" + requirement = "MEETS_ALL" + condition = [ + { + action_condition = { + action = "COUNT" + } + } + + ] + } + ] + } +} diff --git a/_example/xss_match_statement/outputs.tf b/_example/xss_match_statement/outputs.tf new file mode 100644 index 0000000..ac5c892 --- /dev/null +++ b/_example/xss_match_statement/outputs.tf @@ -0,0 +1,14 @@ +output "arn" { + value = module.waf.arn + description = "The Amazon Resource Name (ARN) specifying the role." +} + +output "tags" { + value = module.waf.tags + description = "A mapping of tags to assign to the resource." +} + +output "id" { + value = module.waf.id + description = "A mapping of tags to assign to the resource." +} diff --git a/_example/xss_match_statement/versions.tf b/_example/xss_match_statement/versions.tf new file mode 100644 index 0000000..979ddcf --- /dev/null +++ b/_example/xss_match_statement/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.13.1" + } + } +} \ No newline at end of file diff --git a/_test/waf.go b/_test/waf.go deleted file mode 100644 index bebf647..0000000 --- a/_test/waf.go +++ /dev/null @@ -1,35 +0,0 @@ -// Managed By : CloudDrove -// Description : This Terratest is used to test the Terraform Route53 module. -// Copyright @ CloudDrove. All Right Reserved. -package test - -import ( - "testing" - "strings" - "github.com/stretchr/testify/assert" - "github.com/gruntwork-io/terratest/modules/terraform" -) - -func Test(t *testing.T) { - t.Parallel() - - terraformOptions := &terraform.Options{ - // Source path of Terraform directory. - TerraformDir: "../_example", - Upgrade: true, - } - - // This will run `terraform init` and `terraform apply` and fail the test if there are any errors - terraform.InitAndApply(t, terraformOptions) - - // To clean up any resources that have been created, run 'terraform destroy' towards the end of the test - defer terraform.Destroy(t, terraformOptions) - - // To get the value of an output variable, run 'terraform output' - Tags := terraform.OutputMap(t, terraformOptions, "tags") - Arn := strings.Join(terraform.OutputList(t, terraformOptions, "arn"),"") - - // Check that we get back the outputs that we expect - assert.Equal(t, "waf", Tags["Name"]) - assert.Contains(t, Arn, "arn:aws:iam") -} diff --git a/main.tf b/main.tf index 604f1a2..8099c76 100644 --- a/main.tf +++ b/main.tf @@ -16,7 +16,7 @@ module "labels" { #Module : WAF #Description : Provides a WAFv2 IP Set Resource. resource "aws_wafv2_ip_set" "main" { - count = var.ip_addresses != null ? 1 : 0 + count = var.enable && var.ip_addresses != null ? 1 : 0 name = format("ip-%s", module.labels.id) scope = var.waf_scop @@ -28,9 +28,9 @@ resource "aws_wafv2_ip_set" "main" { #Module : WAF #Description : Terraform module to create WAF resource on AWS. resource "aws_wafv2_web_acl" "main" { - count = var.waf_enabled ? 1 : 0 + count = var.enable && var.waf_enabled ? 1 : 0 name = module.labels.id - description = "WAFv2 ACL for ${module.labels.id}" + description = "WAFv2 ACL for" scope = var.waf_scop default_action { @@ -95,16 +95,6 @@ resource "aws_wafv2_web_acl" "main" { name = lookup(managed_rule_group_statement.value, "name") vendor_name = lookup(managed_rule_group_statement.value, "vendor_name", "AWS") - dynamic "rule_action_override" { - for_each = length(lookup(managed_rule_group_statement.value, "rule_action_override", {})) == 0 ? [] : toset(lookup(managed_rule_group_statement.value, "rule_action_override")) - content { - name = rule_action_override.value - action_to_use { - count {} - } - } - } - dynamic "scope_down_statement" { for_each = length(lookup(managed_rule_group_statement.value, "scope_down_statement", {})) == 0 ? [] : [lookup(managed_rule_group_statement.value, "scope_down_statement", {})] content { @@ -376,7 +366,7 @@ resource "aws_wafv2_web_acl" "main" { for_each = length(lookup(byte_match_statement.value, "field_to_match", {})) == 0 ? [] : [lookup(byte_match_statement.value, "field_to_match", {})] content { dynamic "uri_path" { - for_each = length(lookup(field_to_match.value, "uri_path", {})) == 0 ? [] : [lookup(field_to_match.value, "uri_path")] + for_each = lookup(field_to_match.value, "uri_path", null) != null ? [1] : [] content {} } dynamic "all_query_arguments" { @@ -425,7 +415,6 @@ resource "aws_wafv2_web_acl" "main" { arn = lookup(ip_set_reference_statement.value, "arn") } } - dynamic "size_constraint_statement" { for_each = length(lookup(rule.value, "size_constraint_statement", {})) == 0 ? [] : [lookup(rule.value, "size_constraint_statement", {})] content { @@ -437,7 +426,7 @@ resource "aws_wafv2_web_acl" "main" { content {} } dynamic "all_query_arguments" { - for_each = length(lookup(field_to_match.value, "all_query_arguments", {})) == 0 ? [] : [lookup(field_to_match.value, "all_query_arguments")] + for_each = lookup(field_to_match.value, "all_query_arguments", null) != null ? [1] : [] content {} } dynamic "body" { @@ -747,6 +736,223 @@ resource "aws_wafv2_web_acl" "main" { } } + dynamic "xss_match_statement" { + for_each = lookup(rule.value, "xss_match_statement", null) != null ? [rule.value.xss_match_statement] : [] + + content { + + dynamic "field_to_match" { + for_each = lookup(rule.value.xss_match_statement, "field_to_match", null) != null ? [rule.value.xss_match_statement.field_to_match] : [] + + content { + dynamic "all_query_arguments" { + for_each = lookup(field_to_match.value, "all_query_arguments", null) != null ? [1] : [] + + content {} + } + + dynamic "body" { + for_each = lookup(field_to_match.value, "body", null) != null ? [1] : [] + + content {} + } + + dynamic "method" { + for_each = lookup(field_to_match.value, "method", null) != null ? [1] : [] + + content {} + } + + dynamic "query_string" { + for_each = lookup(field_to_match.value, "query_string", null) != null ? [1] : [] + + content {} + } + + dynamic "single_header" { + for_each = lookup(field_to_match.value, "single_header", null) != null ? [field_to_match.value.single_header] : [] + + content { + name = single_header.value.name + } + } + + dynamic "single_query_argument" { + for_each = lookup(field_to_match.value, "single_query_argument", null) != null ? [field_to_match.value.single_query_argument] : [] + + content { + name = single_query_argument.value.name + } + } + + dynamic "uri_path" { + for_each = lookup(field_to_match.value, "uri_path", null) != null ? [1] : [] + + content {} + } + } + } + + dynamic "text_transformation" { + for_each = lookup(rule.value.xss_match_statement, "text_transformation", null) != null ? [ + for rule in lookup(rule.value.xss_match_statement, "text_transformation") : { + priority = rule.priority + type = rule.type + }] : [] + + content { + priority = text_transformation.value.priority + type = text_transformation.value.type + } + } + } + } + + dynamic "sqli_match_statement" { + for_each = lookup(rule.value, "sqli_match_statement", null) != null ? [rule.value.sqli_match_statement] : [] + + content { + + dynamic "field_to_match" { + for_each = lookup(rule.value.sqli_match_statement, "field_to_match", null) != null ? [rule.value.sqli_match_statement.field_to_match] : [] + + content { + dynamic "all_query_arguments" { + for_each = lookup(field_to_match.value, "all_query_arguments", null) != null ? [1] : [] + + content {} + } + + dynamic "body" { + for_each = lookup(field_to_match.value, "body", null) != null ? [1] : [] + + content {} + } + + dynamic "method" { + for_each = lookup(field_to_match.value, "method", null) != null ? [1] : [] + + content {} + } + + dynamic "query_string" { + for_each = lookup(field_to_match.value, "query_string", null) != null ? [1] : [] + + content {} + } + + dynamic "single_header" { + for_each = lookup(field_to_match.value, "single_header", null) != null ? [field_to_match.value.single_header] : [] + + content { + name = single_header.value.name + } + } + + dynamic "single_query_argument" { + for_each = lookup(field_to_match.value, "single_query_argument", null) != null ? [field_to_match.value.single_query_argument] : [] + + content { + name = single_query_argument.value.name + } + } + + dynamic "uri_path" { + for_each = lookup(field_to_match.value, "uri_path", null) != null ? [1] : [] + + content {} + } + } + } + + dynamic "text_transformation" { + for_each = lookup(rule.value.sqli_match_statement, "text_transformation", null) != null ? [ + for rule in lookup(rule.value.sqli_match_statement, "text_transformation") : { + priority = rule.priority + type = rule.type + }] : [] + + content { + priority = text_transformation.value.priority + type = text_transformation.value.type + } + } + } + } + + dynamic "regex_match_statement" { + for_each = lookup(rule.value, "regex_match_statement", null) != null ? [rule.value.regex_match_statement] : [] + + content { + regex_string = regex_match_statement.value.regex_string + + dynamic "field_to_match" { + for_each = lookup(rule.value.regex_match_statement, "field_to_match", null) != null ? [rule.value.regex_match_statement.field_to_match] : [] + + content { + dynamic "all_query_arguments" { + for_each = lookup(field_to_match.value, "all_query_arguments", null) != null ? [1] : [] + + content {} + } + + dynamic "body" { + for_each = lookup(field_to_match.value, "body", null) != null ? [1] : [] + + content {} + } + + dynamic "method" { + for_each = lookup(field_to_match.value, "method", null) != null ? [1] : [] + + content {} + } + + dynamic "query_string" { + for_each = lookup(field_to_match.value, "query_string", null) != null ? [1] : [] + + content {} + } + + dynamic "single_header" { + for_each = lookup(field_to_match.value, "single_header", null) != null ? [field_to_match.value.single_header] : [] + + content { + name = single_header.value.name + } + } + + dynamic "single_query_argument" { + for_each = lookup(field_to_match.value, "single_query_argument", null) != null ? [field_to_match.value.single_query_argument] : [] + + content { + name = single_query_argument.value.name + } + } + + dynamic "uri_path" { + for_each = lookup(field_to_match.value, "uri_path", null) != null ? [1] : [] + + content {} + } + } + } + + dynamic "text_transformation" { + for_each = lookup(rule.value.regex_match_statement, "text_transformation", null) != null ? [ + for rule in lookup(rule.value.regex_match_statement, "text_transformation") : { + priority = rule.priority + type = rule.type + }] : [] + + content { + priority = text_transformation.value.priority + type = text_transformation.value.type + } + } + } + } + ### NOT STATEMENTS dynamic "not_statement" { for_each = length(lookup(rule.value, "not_statement", {})) == 0 ? [] : [lookup(rule.value, "not_statement", {})] @@ -967,7 +1173,7 @@ resource "aws_wafv2_web_acl" "main" { for_each = length(lookup(rule.value, "visibility_config")) == 0 ? [] : [lookup(rule.value, "visibility_config", {})] content { cloudwatch_metrics_enabled = lookup(visibility_config.value, "cloudwatch_metrics_enabled", true) - metric_name = lookup(visibility_config.value, "metric_name", "${module.labels.id}") + metric_name = lookup(visibility_config.value, "metric_name", module.labels.id) sampled_requests_enabled = lookup(visibility_config.value, "sampled_requests_enabled", true) } } @@ -980,7 +1186,7 @@ resource "aws_wafv2_web_acl" "main" { for_each = length(var.visibility_config) == 0 ? [] : [var.visibility_config] content { cloudwatch_metrics_enabled = lookup(visibility_config.value, "cloudwatch_metrics_enabled", true) - metric_name = lookup(visibility_config.value, "metric_name", "${module.labels.id}") + metric_name = lookup(visibility_config.value, "metric_name", module.labels.id) sampled_requests_enabled = lookup(visibility_config.value, "sampled_requests_enabled", true) } } @@ -990,19 +1196,19 @@ resource "aws_wafv2_web_acl" "main" { # WAFv2 web acl association with ALB ##### resource "aws_wafv2_web_acl_association" "main" { - count = var.waf_enabled && var.web_acl_association && length(var.resource_arn_list) == 0 ? 1 : 0 + count = var.enable && var.waf_enabled && var.web_acl_association && length(var.resource_arn_list) > 0 ? 1 : 0 resource_arn = var.resource_arn - web_acl_arn = join("", aws_wafv2_web_acl.main.*.arn) + web_acl_arn = join("", aws_wafv2_web_acl.main[*].arn) depends_on = [aws_wafv2_web_acl.main] } resource "aws_wafv2_web_acl_association" "alb_list" { - count = var.waf_enabled && var.web_acl_association && length(var.resource_arn_list) > 0 ? length(var.resource_arn_list) : 0 + count = var.enable && var.waf_enabled && var.web_acl_association && length(var.resource_arn_list) > 0 ? length(var.resource_arn_list) : 0 resource_arn = var.resource_arn_list[count.index] - web_acl_arn = join("", aws_wafv2_web_acl.main.*.arn) + web_acl_arn = join("", aws_wafv2_web_acl.main[*].arn) depends_on = [aws_wafv2_web_acl.main] } @@ -1016,31 +1222,117 @@ data "aws_region" "this" {} # ##logs_alb # + +##----------------------------------------------------------------------------- +## Below resource will create kms key. This key will used for encryption of flow logs stored in S3 bucket or cloudwatch log group. +##----------------------------------------------------------------------------- + +resource "aws_kms_key" "kms" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + deletion_window_in_days = var.kms_key_deletion_window + enable_key_rotation = var.enable_key_rotation +} + +resource "aws_kms_alias" "kms-alias" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + name = format("alias/%s-flow-log-key", module.labels.id) + target_key_id = aws_kms_key.kms[0].key_id +} + +##----------------------------------------------------------------------------- +## Below resource will attach policy to above created kms key. The above created key require policy to be attached so that cloudwatch log group can access it. +## It will be only created when vpc flow logs are stored in cloudwatch log group. +##----------------------------------------------------------------------------- +resource "aws_kms_key_policy" "example" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + key_id = aws_kms_key.kms[0].id + policy = jsonencode({ + "Version" : "2012-10-17", + "Id" : "key-default-1", + "Statement" : [{ + "Sid" : "Enable IAM User Permissions", + "Effect" : "Allow", + "Principal" : { + "AWS" : "arn:aws:iam::${data.aws_caller_identity.this.account_id}:root" + }, + "Action" : "kms:*", + "Resource" : "*" + }, + { + "Effect" : "Allow", + "Principal" : { "Service" : "logs.${data.aws_region.this.name}.amazonaws.com" }, + "Action" : [ + "kms:Encrypt*", + "kms:Decrypt*", + "kms:ReEncrypt*", + "kms:GenerateDataKey*", + "kms:Describe*" + ], + "Resource" : "*" + } + ] + }) + +} + #S3 Bucket to store WebACL Traffic Logs. This resource is needed by Amazon Kinesis Firehose as data delivery output target. resource "aws_s3_bucket" "webacl_traffic_information" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 bucket = format("%s-waf-logs", module.labels.id) tags = module.labels.tags } + +resource "aws_s3_bucket_ownership_controls" "webacl_traffic_information" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) + rule { + object_ownership = "BucketOwnerPreferred" + } +} + resource "aws_s3_bucket_acl" "webacl_traffic_information" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 - bucket = join("", aws_s3_bucket.webacl_traffic_information.*.id) + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) acl = "private" + depends_on = [ + aws_s3_bucket_ownership_controls.webacl_traffic_information + ] } + +resource "aws_s3_bucket_server_side_encryption_configuration" "example" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = aws_kms_key.kms[0].arn + sse_algorithm = var.s3_sse_algorithm //"aws:kms" + } + } +} + +resource "aws_s3_bucket_public_access_block" "example" { + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + resource "aws_s3_bucket_versioning" "webacl_traffic_information" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 - bucket = join("", aws_s3_bucket.webacl_traffic_information.*.id) + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) versioning_configuration { status = "Enabled" } } resource "aws_s3_bucket_server_side_encryption_configuration" "webacl_traffic_information" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 - bucket = join("", aws_s3_bucket.webacl_traffic_information.*.id) + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) rule { apply_server_side_encryption_by_default { sse_algorithm = "AES256" @@ -1050,7 +1342,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "webacl_traffic_in # AWS Glue Catalog Database. This resource is needed by Amazon Kinesis Firehose as data format conversion configuration, for transforming from JSON to Parquet. resource "aws_glue_catalog_database" "database" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = format("glue-%s", module.labels.id) description = "Glue Catalog Database for ${lower(module.labels.id)} WAF Logs" @@ -1058,10 +1350,10 @@ resource "aws_glue_catalog_database" "database" { # This table store column information that is needed by Amazon Kinesis Firehose as data format conversion configuration, for transforming from JSON to Parquet. resource "aws_glue_catalog_table" "table" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = format("glue-table-%s", module.labels.id) - database_name = join("", aws_glue_catalog_database.database.*.name) + database_name = join("", aws_glue_catalog_database.database[*].name) description = "Table which stores schema of WAF Logs for ${lower(module.labels.id)} WebACL" @@ -1090,7 +1382,7 @@ resource "aws_glue_catalog_table" "table" { } storage_descriptor { - location = "s3://${join("", aws_s3_bucket.webacl_traffic_information.*.id)}/logs" + location = "s3://${join("", aws_s3_bucket.webacl_traffic_information[*].id)}/logs" input_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat" output_format = "org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat" @@ -1152,20 +1444,21 @@ resource "aws_glue_catalog_table" "table" { # This log group is needed by Amazon Kinesis Firehose for storing delivery error information. resource "aws_cloudwatch_log_group" "firehose_error_logs" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = "/aws/kinesisfirehose/aws-waf-logs-${lower(module.labels.id)}-WebACL" retention_in_days = "30" + kms_key_id = aws_kms_key.kms[0].arn tags = module.labels.tags } # This log stream is the one which hold the information inside the log group above. resource "aws_cloudwatch_log_stream" "firehose_error_logs" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = module.labels.id - log_group_name = join("", aws_cloudwatch_log_group.firehose_error_logs.*.name) + log_group_name = join("", aws_cloudwatch_log_group.firehose_error_logs[*].name) } # Policy document that will allow the Firehose to assume an IAM Role. @@ -1187,7 +1480,7 @@ data "aws_iam_policy_document" "firehose_assume_role_policy" { # IAM Role for the Firehose, so it able to access those resources above. resource "aws_iam_role" "firehose" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = format("ServiceRoleForFirehose_%s_WebACL", module.labels.id) @@ -1210,7 +1503,7 @@ data "aws_iam_policy_document" "allow_s3_actions" { type = "AWS" identifiers = [ - join("", aws_iam_role.firehose.*.arn), + join("", aws_iam_role.firehose[*].arn), ] } @@ -1224,17 +1517,17 @@ data "aws_iam_policy_document" "allow_s3_actions" { ] resources = [ - join("", aws_s3_bucket.webacl_traffic_information.*.arn), - "${join("", aws_s3_bucket.webacl_traffic_information.*.arn)}/*", + join("", aws_s3_bucket.webacl_traffic_information[*].arn), + "${join("", aws_s3_bucket.webacl_traffic_information[*].arn)}/*", ] } } # Attach the policy above to the bucket. resource "aws_s3_bucket_policy" "webacl_traffic_information_lb" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 - bucket = join("", aws_s3_bucket.webacl_traffic_information.*.id) + bucket = join("", aws_s3_bucket.webacl_traffic_information[*].id) policy = data.aws_iam_policy_document.allow_s3_actions.json } @@ -1250,17 +1543,17 @@ data "aws_iam_policy_document" "allow_put_log_events" { effect = "Allow" resources = [ - join("", aws_s3_bucket.webacl_traffic_information.*.arn), + join("", aws_s3_bucket.webacl_traffic_information[*].arn), ] } } # Attach the policy above to the IAM Role. resource "aws_iam_role_policy" "allow_put_log_events" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = "AllowWritingToLogStreams" - role = join("", aws_iam_role.firehose.*.name) + role = join("", aws_iam_role.firehose[*].name) policy = data.aws_iam_policy_document.allow_put_log_events.json } @@ -1277,9 +1570,9 @@ data "aws_iam_policy_document" "allow_glue_get_table_versions" { effect = "Allow" resources = [ - "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:table/${join("", aws_glue_catalog_database.database.*.name)}/logs", - "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:table/${join("", aws_glue_catalog_database.database.*.name)}/${join("", aws_glue_catalog_table.table.*.name)}", - "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:database/${join("", aws_glue_catalog_database.database.*.name)}", + "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:table/${join("", aws_glue_catalog_database.database[*].name)}/logs", + "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:table/${join("", aws_glue_catalog_database.database[*].name)}/${join("", aws_glue_catalog_table.table[*].name)}", + "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:database/${join("", aws_glue_catalog_database.database[*].name)}", "arn:aws:glue:${data.aws_region.this.name}:${data.aws_caller_identity.this.account_id}:catalog", ] } @@ -1287,17 +1580,17 @@ data "aws_iam_policy_document" "allow_glue_get_table_versions" { # Attach the policy above to the IAM Role. resource "aws_iam_role_policy" "allow_glue_get_table_versions" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = format("AllowGettingGlueTableVersions-%s", module.labels.id) - role = join("", aws_iam_role.firehose.*.name) + role = join("", aws_iam_role.firehose[*].name) policy = data.aws_iam_policy_document.allow_glue_get_table_versions.json } # Creating the Firehose. resource "aws_kinesis_firehose_delivery_stream" "waf" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 name = format("aws-waf-logs-%s", module.labels.id) destination = "extended_s3" @@ -1305,8 +1598,8 @@ resource "aws_kinesis_firehose_delivery_stream" "waf" { extended_s3_configuration { - role_arn = join("", aws_iam_role.firehose.*.arn) - bucket_arn = join("", aws_s3_bucket.webacl_traffic_information.*.arn) + role_arn = join("", aws_iam_role.firehose[*].arn) + bucket_arn = join("", aws_s3_bucket.webacl_traffic_information[*].arn) buffering_size = var.firehose_buffer_size buffering_interval = var.firehose_buffer_interval @@ -1316,8 +1609,8 @@ resource "aws_kinesis_firehose_delivery_stream" "waf" { cloudwatch_logging_options { enabled = "true" - log_group_name = join("", aws_cloudwatch_log_group.firehose_error_logs.*.name) - log_stream_name = join("", aws_cloudwatch_log_stream.firehose_error_logs.*.name) + log_group_name = join("", aws_cloudwatch_log_group.firehose_error_logs[*].name) + log_stream_name = join("", aws_cloudwatch_log_stream.firehose_error_logs[*].name) } data_format_conversion_configuration { @@ -1338,9 +1631,9 @@ resource "aws_kinesis_firehose_delivery_stream" "waf" { } schema_configuration { - role_arn = join("", aws_iam_role.firehose.*.arn) - database_name = join("", aws_glue_catalog_table.table.*.database_name) - table_name = join("", aws_glue_catalog_table.table.*.name) + role_arn = join("", aws_iam_role.firehose[*].arn) + database_name = join("", aws_glue_catalog_table.table[*].database_name) + table_name = join("", aws_glue_catalog_table.table[*].name) region = data.aws_region.this.name } } @@ -1354,10 +1647,10 @@ resource "aws_kinesis_firehose_delivery_stream" "waf" { # WAFv2 web acl logging configuration with kinesis firehose ##### resource "aws_wafv2_web_acl_logging_configuration" "main" { - count = var.waf_enabled && var.create_logging_configuration ? 1 : 0 + count = var.enable && var.waf_enabled && var.create_logging_configuration ? 1 : 0 - log_destination_configs = [join("", aws_kinesis_firehose_delivery_stream.waf.*.arn)] - resource_arn = join("", aws_wafv2_web_acl.main.*.arn) + log_destination_configs = [join("", aws_kinesis_firehose_delivery_stream.waf[*].arn)] + resource_arn = join("", aws_wafv2_web_acl.main[*].arn) dynamic "redacted_fields" { for_each = var.redacted_fields diff --git a/outputs.tf b/outputs.tf index 03edb9c..e22a7dd 100644 --- a/outputs.tf +++ b/outputs.tf @@ -1,7 +1,7 @@ # Module : Iam Role # Description : Terraform module to create Iam Role resource on AWS. output "arn" { - value = join("", aws_wafv2_ip_set.main.*.arn) + value = join("", aws_wafv2_ip_set.main[*].arn) description = "The Amazon Resource Name (ARN) specifying the role." } @@ -11,11 +11,11 @@ output "tags" { } output "id" { - value = join("", aws_wafv2_ip_set.main.*.name) + value = join("", aws_wafv2_ip_set.main[*].name) description = "Name of specifying the role." } output "ip_set_arn" { - value = join("", aws_wafv2_ip_set.main.*.arn) + value = join("", aws_wafv2_ip_set.main[*].arn) description = "The ARN of Ip_set" } \ No newline at end of file diff --git a/variables.tf b/variables.tf index dcfa385..1788800 100644 --- a/variables.tf +++ b/variables.tf @@ -21,7 +21,7 @@ variable "repository" { variable "label_order" { type = list(any) - default = [] + default = ["name", "environment"] description = "Label order, e.g. `name`,`application`." } @@ -31,19 +31,12 @@ variable "managedby" { description = "ManagedBy, eg 'CloudDrove'" } -variable "attributes" { - type = list(any) - default = [] - description = "Additional attributes (e.g. `1`)." -} - -variable "tags" { - type = map(any) - default = {} - description = "Additional tags (e.g. map(`BusinessUnit`,`XYZ`)." +variable "enable" { + type = bool + default = true + description = "Flag to control the vpc creation." } - variable "waf_enabled" { type = bool default = false @@ -96,11 +89,6 @@ variable "ip_addresses" { description = "(Required) Contains an array of strings that specify one or more IP addresses or blocks of IP addresses in Classless Inter-Domain Routing (CIDR) notation. AWS WAF supports all address ranges for IP versions IPv4 and IPv6." } -variable "block_sensitive_paths" { - type = bool - default = null -} - variable "rules" { description = "List of WAF rules." type = any @@ -138,3 +126,21 @@ variable "logging_filter" { default = {} description = "A configuration block that specifies which web requests are kept in the logs and which are dropped. You can filter on the rule action and on the web request labels that were applied by matching rules during web ACL evaluation." } + +variable "kms_key_deletion_window" { + type = number + default = 10 + description = "KMS Key deletion window in days." +} + +variable "enable_key_rotation" { + type = bool + default = true + description = "Specifies whether key rotation is enabled. Defaults to true(security best practice)" +} + +variable "s3_sse_algorithm" { + type = string + default = "aws:kms" + description = "Server-side encryption algorithm to use. Valid values are AES256 and aws:kms" +} \ No newline at end of file diff --git a/versions.tf b/versions.tf new file mode 100644 index 0000000..5438a11 --- /dev/null +++ b/versions.tf @@ -0,0 +1,11 @@ +# Terraform version +terraform { + required_version = ">= 1.5.7" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.13.1" + } + } +}