├── .dockerignore ├── .github ├── conventional-commit-lint.yaml ├── release-please.yml ├── renovate.json ├── trusted-contribution.yml └── workflows │ ├── lint.yaml │ └── stale.yml ├── .gitignore ├── CHANGELOG.md ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── build ├── int.cloudbuild.yaml └── lint.cloudbuild.yaml ├── examples ├── juiceshop_example │ ├── README.md │ ├── main.tf │ ├── variables.tf │ ├── versions.tf │ └── waap-demo-proxy-bundle.zip └── web_app_protection_example │ ├── README.md │ ├── build │ ├── .gitlab-ci.yml │ ├── Jenkinsfile │ ├── README.md │ ├── cloudbuild.yaml │ └── pipeline-functions.sh │ ├── environments │ ├── README.md │ ├── dev │ │ ├── backend.tf │ │ └── terraform.tfvars.example │ └── prd │ │ ├── backend.tf │ │ └── terraform.tfvars.example │ ├── main.tf │ ├── scripts │ ├── README.md │ ├── dashboard.json │ └── startup-script.sh │ ├── variables.tf │ └── versions.tf ├── modules ├── apigee │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── mig-network │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf ├── mig │ ├── README.md │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── waap-analytics │ ├── README.md │ ├── main.tf │ ├── variables.tf │ └── versions.tf └── test ├── .gitignore ├── integration ├── discover_test.go ├── go.mod └── go.sum └── setup ├── .gitignore ├── iam.tf ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .terraform 3 | .terraform.d 4 | .kitchen 5 | terraform.tfstate.d 6 | test/fixtures/*/.terraform 7 | test/fixtures/*/terraform.tfstate.d 8 | examples/.kitchen 9 | examples/*/.terraform 10 | examples/*/terraform.tfstate.d 11 | -------------------------------------------------------------------------------- /.github/conventional-commit-lint.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # NOTE: This file is automatically generated from: 16 | # https://github.com/GoogleCloudPlatform/cloud-foundation-toolkit/blob/main/infra/terraform/test-org/github 17 | 18 | enabled: true 19 | always_check_pr_title: true 20 | -------------------------------------------------------------------------------- /.github/release-please.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | releaseType: terraform-module 16 | handleGHRelease: true 17 | primaryBranch: main 18 | bumpMinorPreMajor: true 19 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["github>GoogleCloudPlatform/cloud-foundation-toolkit//infra/terraform/test-org/github/resources/renovate"] 4 | } 5 | -------------------------------------------------------------------------------- /.github/trusted-contribution.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2023-2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # NOTE: This file is automatically generated from: 16 | # https://github.com/GoogleCloudPlatform/cloud-foundation-toolkit/blob/main/infra/terraform/test-org/github 17 | 18 | annotations: 19 | - type: comment 20 | text: "/gcbrun" 21 | trustedContributors: 22 | - release-please[bot] 23 | - renovate[bot] 24 | - renovate-bot 25 | - forking-renovate[bot] 26 | - dependabot[bot] 27 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2023-2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # NOTE: This file is automatically generated from values at: 16 | # https://github.com/GoogleCloudPlatform/cloud-foundation-toolkit/blob/main/infra/terraform/test-org/org/locals.tf 17 | 18 | name: 'lint' 19 | 20 | on: 21 | workflow_dispatch: 22 | pull_request: 23 | branches: 24 | - main 25 | 26 | concurrency: 27 | group: '${{ github.workflow }}-${{ github.head_ref || github.ref }}' 28 | cancel-in-progress: true 29 | 30 | jobs: 31 | lint: 32 | name: 'lint' 33 | runs-on: 'ubuntu-latest' 34 | steps: 35 | - uses: 'actions/checkout@v4' 36 | - id: variables 37 | run: | 38 | MAKEFILE=$(find . -name Makefile -print -quit) 39 | if [ -z "$MAKEFILE" ]; then 40 | echo dev-tools=gcr.io/cloud-foundation-cicd/cft/developer-tools:1 >> "$GITHUB_OUTPUT" 41 | else 42 | VERSION=$(grep "DOCKER_TAG_VERSION_DEVELOPER_TOOLS := " $MAKEFILE | cut -d\ -f3) 43 | IMAGE=$(grep "DOCKER_IMAGE_DEVELOPER_TOOLS := " $MAKEFILE | cut -d\ -f3) 44 | REGISTRY=$(grep "REGISTRY_URL := " $MAKEFILE | cut -d\ -f3) 45 | echo dev-tools=${REGISTRY}/${IMAGE}:${VERSION} >> "$GITHUB_OUTPUT" 46 | fi 47 | - run: docker run --rm -v ${{ github.workspace }}:/workspace ${{ steps.variables.outputs.dev-tools }} module-swapper 48 | - run: docker run --rm -v ${{ github.workspace }}:/workspace ${{ steps.variables.outputs.dev-tools }} /usr/local/bin/test_lint.sh 49 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022-2025 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # NOTE: This file is automatically generated from: 16 | # https://github.com/GoogleCloudPlatform/cloud-foundation-toolkit/blob/main/infra/terraform/test-org/github 17 | 18 | name: "Close stale issues" 19 | on: 20 | schedule: 21 | - cron: "0 23 * * *" 22 | 23 | jobs: 24 | stale: 25 | if: github.repository_owner == 'GoogleCloudPlatform' || github.repository_owner == 'terraform-google-modules' 26 | runs-on: ubuntu-latest 27 | steps: 28 | - uses: actions/stale@v9 29 | with: 30 | repo-token: ${{ secrets.GITHUB_TOKEN }} 31 | stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' 32 | stale-pr-message: 'This PR is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' 33 | exempt-issue-labels: 'triaged' 34 | exempt-pr-labels: 'dependencies,autorelease: pending' 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX leaves these everywhere on SMB shares 2 | ._* 3 | 4 | # OSX trash 5 | .DS_Store 6 | 7 | # Python 8 | *.pyc 9 | 10 | # Emacs save files 11 | *~ 12 | \#*\# 13 | .\#* 14 | 15 | # Vim-related files 16 | [._]*.s[a-w][a-z] 17 | [._]s[a-w][a-z] 18 | *.un~ 19 | Session.vim 20 | .netrwhist 21 | 22 | ### https://raw.github.com/github/gitignore/90f149de451a5433aebd94d02d11b0e28843a1af/Terraform.gitignore 23 | 24 | # Local .terraform directories 25 | **/.terraform/* 26 | 27 | # .tfstate files 28 | *.tfstate 29 | *.tfstate.* 30 | 31 | # Crash log files 32 | crash.log 33 | 34 | # Kitchen files 35 | **/inspec.lock 36 | **/.kitchen 37 | **/kitchen.local.yml 38 | **/Gemfile.lock 39 | 40 | # Ignore any .tfvars files that are generated automatically for each Terraform run. Most 41 | # .tfvars files are managed as part of configuration and so should be included in 42 | # version control. 43 | #**/*.tfvars 44 | 45 | credentials.json 46 | 47 | # tf lock file 48 | .terraform.lock.hcl 49 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on 6 | [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 7 | and this project adheres to 8 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 9 | This changelog is generated automatically based on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 10 | 11 | ## 0.1.0 (2023-12-20) 12 | 13 | 14 | ### ⚠ BREAKING CHANGES 15 | 16 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) 17 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) 18 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) 19 | * use upstream Apigee module 20 | 21 | ### Features 22 | 23 | * [CIT-109] Added new APIs to be enabled ([#20](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/20)) ([4e66487](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/4e664876817bb39765bb1da73df46e31ccacc332)) 24 | * [CIT-113] Creating folders structure. ([#19](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/19)) ([804ada0](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/804ada0f5146d0eb20e3e39ec11b9c213d58dd32)) 25 | * [CIT-113] Creating module for mig. ([#21](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/21)) ([9e0082a](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/9e0082af379ad9c11907f8214c7aacffd3268fd2)) 26 | * add Cloud Armor to web-app-protection example ([#26](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/26)) ([5c260aa](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/5c260aaf7854d4392869661abdf7905f263ee70d)) 27 | * Add waap-analytics module ([#4](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/4)) ([5cfb19c](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/5cfb19c969e61a7c40cbaef6ea234aa84b98abf2)) 28 | * Added traffic splitting option between backends ([#48](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/48)) ([3ce2925](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/3ce292584a7372d4bdb7234e6d41875dab10f9e8)) 29 | * Adding Cloud Build support to web_app_protection_example ([#43](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/43)) ([02f4203](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/02f420319cafbb52bc5ecf9987c40ccbf435098a)) 30 | * Created custom dashboard in cloud monitoring to web_app_protection_example ([#41](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/41)) ([6b11e48](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/6b11e48daafba99f2cbe732251efdebc5902f9b1)) 31 | * created pipeline file for gitlab ci ([c440786](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/c4407864bf3e42792a6691634ba3810182322355)) 32 | * created pipeline file for gitlab ci ([71001db](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/71001dbd2c4a1697ffdd8366600829238dbae077)) 33 | * Feature/jenkinsfile ([#22](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/22)) ([2b9e93d](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/2b9e93dd96e72c82c1a8a29aa5eea6b59c481063)) 34 | * glb backend ([#23](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/23)) ([7664a96](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/7664a96d7c553301f0fb21ddc26efecaa30a83ac)) 35 | * Log Sink Exclusions to BQ ([#11](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/11)) ([5e5a050](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/5e5a050b0a79e9d9d03b38d7ebfef1bf829bc6f8)) 36 | * multi-environment web app protection example ([#30](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/30)) ([b15c395](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/b15c395fc2cd96a5141eb095ab60765eb9b6d287)) 37 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) ([d8d9d1a](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/d8d9d1a95e82ea411fc389feb72e4558dd1e0696)) 38 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) ([059d200](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/059d2009da6b31a8200518fdacae1ddf0405fa67)) 39 | * Upgrade Apigee module ([#45](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/45)) ([a806028](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/a8060287d68056c048db5df4b9e5982e8deaa433)) 40 | * use upstream Apigee module ([130a50f](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/130a50f01cb32cac6aa0a6554690a1d9da14a929)) 41 | * WAAP v2 changes ([#3](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/3)) ([9b9e85f](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/9b9e85fd7e8f241a8ae2ed5c3754bdd681931d65)) 42 | 43 | 44 | ### Bug Fixes 45 | 46 | * add provider meta attrib ([#32](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/32)) ([f6a6589](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/f6a65897af0e47e26d5ff353e37e123b2450eb00)) 47 | * changed pipeline files and updated readme ([#49](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/49)) ([cac1135](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/cac11356f3751a882e3d284c7c9047431697d26a)) 48 | * cloud armor rule descriptions & update to proxy bundle ([#27](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/27)) ([5078cef](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/5078cef090b6791528ddb4acb8dcc8a30f61c88f)) 49 | * doc generation ([bd6c3a1](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/bd6c3a1c41e4a687935a279b9d546abff59d2ebc)) 50 | * doc generation ([af913fb](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/af913fb6200f71a7f880bb3565477c5c1a2ea3aa)) 51 | * docs ([13a7d79](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/13a7d79b0cf5d146aabc98da4054173c882153cc)) 52 | * docs ([7ce0783](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/7ce0783b30c011ba7fd2f5a1305bf280dc751bc0)) 53 | * docs ([fabdbe9](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/fabdbe9735220707963c8de46450f6e4d4ad3bfa)) 54 | * docs ([eaa8a43](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/eaa8a43c96abe0b7b4daa42dfede58c2d453f8dc)) 55 | * gcloud create recaptcha key with waf_feature ([#7](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/7)) ([a86a1ab](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/a86a1ab63f921c0b5b68ecc1189c2bd294cd3ce9)) 56 | * increase timeout ([c07f5c4](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/c07f5c451508a4dc917ddcf2b309487b050223da)) 57 | * increase timeout ([66794a7](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/66794a72508f2b80556c50c9c553cc9cacbd0862)) 58 | * Refactor web-app-protection example for multi environment usage ([#28](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/28)) ([46face2](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/46face2ebbd5b81f5333ea40d3854e82a10b2e76)) 59 | * renamed jenkins-functions.sh and updated references in web_app_protection_example ([#44](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/44)) ([ee1b2b4](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/ee1b2b49dcbe224d00aff52d1c2fa2d437030e48)) 60 | * space indentation ([81ba8f8](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/81ba8f8e33c7f31a883d46f3d624044f25b6949d)) 61 | * space indentation ([1fdfb15](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/1fdfb15674d70bf8f33f6504b7ae15bb52523d49)) 62 | * update CA rules ([#6](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/6)) ([50fc209](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/50fc209d951d981b308e24e980a185b89c81e876)) 63 | * Updated variable descriptions, references and fixed target_tags in web_app_protection_example ([#40](https://github.com/GoogleCloudPlatform/terraform-google-waap/issues/40)) ([6947b42](https://github.com/GoogleCloudPlatform/terraform-google-waap/commit/6947b4249256eeb165ff7a26404945236425e670)) 64 | 65 | ## [0.1.0](https://github.com/terraform-google-modules/terraform-google-waap/releases/tag/v0.1.0) - 20XX-YY-ZZ 66 | 67 | ### Features 68 | 69 | - Initial release 70 | 71 | [0.1.0]: https://github.com/terraform-google-modules/terraform-google-waap/releases/tag/v0.1.0 72 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # NOTE: This file is automatically generated from values at: 2 | # https://github.com/GoogleCloudPlatform/cloud-foundation-toolkit/blob/main/infra/terraform/test-org/org/locals.tf 3 | 4 | * @GoogleCloudPlatform/blueprint-solutions 5 | 6 | # NOTE: GitHub CODEOWNERS locations: 7 | # https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-and-branch-protection 8 | 9 | CODEOWNERS @GoogleCloudPlatform/blueprint-solutions 10 | .github/CODEOWNERS @GoogleCloudPlatform/blueprint-solutions 11 | docs/CODEOWNERS @GoogleCloudPlatform/blueprint-solutions 12 | 13 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement (CLA). You (or your employer) retain the copyright to your 10 | contribution; this simply gives us permission to use and redistribute your 11 | contributions as part of the project. Head over to 12 | to see your current agreements on file or 13 | to sign a new one. 14 | 15 | You generally only need to submit a CLA once, so if you've already submitted one 16 | (even if it was for a different project), you probably don't need to do it 17 | again. 18 | 19 | ## Code Reviews 20 | 21 | All submissions, including submissions by project members, require review. We 22 | use GitHub pull requests for this purpose. Consult 23 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 24 | information on using pull requests. 25 | 26 | ## Development 27 | 28 | The following dependencies must be installed on the development system: 29 | 30 | - [Docker Engine][docker-engine] 31 | - [Google Cloud SDK][google-cloud-sdk] 32 | - [make] 33 | 34 | ### Generating Documentation for Inputs and Outputs 35 | 36 | The Inputs and Outputs tables in the READMEs of the root module, 37 | submodules, and example modules are automatically generated based on 38 | the `variables` and `outputs` of the respective modules. These tables 39 | must be refreshed if the module interfaces are changed. 40 | 41 | #### Execution 42 | 43 | Run `make generate_docs` to generate new Inputs and Outputs tables. 44 | 45 | ### Integration Testing 46 | 47 | Integration tests are used to verify the behaviour of the root module, 48 | submodules, and example modules. Additions, changes, and fixes should 49 | be accompanied with tests. 50 | 51 | The integration tests are run using [Kitchen][kitchen], 52 | [Kitchen-Terraform][kitchen-terraform], and [InSpec][inspec]. These 53 | tools are packaged within a Docker image for convenience. 54 | 55 | The general strategy for these tests is to verify the behaviour of the 56 | [example modules](./examples/), thus ensuring that the root module, 57 | submodules, and example modules are all functionally correct. 58 | 59 | #### Test Environment 60 | The easiest way to test the module is in an isolated test project. The setup for such a project is defined in [test/setup](./test/setup/) directory. 61 | 62 | To use this setup, you need a service account with these permissions (on a Folder or Organization): 63 | - Project Creator 64 | - Project Billing Manager 65 | 66 | The project that the service account belongs to must have the following APIs enabled (the setup won't 67 | create any resources on the service account's project): 68 | - Cloud Resource Manager 69 | - Cloud Billing 70 | - Service Usage 71 | - Identity and Access Management (IAM) 72 | 73 | Export the Service Account credentials to your environment like so: 74 | 75 | ``` 76 | export SERVICE_ACCOUNT_JSON=$(< credentials.json) 77 | ``` 78 | 79 | You will also need to set a few environment variables: 80 | ``` 81 | export TF_VAR_org_id="your_org_id" 82 | export TF_VAR_folder_id="your_folder_id" 83 | export TF_VAR_billing_account="your_billing_account_id" 84 | ``` 85 | 86 | With these settings in place, you can prepare a test project using Docker: 87 | ``` 88 | make docker_test_prepare 89 | ``` 90 | 91 | #### Noninteractive Execution 92 | 93 | Run `make docker_test_integration` to test all of the example modules 94 | noninteractively, using the prepared test project. 95 | 96 | #### Interactive Execution 97 | 98 | 1. Run `make docker_run` to start the testing Docker container in 99 | interactive mode. 100 | 101 | 1. Run `kitchen_do create ` to initialize the working 102 | directory for an example module. 103 | 104 | 1. Run `kitchen_do converge ` to apply the example module. 105 | 106 | 1. Run `kitchen_do verify ` to test the example module. 107 | 108 | 1. Run `kitchen_do destroy ` to destroy the example module 109 | state. 110 | 111 | ### Linting and Formatting 112 | 113 | Many of the files in the repository can be linted or formatted to 114 | maintain a standard of quality. 115 | 116 | #### Execution 117 | 118 | Run `make docker_test_lint`. 119 | 120 | [docker-engine]: https://www.docker.com/products/docker-engine 121 | [flake8]: http://flake8.pycqa.org/en/latest/ 122 | [gofmt]: https://golang.org/cmd/gofmt/ 123 | [google-cloud-sdk]: https://cloud.google.com/sdk/install 124 | [hadolint]: https://github.com/hadolint/hadolint 125 | [inspec]: https://inspec.io/ 126 | [kitchen-terraform]: https://github.com/newcontext-oss/kitchen-terraform 127 | [kitchen]: https://kitchen.ci/ 128 | [make]: https://en.wikipedia.org/wiki/Make_(software) 129 | [shellcheck]: https://www.shellcheck.net/ 130 | [terraform-docs]: https://github.com/segmentio/terraform-docs 131 | [terraform]: https://terraform.io/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # Please note that this file was generated from [terraform-google-module-template](https://github.com/terraform-google-modules/terraform-google-module-template). 16 | # Please make sure to contribute relevant changes upstream! 17 | 18 | # Make will use bash instead of sh 19 | SHELL := /usr/bin/env bash 20 | 21 | DOCKER_TAG_VERSION_DEVELOPER_TOOLS := 1 22 | DOCKER_IMAGE_DEVELOPER_TOOLS := cft/developer-tools 23 | REGISTRY_URL := gcr.io/cloud-foundation-cicd 24 | 25 | # Enter docker container for local development 26 | .PHONY: docker_run 27 | docker_run: 28 | docker run --rm -it \ 29 | -e SERVICE_ACCOUNT_JSON \ 30 | -v "$(CURDIR)":/workspace \ 31 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 32 | /bin/bash 33 | 34 | # Execute prepare tests within the docker container 35 | .PHONY: docker_test_prepare 36 | docker_test_prepare: 37 | docker run --rm -it \ 38 | -e SERVICE_ACCOUNT_JSON \ 39 | -e TF_VAR_org_id \ 40 | -e TF_VAR_folder_id \ 41 | -e TF_VAR_billing_account \ 42 | -v "$(CURDIR)":/workspace \ 43 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 44 | /usr/local/bin/execute_with_credentials.sh prepare_environment 45 | 46 | # Clean up test environment within the docker container 47 | .PHONY: docker_test_cleanup 48 | docker_test_cleanup: 49 | docker run --rm -it \ 50 | -e SERVICE_ACCOUNT_JSON \ 51 | -e TF_VAR_org_id \ 52 | -e TF_VAR_folder_id \ 53 | -e TF_VAR_billing_account \ 54 | -v "$(CURDIR)":/workspace \ 55 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 56 | /usr/local/bin/execute_with_credentials.sh cleanup_environment 57 | 58 | # Execute integration tests within the docker container 59 | .PHONY: docker_test_integration 60 | docker_test_integration: 61 | docker run --rm -it \ 62 | -e SERVICE_ACCOUNT_JSON \ 63 | -v "$(CURDIR)":/workspace \ 64 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 65 | /usr/local/bin/test_integration.sh 66 | 67 | # Execute lint tests within the docker container 68 | .PHONY: docker_test_lint 69 | docker_test_lint: 70 | docker run --rm -it \ 71 | -e EXCLUDE_LINT_DIRS \ 72 | -v "$(CURDIR)":/workspace \ 73 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 74 | /usr/local/bin/test_lint.sh 75 | 76 | # Generate documentation 77 | .PHONY: docker_generate_docs 78 | docker_generate_docs: 79 | docker run --rm -it \ 80 | -v "$(CURDIR)":/workspace \ 81 | $(REGISTRY_URL)/${DOCKER_IMAGE_DEVELOPER_TOOLS}:${DOCKER_TAG_VERSION_DEVELOPER_TOOLS} \ 82 | /bin/bash -c 'source /usr/local/bin/task_helper_functions.sh && generate_docs' 83 | 84 | # Alias for backwards compatibility 85 | .PHONY: generate_docs 86 | generate_docs: docker_generate_docs 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Web Application and API Protection (WAAP) Blueprint 2 | 3 | This repository contains Terraform modules and example configurations to deploy the [Web Application and API Protection (WAAP)](https://cloud.google.com/solutions/web-app-and-api-protection) solution on Google Cloud. 4 | 5 | 6 | ## Usage 7 | 8 | Refer to the [JuiceShop Example](./examples/juiceshop_example/) for a functional example deployment of the WAAP solution. 9 | 10 | 11 | 12 | 13 | 14 | ## Requirements 15 | 16 | These sections describe requirements for using this module. 17 | 18 | ### Software 19 | 20 | The following dependencies must be available: 21 | 22 | - [Terraform][terraform] v0.13 23 | - [Terraform Provider for GCP][terraform-provider-gcp] plugin v3.53 24 | 25 | ### Service Account 26 | 27 | A service account with the following roles must be used to provision 28 | the resources of this module: 29 | 30 | - Editor `roles/editor` 31 | - reCAPTCHA Enterprise Admin: `roles/recaptchaenterprise.admin` 32 | - Artifact Registry Admin: `roles/artifactregistry.admin` 33 | 34 | The [Project Factory module][project-factory-module] and the 35 | [IAM module][iam-module] may be used in combination to provision a 36 | service account with the necessary roles applied. 37 | 38 | ### APIs 39 | 40 | A project with the following APIs enabled must be used to host the 41 | resources of this module: 42 | 43 | - Apigee API: `apigee.googleapis.com` 44 | - Artifact Registry API: `artifactregistry.googleapis.com` 45 | - Cloud Build API: `cloudbuild.googleapis.com` 46 | - Cloud KMS API: `cloudkms.googleapis.com` 47 | - Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` 48 | - Compute API: `compute.googleapis.com` 49 | - Data Loss Prevention API: `dlp.googleapis.com` 50 | - Identity and Access Management API: `iam.googleapis.com` 51 | - Cloud Monitoring API: `monitoring.googleapis.com` 52 | - reCAPTCHA Enterprise API: `recaptchaenterprise.googleapis.com` 53 | - Service Networking API: `servicenetworking.googleapis.com` 54 | - Service Usage API: `serviceusage.googleapis.com` 55 | 56 | The [Project Factory module][project-factory-module] can be used to 57 | provision a project with the necessary APIs enabled. See [this example](./test/setup/main.tf) for properly configuring project factory to enable these APIs. 58 | 59 | ## Contributing 60 | 61 | Refer to the [contribution guidelines](./CONTRIBUTING.md) for 62 | information on contributing to this module. 63 | 64 | [iam-module]: https://registry.terraform.io/modules/terraform-google-modules/iam/google 65 | [project-factory-module]: https://registry.terraform.io/modules/terraform-google-modules/project-factory/google 66 | [terraform-provider-gcp]: https://www.terraform.io/docs/providers/google/index.html 67 | [terraform]: https://www.terraform.io/downloads.html 68 | 69 | ## Security Disclosures 70 | 71 | Please see our [security disclosure process](./SECURITY.md). 72 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | To report a security issue, please use http://g.co/vulnz. We use 2 | http://g.co/vulnz for our intake, and do coordination and disclosure here on 3 | GitHub (including using GitHub Security Advisory). The Google Security Team will 4 | respond within 5 working days of your report on g.co/vulnz. 5 | -------------------------------------------------------------------------------- /build/int.cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 Google LLC 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | timeout: 9600s 17 | steps: 18 | - id: swap-module-refs 19 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 20 | args: ['module-swapper'] 21 | - id: prepare 22 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 23 | args: ['/bin/bash', '-c', 'source /usr/local/bin/task_helper_functions.sh && prepare_environment && sleep 120'] 24 | env: 25 | - 'TF_VAR_org_id=$_ORG_ID' 26 | - 'TF_VAR_folder_id=$_FOLDER_ID' 27 | - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' 28 | - 'TF_VAR_project_id=$(terraform output -raw project_id)' 29 | 30 | # juiceshop_example (WAAP) 31 | - id: juiceshop-example-init 32 | waitFor: 33 | - prepare 34 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 35 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/juiceshop_example --stage init --verbose'] 36 | - id: juiceshop-example-apply 37 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 38 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/juiceshop_example --stage apply --verbose'] 39 | - id: juiceshop-example-verify 40 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 41 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/juiceshop_example --stage verify --verbose'] 42 | - id: juiceshop-example-teardown 43 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 44 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/juiceshop_example --stage teardown --verbose'] 45 | 46 | # webapp_protection_example (multi-env) 47 | - id: webapp-protection-multienv-example-init 48 | waitFor: 49 | - prepare 50 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 51 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/web_app_protection_example --stage init --verbose'] 52 | - id: webapp-protection-multienv-example-apply 53 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 54 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/web_app_protection_example --stage apply --verbose'] 55 | - id: webapp-protection-multienv-example-verify 56 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 57 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/web_app_protection_example --stage verify --verbose'] 58 | - id: webapp-protection-multienv-example-teardown 59 | name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 60 | args: ['/bin/bash', '-c', 'cft test run TestAll/examples/web_app_protection_example --stage teardown --verbose'] 61 | tags: 62 | - 'ci' 63 | - 'integration' 64 | substitutions: 65 | _DOCKER_IMAGE_DEVELOPER_TOOLS: 'cft/developer-tools' 66 | _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '1' 67 | -------------------------------------------------------------------------------- /build/lint.cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2019 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # https://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | steps: 16 | - name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' 17 | id: 'lint' 18 | args: ['/usr/local/bin/test_lint.sh'] 19 | tags: 20 | - 'ci' 21 | - 'lint' 22 | substitutions: 23 | _DOCKER_IMAGE_DEVELOPER_TOOLS: 'cft/developer-tools' 24 | _DOCKER_TAG_VERSION_DEVELOPER_TOOLS: '1' 25 | -------------------------------------------------------------------------------- /examples/juiceshop_example/README.md: -------------------------------------------------------------------------------- 1 | # JuiceShop WAAP Example 2 | 3 | This usage example shows how to successfully deploy the WAAP solution to increase security of the [OWASP JuiceShop](https://github.com/juice-shop/juice-shop) application and APIs. 4 | 5 | This example deploys: 6 | - Apigee Organization, Instance, Environment Group, Environment 7 | - Apigee configuration (proxy deployment, target server, product, developer, credentials) 8 | - Private Service Connect Network Endpoint Group (NEG) 9 | - Cloud KMS Keyring and Keys 10 | - VPC Network, Subnets, Cloud Router 11 | - Firewall Rules 12 | - GCE Managed Instance Group running JuiceShop application 13 | - HTTPS Load Balancer with Cloud Armor Security Policy 14 | - reCAPTCHA Enterprise Key 15 | - Google Managed SSL Certificates 16 | - Artifact Registry Repository 17 | - BigQuery dataset for log analysis 18 | 19 | ## Requirements 20 | * A GCP Project with the APIs listed below enabled ([Example](../../test/setup/main.tf)) 21 | * Grant your user the permissions outlined below before deploying. 22 | * Run `gcloud auth application-default login` before following the steps below. 23 | 24 | ### IAM Permissions 25 | 26 | The user or service account deploying this example must have the following IAM roles: 27 | 28 | - Editor `roles/editor` 29 | - reCAPTCHA Enterprise Admin: `roles/recaptchaenterprise.admin` 30 | - Artifact Registry Admin: `roles/artifactregistry.admin` 31 | 32 | The [Project Factory module][project-factory-module] and the 33 | [IAM module][iam-module] may be used in combination to provision a 34 | service account with the necessary roles applied. 35 | 36 | ### APIs 37 | 38 | A project with the following APIs enabled must be used to host the 39 | resources of this module: 40 | 41 | - Apigee API: `apigee.googleapis.com` 42 | - Artifact Registry API: `artifactregistry.googleapis.com` 43 | - Cloud Build API: `cloudbuild.googleapis.com` 44 | - Cloud KMS API: `cloudkms.googleapis.com` 45 | - Cloud Resource Manager API: `cloudresourcemanager.googleapis.com` 46 | - Compute API: `compute.googleapis.com` 47 | - Data Loss Prevention API: `dlp.googleapis.com` 48 | - Identity and Access Management API: `iam.googleapis.com` 49 | - Cloud Monitoring API: `monitoring.googleapis.com` 50 | - reCAPTCHA Enterprise API: `recaptchaenterprise.googleapis.com` 51 | - Service Networking API: `servicenetworking.googleapis.com` 52 | - Service Usage API: `serviceusage.googleapis.com` 53 | 54 | The [Project Factory module][project-factory-module] can be used to 55 | provision a project with the necessary APIs enabled. See [this example](./test/setup/main.tf) for properly configuring project factory to enable these APIs. 56 | 57 | ## Setup 58 | 59 | To deploy this example: 60 | 61 | ### Build the infrastructure 62 | 1. Clone `waap` repo 63 | ```sh 64 | git clone https://github.com/GoogleCloudPlatform/terraform-google-waap.git 65 | ``` 66 | 2. Change to `juiceshop_example` directory: 67 | ```sh 68 | cd terraform-google-waap/examples/juiceshop_example 69 | ``` 70 | 3. Run `terraform init` from within this example directory. 71 | ```sh 72 | terraform init 73 | ``` 74 | 4. (Optional) Create a `terraform.tfvars` file to provide values for `project_id` and optionally `region`. 75 | 76 | 5. Run `terraform apply` within this example directory. If you skipped step 4, you may be prompted for the `project_id` value at this stage. Deployment may take up to 60 minutes. 77 | 78 | 79 | ## Inputs 80 | 81 | | Name | Description | Type | Default | Required | 82 | |------|-------------|------|---------|:--------:| 83 | | project\_id | GCP Project ID in which to create example resources | `string` | n/a | yes | 84 | | region | Region in which to create regional resources. | `string` | `"us-central1"` | no | 85 | 86 | ## Outputs 87 | 88 | No outputs. 89 | 90 | 91 | -------------------------------------------------------------------------------- /examples/juiceshop_example/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | module "vpc" { 19 | source = "terraform-google-modules/network/google" 20 | version = "~> 4.0" 21 | 22 | project_id = var.project_id 23 | network_name = "waap-vpc" 24 | routing_mode = "GLOBAL" 25 | 26 | subnets = [ 27 | { 28 | subnet_name = "apigee-neg-subnet" 29 | subnet_ip = "10.0.4.0/22" 30 | subnet_region = var.region 31 | }, 32 | { 33 | subnet_name = "juiceshop-subnet" 34 | subnet_ip = "10.0.8.0/22" 35 | subnet_region = var.region 36 | } 37 | ] 38 | } 39 | 40 | module "cloud_router" { 41 | source = "terraform-google-modules/cloud-router/google" 42 | version = "~> 4.0" 43 | project = var.project_id 44 | name = "juiceshop-router" 45 | network = module.vpc.network_name 46 | region = var.region 47 | 48 | nats = [{ 49 | name = "juiceshop-nat" 50 | }] 51 | } 52 | 53 | module "apigee" { 54 | source = "GoogleCloudPlatform/waap/google//modules/apigee" 55 | version = "~> 0.1" 56 | 57 | project_id = var.project_id 58 | create_apigee_org = true 59 | billing_type = "EVALUATION" 60 | analytics_region = var.region 61 | prevent_key_destroy = false 62 | apigee_environments = { 63 | demo = { 64 | display_name = "demo" 65 | description = "Juiceshop Demo env" 66 | envgroups = ["demo"] 67 | regions = [var.region] 68 | } 69 | } 70 | apigee_envgroups = { 71 | demo = [module.nip_apigee_hostname.hostname] 72 | } 73 | apigee_instances = { 74 | (var.region) = { 75 | display_name = "instance-1" 76 | runtime_ip_cidr_range = "10.0.0.0/22" 77 | troubleshooting_ip_cidr_range = "10.1.0.0/28" 78 | } 79 | } 80 | network_id = module.vpc.network_id 81 | subnet_id = module.vpc.subnets_ids[0] 82 | 83 | ssl_certificate = module.nip_apigee_hostname.ssl_certificate 84 | external_ip = module.nip_apigee_hostname.ip_address 85 | } 86 | 87 | module "nip_apigee_hostname" { 88 | source = "github.com/apigee/terraform-modules//modules/nip-development-hostname?ref=v0.12.0" 89 | 90 | project_id = var.project_id 91 | address_name = "apigee-external" 92 | subdomain_prefixes = ["demo"] 93 | } 94 | 95 | # ---------------------------------------------------------------------------------------------------------------------- 96 | # Configure 3P Apigee 97 | # ---------------------------------------------------------------------------------------------------------------------- 98 | data "google_client_config" "current" {} 99 | 100 | resource "google_service_account" "apigee_waap" { 101 | project = var.project_id 102 | account_id = "apigee-waap" 103 | display_name = "apigee-waap" 104 | } 105 | 106 | resource "google_project_iam_member" "apigee_waap" { 107 | for_each = toset(local.apigee_waap_svc_account_roles) 108 | project = var.project_id 109 | role = each.value 110 | member = "serviceAccount:${google_service_account.apigee_waap.email}" 111 | } 112 | 113 | provider "apigee" { 114 | access_token = data.google_client_config.current.access_token 115 | organization = module.apigee.apigee_org_id 116 | server = "apigee.googleapis.com" 117 | } 118 | 119 | resource "apigee_proxy" "juiceshop_proxy" { 120 | name = "waap-demo-proxy-bundle" 121 | bundle = "${path.module}/waap-demo-proxy-bundle.zip" 122 | bundle_hash = filebase64sha256("${path.module}/waap-demo-proxy-bundle.zip") 123 | 124 | depends_on = [ 125 | module.apigee 126 | ] 127 | } 128 | 129 | resource "apigee_proxy_deployment" "juiceshop_proxy_deployment" { 130 | proxy_name = apigee_proxy.juiceshop_proxy.name 131 | environment_name = "demo" 132 | revision = apigee_proxy.juiceshop_proxy.revision 133 | service_account = google_service_account.apigee_waap.email 134 | depends_on = [ 135 | apigee_target_server.target_server 136 | ] 137 | } 138 | 139 | resource "apigee_target_server" "target_server" { 140 | environment_name = "demo" 141 | name = "waap-demo-ts" 142 | host = module.nip_juiceshop_hostname.hostname 143 | port = 443 144 | ssl_enabled = true 145 | 146 | depends_on = [ 147 | module.apigee 148 | ] 149 | } 150 | 151 | resource "apigee_product" "juiceshop_product" { 152 | name = "waap-product" 153 | display_name = "waap-product" 154 | auto_approval_type = true 155 | environments = [ 156 | "demo" 157 | ] 158 | # scopes = [ 159 | # "openid", 160 | # "profile" 161 | # ] 162 | attributes = { 163 | access = "public" 164 | } 165 | operation { 166 | api_source = apigee_proxy.juiceshop_proxy.name 167 | path = "/" 168 | methods = ["GET", "POST", "PUT", "DELETE", "PATCH"] 169 | } 170 | } 171 | 172 | resource "apigee_developer" "example" { 173 | email = "developer@waap.com" 174 | first_name = "WAAP" 175 | last_name = "Developer" 176 | user_name = "waap" 177 | 178 | depends_on = [ 179 | module.apigee 180 | ] 181 | } 182 | 183 | resource "apigee_developer_app" "example" { 184 | developer_email = apigee_developer.example.email 185 | name = "JuiceShop" 186 | attributes = { 187 | siteKey = local.recaptcha_key_id 188 | } 189 | } 190 | 191 | resource "random_string" "consumer_key" { 192 | length = 16 193 | special = false 194 | } 195 | 196 | resource "random_string" "consumer_secret" { 197 | length = 16 198 | special = false 199 | } 200 | 201 | resource "apigee_developer_app_credential" "example" { 202 | developer_email = apigee_developer.example.email 203 | developer_app_name = apigee_developer_app.example.name 204 | consumer_key = random_string.consumer_key.result 205 | consumer_secret = random_string.consumer_secret.result 206 | api_products = [ 207 | apigee_product.juiceshop_product.name 208 | ] 209 | } 210 | 211 | # ---------------------------------------------------------------------------------------------------------------------- 212 | # JuiceShop App 213 | # ---------------------------------------------------------------------------------------------------------------------- 214 | 215 | module "recaptcha_key_gcloud" { 216 | source = "terraform-google-modules/gcloud/google" 217 | version = "~> 3.1.2" 218 | 219 | platform = "linux" 220 | additional_components = ["jq"] 221 | 222 | create_cmd_entrypoint = "gcloud" 223 | create_cmd_body = "recaptcha keys create --web --display-name=${local.recaptcha_key_name} --waf-feature=SESSION_TOKEN --waf-service=CA --integration-type=SCORE --domains=${module.nip_juiceshop_hostname.hostname} --project=${var.project_id}" 224 | } 225 | 226 | module "recaptcha_keys_list_gcloud" { 227 | source = "terraform-google-modules/gcloud/google" 228 | version = "~> 3.1.2" 229 | 230 | platform = "linux" 231 | 232 | create_cmd_entrypoint = "gcloud" 233 | create_cmd_body = "recaptcha keys list --format=json --project=${var.project_id} --filter=\"displayName=${local.recaptcha_key_name}\" > ${path.module}/recaptcha_keys_list.json" 234 | module_depends_on = [ 235 | module.recaptcha_key_gcloud 236 | ] 237 | } 238 | 239 | data "local_file" "recaptcha_keys_list_file" { 240 | filename = "${path.module}/recaptcha_keys_list.json" 241 | depends_on = [ 242 | module.recaptcha_keys_list_gcloud 243 | ] 244 | } 245 | 246 | resource "google_artifact_registry_repository" "waap_repo" { 247 | format = "DOCKER" 248 | location = var.region 249 | project = var.project_id 250 | repository_id = "waap-repo" 251 | } 252 | 253 | 254 | # Clone Git Repo 255 | resource "null_resource" "git_clone_source" { 256 | provisioner "local-exec" { 257 | command = "git clone https://github.com/ssvaidyanathan/juice-shop.git ${path.module}/juice-shop" 258 | } 259 | } 260 | 261 | resource "time_sleep" "wait_for_git_seconds" { 262 | depends_on = [ 263 | null_resource.git_clone_source 264 | ] 265 | 266 | create_duration = "60s" 267 | } 268 | 269 | # Build Docker Image 270 | module "build_juiceshop_image" { 271 | source = "terraform-google-modules/gcloud/google" 272 | version = "~> 2.0" 273 | 274 | platform = "linux" 275 | 276 | create_cmd_entrypoint = "gcloud" 277 | create_cmd_body = "builds submit ${path.module}/juice-shop/ --config=${path.module}/juice-shop/cloudbuild.yaml --project=${var.project_id} --substitutions=_API_ENDPOINT=https://${module.nip_apigee_hostname.hostname},_BASEPATH=/owasp,_APIKEY=${apigee_developer_app_credential.example.consumer_key},_RECAPTCHA_KEY=${local.recaptcha_key_id},_IMAGETAG=${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.waap_repo.repository_id}/juiceshop-image" 278 | 279 | module_depends_on = [ 280 | time_sleep.wait_for_git_seconds 281 | ] 282 | } 283 | 284 | # ---------------------------------------------------------------------------------------------------------------------- 285 | # JuiceShop Infra 286 | # ---------------------------------------------------------------------------------------------------------------------- 287 | locals { 288 | health_check = { 289 | type = "http" 290 | check_interval_sec = 10 291 | healthy_threshold = 2 292 | timeout_sec = 5 293 | unhealthy_threshold = 3 294 | port = 3000 295 | request_path = "/rest/admin/application-version" 296 | host = null 297 | initial_delay_sec = 30 298 | proxy_header = "NONE" 299 | request = null 300 | response = null 301 | logging = true 302 | } 303 | apigee_waap_svc_account_roles = [ 304 | "roles/dlp.user", "roles/recaptchaenterprise.agent" 305 | ] 306 | recaptcha_key_name = "juiceshop-session-token-key" 307 | recaptcha_key_id = split("/", jsondecode(data.local_file.recaptcha_keys_list_file.content)[0].name)[3] 308 | } 309 | 310 | data "google_compute_default_service_account" "default" { 311 | project = var.project_id 312 | } 313 | 314 | module "gce_container" { 315 | source = "terraform-google-modules/container-vm/google" 316 | version = "~> 3.0" 317 | 318 | container = { 319 | name = "juiceshop-demo-mig-template" 320 | image = "${var.region}-docker.pkg.dev/${var.project_id}/${google_artifact_registry_repository.waap_repo.repository_id}/juiceshop-image" 321 | securityContext = { 322 | privileged : false 323 | } 324 | stdin : false 325 | tty : true 326 | 327 | # Declare volumes to be mounted. 328 | # This is similar to how docker volumes are declared. 329 | volumeMounts = [] 330 | } 331 | 332 | # Declare the Volumes which will be used for mounting. 333 | volumes = [] 334 | 335 | restart_policy = "Always" 336 | 337 | depends_on = [ 338 | module.build_juiceshop_image 339 | ] 340 | } 341 | 342 | module "instance_template" { 343 | source = "terraform-google-modules/vm/google//modules/instance_template" 344 | version = "~> 7.9.0" 345 | 346 | name_prefix = "juiceshop-instance-template" 347 | project_id = var.project_id 348 | service_account = { 349 | email = data.google_compute_default_service_account.default.email 350 | scopes = ["cloud-platform"] 351 | } 352 | machine_type = "n2-standard-2" 353 | labels = { 354 | container-vm = module.gce_container.vm_container_label 355 | } 356 | metadata = { 357 | google-logging-enabled = "true", 358 | google-monitoring-enabled = "true" 359 | gce-container-declaration = module.gce_container.metadata_value 360 | } 361 | tags = ["http-server", "https-server", "juiceshop"] 362 | 363 | /* network */ 364 | network = module.vpc.network_id 365 | subnetwork = module.vpc.subnets_ids[1] 366 | subnetwork_project = var.project_id 367 | 368 | access_config = [{ 369 | network_tier = "PREMIUM" 370 | nat_ip = null 371 | }] 372 | automatic_restart = true 373 | on_host_maintenance = "MIGRATE" 374 | 375 | /* image */ 376 | source_image = module.gce_container.source_image 377 | # source_image_project = var.project_id 378 | 379 | /* disks */ 380 | disk_size_gb = 10 381 | disk_type = "pd-balanced" 382 | auto_delete = true 383 | } 384 | 385 | module "mig" { 386 | source = "terraform-google-modules/vm/google//modules/mig" 387 | version = "~> 7.9.0" 388 | 389 | project_id = var.project_id 390 | hostname = "juiceshop-demo" 391 | region = var.region 392 | instance_template = module.instance_template.self_link 393 | # target_size = var.target_size 394 | # target_pools = var.target_pools 395 | # distribution_policy_zones = var.distribution_policy_zones 396 | named_ports = [{ 397 | name = "http-juiceshop" 398 | port = 3000 399 | }] 400 | 401 | update_policy = [{ 402 | minimal_action = "REPLACE" 403 | type = "PROACTIVE" 404 | instance_redistribution_type = null 405 | max_surge_fixed = 0 406 | max_surge_percent = null 407 | max_unavailable_fixed = 4 408 | max_unavailable_percent = null 409 | min_ready_sec = null 410 | replacement_method = null 411 | }] 412 | 413 | /* health check */ 414 | health_check = local.health_check 415 | 416 | /* autoscaler */ 417 | autoscaling_enabled = true 418 | max_replicas = 2 419 | min_replicas = 1 420 | cooldown_period = 60 421 | autoscaling_cpu = [ 422 | { 423 | target = 0.6 424 | predictive_method = null 425 | }, 426 | ] 427 | } 428 | 429 | 430 | module "nip_juiceshop_hostname" { 431 | source = "github.com/apigee/terraform-modules//modules/nip-development-hostname?ref=v0.12.0" 432 | 433 | project_id = var.project_id 434 | address_name = "juiceshop-lb-ip" 435 | } 436 | 437 | module "lb-http" { 438 | source = "GoogleCloudPlatform/lb-http/google" 439 | version = "~> 6.3.0" 440 | 441 | project = var.project_id 442 | name = "juiceshop" 443 | target_tags = ["juiceshop"] 444 | 445 | firewall_networks = [module.vpc.network_name] 446 | address = module.nip_juiceshop_hostname.ip_address 447 | create_address = false 448 | ssl = true 449 | use_ssl_certificates = true 450 | ssl_certificates = ["https://www.googleapis.com/compute/v1/${module.nip_juiceshop_hostname.ssl_certificate}"] 451 | https_redirect = true 452 | 453 | backends = { 454 | default = { 455 | description = null 456 | protocol = "HTTP" 457 | port = 80 458 | port_name = "http-juiceshop" 459 | timeout_sec = 10 460 | enable_cdn = false 461 | custom_request_headers = null 462 | custom_response_headers = null 463 | security_policy = google_compute_security_policy.waap_policies.name 464 | 465 | connection_draining_timeout_sec = null 466 | session_affinity = null 467 | affinity_cookie_ttl_sec = null 468 | 469 | health_check = local.health_check 470 | 471 | log_config = { 472 | enable = true 473 | sample_rate = 1.0 474 | } 475 | 476 | groups = [ 477 | { 478 | # Each node pool instance group should be added to the backend. 479 | group = module.mig.instance_group 480 | max_utilization = 0.8 481 | balancing_mode = null 482 | capacity_scaler = null 483 | description = null 484 | max_connections = null 485 | max_connections_per_instance = null 486 | max_connections_per_endpoint = null 487 | max_rate = null 488 | max_rate_per_instance = null 489 | max_rate_per_endpoint = null 490 | }, 491 | ] 492 | 493 | iap_config = { 494 | enable = false 495 | oauth2_client_id = "" 496 | oauth2_client_secret = "" 497 | } 498 | } 499 | } 500 | } 501 | 502 | 503 | 504 | # ---------------------------------------------------------------------------------------------------------------------- 505 | # Firewalls 506 | # ---------------------------------------------------------------------------------------------------------------------- 507 | module "firewall_rules" { 508 | source = "terraform-google-modules/network/google//modules/firewall-rules" 509 | version = "~> 6.0.0" 510 | 511 | project_id = var.project_id 512 | network_name = module.vpc.network_name 513 | 514 | rules = [ 515 | { 516 | name = "allow-all-egress-juiceshop-https" 517 | description = null 518 | direction = "EGRESS" 519 | priority = 1000 520 | ranges = ["0.0.0.0/0"] 521 | source_tags = null 522 | source_service_accounts = null 523 | target_tags = ["juiceshop"] 524 | target_service_accounts = null 525 | allow = [ 526 | { 527 | protocol = "tcp" 528 | ports = ["443"] 529 | } 530 | ] 531 | deny = [] 532 | log_config = { 533 | metadata = "INCLUDE_ALL_METADATA" 534 | } 535 | }, 536 | { 537 | name = "allow-juiceshop-demo-lb-health-check" 538 | description = null 539 | direction = "INGRESS" 540 | priority = 1000 541 | ranges = ["130.211.0.0/22", "35.191.0.0/16"] 542 | source_tags = null 543 | source_service_accounts = null 544 | target_tags = ["juiceshop"] 545 | target_service_accounts = null 546 | allow = [ 547 | { 548 | protocol = "tcp" 549 | ports = ["80", "443", "3000"] 550 | } 551 | ] 552 | deny = [] 553 | log_config = { 554 | metadata = "INCLUDE_ALL_METADATA" 555 | } 556 | }, 557 | { 558 | name = "default-allow-http" 559 | description = null 560 | direction = "INGRESS" 561 | priority = 1000 562 | ranges = ["0.0.0.0/0"] 563 | source_tags = null 564 | source_service_accounts = null 565 | target_tags = ["http-server"] 566 | target_service_accounts = null 567 | allow = [ 568 | { 569 | protocol = "tcp" 570 | ports = ["80"] 571 | } 572 | ] 573 | deny = [] 574 | log_config = { 575 | metadata = "INCLUDE_ALL_METADATA" 576 | } 577 | }, 578 | { 579 | name = "default-allow-https" 580 | description = null 581 | direction = "INGRESS" 582 | priority = 1000 583 | ranges = ["0.0.0.0/0"] 584 | source_tags = null 585 | source_service_accounts = null 586 | target_tags = ["https-server"] 587 | target_service_accounts = null 588 | allow = [ 589 | { 590 | protocol = "tcp" 591 | ports = ["443"] 592 | } 593 | ] 594 | deny = [] 595 | log_config = { 596 | metadata = "INCLUDE_ALL_METADATA" 597 | } 598 | }, 599 | { 600 | name = "default-allow-http-3000" 601 | description = null 602 | direction = "INGRESS" 603 | priority = 1000 604 | ranges = ["0.0.0.0/0"] 605 | source_tags = null 606 | source_service_accounts = null 607 | target_tags = null 608 | target_service_accounts = null 609 | allow = [ 610 | { 611 | protocol = "tcp" 612 | ports = ["3000"] 613 | } 614 | ] 615 | deny = [] 616 | log_config = { 617 | metadata = "INCLUDE_ALL_METADATA" 618 | } 619 | }, 620 | { 621 | name = "default-allow-custom" 622 | description = "Allows connection from any source to any instance on the network using custom protocols." 623 | direction = "INGRESS" 624 | priority = 65534 625 | ranges = ["10.0.32.0/20"] 626 | source_tags = null 627 | source_service_accounts = null 628 | target_tags = null 629 | target_service_accounts = null 630 | allow = [ 631 | { 632 | protocol = "all" 633 | ports = [] 634 | } 635 | ] 636 | deny = [] 637 | log_config = { 638 | metadata = "INCLUDE_ALL_METADATA" 639 | } 640 | }, 641 | { 642 | name = "all-rdp-ssh-iap" 643 | description = "Allows RDP/SSH connections from IAP." 644 | direction = "INGRESS" 645 | priority = 65534 646 | ranges = ["35.235.240.0/20"] 647 | source_tags = null 648 | source_service_accounts = null 649 | target_tags = null 650 | target_service_accounts = null 651 | allow = [ 652 | { 653 | ports = ["3389", "22"] 654 | protocol = "tcp" 655 | } 656 | ] 657 | deny = [] 658 | log_config = { 659 | metadata = "INCLUDE_ALL_METADATA" 660 | } 661 | }, 662 | ] 663 | } 664 | 665 | # ---------------------------------------------------------------------------------------------------------------------- 666 | # Configure Cloud Armour 667 | # ---------------------------------------------------------------------------------------------------------------------- 668 | resource "google_compute_security_policy" "waap_policies" { 669 | name = "waap-demo-juice-shop" 670 | project = var.project_id 671 | 672 | rule { 673 | action = "allow" 674 | description = "Default rule, higher priority overrides it" 675 | 676 | match { 677 | config { 678 | src_ip_ranges = ["*"] 679 | } 680 | versioned_expr = "SRC_IPS_V1" 681 | } 682 | priority = 2147483647 683 | } 684 | 685 | rule { 686 | action = "deny(403)" 687 | description = "Deny all requests below 0.9 recaptcha score" 688 | preview = true 689 | 690 | match { 691 | expr { 692 | expression = "token.recaptcha_session.score <= 0.9" 693 | } 694 | } 695 | priority = 8998 696 | } 697 | 698 | rule { 699 | action = "deny(403)" 700 | description = "Block US IP & header: Hacker" 701 | 702 | match { 703 | expr { 704 | expression = "origin.region_code == 'US' && request.headers['user-agent'].contains('Hacker')" 705 | } 706 | } 707 | priority = 7000 708 | } 709 | 710 | rule { 711 | action = "deny(403)" 712 | description = "Regular Expression Rule" 713 | 714 | match { 715 | expr { 716 | expression = "request.headers['user-agent'].contains('Hacker')" 717 | } 718 | } 719 | priority = 7001 720 | } 721 | 722 | rule { 723 | action = "deny(403)" 724 | description = "block sql injection" 725 | 726 | match { 727 | expr { 728 | expression = "evaluatePreconfiguredWaf('sqli-v33-stable', {'sensitivity': 1})" 729 | } 730 | } 731 | priority = 9000 732 | } 733 | 734 | rule { 735 | action = "deny(403)" 736 | description = "block xss" 737 | 738 | match { 739 | expr { 740 | expression = "evaluatePreconfiguredWaf('xss-v33-stable', {'sensitivity': 1})" 741 | } 742 | } 743 | priority = 3000 744 | } 745 | 746 | rule { 747 | action = "deny(403)" 748 | description = "block local file inclusion" 749 | 750 | match { 751 | expr { 752 | expression = "evaluatePreconfiguredExpr('lfi-stable')" 753 | } 754 | } 755 | priority = 9005 756 | } 757 | 758 | rule { 759 | action = "deny(403)" 760 | description = "block remote code execution" 761 | 762 | match { 763 | expr { 764 | expression = "evaluatePreconfiguredExpr('rce-stable')" 765 | } 766 | } 767 | priority = 9010 768 | } 769 | 770 | rule { 771 | action = "deny(403)" 772 | description = "block scanner detection" 773 | preview = true 774 | 775 | match { 776 | expr { 777 | expression = "evaluatePreconfiguredExpr('scannerdetection-stable')" 778 | } 779 | } 780 | priority = 9015 781 | } 782 | 783 | rule { 784 | action = "deny(403)" 785 | description = "block protocol attack" 786 | 787 | match { 788 | expr { 789 | expression = "evaluatePreconfiguredExpr('protocolattack-stable')" 790 | } 791 | } 792 | priority = 9020 793 | } 794 | 795 | rule { 796 | action = "deny(403)" 797 | description = "block session fixation" 798 | 799 | match { 800 | expr { 801 | expression = "evaluatePreconfiguredExpr('sessionfixation-stable')" 802 | } 803 | } 804 | priority = 9025 805 | } 806 | 807 | } 808 | 809 | // WAAP Analytics 810 | module "analytics" { 811 | source = "GoogleCloudPlatform/waap/google//modules/waap-analytics" 812 | version = "~> 0.1" 813 | 814 | project_id = var.project_id 815 | log_sink_name = "juiceshop_log_sink" 816 | ca_policy_name = google_compute_security_policy.waap_policies.name 817 | dataset_name = "juiceshop_analytics" 818 | sa_name = "juiceshop-bq-sa" 819 | } 820 | -------------------------------------------------------------------------------- /examples/juiceshop_example/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "project_id" { 18 | description = "GCP Project ID in which to create example resources" 19 | type = string 20 | } 21 | 22 | variable "region" { 23 | description = "Region in which to create regional resources." 24 | type = string 25 | default = "us-central1" 26 | } 27 | -------------------------------------------------------------------------------- /examples/juiceshop_example/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 0.13.0" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.45" 23 | } 24 | apigee = { 25 | source = "scastria/apigee" 26 | version = "~> 0.1.0" 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /examples/juiceshop_example/waap-demo-proxy-bundle.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleCloudPlatform/terraform-google-waap/c8000073f9d13bbc783bcc0307ae8e7bd1c3fcfb/examples/juiceshop_example/waap-demo-proxy-bundle.zip -------------------------------------------------------------------------------- /examples/web_app_protection_example/README.md: -------------------------------------------------------------------------------- 1 | # Web App Protection Example - Managed Instance Group Backend 2 | 3 | This usage example shows how to successfully deploy a terraform-based infrastructure contemplating the use of Cloud CDN and Cloud Armor tools. These tools are deployed with predefined rules to protect an environment of Web applications hosted in Managed Instance Groups using the Global Load Balancer. 4 | 5 | Additionally, we included infrastructure pipeline examples to facilitate the adoption of the model in environments that already used Jenkins, Gitlab CI and Cloud Build as a CI/CD tool for infrastructure solutions. 6 | 7 | ## This example deploys: 8 | 9 | - VPC Network, Subnets, Cloud Router 10 | - Firewall Rules 11 | - GCE Managed Instance Group running Sample application 12 | - Global Load Balancer 13 | - Cloud CDN with some security best practices 14 | - Cloud Armor with top 10 OWASP Rules and reCAPTCHA integration 15 | - Traffic Management 16 | 17 | ## Requirements 18 | 19 | - A GCP Project containing the enabled APIs listed in this document. 20 | - All the permissions outlined later on this document must be granted to the users. 21 | - Run gcloud auth application-default login before following the step by step instructions below. 22 | 23 | ## IAM Permissions 24 | 25 | - The user or service account deploying this example must have the following IAM roles:Owner roles/owner 26 | - reCAPTCHA Enterprise Admin: roles/recaptchaenterprise.admin 27 | - Artifact Registry Admin: roles/artifactregistry.admin 28 | 29 | ## APIs 30 | 31 | A project with the following APIs enabled must be used to host the resources of this module: 32 | 33 | - Cloud Resource Manager API: cloudresourcemanager.googleapis.com 34 | - Compute API: compute.googleapis.com 35 | - Identity and Access Management API: iam.googleapis.com 36 | - Cloud Monitoring API: monitoring.googleapis.com 37 | - reCAPTCHA Enterprise API: recaptchaenterprise.googleapis.com 38 | - Service Networking API: servicenetworking.googleapis.com 39 | - Service Usage API: serviceusage.googleapis.com 40 | 41 | ## Setup 42 | 43 | Here's a step by step to deploy and test this example: 44 | 45 | ### Build the infrastructure 46 | 47 | 1. Clone waap repo 48 | 49 | ```git clone``` [https://github.com/GoogleCloudPlatform/terraform-google-waap.git](https://github.com/GoogleCloudPlatform/terraform-google-waap.git) 50 | 51 | 2. Change to web-app-protection-example directory: 52 | 53 | ```cd terraform-google-waap/examples/web-app-protection-example``` 54 | 55 | 3. Run terraform init from within this example directory. 56 | 57 | ```terraform init``` 58 | 59 | Note: The "project_id" is the only variable that must be changed. Any other local value change is optional. 60 | 61 | 5. Run terraform plan and check the prompt output. 62 | 63 | ```terraform plan``` 64 | 65 | 6. Run terraform apply within this example directory. 66 | 67 | ```terraform apply``` 68 | 69 | ## Optional 70 | 71 | Some features are optional, are not created by default and are enabled based on their variables. 72 | 73 | An example is the URL map feature, used to forward HTTP(S) requests to backend services. 74 | 75 | If you need to perform traffic splitting between your backends, just set the `url_map` variable to `true` and configure the 'google_compute_url_map' resource. 76 | 77 | 78 | ## Inputs 79 | 80 | | Name | Description | Type | Default | Required | 81 | |------|-------------|------|---------|:--------:| 82 | | project\_id | Google Project ID in which the resources will be created. | `string` | n/a | yes | 83 | | url\_map | Enable or disable the 'google\_compute\_url\_map' feature to route requests to backends based on rules. | `bool` | `false` | no | 84 | 85 | ## Outputs 86 | 87 | No outputs. 88 | 89 | 90 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/build/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | image: 16 | name: gcr.io/cloud-foundation-cicd/cft/developer-tools-light:1 17 | 18 | variables: 19 | REPO_FOLDER: "web_app_protection_example" 20 | STATE_BUCKET_NAME: "$STATE_BUCKET_NAME" 21 | TF_SA_EMAIL: "$TF_SA_EMAIL" 22 | 23 | before_script: 24 | - | 25 | echo "Setting up gcloud for impersonation" 26 | gcloud config set auth/impersonate_service_account "$TF_SA_EMAIL" 27 | echo "Adding bucket information to backends" 28 | for i in `find -name 'backend.tf'`; do sed -r -i "s/_BUCKET_GCS_/"$STATE_BUCKET_NAME"/" $i; done 29 | for i in `find -name 'pipeline-functions.sh'`;do chmod +x $i; done 30 | cd examples/"$REPO_FOLDER" ; cp -Prf ../../modules . 31 | for i in `find environments/ -mindepth 1 -maxdepth 1 -type d` ; do cp -Prf *.tf ./scripts/ $i ; done 32 | 33 | stages: 34 | - tf_init 35 | - tf_plan_all 36 | - tf_plan 37 | - tf_apply 38 | 39 | tf_init: 40 | stage: tf_init 41 | script: 42 | - export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT="$TF_SA_EMAIL" 43 | - build/pipeline-functions.sh init "$CI_COMMIT_BRANCH" 44 | only: 45 | - dev 46 | - prd 47 | - npd 48 | artifacts: 49 | paths: 50 | - $CI_PROJECT_DIR/examples/$REPO_FOLDER/environments 51 | tags: 52 | - gitlab-runner 53 | 54 | tf_plan_all: 55 | stage: tf_plan_all 56 | script: 57 | - export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT="$TF_SA_EMAIL" 58 | - build/pipeline-functions.sh plan_validate_all "$CI_COMMIT_BRANCH" 59 | except: 60 | - dev 61 | - prd 62 | - npd 63 | tags: 64 | - gitlab-runner 65 | 66 | tf_plan: 67 | stage: tf_plan 68 | script: 69 | - export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT="$TF_SA_EMAIL" 70 | - build/pipeline-functions.sh plan "$CI_COMMIT_BRANCH" 71 | only: 72 | - dev 73 | - prd 74 | - npd 75 | artifacts: 76 | paths: 77 | - $CI_PROJECT_DIR/examples/$REPO_FOLDER/tmp_plan/*.tfplan 78 | tags: 79 | - gitlab-runner 80 | 81 | tf_apply: 82 | stage: tf_apply 83 | script: 84 | - export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT="$TF_SA_EMAIL" 85 | - build/pipeline-functions.sh apply "$CI_COMMIT_BRANCH" 86 | only: 87 | - dev 88 | - prd 89 | - npd 90 | tags: 91 | - gitlab-runner 92 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/build/Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | environment { 5 | _BUILD_ID="${env.BUILD_ID}" 6 | _BRANCH_NAME="${env.BRANCH_NAME}" 7 | 8 | _TF_SA_EMAIL="<_TF_SA_EMAIL>" 9 | _STATE_BUCKET_NAME="<_STATE_BUCKET_NAME>" 10 | _PROJECT_ID="<_PROJECT_ID>" 11 | _POLICY_REPO="CLOUDSOURCE" 12 | 13 | 14 | EXAMPLE_BUILD="web_app_protection_example" 15 | _GCLOUD_PATH="/var/lib/jenkins/google-cloud-sdk/bin" 16 | } 17 | 18 | stages{ 19 | stage("Terraform Setup"){ 20 | steps{ 21 | sh ''' 22 | echo "Setting up gcloud for impersonation" 23 | ${_GCLOUD_PATH}/gcloud config set auth/impersonate_service_account ${_TF_SA_EMAIL} 24 | echo "Adding bucket information to backends" 25 | for i in `find -name 'backend.tf'`; do sed -r -i "s/_BUCKET_GCS_/${_STATE_BUCKET_NAME}/" $i; done 26 | for i in `find -name 'pipeline-functions.sh'`;do chmod +x $i; done 27 | cd ${WORKSPACE}/examples/${EXAMPLE_BUILD} ; cp -Prf ../../modules . 28 | for i in `find environments/ -mindepth 1 -maxdepth 1 -type d` ; do cp -Prf *.tf ./scripts/ $i ; done 29 | ''' 30 | } 31 | } 32 | stage('TF init') { 33 | when { 34 | anyOf { 35 | branch 'dev' 36 | branch 'prd' 37 | branch 'npd' 38 | } 39 | } 40 | steps { 41 | sh ''' 42 | export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=${_TF_SA_EMAIL} 43 | cd ${WORKSPACE}/examples/${EXAMPLE_BUILD} 44 | build/pipeline-functions.sh init $BRANCH_NAME 45 | ''' 46 | } 47 | } 48 | stage('TF Plan ALL') { 49 | when { 50 | not { 51 | anyOf { 52 | branch 'dev' 53 | branch 'prd' 54 | branch 'npd' 55 | } 56 | } 57 | } 58 | steps { 59 | sh ''' 60 | export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=${_TF_SA_EMAIL} 61 | cd ${WORKSPACE}/examples/${EXAMPLE_BUILD} 62 | build/pipeline-functions.sh plan_validate_all $BRANCH_NAME 63 | ''' 64 | } 65 | } 66 | stage('TF plan') { 67 | when { 68 | anyOf { 69 | branch 'dev' 70 | branch 'prd' 71 | branch 'npd' 72 | } 73 | } 74 | steps { 75 | sh ''' 76 | export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=${_TF_SA_EMAIL} 77 | cd ${WORKSPACE}/examples/${EXAMPLE_BUILD} 78 | build/pipeline-functions.sh plan $BRANCH_NAME 79 | ''' 80 | } 81 | } 82 | stage('TF apply') { 83 | when { 84 | anyOf { 85 | branch 'dev' 86 | branch 'prd' 87 | branch 'npd' 88 | } 89 | } 90 | steps { 91 | sh ''' 92 | export GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=${_TF_SA_EMAIL} 93 | cd ${WORKSPACE}/examples/${EXAMPLE_BUILD} 94 | build/pipeline-functions.sh apply $BRANCH_NAME 95 | ''' 96 | } 97 | } 98 | } 99 | post{ 100 | cleanup{ 101 | cleanWs() 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/build/README.md: -------------------------------------------------------------------------------- 1 | # Web App Protection Example - CI/CD Tools build example 2 | 3 | In this folder we include examples of descriptive files for pipelines for CI/CD tools. 4 | 5 | The files are divided into groups of two different functionalities: 6 | 7 | 1. The first group is the files where we describe the steps of the pipelines of each CI/CD tool. 8 | 9 | 2. The second group is a group of common files between the pipeles, where the terraform functions and commands that will be used are described. 10 | 11 | Segmenting in this way, we make it possible to have a centralized point for changes to pipeline-functions.sh file functions so that whenever a new CI/CD tool is included in the environment, it will not be necessary to rewrite the functions, just create the steps of the new one CI/CD tool calling the pipeline-functions.sh file that already has all functions and conditions duly declared. 12 | 13 | ## Examples already created: 14 | 15 | ### Jenkins 16 | 17 | If you already have a Jenkins environment to run your terraform code or if you intend to use Jenkins as your infrastructure CI/CD Tool, we created a [JenkinsFile](Jenkinsfile) example with a pipeline suggestion for this environment. Please, refer to the [JenkinsFile](Jenkinsfile) to view the example. 18 | 19 | - Requirements 20 | 21 | 1. [Jenkins installed and running](https://www.jenkins.io/doc/book/installing/) 22 | 23 | 2. [Jenkins git plugin installed and running](https://plugins.jenkins.io/git/) 24 | 25 | 3. [Terraform installed in the Jenkins instance](https://developer.hashicorp.com/terraform/tutorials/gcp-get-started/install-cli) 26 | 27 | - Jenkins file rules and conditions 28 | 29 | This jenkins file contains rules based on branch names considering two different behavior: 30 | 31 | 1. Behavior 1: this behavior happens when the code is pushed to a branch with the same name as an environment folder inside web-app-protection-example/environments/ (E.g., "prd", "dev"or "npd" ). 32 | 33 | For this condition, the pipeline code will run steps related to terraform init and terraform apply. When working with multiple environments, it’s really important to change the values of the variables inside the terraform.tfvars files for each environment in order to avoid conflicts. 34 | 35 | 2. Behavior 2: differently from behavior 1, this behavior happens when the branch name doesn't match an environment folder name. 36 | 37 | For this condition, the pipeline code will run steps related to terraform init and terraform plan, generating a plan output without changing your infrastructure. 38 | 39 | ### Gitlab 40 | 41 | If you already have a Gitlab environment to run your terraform code or if you intend to use Jenkins as your infrastructure CI/CD Tool, we created a [Gitlab-ci](.gitlab-ci.yml) example with a pipeline suggestion for this environment. Please, refer to the [Gitlab-ci](.gitlab-ci.yml) to view the example. 42 | 43 | - Requirements 44 | 45 | 1. Gitlab installed and running 46 | 2. A dedicated service account to terraform perform actions in the environment with owner permissions in the target project 47 | 3. A dedicated bucket to store the terraform state file 48 | 4. Change the variables "TF_SA_EMAIL" and "STATE_BUCKET_NAME" with the terraform service account email and the terraform state bucket name. 49 | 50 | - Gitlab-ci file rules and conditions 51 | 52 | This [Gitlab-ci](.gitlab-ci.yml) contains rules based on branch names considering two different behavior: 53 | 54 | 1. Behavior 1: this behavior happens when the code is pushed to a branch with the same name as an environment folder inside web-app-protection-example/environments/ (E.g., "prd", "dev"or "npd" ). 55 | 56 | For this condition, the pipeline code will run steps related to terraform init and terraform apply. When working with multiple environments, it’s really important to change the values of the variables inside the terraform.tfvars files for each environment in order to avoid conflicts. 57 | 58 | 2. Behavior 2: differently from behavior 1, this behavior happens when the branch name doesn't match an environment folder name. 59 | 60 | For this condition, the pipeline code will run steps related to terraform init and terraform plan, generating a plan output without changing your infrastructure. 61 | 62 | ### Cloud Build 63 | 64 | If you intend to use Cloud Build as your infrastructure CI/CD Tool, we created a [Cloud Build](cloudbuild.yaml) example with a pipeline suggestion for this environment. Please, refer to the [Cloud Build](cloudbuild.yaml) to view the example. 65 | 66 | - Requirements 67 | 68 | 1. Cloud Build API Enabled. 69 | 2. You can use the default Cloud Build service account or a service account dedicated to terraform, as long as it has the necessary permissions. 70 | 3. A dedicated bucket to store the terraform state file. 71 | 4. At the time of creating the Cloud Build trigger set the substitution variable "_STATE_BUCKET_NAME" with the name of the terraform state bucket. 72 | 73 | - Cloud Buid file rules and conditions 74 | 75 | This [Cloud Build](cloudbuild.yaml) contains rules based on branch names considering two different behavior: 76 | 77 | 1. Behavior 1: this behavior happens when the code is pushed to a branch with the same name as an environment folder inside web-app-protection-example/environments/ (E.g., "prd", "dev"or "npd" ). 78 | 79 | For this condition, the pipeline code will run steps related to terraform init and terraform apply. When working with multiple environments, it’s really important to change the values of the variables inside the terraform.tfvars files for each environment in order to avoid conflicts. 80 | 81 | 2. Behavior 2: differently from behavior 1, this behavior happens when the branch name doesn't match an environment folder name. 82 | 83 | For this condition, the pipeline code will run steps related to terraform init and terraform plan, generating a plan output without changing your infrastructure. 84 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/build/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | # Copyright 2022 Google LLC 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | options: 16 | env: 17 | - 'BRANCH_NAME=$BRANCH_NAME' 18 | - 'EXAMPLE_BUILD=web_app_protection_example' 19 | - 'STATE_BUCKET_NAME=$_STATE_BUCKET_NAME' 20 | 21 | substitutions: 22 | _BEFORE_SCRIPT: | 23 | echo "Adding bucket information to backends" 24 | for i in `find -name 'backend.tf'`; do sed -r -i "s/_BUCKET_GCS_/"$STATE_BUCKET_NAME"/" $i; done 25 | for i in `find -name 'pipeline-functions.sh'`;do chmod +x $i; done 26 | cd examples/"$EXAMPLE_BUILD" ; cp -Prf ../../modules . 27 | for i in `find environments/ -mindepth 1 -maxdepth 1 -type d` ; do cp -Prf *.tf ./scripts/ $i ; done 28 | 29 | steps: 30 | - name: 'gcr.io/cloud-foundation-cicd/cft/developer-tools-light:1' 31 | entrypoint: '/bin/bash' 32 | id: 'tf_init' 33 | args: 34 | - '-c' 35 | - | 36 | if [[ "$$BRANCH_NAME" == "dev" || "$$BRANCH_NAME" == "prd" || "$$BRANCH_NAME" == "npd" ]]; then 37 | $_BEFORE_SCRIPT 38 | build/pipeline-functions.sh init "$$BRANCH_NAME" 39 | fi 40 | 41 | - name: 'gcr.io/cloud-foundation-cicd/cft/developer-tools-light:1' 42 | entrypoint: '/bin/bash' 43 | id: 'tf_plan_all' 44 | args: 45 | - '-c' 46 | - | 47 | if [[ "$$BRANCH_NAME" != "dev" && "$$BRANCH_NAME" != "prd" && "$$BRANCH_NAME" != "npd" ]]; then 48 | $_BEFORE_SCRIPT 49 | build/pipeline-functions.sh plan_validate_all "$$BRANCH_NAME" 50 | fi 51 | 52 | - name: 'gcr.io/cloud-foundation-cicd/cft/developer-tools-light:1' 53 | entrypoint: '/bin/bash' 54 | id: 'tf_plan' 55 | args: 56 | - '-c' 57 | - | 58 | if [[ "$$BRANCH_NAME" == "dev" || "$$BRANCH_NAME" == "prd" || "$$BRANCH_NAME" == "npd" ]]; then 59 | $_BEFORE_SCRIPT 60 | build/pipeline-functions.sh plan "$$BRANCH_NAME" 61 | fi 62 | 63 | - name: 'gcr.io/cloud-foundation-cicd/cft/developer-tools-light:1' 64 | entrypoint: '/bin/bash' 65 | id: 'tf_apply' 66 | args: 67 | - '-c' 68 | - | 69 | if [[ "$$BRANCH_NAME" == "dev" || "$$BRANCH_NAME" == "prd" || "$$BRANCH_NAME" == "npd" ]]; then 70 | $_BEFORE_SCRIPT 71 | build/pipeline-functions.sh apply "$$BRANCH_NAME" 72 | fi 73 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/build/pipeline-functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -e 18 | 19 | action=$1 20 | branch=$2 21 | policysource=$3 22 | project_id=$4 23 | policy_type=$5 # FILESYSTEM or CLOUDSOURCE 24 | base_dir=$(pwd) 25 | tmp_plan="${base_dir}/tmp_plan" #if you change this, update build triggers 26 | environments_regex="^(dev|npd|prd)$" 27 | _GCLOUD_PATH="/var/lib/jenkins/google-cloud-sdk/bin" 28 | 29 | ## Terraform apply for single environment. 30 | tf_apply() { 31 | local path=$1 32 | local tf_env=$2 33 | local tf_component=$3 34 | echo "*************** TERRAFORM APPLY *******************" 35 | echo " At environment: ${tf_component}/${tf_env} " 36 | echo "***************************************************" 37 | if [ -d "$path" ]; then 38 | cd "$path" || exit 39 | terraform apply -no-color -input=false -auto-approve "${tmp_plan}/${tf_component}-${tf_env}.tfplan" || exit 1 40 | cd "$base_dir" || exit 41 | else 42 | echo "ERROR: ${path} does not exist" 43 | fi 44 | } 45 | 46 | ## terraform init for single environment. 47 | tf_init() { 48 | local path=$1 49 | local tf_env=$2 50 | local tf_component=$3 51 | echo "*************** TERRAFORM INIT *******************" 52 | echo " At environment: ${tf_component}/${tf_env} " 53 | echo "**************************************************" 54 | if [ -d "$path" ]; then 55 | cd "$path" || exit 56 | terraform init -no-color || exit 11 57 | cd "$base_dir" || exit 58 | else 59 | echo "ERROR: ${path} does not exist" 60 | fi 61 | } 62 | 63 | ## terraform plan for single environment. 64 | tf_plan() { 65 | local path=$1 66 | local tf_env=$2 67 | local tf_component=$3 68 | echo "*************** TERRAFORM PLAN *******************" 69 | echo " At environment: ${tf_component}/${tf_env} " 70 | echo "**************************************************" 71 | if [ ! -d "${tmp_plan}" ]; then 72 | mkdir "${tmp_plan}" || exit 73 | fi 74 | if [ -d "$path" ]; then 75 | cd "$path" || exit 76 | terraform plan -no-color -input=false -out "${tmp_plan}/${tf_component}-${tf_env}.tfplan" || exit 21 77 | cd "$base_dir" || exit 78 | else 79 | echo "ERROR: ${tf_env} does not exist" 80 | fi 81 | } 82 | 83 | ## terraform init/plan/validate for all valid environments matching regex. 84 | tf_plan_validate_all() { 85 | local env 86 | local component 87 | find "$base_dir" -mindepth 1 -maxdepth 1 -type d \ 88 | -not -path "$base_dir/modules" \ 89 | -not -path "$base_dir/.terraform" | while read -r component_path ; do 90 | component="$(basename "$component_path")" 91 | find "$component_path" -mindepth 1 -maxdepth 1 -type d | while read -r env_path ; do 92 | env="$(basename "$env_path")" 93 | if [[ "$env" =~ $environments_regex ]] ; then 94 | tf_init "$env_path" "$env" "$component" 95 | tf_plan "$env_path" "$env" "$component" 96 | # tf_validate "$env_path" "$env" "$policysource" "$component" # !TODO 97 | else 98 | echo "$component/$env doesn't match $environments_regex; skipping" 99 | fi 100 | done 101 | done 102 | } 103 | 104 | ## terraform show for single environment. 105 | tf_show() { 106 | local path=$1 107 | local tf_env=$2 108 | local tf_component=$3 109 | echo "*************** TERRAFORM SHOW *******************" 110 | echo " At environment: ${tf_component}/${tf_env} " 111 | echo "**************************************************" 112 | if [ -d "$path" ]; then 113 | cd "$path" || exit 114 | terraform show -no-color "${tmp_plan}/${tf_component}-${tf_env}.tfplan" || exit 41 115 | cd "$base_dir" || exit 116 | else 117 | echo "ERROR: ${path} does not exist" 118 | fi 119 | } 120 | 121 | ## terraform validate for single environment. 122 | tf_validate() { 123 | local path=$1 124 | local tf_env=$2 125 | local policy_file_path=$3 126 | local tf_component=$4 127 | echo "*************** TERRAFORM VALIDATE ******************" 128 | echo " At environment: ${tf_component}/${tf_env} " 129 | echo " Using policy from: ${policy_file_path} " 130 | echo "*****************************************************" 131 | if [ -z "$policy_file_path" ]; then 132 | echo "no policy repo found! Check the argument provided for policysource to this script." 133 | echo "https://github.com/GoogleCloudPlatform/terraform-validator/blob/main/docs/policy_library.md" 134 | else 135 | if [ -d "$path" ]; then 136 | cd "$path" || exit 137 | terraform show -no-color -json "${tmp_plan}/${tf_component}-${tf_env}.tfplan" > "${tf_env}.json" || exit 32 138 | if [[ "$policy_type" == "CLOUDSOURCE" ]]; then 139 | # Check if $policy_file_path is empty so we clone the policies repo only once 140 | if [ -z "$(ls -A "${policy_file_path}" 2> /dev/null)" ]; then 141 | $_GCLOUD_PATH/gcloud source repos clone gcp-policies "${policy_file_path}" --project="${project_id}" || exit 34 142 | pushd . 143 | cd "${policy_file_path}" 144 | # Commented command below works only on Git 2.22.0+ 145 | # current_branch=$(git branch --show-current) 146 | # As Cloud Build is based on step 4-projects docker image having 147 | # git version 2.20.1 installed the command below keeps compatibility 148 | current_branch=$(git symbolic-ref --short HEAD) 149 | echo "current gcp-policies branch $current_branch" 150 | if [[ "$current_branch" != "main" ]]; then 151 | git checkout main || exit 35 152 | fi 153 | popd 154 | fi 155 | fi 156 | $_GCLOUD_PATH/gcloud beta terraform vet "${tf_env}.json" --policy-library="${policy_file_path}" --project="${project_id}" || exit 33 157 | cd "$base_dir" || exit 158 | else 159 | echo "ERROR: ${path} does not exist" 160 | fi 161 | fi 162 | } 163 | 164 | # Runs single action for each instance of env in folder hierarchy. 165 | single_action_runner() { 166 | local env 167 | local component 168 | find "$base_dir" -mindepth 1 -maxdepth 1 -type d \ 169 | -not -path "$base_dir/modules" \ 170 | -not -path "$base_dir/.terraform" | while read -r component_path ; do 171 | component="$(basename "$component_path")" 172 | # sort -r is added to ensure shared is first if it exists. 173 | find "$component_path" -mindepth 1 -maxdepth 1 -type d | sort -r | while read -r env_path ; do 174 | env="$(basename "$env_path")" 175 | # perform action only if folder matches branch OR folder is shared & branch is production. 176 | if [[ "$env" == "$branch" ]] || [[ "$env" == "shared" && "$branch" == "production" ]]; then 177 | case "$action" in 178 | apply ) 179 | tf_apply "$env_path" "$env" "$component" 180 | ;; 181 | 182 | init ) 183 | tf_init "$env_path" "$env" "$component" 184 | ;; 185 | 186 | plan ) 187 | tf_plan "$env_path" "$env" "$component" 188 | ;; 189 | 190 | show ) 191 | tf_show "$env_path" "$env" "$component" 192 | ;; 193 | 194 | validate ) 195 | tf_validate "$env_path" "$env" "$policysource" "$component" 196 | ;; 197 | * ) 198 | echo "unknown option: ${action}" 199 | ;; 200 | esac 201 | else 202 | echo "${env} doesn't match ${branch}; skipping" 203 | fi 204 | done 205 | done 206 | } 207 | 208 | case "$action" in 209 | init|plan|apply|show|validate ) 210 | single_action_runner 211 | ;; 212 | 213 | plan_validate_all ) 214 | tf_plan_validate_all 215 | ;; 216 | 217 | * ) 218 | echo "unknown option: ${1}" 219 | exit 99 220 | ;; 221 | esac 222 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/environments/README.md: -------------------------------------------------------------------------------- 1 | # Multi-environment strategy 2 | 3 | In addition to the option of using the single environment as referenced in the readme file in the previous folder, the option of multiple environments also applies to this solution. 4 | 5 | Here we have the options to deploy the solutions by segmenting environments, with a small adjustment in the step-by-step execution where we will inform terraform a file of variables and also a customized backend located inside the folder of each environment (in this example folder, we have dev and prd). 6 | 7 | Therefore, we will maintain the same steps of the single environment strategy, adding the environment indicators in the appropriate steps defining which will be the variables file used (only project_id is mandatory) and which will be the backend configuration. 8 | 9 | 10 | ## Setup 11 | 12 | Here's a step by step to deploy and test this example using multi-environment: 13 | 14 | ### Build the infrastructure (example using the DEV environment) 15 | 16 | 1. Clone waap repo 17 | 18 | ```git clone [https://github.com/GoogleCloudPlatform/terraform-google-waap.git](https://github.com/GoogleCloudPlatform/terraform-google-waap.git)``` 19 | 20 | 2. Change to web_app_protection_example directory: 21 | 22 | ```cd terraform-google-waap/examples/web_app_protection_example``` 23 | 24 | 2.1 Rename the file terraform.tfvars.example to terraform.tfvars 25 | 26 | ```mv -v environments/dev/terraform.tfvars.example environments/dev/terraform.tfvars``` 27 | 28 | 3. Run terraform init from within this example directory. 29 | 30 | ```terraform init -backend-config=environments/dev/backend.tf``` 31 | 32 | 4. Change the terraform.tfvars file values, including values related to your environment. 33 | 34 | Note: The "project_id" is the only variable that must be changed. Any other variable change is optional. 35 | 36 | 5. Run terraform plan and check the prompt output. 37 | 38 | ```terraform plan -var-file="environments/dev/terraform.tfvars"``` 39 | 40 | 6. Run terraform apply within this example directory. 41 | 42 | ```terraform apply -var-file="environments/dev/terraform.tfvars"``` 43 | 44 | ### * Note that we are using the dev environment as a example. You can change from "dev" to "prd" or personalize the environment name according to your needs. 45 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/environments/dev/backend.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | terraform { 17 | backend "gcs" { 18 | bucket = "_BUCKET_GCS_" 19 | prefix = "terraform/web-app-protection-example/dev" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/environments/dev/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | /*********************** 2 | *** Variables *** 3 | ************************/ 4 | project_id = "" # Madatory for multi environments. 5 | url_map = "" # Enable url map resource. 6 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/environments/prd/backend.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | terraform { 17 | backend "gcs" { 18 | bucket = "_BUCKET_GCS_" 19 | prefix = "terraform/web-app-protection-example/prd" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/environments/prd/terraform.tfvars.example: -------------------------------------------------------------------------------- 1 | /*********************** 2 | *** Variables *** 3 | ************************/ 4 | project_id = "" # Madatory for multi environments. 5 | url_map = "" # Enable url map resource. 6 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | ## --------------------------------------------------------------------------------------------------------------------- 18 | ## NETWORKS 19 | ## Modules created for configuring networks used in two different regions... 20 | ## --------------------------------------------------------------------------------------------------------------------- 21 | 22 | locals { 23 | network_cfg = { 24 | "network1" = { 25 | network_name = "vpc-webapp-r1" 26 | cnat_region = "us-central1" 27 | subnets = [ 28 | { 29 | subnet_name = "webapp-r1-subnet01" 30 | subnet_ip = "10.0.16.0/24" 31 | subnet_region = "us-central1" 32 | }, 33 | { 34 | subnet_name = "webapp-r1-subnet02" 35 | subnet_ip = "10.0.18.0/24" 36 | subnet_region = "us-west1" 37 | }, 38 | ] 39 | }, 40 | "network2" = { 41 | network_name = "vpc-webapp-r2" 42 | cnat_region = "us-east1" 43 | subnets = [ 44 | { 45 | subnet_name = "webapp-r2-subnet01" 46 | subnet_ip = "10.0.32.0/24" 47 | subnet_region = "us-east1" 48 | }, 49 | { 50 | subnet_name = "webapp-r2-subnet02" 51 | subnet_ip = "10.0.34.0/24" 52 | subnet_region = "us-east4" 53 | }, 54 | ] 55 | }, 56 | } 57 | } 58 | 59 | module "network" { 60 | source = "GoogleCloudPlatform/waap/google//modules/mig-network" 61 | version = "~> 0.1" 62 | 63 | for_each = local.network_cfg 64 | 65 | project_id = var.project_id 66 | 67 | region = each.value.cnat_region 68 | network_name = each.value.network_name 69 | subnets = each.value.subnets 70 | } 71 | 72 | ## --------------------------------------------------------------------------------------------------------------------- 73 | ## MIGs 74 | ## Creation of templates and configuration of MIGs. 75 | ## --------------------------------------------------------------------------------------------------------------------- 76 | 77 | ## Configuration for each Managed Instance Group 78 | locals { 79 | mig_cfg = { 80 | "mig01" = { 81 | machine_type = "e2-small" 82 | source_image = "debian-11" 83 | source_image_project = "debian-cloud" 84 | disk_size = "50" 85 | 86 | startup_script = file("./scripts/startup-script.sh") 87 | 88 | mig_name = "mig-01" 89 | region = "us-central1" 90 | 91 | target_size = 2 92 | max_surge_fixed = 4 93 | max_unavailable_fixed = 0 94 | 95 | port_name = "http" 96 | backend_port = 80 97 | 98 | network = module.network["network1"].network_name 99 | subnetwork = module.network["network1"].subnets[0] 100 | 101 | service_account = "sa-mig-01" 102 | roles = ["roles/monitoring.metricWriter", "roles/logging.logWriter"] 103 | scopes = ["logging-write", "monitoring-write", "cloud-platform"] 104 | 105 | tags = ["mig-01", "lb-web-hc"] 106 | }, 107 | "mig02" = { 108 | machine_type = "e2-small" 109 | source_image = "debian-11" 110 | source_image_project = "debian-cloud" 111 | disk_size = "50" 112 | 113 | startup_script = file("./scripts/startup-script.sh") 114 | 115 | mig_name = "mig-02" 116 | region = "us-east1" 117 | 118 | target_size = 2 119 | max_surge_fixed = 3 120 | max_unavailable_fixed = 0 121 | 122 | port_name = "http" 123 | backend_port = 80 124 | 125 | network = module.network["network2"].network_name 126 | subnetwork = module.network["network2"].subnets[0] 127 | 128 | service_account = "sa-mig-02" 129 | roles = ["roles/monitoring.metricWriter", "roles/logging.logWriter"] 130 | scopes = ["logging-write", "monitoring-write", "cloud-platform"] 131 | 132 | tags = ["mig-02", "lb-web-hc"] 133 | }, 134 | # Add more settings for other MIGs if needed 135 | } 136 | } 137 | module "mig" { 138 | source = "GoogleCloudPlatform/waap/google//modules/mig" 139 | version = "~> 0.1" 140 | for_each = local.mig_cfg 141 | 142 | project_id = var.project_id 143 | 144 | machine_type = each.value.machine_type 145 | source_image = each.value.source_image 146 | source_image_project = each.value.source_image_project 147 | disk_size_gb = each.value.disk_size 148 | 149 | startup_script = each.value.startup_script 150 | 151 | mig_name = each.value.mig_name 152 | region = each.value.region 153 | 154 | target_size = each.value.target_size 155 | max_surge_fixed = each.value.max_surge_fixed 156 | max_unavailable_fixed = each.value.max_unavailable_fixed 157 | 158 | port_name = each.value.port_name 159 | backend_port = each.value.backend_port 160 | 161 | network = each.value.network 162 | subnetwork = each.value.subnetwork 163 | 164 | service_account = each.value.service_account 165 | roles = each.value.roles 166 | scopes = each.value.scopes 167 | 168 | tags = each.value.tags 169 | } 170 | 171 | ## --------------------------------------------------------------------------------------------------------------------- 172 | ## RECAPTCHA 173 | ## Score Recaptcha Configuration. 174 | ## --------------------------------------------------------------------------------------------------------------------- 175 | 176 | resource "google_recaptcha_enterprise_key" "primary" { 177 | display_name = "web_recaptcha" 178 | project = var.project_id 179 | 180 | testing_options { 181 | testing_score = 0.5 182 | } 183 | 184 | web_settings { 185 | integration_type = "SCORE" 186 | allow_all_domains = true 187 | allow_amp_traffic = false 188 | } 189 | } 190 | 191 | resource "random_id" "suffix" { 192 | byte_length = 4 193 | } 194 | 195 | ## --------------------------------------------------------------------------------------------------------------------- 196 | ## CLOUD ARMOR 197 | ## Backend Policy configuration with owasp rules. 198 | ## --------------------------------------------------------------------------------------------------------------------- 199 | 200 | resource "google_compute_security_policy" "edge_policy" { 201 | project = var.project_id 202 | name = "edge-policy-${random_id.suffix.hex}" 203 | type = "CLOUD_ARMOR_EDGE" 204 | description = "Edge Security Policy" 205 | 206 | rule { 207 | action = "allow" 208 | description = "Default rule, higher priority overrides it" 209 | priority = 2147483647 210 | match { 211 | versioned_expr = "SRC_IPS_V1" 212 | config { 213 | src_ip_ranges = ["*"] 214 | } 215 | } 216 | } 217 | 218 | rule { 219 | action = "deny(403)" 220 | description = "Deny Specific IP address" 221 | priority = 7000 222 | 223 | match { 224 | versioned_expr = "SRC_IPS_V1" 225 | config { 226 | src_ip_ranges = ["85.172.66.254/32"] 227 | } 228 | } 229 | } 230 | 231 | rule { 232 | action = "deny(403)" 233 | priority = 7005 234 | description = "Deny Specific Region" 235 | match { 236 | expr { 237 | expression = "origin.region_code == 'CH'" 238 | } 239 | } 240 | } 241 | lifecycle { 242 | create_before_destroy = true 243 | } 244 | } 245 | 246 | module "backend_policy" { 247 | source = "GoogleCloudPlatform/cloud-armor/google" 248 | version = "0.3.0" 249 | 250 | project_id = var.project_id 251 | name = "backend-policy-${random_id.suffix.hex}" 252 | description = "Backend Security Policy" 253 | default_rule_action = "allow" 254 | type = "CLOUD_ARMOR" 255 | layer_7_ddos_defense_enable = true 256 | layer_7_ddos_defense_rule_visibility = "STANDARD" 257 | 258 | recaptcha_redirect_site_key = google_recaptcha_enterprise_key.primary.name 259 | 260 | pre_configured_rules = { 261 | "sqli_sensitivity_level_1" = { 262 | action = "deny(403)" 263 | priority = 9000 264 | description = "Block SQL Injection" 265 | target_rule_set = "sqli-v33-stable" 266 | rate_limit_options = { 267 | rate_limit_http_request_count = 100 268 | rate_limit_http_request_interval_sec = 10 269 | ban_duration_sec = 60 270 | } 271 | } 272 | 273 | "xss-stable_level_1" = { 274 | action = "deny(403)" 275 | priority = 9005 276 | description = "Block XSS" 277 | target_rule_set = "xss-v33-stable" 278 | sensitivity_level = 1 279 | rate_limit_options = { 280 | rate_limit_http_request_count = 100 281 | rate_limit_http_request_interval_sec = 10 282 | ban_duration_sec = 60 283 | } 284 | } 285 | 286 | "lfi-stable_level_1" = { 287 | action = "deny(403)" 288 | priority = 9010 289 | description = "Block Local File Inclusion" 290 | target_rule_set = "lfi-v33-stable" 291 | sensitivity_level = 1 292 | rate_limit_options = { 293 | rate_limit_http_request_count = 100 294 | rate_limit_http_request_interval_sec = 10 295 | ban_duration_sec = 60 296 | } 297 | } 298 | 299 | "rfi-stable_level_1" = { 300 | action = "deny(403)" 301 | priority = 9015 302 | description = "Block Remote File Inclusion" 303 | target_rule_set = "rfi-v33-stable" 304 | sensitivity_level = 1 305 | rate_limit_options = { 306 | rate_limit_http_request_count = 100 307 | rate_limit_http_request_interval_sec = 10 308 | ban_duration_sec = 60 309 | } 310 | } 311 | 312 | "methodenforcement-stable_level_1" = { 313 | action = "deny(403)" 314 | priority = 9020 315 | description = "Block Method Enforcement" 316 | target_rule_set = "methodenforcement-v33-stable" 317 | sensitivity_level = 1 318 | rate_limit_options = { 319 | rate_limit_http_request_count = 100 320 | rate_limit_http_request_interval_sec = 10 321 | ban_duration_sec = 60 322 | } 323 | } 324 | 325 | "rce-stable_level_1" = { 326 | action = "deny(403)" 327 | priority = 9025 328 | description = "Block Remote Code Execution" 329 | target_rule_set = "rce-v33-stable" 330 | sensitivity_level = 1 331 | rate_limit_options = { 332 | rate_limit_http_request_count = 100 333 | rate_limit_http_request_interval_sec = 10 334 | ban_duration_sec = 60 335 | } 336 | } 337 | 338 | "protocolattack-stable_level_1" = { 339 | action = "deny(403)" 340 | priority = 9030 341 | description = "Block Protocol Attack" 342 | target_rule_set = "protocolattack-v33-stable" 343 | sensitivity_level = 1 344 | rate_limit_options = { 345 | rate_limit_http_request_count = 100 346 | rate_limit_http_request_interval_sec = 10 347 | ban_duration_sec = 60 348 | } 349 | } 350 | 351 | "scannerdetection-stable_level_1" = { 352 | action = "deny(403)" 353 | priority = 9035 354 | description = "Block Scanner Detection" 355 | target_rule_set = "scannerdetection-v33-stable" 356 | sensitivity_level = 1 357 | rate_limit_options = { 358 | rate_limit_http_request_count = 100 359 | rate_limit_http_request_interval_sec = 10 360 | ban_duration_sec = 60 361 | } 362 | } 363 | 364 | "php-stable_level_1" = { 365 | action = "deny(403)" 366 | priority = 9040 367 | description = "Block PHP Injection Attack" 368 | target_rule_set = "php-v33-stable" 369 | sensitivity_level = 1 370 | rate_limit_options = { 371 | rate_limit_http_request_count = 100 372 | rate_limit_http_request_interval_sec = 10 373 | ban_duration_sec = 60 374 | } 375 | } 376 | 377 | "sessionfixation-stable_level_1" = { 378 | action = "deny(403)" 379 | priority = 9045 380 | description = "Block Session Fixation Attack" 381 | target_rule_set = "sessionfixation-v33-stable" 382 | sensitivity_level = 1 383 | rate_limit_options = { 384 | rate_limit_http_request_count = 100 385 | rate_limit_http_request_interval_sec = 10 386 | ban_duration_sec = 60 387 | } 388 | } 389 | 390 | "java-stable_level_1" = { 391 | action = "deny(403)" 392 | priority = 9050 393 | description = "Block Java Attack" 394 | target_rule_set = "java-v33-stable" 395 | sensitivity_level = 1 396 | rate_limit_options = { 397 | rate_limit_http_request_count = 100 398 | rate_limit_http_request_interval_sec = 10 399 | ban_duration_sec = 60 400 | } 401 | } 402 | 403 | "nodejs-stable_level_1" = { 404 | action = "deny(403)" 405 | priority = 9055 406 | description = "Block NodeJS Attack" 407 | target_rule_set = "nodejs-v33-stable" 408 | sensitivity_level = 1 409 | rate_limit_options = { 410 | rate_limit_http_request_count = 100 411 | rate_limit_http_request_interval_sec = 10 412 | ban_duration_sec = 60 413 | } 414 | } 415 | 416 | "cve-canary_level_1" = { 417 | action = "deny(403)" 418 | priority = 9060 419 | description = "Fix Log4j Vulnerability" 420 | target_rule_set = "cve-canary" 421 | sensitivity_level = 1 422 | rate_limit_options = { 423 | rate_limit_http_request_count = 100 424 | rate_limit_http_request_interval_sec = 10 425 | ban_duration_sec = 60 426 | } 427 | } 428 | 429 | "json-sqli-canary_level_2" = { 430 | action = "deny(403)" 431 | priority = 9065 432 | description = "JSON-based SQL injection bypass vulnerability" 433 | target_rule_set = "json-sqli-canary" 434 | sensitivity_level = 2 435 | rate_limit_options = { 436 | rate_limit_http_request_count = 100 437 | rate_limit_http_request_interval_sec = 10 438 | ban_duration_sec = 60 439 | } 440 | } 441 | } 442 | } 443 | 444 | ## --------------------------------------------------------------------------------------------------------------------- 445 | ## LOAD BALANCER 446 | ## Configuration of the Load Balancer and its resources. 447 | ## --------------------------------------------------------------------------------------------------------------------- 448 | 449 | locals { 450 | health_check = { 451 | check_interval_sec = 60 452 | timeout_sec = 60 453 | healthy_threshold = 2 454 | unhealthy_threshold = 2 455 | request_path = "/" 456 | port = 80 457 | host = null 458 | logging = false 459 | } 460 | 461 | } 462 | module "lb-http" { 463 | source = "GoogleCloudPlatform/lb-http/google" 464 | version = "9.0.0" 465 | 466 | project = var.project_id 467 | name = "lb-web-app" 468 | target_tags = ["lb-web-hc"] 469 | 470 | load_balancing_scheme = "EXTERNAL_MANAGED" 471 | 472 | firewall_networks = [module.network["network1"].network_name, module.network["network2"].network_name] 473 | firewall_projects = [var.project_id, var.project_id] 474 | use_ssl_certificates = false 475 | ssl = false 476 | https_redirect = false 477 | quic = true 478 | 479 | create_url_map = var.url_map ? false : true 480 | url_map = try(google_compute_url_map.traffic_mgmt[0].self_link, null) 481 | 482 | backends = { 483 | default = { 484 | 485 | description = "Web App Default Backend" 486 | protocol = "HTTP" 487 | port = 80 488 | port_name = "http" 489 | timeout_sec = 600 490 | enable_cdn = true 491 | connection_draining_timeout_sec = null 492 | compression_mode = "AUTOMATIC" 493 | security_policy = module.backend_policy.policy.name 494 | edge_security_policy = google_compute_security_policy.edge_policy.id 495 | session_affinity = null 496 | affinity_cookie_ttl_sec = null 497 | custom_request_headers = null 498 | custom_response_headers = null 499 | 500 | health_check = local.health_check 501 | log_config = { 502 | enable = true 503 | sample_rate = 0.05 504 | } 505 | 506 | cdn_policy = { 507 | cache_mode = "CACHE_ALL_STATIC" 508 | default_ttl = 3600 509 | client_ttl = 1800 510 | max_ttl = 28800 511 | serve_while_stale = 86400 512 | negative_caching = true 513 | 514 | negative_caching_policy = { 515 | code = 404 516 | ttl = 60 517 | } 518 | 519 | cache_key_policy = { 520 | include_host = true 521 | include_protocol = true 522 | include_query_string = true 523 | include_named_cookies = ["__next_preview_data", "__prerender_bypass"] 524 | } 525 | } 526 | 527 | groups = [ 528 | { 529 | group = module.mig["mig01"].instance_group 530 | balancing_mode = "UTILIZATION" 531 | capacity_scaler = null 532 | description = null 533 | max_connections = null 534 | max_connections_per_instance = null 535 | max_connections_per_endpoint = null 536 | max_rate = null 537 | max_rate_per_instance = null 538 | max_rate_per_endpoint = null 539 | max_utilization = 0.9 540 | }, 541 | { 542 | group = module.mig["mig02"].instance_group 543 | balancing_mode = "UTILIZATION" 544 | capacity_scaler = null 545 | description = null 546 | max_connections = null 547 | max_connections_per_instance = null 548 | max_connections_per_endpoint = null 549 | max_rate = null 550 | max_rate_per_instance = null 551 | max_rate_per_endpoint = null 552 | max_utilization = 0.9 553 | }, 554 | ] 555 | 556 | iap_config = { 557 | enable = false 558 | oauth2_client_id = "" 559 | oauth2_client_secret = "" 560 | } 561 | } 562 | web-app01 = { 563 | 564 | description = "Web App Backend 01" 565 | protocol = "HTTP" 566 | port = 80 567 | port_name = "http" 568 | timeout_sec = 600 569 | enable_cdn = true 570 | connection_draining_timeout_sec = null 571 | compression_mode = "AUTOMATIC" 572 | security_policy = module.backend_policy.policy.name 573 | edge_security_policy = google_compute_security_policy.edge_policy.id 574 | session_affinity = null 575 | affinity_cookie_ttl_sec = null 576 | custom_request_headers = null 577 | custom_response_headers = null 578 | 579 | health_check = local.health_check 580 | log_config = { 581 | enable = true 582 | sample_rate = 0.05 583 | } 584 | 585 | cdn_policy = { 586 | cache_mode = "CACHE_ALL_STATIC" 587 | default_ttl = 3600 588 | client_ttl = 1800 589 | max_ttl = 28800 590 | serve_while_stale = 86400 591 | negative_caching = true 592 | 593 | negative_caching_policy = { 594 | code = 404 595 | ttl = 60 596 | } 597 | 598 | cache_key_policy = { 599 | include_host = true 600 | include_protocol = true 601 | include_query_string = true 602 | include_named_cookies = ["__next_preview_data", "__prerender_bypass"] 603 | } 604 | } 605 | 606 | groups = [ 607 | { 608 | group = module.mig["mig01"].instance_group 609 | balancing_mode = "UTILIZATION" 610 | capacity_scaler = null 611 | description = null 612 | max_connections = null 613 | max_connections_per_instance = null 614 | max_connections_per_endpoint = null 615 | max_rate = null 616 | max_rate_per_instance = null 617 | max_rate_per_endpoint = null 618 | max_utilization = 0.9 619 | }, 620 | ] 621 | iap_config = { 622 | enable = false 623 | oauth2_client_id = null 624 | oauth2_client_secret = null 625 | } 626 | } 627 | 628 | web-app02 = { 629 | 630 | description = "Web App Backend 02" 631 | protocol = "HTTP" 632 | port = 80 633 | port_name = "http" 634 | timeout_sec = 600 635 | enable_cdn = true 636 | connection_draining_timeout_sec = null 637 | compression_mode = "AUTOMATIC" 638 | security_policy = module.backend_policy.policy.name 639 | edge_security_policy = google_compute_security_policy.edge_policy.id 640 | session_affinity = null 641 | affinity_cookie_ttl_sec = null 642 | custom_request_headers = null 643 | custom_response_headers = null 644 | 645 | health_check = local.health_check 646 | 647 | log_config = { 648 | enable = true 649 | sample_rate = 0.05 650 | } 651 | 652 | cdn_policy = { 653 | cache_mode = "CACHE_ALL_STATIC" 654 | default_ttl = 3600 655 | client_ttl = 1800 656 | max_ttl = 28800 657 | serve_while_stale = 86400 658 | negative_caching = true 659 | 660 | negative_caching_policy = { 661 | code = 404 662 | ttl = 60 663 | } 664 | 665 | cache_key_policy = { 666 | include_host = true 667 | include_protocol = true 668 | include_query_string = true 669 | include_named_cookies = ["__next_preview_data", "__prerender_bypass"] 670 | } 671 | } 672 | 673 | groups = [ 674 | { 675 | group = module.mig["mig02"].instance_group 676 | balancing_mode = "UTILIZATION" 677 | capacity_scaler = null 678 | description = null 679 | max_connections = null 680 | max_connections_per_instance = null 681 | max_connections_per_endpoint = null 682 | max_rate = null 683 | max_rate_per_instance = null 684 | max_rate_per_endpoint = null 685 | max_utilization = 0.9 686 | }, 687 | ] 688 | iap_config = { 689 | enable = false 690 | oauth2_client_id = null 691 | oauth2_client_secret = null 692 | } 693 | } 694 | } 695 | } 696 | 697 | resource "google_compute_url_map" "traffic_mgmt" { 698 | count = var.url_map ? 1 : 0 699 | 700 | project = var.project_id 701 | 702 | name = "lb-web-app" 703 | description = "UrlMap used to route requests to a backend service based on rules." 704 | default_service = module.lb-http.backend_services["default"].self_link 705 | 706 | host_rule { 707 | hosts = ["*"] 708 | path_matcher = "allpaths" 709 | } 710 | 711 | path_matcher { 712 | name = "allpaths" 713 | default_service = module.lb-http.backend_services["default"].self_link 714 | 715 | path_rule { 716 | paths = ["/"] 717 | route_action { 718 | weighted_backend_services { 719 | backend_service = module.lb-http.backend_services["web-app01"].self_link 720 | weight = 400 721 | } 722 | weighted_backend_services { 723 | backend_service = module.lb-http.backend_services["web-app02"].self_link 724 | weight = 600 725 | } 726 | } 727 | } 728 | } 729 | } 730 | 731 | ## --------------------------------------------------------------------------------------------------------------------- 732 | ## MONITORING 733 | ## Dashboard 734 | ## --------------------------------------------------------------------------------------------------------------------- 735 | 736 | resource "google_monitoring_dashboard" "dashboard" { 737 | dashboard_json = file("./scripts/dashboard.json") 738 | project = var.project_id 739 | 740 | lifecycle { 741 | ignore_changes = [ 742 | dashboard_json 743 | ] 744 | } 745 | 746 | } 747 | 748 | locals { 749 | policies = { 750 | "sql_injection" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"sqli\" AND jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds!=\"owasp-crs-id942550-sqli\"" 751 | "cross_site_scripting" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"xss\"" 752 | "local_file_inclusion" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"lfi\"" 753 | "remote_code_execution" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"rce\"" 754 | "remote_file_inclusion" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"rfi\"" 755 | "method_enforcement" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"methodenforcement\"" 756 | "scanner_detection" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"scannerdetection\"" 757 | "protocol_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"protocolattack\"" 758 | "php_injection_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"php\"" 759 | "session_fixation_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"sessionfixation\"" 760 | "java_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"java\"" 761 | "nodejs_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"nodejs\"" 762 | "log4j_attack" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"cve\"" 763 | "json_sql_injection" = "jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds=~\"owasp-crs-id942550-sqli\"" 764 | } 765 | } 766 | 767 | resource "google_logging_metric" "logging_metric" { 768 | for_each = local.policies 769 | project = var.project_id 770 | 771 | name = "${each.key}/metric" 772 | filter = each.value 773 | metric_descriptor { 774 | metric_kind = "DELTA" 775 | value_type = "INT64" 776 | 777 | labels { 778 | key = "signature_id" 779 | value_type = "STRING" 780 | } 781 | } 782 | label_extractors = { 783 | "signature_id" = "EXTRACT(jsonPayload.enforcedSecurityPolicy.preconfiguredExprIds)" 784 | } 785 | } 786 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/scripts/README.md: -------------------------------------------------------------------------------- 1 | # Startup scripts folder 2 | 3 | In this folder we have the startup scripts used to configure the instances that are the backends of the solution. 4 | 5 | The content of the scripts listed in this folder is a literal representation of the startup-script of a Compute Instance. Therefore, if you want to reproduce the settings defined for the startup script of a backend that is already running in the GCP environment, just copy the content of the startup script of the desired instance and replace the content of the startup-script.sh file located on this folder. 6 | 7 | This action, if performed before deploying the environment, will cause the managed instance groups templates to be created already using the desired startup script settings. 8 | 9 | If the action is performed after the environment has already gone through the first deployment process, this action will recreate the Managed Instance Group template file and will trigger the Managed Instance Group to replace the current instances with new instances using the new template. 10 | 11 | ### * Note that in the example we used, we automated not only the process of deploying the instance with its basic configurations, but also added the configurations related to our example application. 12 | 13 | As we are using Managed Instance Groups, we recommend that a similar automation process be performed for the deployment of your application using the startup script. 14 | 15 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/scripts/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "displayName": "Web App Protection Dashboard", 3 | "mosaicLayout": { 4 | "columns": 12, 5 | "tiles": [ 6 | { 7 | "height": 4, 8 | "widget": { 9 | "title": "Backend Latency", 10 | "xyChart": { 11 | "chartOptions": {}, 12 | "dataSets": [ 13 | { 14 | "plotType": "LINE", 15 | "targetAxis": "Y1", 16 | "timeSeriesQuery": { 17 | "timeSeriesQueryLanguage": "fetch https_lb_rule::loadbalancing.googleapis.com/https/backend_latencies\n| align delta()\n| group_by [resource.backend_target_name, resource.url_map_name],\n [value_backend_latencies_aggregate: aggregate(value.backend_latencies)]\n| value\n [value_backend_latencies_aggregate_percentile_from:\n percentile_from(value_backend_latencies_aggregate, 95)]" 18 | } 19 | } 20 | ], 21 | "timeshiftDuration": "0s", 22 | "yAxis": { 23 | "scale": "LINEAR" 24 | } 25 | } 26 | }, 27 | "width": 6, 28 | "xPos": 6, 29 | "yPos": 4 30 | }, 31 | { 32 | "height": 4, 33 | "widget": { 34 | "title": "Total latency", 35 | "xyChart": { 36 | "chartOptions": {}, 37 | "dataSets": [ 38 | { 39 | "plotType": "LINE", 40 | "targetAxis": "Y1", 41 | "timeSeriesQuery": { 42 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/total_latencies'\n| group_by 1m,\n [value_total_latencies_percentile: percentile(value.total_latencies, 99)]\n| every 1m\n| group_by [resource.url_map_name, resource.project_id],\n [value_total_latencies_percentile_aggregate:\n aggregate(value_total_latencies_percentile)]" 43 | } 44 | } 45 | ], 46 | "timeshiftDuration": "0s", 47 | "yAxis": { 48 | "scale": "LINEAR" 49 | } 50 | } 51 | }, 52 | "width": 6, 53 | "xPos": 6 54 | }, 55 | { 56 | "height": 4, 57 | "widget": { 58 | "title": "Frontend RTT", 59 | "xyChart": { 60 | "chartOptions": {}, 61 | "dataSets": [ 62 | { 63 | "minAlignmentPeriod": "60s", 64 | "plotType": "LINE", 65 | "targetAxis": "Y1", 66 | "timeSeriesQuery": { 67 | "timeSeriesFilter": { 68 | "aggregation": { 69 | "alignmentPeriod": "60s", 70 | "crossSeriesReducer": "REDUCE_PERCENTILE_50", 71 | "groupByFields": [ 72 | "resource.label.\"url_map_name\"" 73 | ], 74 | "perSeriesAligner": "ALIGN_DELTA" 75 | }, 76 | "filter": "metric.type=\"loadbalancing.googleapis.com/https/frontend_tcp_rtt\" resource.type=\"https_lb_rule\"" 77 | } 78 | } 79 | } 80 | ], 81 | "timeshiftDuration": "0s", 82 | "yAxis": { 83 | "scale": "LINEAR" 84 | } 85 | } 86 | }, 87 | "width": 3, 88 | "xPos": 3, 89 | "yPos": 4 90 | }, 91 | { 92 | "height": 4, 93 | "widget": { 94 | "title": " 5XX Response Ratio", 95 | "xyChart": { 96 | "chartOptions": {}, 97 | "dataSets": [ 98 | { 99 | "plotType": "LINE", 100 | "targetAxis": "Y1", 101 | "timeSeriesQuery": { 102 | "timeSeriesQueryLanguage": "fetch https_lb_rule::loadbalancing.googleapis.com/https/request_count\n| align delta()\n| group_by [resource.url_map_name, resource.project_id],\n [c'metric response code class = if sum /':\n sum(if(metric.response_code_class = 500, value.request_count, 0))\n / sum(value.request_count)]\n| value\n [c'metric response code class = if sum / scale':\n scale(c'metric response code class = if sum /', '%')]" 103 | } 104 | } 105 | ], 106 | "timeshiftDuration": "0s", 107 | "yAxis": { 108 | "scale": "LINEAR" 109 | } 110 | } 111 | }, 112 | "width": 3, 113 | "yPos": 4 114 | }, 115 | { 116 | "height": 3, 117 | "widget": { 118 | "title": "Client Response Code", 119 | "xyChart": { 120 | "chartOptions": {}, 121 | "dataSets": [ 122 | { 123 | "plotType": "STACKED_AREA", 124 | "targetAxis": "Y1", 125 | "timeSeriesQuery": { 126 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/request_count'\n| group_by 1h, [row_count: row_count()]\n| every 1h\n| group_by [metric.response_code_class],\n [row_count_aggregate: aggregate(row_count)]" 127 | } 128 | } 129 | ], 130 | "timeshiftDuration": "0s", 131 | "yAxis": { 132 | "scale": "LINEAR" 133 | } 134 | } 135 | }, 136 | "width": 3, 137 | "xPos": 6, 138 | "yPos": 8 139 | }, 140 | { 141 | "height": 2, 142 | "widget": { 143 | "scorecard": { 144 | "timeSeriesQuery": { 145 | "timeSeriesFilter": { 146 | "aggregation": { 147 | "alignmentPeriod": "60s", 148 | "crossSeriesReducer": "REDUCE_SUM", 149 | "perSeriesAligner": "ALIGN_SUM" 150 | }, 151 | "filter": "metric.type=\"loadbalancing.googleapis.com/https/response_bytes_count\" resource.type=\"https_lb_rule\"" 152 | } 153 | } 154 | }, 155 | "title": "Total Egress" 156 | }, 157 | "width": 2, 158 | "yPos": 2 159 | }, 160 | { 161 | "height": 2, 162 | "widget": { 163 | "scorecard": { 164 | "timeSeriesQuery": { 165 | "timeSeriesFilter": { 166 | "aggregation": { 167 | "alignmentPeriod": "60s", 168 | "crossSeriesReducer": "REDUCE_SUM", 169 | "perSeriesAligner": "ALIGN_SUM" 170 | }, 171 | "filter": "metric.type=\"loadbalancing.googleapis.com/https/response_bytes_count\" resource.type=\"https_lb_rule\" metric.label.\"cache_result\"=\"MISS\"" 172 | } 173 | } 174 | }, 175 | "title": "Cache Miss Egress" 176 | }, 177 | "width": 2, 178 | "xPos": 4, 179 | "yPos": 2 180 | }, 181 | { 182 | "height": 3, 183 | "widget": { 184 | "title": "Request Count by Country", 185 | "xyChart": { 186 | "chartOptions": {}, 187 | "dataSets": [ 188 | { 189 | "plotType": "STACKED_AREA", 190 | "targetAxis": "Y1", 191 | "timeSeriesQuery": { 192 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/request_count'\n| group_by 1h, [row_count: row_count()]\n| every 1h\n| group_by [metric.client_country], [row_count_aggregate: aggregate(row_count)]" 193 | } 194 | } 195 | ], 196 | "timeshiftDuration": "0s", 197 | "yAxis": { 198 | "scale": "LINEAR" 199 | } 200 | } 201 | }, 202 | "width": 3, 203 | "xPos": 9, 204 | "yPos": 8 205 | }, 206 | { 207 | "height": 2, 208 | "widget": { 209 | "scorecard": { 210 | "gaugeView": { 211 | "upperBound": 1.0 212 | }, 213 | "timeSeriesQuery": { 214 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/request_count'\n| filter\n (metric.response_code_class == 500)\n| group_by 1m, [value_request_count_aggregate: aggregate(value.request_count)]\n| every 1m\n| group_by [],\n [value_request_count_aggregate_aggregate:\n aggregate(value_request_count_aggregate)]" 215 | } 216 | }, 217 | "title": "5xx Errors" 218 | }, 219 | "width": 2, 220 | "xPos": 4 221 | }, 222 | { 223 | "height": 2, 224 | "widget": { 225 | "scorecard": { 226 | "sparkChartView": { 227 | "sparkChartType": "SPARK_LINE" 228 | }, 229 | "timeSeriesQuery": { 230 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/total_latencies'\n| group_by 1m,\n [value_total_latencies_percentile: percentile(value.total_latencies, 99)]\n| every 1m\n| group_by [resource.url_map_name, resource.project_id],\n [value_total_latencies_percentile_aggregate:\n aggregate(value_total_latencies_percentile)]" 231 | } 232 | }, 233 | "title": "Total Latency" 234 | }, 235 | "width": 2 236 | }, 237 | { 238 | "height": 2, 239 | "widget": { 240 | "scorecard": { 241 | "timeSeriesQuery": { 242 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/request_count'\n| group_by 1m, [value_request_count_aggregate: aggregate(value.request_count)]\n| every 1m\n| group_by [],\n [value_request_count_aggregate_aggregate:\n aggregate(value_request_count_aggregate)]" 243 | } 244 | }, 245 | "title": "Total Requests" 246 | }, 247 | "width": 2, 248 | "xPos": 2 249 | }, 250 | { 251 | "height": 2, 252 | "widget": { 253 | "scorecard": { 254 | "timeSeriesQuery": { 255 | "timeSeriesQueryLanguage": "fetch https_lb_rule\n| metric 'loadbalancing.googleapis.com/https/response_bytes_count'\n| filter (metric.cache_result == 'HIT')\n| group_by 1m,\n [value_response_bytes_count_aggregate:\n aggregate(value.response_bytes_count)]\n| every 1m\n| group_by [],\n [value_response_bytes_count_aggregate_aggregate:\n aggregate(value_response_bytes_count_aggregate)]" 256 | } 257 | }, 258 | "title": "Cache Hit Egress" 259 | }, 260 | "width": 2, 261 | "xPos": 2, 262 | "yPos": 2 263 | }, 264 | { 265 | "height": 3, 266 | "widget": { 267 | "title": "Requests (Total) by Policy Name", 268 | "xyChart": { 269 | "chartOptions": {}, 270 | "dataSets": [ 271 | { 272 | "minAlignmentPeriod": "60s", 273 | "plotType": "STACKED_AREA", 274 | "targetAxis": "Y1", 275 | "timeSeriesQuery": { 276 | "timeSeriesFilter": { 277 | "aggregation": { 278 | "alignmentPeriod": "60s", 279 | "crossSeriesReducer": "REDUCE_SUM", 280 | "groupByFields": [ 281 | "resource.label.\"policy_name\"" 282 | ], 283 | "perSeriesAligner": "ALIGN_SUM" 284 | }, 285 | "filter": "metric.type=\"networksecurity.googleapis.com/https/request_count\" resource.type=\"network_security_policy\"", 286 | "secondaryAggregation": { 287 | "alignmentPeriod": "60s" 288 | } 289 | } 290 | } 291 | } 292 | ], 293 | "timeshiftDuration": "0s", 294 | "yAxis": { 295 | "scale": "LINEAR" 296 | } 297 | } 298 | }, 299 | "width": 3, 300 | "yPos": 8 301 | }, 302 | { 303 | "height": 3, 304 | "widget": { 305 | "title": "Requests (Blocked) by Policy Name", 306 | "xyChart": { 307 | "chartOptions": {}, 308 | "dataSets": [ 309 | { 310 | "minAlignmentPeriod": "60s", 311 | "plotType": "STACKED_AREA", 312 | "targetAxis": "Y1", 313 | "timeSeriesQuery": { 314 | "timeSeriesFilter": { 315 | "aggregation": { 316 | "alignmentPeriod": "60s", 317 | "crossSeriesReducer": "REDUCE_SUM", 318 | "groupByFields": [ 319 | "resource.label.\"policy_name\"" 320 | ], 321 | "perSeriesAligner": "ALIGN_SUM" 322 | }, 323 | "filter": "metric.type=\"networksecurity.googleapis.com/https/request_count\" resource.type=\"network_security_policy\" metric.label.\"blocked\"=\"true\"", 324 | "secondaryAggregation": { 325 | "alignmentPeriod": "60s" 326 | } 327 | } 328 | } 329 | } 330 | ], 331 | "timeshiftDuration": "0s", 332 | "yAxis": { 333 | "scale": "LINEAR" 334 | } 335 | } 336 | }, 337 | "width": 3, 338 | "xPos": 3, 339 | "yPos": 8 340 | }, 341 | { 342 | "height": 4, 343 | "widget": { 344 | "title": "CRS 3.0", 345 | "xyChart": { 346 | "chartOptions": {}, 347 | "dataSets": [ 348 | { 349 | "minAlignmentPeriod": "60s", 350 | "plotType": "LINE", 351 | "targetAxis": "Y1", 352 | "timeSeriesQuery": { 353 | "timeSeriesFilter": { 354 | "aggregation": { 355 | "alignmentPeriod": "60s", 356 | "crossSeriesReducer": "REDUCE_SUM", 357 | "perSeriesAligner": "ALIGN_MEAN" 358 | }, 359 | "filter": "metric.type=\"logging.googleapis.com/user/cross_site_scripting/metric\" resource.type=\"global\"", 360 | "secondaryAggregation": { 361 | "alignmentPeriod": "60s" 362 | } 363 | } 364 | } 365 | }, 366 | { 367 | "minAlignmentPeriod": "60s", 368 | "plotType": "LINE", 369 | "targetAxis": "Y1", 370 | "timeSeriesQuery": { 371 | "timeSeriesFilter": { 372 | "aggregation": { 373 | "alignmentPeriod": "60s", 374 | "crossSeriesReducer": "REDUCE_SUM", 375 | "groupByFields": [ 376 | "metric.label.\"signature_id\"" 377 | ], 378 | "perSeriesAligner": "ALIGN_MEAN" 379 | }, 380 | "filter": "metric.type=\"logging.googleapis.com/user/local_file_inclusion/metric\" resource.type=\"global\"", 381 | "secondaryAggregation": { 382 | "alignmentPeriod": "60s" 383 | } 384 | } 385 | } 386 | }, 387 | { 388 | "minAlignmentPeriod": "60s", 389 | "plotType": "LINE", 390 | "targetAxis": "Y1", 391 | "timeSeriesQuery": { 392 | "timeSeriesFilter": { 393 | "aggregation": { 394 | "alignmentPeriod": "60s", 395 | "crossSeriesReducer": "REDUCE_SUM", 396 | "perSeriesAligner": "ALIGN_MEAN" 397 | }, 398 | "filter": "metric.type=\"logging.googleapis.com/user/method_enforcement/metric\" resource.type=\"global\"", 399 | "secondaryAggregation": { 400 | "alignmentPeriod": "60s" 401 | } 402 | } 403 | } 404 | }, 405 | { 406 | "minAlignmentPeriod": "60s", 407 | "plotType": "LINE", 408 | "targetAxis": "Y1", 409 | "timeSeriesQuery": { 410 | "timeSeriesFilter": { 411 | "aggregation": { 412 | "alignmentPeriod": "60s", 413 | "crossSeriesReducer": "REDUCE_SUM", 414 | "perSeriesAligner": "ALIGN_MEAN" 415 | }, 416 | "filter": "metric.type=\"logging.googleapis.com/user/php_injection_attack/metric\" resource.type=\"global\"", 417 | "secondaryAggregation": { 418 | "alignmentPeriod": "60s" 419 | } 420 | } 421 | } 422 | }, 423 | { 424 | "minAlignmentPeriod": "60s", 425 | "plotType": "LINE", 426 | "targetAxis": "Y1", 427 | "timeSeriesQuery": { 428 | "timeSeriesFilter": { 429 | "aggregation": { 430 | "alignmentPeriod": "60s", 431 | "crossSeriesReducer": "REDUCE_SUM", 432 | "perSeriesAligner": "ALIGN_MEAN" 433 | }, 434 | "filter": "metric.type=\"logging.googleapis.com/user/protocol_attack/metric\" resource.type=\"global\"", 435 | "secondaryAggregation": { 436 | "alignmentPeriod": "60s" 437 | } 438 | } 439 | } 440 | }, 441 | { 442 | "minAlignmentPeriod": "60s", 443 | "plotType": "LINE", 444 | "targetAxis": "Y1", 445 | "timeSeriesQuery": { 446 | "timeSeriesFilter": { 447 | "aggregation": { 448 | "alignmentPeriod": "60s", 449 | "crossSeriesReducer": "REDUCE_SUM", 450 | "perSeriesAligner": "ALIGN_MEAN" 451 | }, 452 | "filter": "metric.type=\"logging.googleapis.com/user/remote_code_execution/metric\" resource.type=\"global\"", 453 | "secondaryAggregation": { 454 | "alignmentPeriod": "60s" 455 | } 456 | } 457 | } 458 | }, 459 | { 460 | "minAlignmentPeriod": "60s", 461 | "plotType": "LINE", 462 | "targetAxis": "Y1", 463 | "timeSeriesQuery": { 464 | "timeSeriesFilter": { 465 | "aggregation": { 466 | "alignmentPeriod": "60s", 467 | "crossSeriesReducer": "REDUCE_SUM", 468 | "perSeriesAligner": "ALIGN_MEAN" 469 | }, 470 | "filter": "metric.type=\"logging.googleapis.com/user/remote_file_inclusion/metric\" resource.type=\"global\"", 471 | "secondaryAggregation": { 472 | "alignmentPeriod": "60s" 473 | } 474 | } 475 | } 476 | }, 477 | { 478 | "minAlignmentPeriod": "60s", 479 | "plotType": "LINE", 480 | "targetAxis": "Y1", 481 | "timeSeriesQuery": { 482 | "timeSeriesFilter": { 483 | "aggregation": { 484 | "alignmentPeriod": "60s", 485 | "crossSeriesReducer": "REDUCE_SUM", 486 | "perSeriesAligner": "ALIGN_MEAN" 487 | }, 488 | "filter": "metric.type=\"logging.googleapis.com/user/scanner_detection/metric\" resource.type=\"global\"", 489 | "secondaryAggregation": { 490 | "alignmentPeriod": "60s" 491 | } 492 | } 493 | } 494 | }, 495 | { 496 | "minAlignmentPeriod": "60s", 497 | "plotType": "LINE", 498 | "targetAxis": "Y1", 499 | "timeSeriesQuery": { 500 | "timeSeriesFilter": { 501 | "aggregation": { 502 | "alignmentPeriod": "60s", 503 | "crossSeriesReducer": "REDUCE_SUM", 504 | "perSeriesAligner": "ALIGN_MEAN" 505 | }, 506 | "filter": "metric.type=\"logging.googleapis.com/user/session_fixation_attack/metric\" resource.type=\"global\"", 507 | "secondaryAggregation": { 508 | "alignmentPeriod": "60s" 509 | } 510 | } 511 | } 512 | }, 513 | { 514 | "minAlignmentPeriod": "60s", 515 | "plotType": "LINE", 516 | "targetAxis": "Y1", 517 | "timeSeriesQuery": { 518 | "timeSeriesFilter": { 519 | "aggregation": { 520 | "alignmentPeriod": "60s", 521 | "crossSeriesReducer": "REDUCE_SUM", 522 | "groupByFields": [ 523 | "metric.label.\"signature_id\"" 524 | ], 525 | "perSeriesAligner": "ALIGN_MEAN" 526 | }, 527 | "filter": "metric.type=\"logging.googleapis.com/user/sql_injection/metric\"", 528 | "secondaryAggregation": { 529 | "alignmentPeriod": "60s" 530 | } 531 | } 532 | } 533 | } 534 | ], 535 | "timeshiftDuration": "0s", 536 | "yAxis": { 537 | "scale": "LINEAR" 538 | } 539 | } 540 | }, 541 | "width": 6, 542 | "yPos": 11 543 | }, 544 | { 545 | "height": 4, 546 | "widget": { 547 | "title": "CRS 3.3 & Complex Rules", 548 | "xyChart": { 549 | "chartOptions": {}, 550 | "dataSets": [ 551 | { 552 | "minAlignmentPeriod": "60s", 553 | "plotType": "LINE", 554 | "targetAxis": "Y1", 555 | "timeSeriesQuery": { 556 | "timeSeriesFilter": { 557 | "aggregation": { 558 | "alignmentPeriod": "60s", 559 | "crossSeriesReducer": "REDUCE_SUM", 560 | "groupByFields": [ 561 | "metric.label.\"signature_id\"" 562 | ], 563 | "perSeriesAligner": "ALIGN_MEAN" 564 | }, 565 | "filter": "metric.type=\"logging.googleapis.com/user/java_attack/metric\" resource.type=\"global\"" 566 | } 567 | } 568 | }, 569 | { 570 | "minAlignmentPeriod": "60s", 571 | "plotType": "LINE", 572 | "targetAxis": "Y1", 573 | "timeSeriesQuery": { 574 | "timeSeriesFilter": { 575 | "aggregation": { 576 | "alignmentPeriod": "60s", 577 | "crossSeriesReducer": "REDUCE_SUM", 578 | "groupByFields": [ 579 | "metric.label.\"signature_id\"" 580 | ], 581 | "perSeriesAligner": "ALIGN_MEAN" 582 | }, 583 | "filter": "metric.type=\"logging.googleapis.com/user/nodejs_attack/metric\" resource.type=\"global\"" 584 | } 585 | } 586 | }, 587 | { 588 | "minAlignmentPeriod": "60s", 589 | "plotType": "LINE", 590 | "targetAxis": "Y1", 591 | "timeSeriesQuery": { 592 | "timeSeriesFilter": { 593 | "aggregation": { 594 | "alignmentPeriod": "60s", 595 | "crossSeriesReducer": "REDUCE_SUM", 596 | "groupByFields": [ 597 | "metric.label.\"signature_id\"" 598 | ], 599 | "perSeriesAligner": "ALIGN_MEAN" 600 | }, 601 | "filter": "metric.type=\"logging.googleapis.com/user/log4j_attack/metric\" resource.type=\"global\"" 602 | } 603 | } 604 | }, 605 | { 606 | "minAlignmentPeriod": "60s", 607 | "plotType": "LINE", 608 | "targetAxis": "Y1", 609 | "timeSeriesQuery": { 610 | "timeSeriesFilter": { 611 | "aggregation": { 612 | "alignmentPeriod": "60s", 613 | "crossSeriesReducer": "REDUCE_SUM", 614 | "groupByFields": [ 615 | "metric.label.\"signature_id\"" 616 | ], 617 | "perSeriesAligner": "ALIGN_MEAN" 618 | }, 619 | "filter": "metric.type=\"logging.googleapis.com/user/json_sql_injection/metric\" resource.type=\"global\"" 620 | } 621 | } 622 | } 623 | ], 624 | "yAxis": { 625 | "scale": "LINEAR" 626 | } 627 | } 628 | }, 629 | "width": 6, 630 | "xPos": 6, 631 | "yPos": 11 632 | } 633 | ] 634 | } 635 | } 636 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/scripts/startup-script.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2022 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -x 18 | curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh 19 | sudo bash add-google-cloud-ops-agent-repo.sh --also-install 20 | ## Install docker and run the juice shop application. 21 | sudo apt-get -y install ca-certificates curl gnupg lsb-release 22 | sudo mkdir -m 0755 -p /etc/apt/keyrings 23 | curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg 24 | echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 25 | $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null 26 | apt-get update 27 | sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 28 | ## Code to deploy Juice Shop 29 | docker pull bkimminich/juice-shop 30 | docker run -d -p 80:3000 bkimminich/juice-shop 31 | 32 | ## Code to deploy Hello World 33 | # docker pull crccheck/hello-world 34 | # docker run -d -p 80:8000 crccheck/hello-world 35 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "project_id" { 18 | description = "Google Project ID in which the resources will be created." 19 | type = string 20 | } 21 | 22 | variable "url_map" { 23 | description = "Enable or disable the 'google_compute_url_map' feature to route requests to backends based on rules." 24 | type = bool 25 | default = false 26 | } 27 | -------------------------------------------------------------------------------- /examples/web_app_protection_example/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | terraform { 17 | required_version = ">= 1.3" 18 | required_providers { 19 | google = { 20 | source = "hashicorp/google" 21 | version = ">= 3.45" 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /modules/apigee/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Inputs 3 | 4 | | Name | Description | Type | Default | Required | 5 | |------|-------------|------|---------|:--------:| 6 | | analytics\_region | GCP region for storing Apigee analytics data (see https://cloud.google.com/apigee/docs/api-platform/get-started/install-cli). | `string` | n/a | yes | 7 | | apigee\_endpoint\_attachments | Apigee endpoint attachments (for southbound networking: https://cloud.google.com/apigee/docs/api-platform/architecture/southbound-networking-patterns-endpoints#create-the-psc-attachments). |
map(object({
region = string
service_attachment = string
}))
| `{}` | no | 8 | | apigee\_envgroups | Apigee groups (NAME => [HOSTNAMES]). | `map(list(string))` | `null` | no | 9 | | apigee\_environments | Apigee Environments. |
map(object({
display_name = optional(string)
description = optional(string, "Terraform-managed")
deployment_type = optional(string)
api_proxy_type = optional(string)
node_config = optional(object({
min_node_count = optional(number)
max_node_count = optional(number)
}))
iam = optional(map(list(string)))
envgroups = optional(list(string))
regions = optional(list(string))
}))
| `null` | no | 10 | | apigee\_instances | Apigee Instances ([REGION] => [INSTANCE]). |
map(object({
display_name = optional(string)
description = optional(string, "Terraform-managed")
runtime_ip_cidr_range = string
troubleshooting_ip_cidr_range = string
disk_encryption_key = optional(string)
consumer_accept_list = optional(list(string))
}))
| `null` | no | 11 | | apigee\_org\_description | Description for Apigee Organization. | `string` | `"Apigee Org"` | no | 12 | | apigee\_org\_name | Display name for Apigee Organization. | `string` | `"Apigee Org"` | no | 13 | | billing\_type | Apigee billing type. Can be one of EVALUATION, PAYG, or SUBSCRIPTION. See https://cloud.google.com/apigee/pricing | `string` | `"EVALUATION"` | no | 14 | | create\_apigee\_org | Set to `true` to create a new Apigee org in the provided `var.project_id`; set to `false` to use the existing Apigee org in this project. | `bool` | `true` | no | 15 | | external\_ip | Reserved global external IP for Apigee Load Balancer | `string` | n/a | yes | 16 | | kms\_project\_id | Project ID in which to create keys for Apigee database and disk (org/instance) | `string` | `""` | no | 17 | | network\_id | VPC network ID | `string` | n/a | yes | 18 | | prevent\_key\_destroy | Prevent destroying KMS keys for Apigee Org and Instances | `bool` | `true` | no | 19 | | project\_id | Project id (also used for the Apigee Organization). | `string` | n/a | yes | 20 | | psa\_ranges | Apigee Private Service Access peering ranges |
object({
apigee-range = string
google-managed-services-support-1 = string
})
|
{
"apigee-range": "10.0.0.0/22",
"google-managed-services-support-1": "10.1.0.0/28"
}
| no | 21 | | runtime\_type | Apigee runtime type. Can be one of CLOUD or HYBRID. | `string` | `"CLOUD"` | no | 22 | | ssl\_certificate | SSL Certificate ID for Apigee Load Balancer | `string` | n/a | yes | 23 | | subnet\_id | Apigee NEG subnet ID | `string` | n/a | yes | 24 | 25 | ## Outputs 26 | 27 | | Name | Description | 28 | |------|-------------| 29 | | apigee\_org\_id | Apigee org ID (same as GCP project ID) | 30 | 31 | 32 | -------------------------------------------------------------------------------- /modules/apigee/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | resource "google_project_service_identity" "apigee_sa" { 18 | provider = google-beta 19 | project = var.project_id 20 | service = "apigee.googleapis.com" 21 | } 22 | 23 | module "apigee" { 24 | source = "github.com/GoogleCloudPlatform/cloud-foundation-fabric//modules/apigee?ref=v24.0.0" 25 | project_id = var.project_id 26 | organization = !var.create_apigee_org ? null : { 27 | display_name = var.apigee_org_name 28 | description = var.apigee_org_description 29 | authorized_network = var.network_id 30 | runtime_type = var.runtime_type 31 | billing_type = var.billing_type 32 | database_encryption_key = module.apigee_org_kms.keys["org-db"] 33 | analytics_region = var.analytics_region 34 | } 35 | envgroups = var.apigee_envgroups 36 | environments = var.apigee_environments 37 | instances = { for k, v in var.apigee_instances : k => { 38 | display_name = v.display_name 39 | description = v.description 40 | runtime_ip_cidr_range = v.runtime_ip_cidr_range 41 | troubleshooting_ip_cidr_range = v.troubleshooting_ip_cidr_range 42 | consumer_accept_list = v.consumer_accept_list 43 | disk_encryption_key = module.apigee_instance_kms[k].keys["inst-disk"] 44 | } } 45 | endpoint_attachments = var.apigee_endpoint_attachments 46 | depends_on = [google_service_networking_connection.apigee_peering] 47 | } 48 | 49 | module "apigee_org_kms" { 50 | source = "terraform-google-modules/kms/google" 51 | version = "~> 2.2.1" 52 | 53 | project_id = var.kms_project_id == "" ? var.project_id : var.kms_project_id 54 | location = var.analytics_region 55 | keyring = "apigee-${var.project_id}" 56 | keys = ["org-db"] 57 | set_decrypters_for = ["org-db"] 58 | set_encrypters_for = ["org-db"] 59 | decrypters = [ 60 | "serviceAccount:${google_project_service_identity.apigee_sa.email}" 61 | ] 62 | encrypters = [ 63 | "serviceAccount:${google_project_service_identity.apigee_sa.email}" 64 | ] 65 | prevent_destroy = var.prevent_key_destroy 66 | } 67 | 68 | module "apigee_instance_kms" { 69 | for_each = var.apigee_instances 70 | source = "terraform-google-modules/kms/google" 71 | version = "~> 2.2.1" 72 | 73 | project_id = var.kms_project_id == "" ? var.project_id : var.kms_project_id 74 | location = each.key 75 | keyring = "apigee-${var.project_id}-inst-${each.key}" 76 | keys = ["inst-disk"] 77 | set_decrypters_for = ["inst-disk"] 78 | set_encrypters_for = ["inst-disk"] 79 | decrypters = [ 80 | "serviceAccount:${google_project_service_identity.apigee_sa.email}" 81 | ] 82 | encrypters = [ 83 | "serviceAccount:${google_project_service_identity.apigee_sa.email}" 84 | ] 85 | prevent_destroy = var.prevent_key_destroy 86 | } 87 | 88 | # Service Networking 89 | # https://cloud.google.com/apigee/docs/api-platform/get-started/install-cli#service-networking 90 | resource "google_compute_global_address" "apigee_ranges" { 91 | for_each = var.psa_ranges 92 | project = var.project_id 93 | name = each.key 94 | purpose = "VPC_PEERING" 95 | address_type = "INTERNAL" 96 | address = split("/", each.value)[0] 97 | prefix_length = split("/", each.value)[1] 98 | network = var.network_id 99 | } 100 | 101 | resource "google_service_networking_connection" "apigee_peering" { 102 | network = var.network_id 103 | service = "servicenetworking.googleapis.com" 104 | reserved_peering_ranges = [ 105 | for k, v in google_compute_global_address.apigee_ranges : v.name 106 | ] 107 | } 108 | 109 | resource "google_compute_network_peering_routes_config" "psa_routes" { 110 | project = var.project_id 111 | peering = google_service_networking_connection.apigee_peering.peering 112 | network = split("/", var.network_id)[4] # grab network name from ID in format projects/{{project}}/global/networks/{{name}} 113 | export_custom_routes = false 114 | import_custom_routes = false 115 | } 116 | 117 | # Routing 118 | # https://cloud.google.com/apigee/docs/api-platform/get-started/install-cli#configure-routing 119 | resource "google_compute_region_network_endpoint_group" "psc_neg" { 120 | project = var.project_id 121 | for_each = var.apigee_instances 122 | name = "apigee-psc-neg-${each.key}" 123 | region = each.key 124 | network = var.network_id 125 | subnetwork = var.subnet_id 126 | network_endpoint_type = "PRIVATE_SERVICE_CONNECT" 127 | psc_target_service = module.apigee.service_attachments[each.key] 128 | lifecycle { 129 | create_before_destroy = true 130 | } 131 | } 132 | 133 | module "psc_lb" { 134 | source = "github.com/apigee/terraform-modules//modules/nb-psc-l7xlb?ref=v0.12.0" 135 | 136 | project_id = var.project_id 137 | name = "apigee-xlb-psc" 138 | network = var.network_id 139 | # psc_service_attachments = { (local.region) = module.apigee_core.instance_service_attachments[local.region] } 140 | ssl_certificate = var.ssl_certificate 141 | external_ip = var.external_ip 142 | psc_negs = [for _, psc_neg in google_compute_region_network_endpoint_group.psc_neg : psc_neg.id] 143 | } 144 | -------------------------------------------------------------------------------- /modules/apigee/outputs.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | output "apigee_org_id" { 18 | value = split("/", module.apigee.org_id)[1] 19 | description = "Apigee org ID (same as GCP project ID)" 20 | } 21 | -------------------------------------------------------------------------------- /modules/apigee/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "apigee_org_name" { 18 | description = "Display name for Apigee Organization." 19 | type = string 20 | default = "Apigee Org" 21 | } 22 | 23 | variable "apigee_org_description" { 24 | description = "Description for Apigee Organization." 25 | type = string 26 | default = "Apigee Org" 27 | } 28 | 29 | variable "analytics_region" { 30 | description = "GCP region for storing Apigee analytics data (see https://cloud.google.com/apigee/docs/api-platform/get-started/install-cli)." 31 | type = string 32 | } 33 | 34 | variable "apigee_envgroups" { 35 | description = "Apigee groups (NAME => [HOSTNAMES])." 36 | type = map(list(string)) 37 | default = null 38 | } 39 | 40 | variable "apigee_environments" { 41 | description = "Apigee Environments." 42 | type = map(object({ 43 | display_name = optional(string) 44 | description = optional(string, "Terraform-managed") 45 | deployment_type = optional(string) 46 | api_proxy_type = optional(string) 47 | node_config = optional(object({ 48 | min_node_count = optional(number) 49 | max_node_count = optional(number) 50 | })) 51 | iam = optional(map(list(string))) 52 | envgroups = optional(list(string)) 53 | regions = optional(list(string)) 54 | })) 55 | default = null 56 | } 57 | 58 | variable "apigee_instances" { 59 | description = "Apigee Instances ([REGION] => [INSTANCE])." 60 | type = map(object({ 61 | display_name = optional(string) 62 | description = optional(string, "Terraform-managed") 63 | runtime_ip_cidr_range = string 64 | troubleshooting_ip_cidr_range = string 65 | disk_encryption_key = optional(string) 66 | consumer_accept_list = optional(list(string)) 67 | })) 68 | default = null 69 | } 70 | 71 | variable "apigee_endpoint_attachments" { 72 | description = "Apigee endpoint attachments (for southbound networking: https://cloud.google.com/apigee/docs/api-platform/architecture/southbound-networking-patterns-endpoints#create-the-psc-attachments)." 73 | type = map(object({ 74 | region = string 75 | service_attachment = string 76 | })) 77 | default = {} 78 | } 79 | 80 | variable "kms_project_id" { 81 | description = "Project ID in which to create keys for Apigee database and disk (org/instance)" 82 | type = string 83 | default = "" 84 | } 85 | 86 | variable "psa_ranges" { 87 | description = "Apigee Private Service Access peering ranges" 88 | type = object({ 89 | apigee-range = string 90 | google-managed-services-support-1 = string 91 | }) 92 | default = { 93 | apigee-range = "10.0.0.0/22" 94 | google-managed-services-support-1 = "10.1.0.0/28" 95 | } 96 | } 97 | 98 | variable "project_id" { 99 | description = "Project id (also used for the Apigee Organization)." 100 | type = string 101 | } 102 | 103 | variable "network_id" { 104 | description = "VPC network ID" 105 | type = string 106 | } 107 | 108 | variable "subnet_id" { 109 | description = "Apigee NEG subnet ID" 110 | type = string 111 | } 112 | 113 | variable "ssl_certificate" { 114 | description = "SSL Certificate ID for Apigee Load Balancer" 115 | type = string 116 | } 117 | 118 | variable "external_ip" { 119 | description = "Reserved global external IP for Apigee Load Balancer" 120 | type = string 121 | } 122 | 123 | variable "billing_type" { 124 | description = "Apigee billing type. Can be one of EVALUATION, PAYG, or SUBSCRIPTION. See https://cloud.google.com/apigee/pricing" 125 | type = string 126 | default = "EVALUATION" 127 | } 128 | 129 | variable "runtime_type" { 130 | description = "Apigee runtime type. Can be one of CLOUD or HYBRID." 131 | type = string 132 | default = "CLOUD" 133 | } 134 | 135 | # variable "region" { 136 | # description = "Region in which to create resources" 137 | # type = string 138 | # default = "us-central1" 139 | # } 140 | 141 | variable "create_apigee_org" { 142 | description = "Set to `true` to create a new Apigee org in the provided `var.project_id`; set to `false` to use the existing Apigee org in this project." 143 | type = bool 144 | default = true 145 | } 146 | 147 | variable "prevent_key_destroy" { 148 | description = "Prevent destroying KMS keys for Apigee Org and Instances" 149 | type = bool 150 | default = true 151 | } 152 | -------------------------------------------------------------------------------- /modules/apigee/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 1.3" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.45" 23 | } 24 | google-beta = { 25 | source = "hashicorp/google-beta" 26 | version = ">= 3.45" 27 | } 28 | } 29 | 30 | provider_meta "google" { 31 | module_name = "blueprints/terraform/terraform-google-waap:apigee/v0.1.0" 32 | } 33 | provider_meta "google-beta" { 34 | module_name = "blueprints/terraform/terraform-google-waap:apigee/v0.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /modules/mig-network/README.md: -------------------------------------------------------------------------------- 1 | # MIG Network submodule 2 | 3 | 4 | ## Inputs 5 | 6 | | Name | Description | Type | Default | Required | 7 | |------|-------------|------|---------|:--------:| 8 | | network\_name | VPC network name | `string` | `""` | no | 9 | | project\_id | Google Project ID | `string` | `""` | no | 10 | | region | Region in which to create resources | `string` | `""` | no | 11 | | subnets | List of subnet configurations |
list(object({
subnet_name = string
subnet_ip = string
subnet_region = string
}))
| n/a | yes | 12 | 13 | ## Outputs 14 | 15 | | Name | Description | 16 | |------|-------------| 17 | | network\_name | The name of the VPC being created | 18 | | subnets | List of created subnets | 19 | 20 | 21 | -------------------------------------------------------------------------------- /modules/mig-network/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | /***************** 18 | *** Create VPC *** 19 | ******************/ 20 | module "mig_vpc" { 21 | source = "terraform-google-modules/network/google" 22 | version = "~> 6.0" 23 | 24 | project_id = var.project_id 25 | network_name = var.network_name 26 | routing_mode = "GLOBAL" 27 | 28 | subnets = var.subnets 29 | } 30 | 31 | /*********************** 32 | *** Create Cloud NAT *** 33 | ************************/ 34 | 35 | module "cloud-nat" { 36 | source = "terraform-google-modules/cloud-nat/google" 37 | version = "~> 1.2" 38 | create_router = true 39 | project_id = var.project_id 40 | region = var.region 41 | network = module.mig_vpc.network_name 42 | router = format("router-%s", var.network_name) 43 | name = format("nat-%s", var.network_name) 44 | } 45 | 46 | /********************************************************************************* 47 | **** Firewall rule to allow incoming ssh connections from Google IAP servers. **** 48 | **********************************************************************************/ 49 | resource "google_compute_firewall" "inbound-ip-ssh" { 50 | name = format("allow-ssh-iap-%s", var.network_name) 51 | project = var.project_id 52 | network = module.mig_vpc.network_name 53 | 54 | direction = "INGRESS" 55 | allow { 56 | protocol = "tcp" 57 | ports = ["22"] 58 | } 59 | source_ranges = [ 60 | "35.235.240.0/20" 61 | ] 62 | target_tags = ["allow-ssh-iap"] 63 | } 64 | -------------------------------------------------------------------------------- /modules/mig-network/outputs.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #!TODO 18 | output "network_name" { 19 | description = "The name of the VPC being created" 20 | value = module.mig_vpc.network_name 21 | } 22 | 23 | output "subnets" { 24 | description = "List of created subnets" 25 | value = module.mig_vpc.subnets_names 26 | } 27 | -------------------------------------------------------------------------------- /modules/mig-network/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "project_id" { 18 | description = "Google Project ID" 19 | type = string 20 | default = "" 21 | } 22 | 23 | variable "region" { 24 | description = "Region in which to create resources" 25 | type = string 26 | default = "" 27 | } 28 | 29 | variable "network_name" { 30 | description = "VPC network name" 31 | type = string 32 | default = "" 33 | } 34 | 35 | variable "subnets" { 36 | description = "List of subnet configurations" 37 | type = list(object({ 38 | subnet_name = string 39 | subnet_ip = string 40 | subnet_region = string 41 | })) 42 | } 43 | -------------------------------------------------------------------------------- /modules/mig-network/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 0.13.0" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.45" 23 | } 24 | } 25 | provider_meta "google" { 26 | module_name = "blueprints/terraform/terraform-google-waap:mig-network/v0.1.0" 27 | } 28 | provider_meta "google-beta" { 29 | module_name = "blueprints/terraform/terraform-google-waap:mig-network/v0.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/mig/README.md: -------------------------------------------------------------------------------- 1 | # MIG submodule 2 | 3 | 4 | ## Inputs 5 | 6 | | Name | Description | Type | Default | Required | 7 | |------|-------------|------|---------|:--------:| 8 | | backend\_port | The backend port number. | `number` | `80` | no | 9 | | disk\_auto\_delete | Whether or not the disk should be auto-deleted. | `bool` | `true` | no | 10 | | disk\_size\_gb | The size of the image in gigabytes. If not specified, it will inherit the size of its base image. | `string` | `"100"` | no | 11 | | disk\_type | The GCE disk type. Can be either pd-ssd, local-ssd, pd-balanced or pd-standard. | `string` | `"pd-standard"` | no | 12 | | machine\_type | Machine type to create, e.g. n1-standard-1 | `string` | `"n1-standard-1"` | no | 13 | | max\_surge\_fixed | The maximum number of instances that can be created above the specified targetSize during the update process. | `number` | n/a | yes | 14 | | max\_unavailable\_fixed | The maximum number of instances that can be unavailable during the update process. | `number` | n/a | yes | 15 | | mig\_name | Name of the managed instance group. | `string` | `""` | no | 16 | | name\_prefix | Name prefix for the instance template | `string` | `"vm-template-"` | no | 17 | | network | Name of the network to deploy instances to. | `string` | `"default"` | no | 18 | | port\_name | The name of the port. | `string` | `"http"` | no | 19 | | project\_id | Google Project ID | `string` | `""` | no | 20 | | region | Region for cloud resources. | `string` | `"us-central1"` | no | 21 | | roles | Permissions to be added to the created service account. | `list(any)` | `[]` | no | 22 | | scopes | List of scopes for the instance template service account | `list(any)` | `[]` | no | 23 | | service\_account | The account ID used to generate the virtual machine service account. | `string` | `""` | no | 24 | | source\_image | Source disk image. If neither source\_image nor source\_image\_family is specified, defaults to the latest public CentOS image. | `string` | `""` | no | 25 | | source\_image\_project | Project where the source image comes from. The default project contains CentOS images. | `string` | `""` | no | 26 | | startup\_script | VM startup script. | `string` | `""` | no | 27 | | subnetwork | The subnetwork to deploy to | `string` | `"default"` | no | 28 | | tags | Network tags, provided as a list | `list(string)` | `[]` | no | 29 | | target\_size | The target number of running instances for this managed instance group. This value should always be explicitly set unless this resource is attached to an autoscaler, in which case it should never be set. | `number` | `1` | no | 30 | 31 | ## Outputs 32 | 33 | | Name | Description | 34 | |------|-------------| 35 | | instance\_group | Managed instance group | 36 | 37 | 38 | -------------------------------------------------------------------------------- /modules/mig/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | resource "google_service_account" "vm_sa" { 18 | project = var.project_id 19 | account_id = var.service_account 20 | } 21 | 22 | resource "google_project_iam_member" "sa_roles" { 23 | for_each = toset(var.roles) 24 | 25 | project = var.project_id 26 | role = each.key 27 | member = "serviceAccount:${google_service_account.vm_sa.email}" 28 | } 29 | 30 | module "instance_template" { 31 | source = "terraform-google-modules/vm/google//modules/instance_template" 32 | version = "~> 8.0.0" 33 | 34 | project_id = var.project_id 35 | name_prefix = var.name_prefix 36 | machine_type = var.machine_type 37 | 38 | source_image = var.source_image 39 | source_image_project = var.source_image_project 40 | disk_size_gb = var.disk_size_gb 41 | disk_type = var.disk_type 42 | auto_delete = var.disk_auto_delete 43 | 44 | startup_script = var.startup_script 45 | 46 | network = var.network 47 | subnetwork = "https://www.googleapis.com/compute/v1/projects/${var.project_id}/regions/${var.region}/subnetworks/${var.subnetwork}" 48 | service_account = { 49 | email = google_service_account.vm_sa.email 50 | scopes = var.scopes 51 | } 52 | 53 | tags = var.tags 54 | } 55 | 56 | module "mig" { 57 | source = "terraform-google-modules/vm/google//modules/mig" 58 | version = "~> 8.0.0" 59 | 60 | project_id = var.project_id 61 | mig_name = var.mig_name 62 | hostname = "${var.mig_name}-vm" 63 | region = var.region 64 | 65 | instance_template = module.instance_template.self_link 66 | target_size = var.target_size 67 | 68 | named_ports = [{ 69 | name = var.port_name 70 | port = var.backend_port 71 | }] 72 | 73 | update_policy = [{ 74 | type = "PROACTIVE" 75 | instance_redistribution_type = "PROACTIVE" 76 | replacement_method = "SUBSTITUTE" 77 | minimal_action = "REPLACE" 78 | max_surge_fixed = var.max_surge_fixed 79 | max_unavailable_fixed = var.max_unavailable_fixed 80 | max_unavailable_percent = null 81 | max_surge_percent = null 82 | min_ready_sec = 100 83 | }] 84 | } 85 | -------------------------------------------------------------------------------- /modules/mig/outputs.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #!TODO 18 | 19 | output "instance_group" { 20 | description = "Managed instance group" 21 | value = module.mig.instance_group 22 | } 23 | -------------------------------------------------------------------------------- /modules/mig/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "project_id" { 18 | description = "Google Project ID" 19 | type = string 20 | default = "" 21 | } 22 | 23 | ## VM Service Account ## 24 | variable "service_account" { 25 | description = "The account ID used to generate the virtual machine service account." 26 | type = string 27 | default = "" 28 | } 29 | 30 | variable "roles" { 31 | description = "Permissions to be added to the created service account." 32 | type = list(any) 33 | default = [] 34 | } 35 | 36 | ## VM Template ## 37 | variable "name_prefix" { 38 | description = "Name prefix for the instance template" 39 | type = string 40 | default = "vm-template-" 41 | } 42 | 43 | variable "machine_type" { 44 | description = "Machine type to create, e.g. n1-standard-1" 45 | type = string 46 | default = "n1-standard-1" 47 | } 48 | variable "source_image" { 49 | description = "Source disk image. If neither source_image nor source_image_family is specified, defaults to the latest public CentOS image." 50 | type = string 51 | default = "" 52 | } 53 | 54 | variable "source_image_project" { 55 | description = "Project where the source image comes from. The default project contains CentOS images. " 56 | type = string 57 | default = "" 58 | } 59 | 60 | variable "disk_auto_delete" { 61 | description = "Whether or not the disk should be auto-deleted." 62 | type = bool 63 | default = true 64 | } 65 | 66 | variable "disk_type" { 67 | description = "The GCE disk type. Can be either pd-ssd, local-ssd, pd-balanced or pd-standard." 68 | type = string 69 | default = "pd-standard" 70 | } 71 | 72 | variable "disk_size_gb" { 73 | description = "The size of the image in gigabytes. If not specified, it will inherit the size of its base image." 74 | type = string 75 | default = "100" 76 | } 77 | 78 | variable "scopes" { 79 | description = "List of scopes for the instance template service account" 80 | type = list(any) 81 | default = [] 82 | } 83 | 84 | variable "startup_script" { 85 | description = "VM startup script." 86 | type = string 87 | default = "" 88 | } 89 | 90 | variable "tags" { 91 | description = "Network tags, provided as a list" 92 | type = list(string) 93 | default = [] 94 | } 95 | 96 | ## Network ## 97 | variable "network" { 98 | description = "Name of the network to deploy instances to." 99 | type = string 100 | default = "default" 101 | } 102 | 103 | variable "subnetwork" { 104 | description = "The subnetwork to deploy to" 105 | type = string 106 | default = "default" 107 | } 108 | 109 | ## Managed Instance Group ## 110 | variable "mig_name" { 111 | description = "Name of the managed instance group." 112 | type = string 113 | default = "" 114 | } 115 | 116 | variable "region" { 117 | description = "Region for cloud resources." 118 | type = string 119 | default = "us-central1" 120 | } 121 | 122 | variable "target_size" { 123 | description = "The target number of running instances for this managed instance group. This value should always be explicitly set unless this resource is attached to an autoscaler, in which case it should never be set." 124 | type = number 125 | default = 1 126 | } 127 | 128 | variable "max_surge_fixed" { 129 | description = "The maximum number of instances that can be created above the specified targetSize during the update process." 130 | type = number 131 | } 132 | 133 | variable "max_unavailable_fixed" { 134 | description = "The maximum number of instances that can be unavailable during the update process." 135 | type = number 136 | } 137 | 138 | variable "port_name" { 139 | description = "The name of the port." 140 | type = string 141 | default = "http" 142 | } 143 | 144 | variable "backend_port" { 145 | description = "The backend port number." 146 | type = number 147 | default = 80 148 | } 149 | -------------------------------------------------------------------------------- /modules/mig/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 0.13.0" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.45" 23 | } 24 | } 25 | provider_meta "google" { 26 | module_name = "blueprints/terraform/terraform-google-waap:mig/v0.1.0" 27 | } 28 | provider_meta "google-beta" { 29 | module_name = "blueprints/terraform/terraform-google-waap:mig/v0.1.0" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /modules/waap-analytics/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## Inputs 3 | 4 | | Name | Description | Type | Default | Required | 5 | |------|-------------|------|---------|:--------:| 6 | | ca\_policy\_name | Name of Cloud Armor Security Policy resource | `string` | n/a | yes | 7 | | dataset\_name | Name of BigQuery dataset where WAAP analytics will be stored | `string` | `"waap_analytics"` | no | 8 | | log\_sink\_name | Name of BigQuery log sink | `string` | `"WAAP_log_sink"` | no | 9 | | project\_id | GCP Project ID in which analytics resources will be created | `string` | n/a | yes | 10 | | sa\_name | Name of service account with BigQuery access to be used by Looker for dashboarding | `string` | `"waap-bq-sa"` | no | 11 | 12 | ## Outputs 13 | 14 | No outputs. 15 | 16 | 17 | -------------------------------------------------------------------------------- /modules/waap-analytics/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module "log_export" { 18 | source = "terraform-google-modules/log-export/google" 19 | version = "~> 7.4.2" 20 | 21 | destination_uri = module.destination.destination_uri 22 | filter = "resource.type:(http_load_balancer) AND jsonPayload.enforcedSecurityPolicy.name:(${var.ca_policy_name})" 23 | exclusions = [ 24 | { 25 | name = "Ignore", 26 | description = "Ignore socket and assets", 27 | filter = "httpRequest.requestUrl =~ \"(socket.io|.js|.css)\"", 28 | disabled = false 29 | } 30 | ] 31 | log_sink_name = var.log_sink_name 32 | parent_resource_id = var.project_id 33 | parent_resource_type = "project" 34 | unique_writer_identity = true 35 | } 36 | 37 | module "destination" { 38 | source = "terraform-google-modules/log-export/google//modules/bigquery" 39 | version = "~> 7.4.2" 40 | 41 | project_id = var.project_id 42 | dataset_name = var.dataset_name 43 | log_sink_writer_identity = module.log_export.writer_identity 44 | } 45 | 46 | resource "google_service_account" "waap_analytics_sa" { 47 | project = var.project_id 48 | account_id = var.sa_name 49 | display_name = "Looker Service account for WAAP dashboarding" 50 | } 51 | 52 | resource "google_bigquery_dataset_iam_member" "editor" { 53 | project = var.project_id 54 | dataset_id = module.destination.resource_name 55 | role = "roles/bigquery.dataEditor" 56 | member = "serviceAccount:${google_service_account.waap_analytics_sa.email}" 57 | } 58 | -------------------------------------------------------------------------------- /modules/waap-analytics/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | variable "project_id" { 18 | description = "GCP Project ID in which analytics resources will be created" 19 | type = string 20 | } 21 | 22 | variable "log_sink_name" { 23 | description = "Name of BigQuery log sink" 24 | type = string 25 | default = "WAAP_log_sink" 26 | } 27 | 28 | variable "ca_policy_name" { 29 | description = "Name of Cloud Armor Security Policy resource" 30 | type = string 31 | } 32 | 33 | variable "dataset_name" { 34 | description = "Name of BigQuery dataset where WAAP analytics will be stored" 35 | type = string 36 | default = "waap_analytics" 37 | } 38 | 39 | variable "sa_name" { 40 | description = "Name of service account with BigQuery access to be used by Looker for dashboarding" 41 | type = string 42 | default = "waap-bq-sa" 43 | } 44 | -------------------------------------------------------------------------------- /modules/waap-analytics/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 0.13.0" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.45" 23 | } 24 | google-beta = { 25 | source = "hashicorp/google-beta" 26 | version = ">= 3.45" 27 | } 28 | } 29 | 30 | provider_meta "google" { 31 | module_name = "blueprints/terraform/terraform-google-waap:waap-analytics/v0.1.0" 32 | } 33 | provider_meta "google-beta" { 34 | module_name = "blueprints/terraform/terraform-google-waap:waap-analytics/v0.1.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | source.sh 2 | -------------------------------------------------------------------------------- /test/integration/discover_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package test 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" 21 | ) 22 | 23 | func TestAll(t *testing.T) { 24 | tft.AutoDiscoverAndTest(t) 25 | } 26 | -------------------------------------------------------------------------------- /test/integration/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/terraform-google-modules/waap/test/integration 2 | 3 | go 1.22 4 | 5 | toolchain go1.22.6 6 | 7 | require github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test v0.16.1 8 | 9 | require ( 10 | cloud.google.com/go v0.110.7 // indirect 11 | cloud.google.com/go/compute v1.23.0 // indirect 12 | cloud.google.com/go/compute/metadata v0.2.3 // indirect 13 | cloud.google.com/go/iam v1.1.2 // indirect 14 | cloud.google.com/go/storage v1.33.0 // indirect 15 | github.com/agext/levenshtein v1.2.3 // indirect 16 | github.com/aws/aws-sdk-go v1.45.5 // indirect 17 | github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect 18 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 19 | github.com/go-errors/errors v1.5.0 // indirect 20 | github.com/go-openapi/jsonpointer v0.20.0 // indirect 21 | github.com/go-openapi/jsonreference v0.20.2 // indirect 22 | github.com/go-openapi/swag v0.22.4 // indirect 23 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 24 | github.com/golang/protobuf v1.5.3 // indirect 25 | github.com/google/go-cmp v0.6.0 // indirect 26 | github.com/google/s2a-go v0.1.7 // indirect 27 | github.com/google/uuid v1.3.1 // indirect 28 | github.com/googleapis/enterprise-certificate-proxy v0.2.5 // indirect 29 | github.com/googleapis/gax-go/v2 v2.12.0 // indirect 30 | github.com/gruntwork-io/terratest v0.47.0 // indirect 31 | github.com/hashicorp/errwrap v1.1.0 // indirect 32 | github.com/hashicorp/go-cleanhttp v0.5.2 // indirect 33 | github.com/hashicorp/go-getter v1.7.5 // indirect 34 | github.com/hashicorp/go-multierror v1.1.1 // indirect 35 | github.com/hashicorp/go-safetemp v1.0.0 // indirect 36 | github.com/hashicorp/go-version v1.6.0 // indirect 37 | github.com/hashicorp/hcl/v2 v2.20.1 // indirect 38 | github.com/hashicorp/terraform-json v0.22.1 // indirect 39 | github.com/jinzhu/copier v0.4.0 // indirect 40 | github.com/jmespath/go-jmespath v0.4.0 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/klauspost/compress v1.16.7 // indirect 43 | github.com/mailru/easyjson v0.7.7 // indirect 44 | github.com/mattn/go-zglob v0.0.4 // indirect 45 | github.com/mitchellh/go-homedir v1.1.0 // indirect 46 | github.com/mitchellh/go-testing-interface v1.14.2-0.20210821155943-2d9075ca8770 // indirect 47 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 48 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 49 | github.com/tidwall/gjson v1.17.1 // indirect 50 | github.com/tidwall/match v1.1.1 // indirect 51 | github.com/tidwall/pretty v1.2.1 // indirect 52 | github.com/tidwall/sjson v1.2.5 // indirect 53 | github.com/tmccombs/hcl2json v0.6.0 // indirect 54 | github.com/ulikunitz/xz v0.5.11 // indirect 55 | github.com/zclconf/go-cty v1.14.4 // indirect 56 | go.opencensus.io v0.24.0 // indirect 57 | golang.org/x/crypto v0.21.0 // indirect 58 | golang.org/x/mod v0.19.0 // indirect 59 | golang.org/x/net v0.23.0 // indirect 60 | golang.org/x/oauth2 v0.12.0 // indirect 61 | golang.org/x/sys v0.18.0 // indirect 62 | golang.org/x/text v0.14.0 // indirect 63 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect 64 | google.golang.org/api v0.138.0 // indirect 65 | google.golang.org/appengine v1.6.8 // indirect 66 | google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect 67 | google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect 68 | google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect 69 | google.golang.org/grpc v1.58.3 // indirect 70 | google.golang.org/protobuf v1.33.0 // indirect 71 | gopkg.in/yaml.v3 v3.0.1 // indirect 72 | k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect 73 | sigs.k8s.io/kustomize/kyaml v0.17.2 // indirect 74 | ) 75 | 76 | require ( 77 | github.com/alexflint/go-filemutex v1.3.0 // indirect 78 | github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect 79 | github.com/google/gnostic-models v0.6.8 // indirect 80 | github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f // indirect 81 | github.com/hashicorp/terraform-config-inspect v0.0.0-20240701073647-9fc3669f7553 // indirect 82 | github.com/stretchr/testify v1.9.0 // indirect 83 | golang.org/x/sync v0.4.0 // indirect 84 | golang.org/x/tools v0.13.0 // indirect 85 | sigs.k8s.io/yaml v1.4.0 // indirect 86 | ) 87 | -------------------------------------------------------------------------------- /test/setup/.gitignore: -------------------------------------------------------------------------------- 1 | terraform.tfvars 2 | source.sh 3 | -------------------------------------------------------------------------------- /test/setup/iam.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | locals { 18 | int_required_roles = [ 19 | "roles/artifactregistry.admin", 20 | "roles/owner", 21 | "roles/cloudkms.viewer", 22 | "roles/recaptchaenterprise.admin", 23 | ] 24 | } 25 | 26 | resource "google_service_account" "int_test" { 27 | project = module.project.project_id 28 | account_id = "ci-account" 29 | display_name = "ci-account" 30 | } 31 | 32 | resource "google_project_iam_member" "int_test" { 33 | for_each = toset(local.int_required_roles) 34 | 35 | project = module.project.project_id 36 | role = each.value 37 | member = "serviceAccount:${google_service_account.int_test.email}" 38 | } 39 | 40 | resource "google_service_account_key" "int_test" { 41 | service_account_id = google_service_account.int_test.id 42 | } 43 | -------------------------------------------------------------------------------- /test/setup/main.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | module "project" { 18 | source = "terraform-google-modules/project-factory/google" 19 | version = "~> 13.0" 20 | 21 | name = "ci-waap" 22 | random_project_id = "true" 23 | org_id = var.org_id 24 | folder_id = var.folder_id 25 | billing_account = var.billing_account 26 | 27 | default_service_account = "keep" 28 | 29 | activate_apis = [ 30 | "apigee.googleapis.com", 31 | "artifactregistry.googleapis.com", 32 | "bigquery.googleapis.com", 33 | "cloudbuild.googleapis.com", 34 | "cloudkms.googleapis.com", 35 | "cloudresourcemanager.googleapis.com", 36 | "compute.googleapis.com", 37 | "osconfig.googleapis.com", 38 | "iap.googleapis.com", 39 | "iam.googleapis.com", 40 | "logging.googleapis.com", 41 | "monitoring.googleapis.com", 42 | "recaptchaenterprise.googleapis.com", 43 | "servicenetworking.googleapis.com", 44 | "serviceusage.googleapis.com", 45 | "dlp.googleapis.com" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /test/setup/outputs.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | output "project_id" { 18 | value = module.project.project_id 19 | } 20 | 21 | output "sa_key" { 22 | value = google_service_account_key.int_test.private_key 23 | sensitive = true 24 | } 25 | -------------------------------------------------------------------------------- /test/setup/variables.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | variable "org_id" { 17 | description = "The numeric organization id" 18 | } 19 | 20 | variable "folder_id" { 21 | description = "The folder to deploy in" 22 | } 23 | 24 | variable "billing_account" { 25 | description = "The billing account id associated with the project, e.g. XXXXXX-YYYYYY-ZZZZZZ" 26 | } 27 | -------------------------------------------------------------------------------- /test/setup/versions.tf: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | terraform { 18 | required_version = ">= 0.13" 19 | required_providers { 20 | google = { 21 | source = "hashicorp/google" 22 | version = ">= 3.25.0" 23 | } 24 | google-beta = { 25 | source = "hashicorp/google-beta" 26 | version = ">= 3.25.0" 27 | } 28 | } 29 | } 30 | --------------------------------------------------------------------------------