diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index 2ce44f1ce..60107655e 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -50,6 +50,11 @@ commandTests: args: [ "--version" ] expectedOutput: [ "([0-9]+\\.){2}[0-9]+\\n$" ] + - name: "trivy" + command: "trivy" + args: [ "--version" ] + expectedOutput: [ "Version: ([0-9]+\\.){2}[0-9]+\\n" ] + - name: "tfupdate" command: "tfupdate" args: [ "--version" ] diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index 7b5152ba4..e8115b487 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -86,13 +86,21 @@ exclude: \.terraform\/.*$ - id: terraform_tfsec - name: Terraform validate with tfsec + name: Terraform validate with tfsec (deprecated, use "terraform_trivy") description: Static analysis of Terraform templates to spot potential security issues. require_serial: true entry: hooks/terraform_tfsec.sh files: \.tf(vars)?$ language: script +- id: terraform_trivy + name: Terraform validate with trivy + description: Static analysis of Terraform templates to spot potential security issues. + require_serial: true + entry: hooks/terraform_trivy.sh + files: \.tf(vars)?$ + language: script + - id: checkov name: checkov (deprecated, use "terraform_checkov") description: Runs checkov on Terraform templates. diff --git a/Dockerfile b/Dockerfile index 165a30f86..fcf33f2a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,6 +37,7 @@ ARG TERRAGRUNT_VERSION=${TERRAGRUNT_VERSION:-false} ARG TERRASCAN_VERSION=${TERRASCAN_VERSION:-false} ARG TFLINT_VERSION=${TFLINT_VERSION:-false} ARG TFSEC_VERSION=${TFSEC_VERSION:-false} +ARG TRIVY_VERSION=${TRIVY_VERSION:-false} ARG TFUPDATE_VERSION=${TFUPDATE_VERSION:-false} ARG HCLEDIT_VERSION=${HCLEDIT_VERSION:-false} @@ -53,6 +54,7 @@ RUN if [ "$INSTALL_ALL" != "false" ]; then \ echo "export TERRASCAN_VERSION=latest" >> /.env && \ echo "export TFLINT_VERSION=latest" >> /.env && \ echo "export TFSEC_VERSION=latest" >> /.env && \ + echo "export TRIVY_VERSION=latest" >> /.env && \ echo "export TFUPDATE_VERSION=latest" >> /.env && \ echo "export HCLEDIT_VERSION=latest" >> /.env \ ; else \ @@ -136,6 +138,17 @@ RUN . /.env && \ ) && chmod +x tfsec \ ; fi +# Trivy +RUN . /.env && \ + if [ "$TRIVY_VERSION" != "false" ]; then \ + if [ "$TARGETARCH" != "amd64" ]; then ARCH="$TARGETARCH"; else ARCH="64bit"; fi; \ + ( \ + TRIVY_RELEASES="https://api.github.com/repos/aquasecurity/trivy/releases" && \ + [ "$TRIVY_VERSION" = "latest" ] && curl -L "$(curl -s ${TRIVY_RELEASES}/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ + || curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${ARCH}.tar.gz")" > trivy.tar.gz \ + ) && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz \ + ; fi + # TFUpdate RUN . /.env && \ if [ "$TFUPDATE_VERSION" != "false" ]; then \ diff --git a/README.md b/README.md index 4f0eee1d4..0f2877d71 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,8 @@ If you are using `pre-commit-terraform` already or want to support its developme * [terraform\_fmt](#terraform_fmt) * [terraform\_providers\_lock](#terraform_providers_lock) * [terraform\_tflint](#terraform_tflint) - * [terraform\_tfsec](#terraform_tfsec) + * [terraform\_tfsec (deprecated)](#terraform_tfsec-deprecated) + * [terraform\_trivy](#terraform_trivy) * [terraform\_validate](#terraform_validate) * [terraform\_wrapper\_module\_for\_each](#terraform_wrapper_module_for_each) * [terrascan](#terrascan) @@ -82,6 +83,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [`terrascan`](https://github.com/tenable/terrascan) required for `terrascan` hook. * [`TFLint`](https://github.com/terraform-linters/tflint) required for `terraform_tflint` hook. * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. +* [`Trivy`](https://github.com/aquasecurity/trivy) required for `terraform_trivy` hook. * [`infracost`](https://github.com/infracost/infracost) required for `infracost_breakdown` hook. * [`jq`](https://github.com/stedolan/jq) required for `terraform_validate` with `--retry-once-with-cleanup` flag, and for `infracost_breakdown` hook. * [`tfupdate`](https://github.com/minamijoyo/tfupdate) required for `tfupdate` hook. @@ -125,6 +127,7 @@ docker build -t pre-commit-terraform \ --build-arg TERRASCAN_VERSION=1.10.0 \ --build-arg TFLINT_VERSION=0.31.0 \ --build-arg TFSEC_VERSION=latest \ + --build-arg TRIVY_VERSION=latest \ --build-arg TFUPDATE_VERSION=latest \ --build-arg HCLEDIT_VERSION=latest \ . @@ -138,7 +141,7 @@ Set `-e PRE_COMMIT_COLOR=never` to disable the color output in `pre-commit`.
MacOS
```bash -brew install pre-commit terraform-docs tflint tfsec checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq +brew install pre-commit terraform-docs tflint tfsec trivy checkov terrascan infracost tfupdate minamijoyo/hcledit/hcledit jq ```
@@ -156,6 +159,7 @@ python3.7 -m pip install -U checkov curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > terraform-docs.tgz && tar -xzf terraform-docs.tgz && rm terraform-docs.tgz && chmod +x terraform-docs && sudo mv terraform-docs /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest | grep -o -E -m 1 "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && sudo mv tfsec /usr/bin/ +curl -L "$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_Linux-64bit.tar.gz")" > trivy.tar.gz && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E -m 1 "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ && terrascan init sudo apt install -y jq && \ curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register @@ -178,6 +182,7 @@ curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/re curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E -m 1 "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xzf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ && terrascan init curl -L "$(curl -s https://api.github.com/repos/terraform-linters/tflint/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.zip")" > tflint.zip && unzip tflint.zip && rm tflint.zip && sudo mv tflint /usr/bin/ curl -L "$(curl -s https://api.github.com/repos/aquasecurity/tfsec/releases/latest | grep -o -E -m 1 "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && sudo mv tfsec /usr/bin/ +curl -L "$(curl -s https://api.github.com/repos/aquasecurity/trivy/releases/latest | grep -o -E -i -m 1 "https://.+?/trivy_.+?_Linux-64bit.tar.gz")" > trivy.tar.gz && tar -xzf trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin sudo apt install -y jq && \ curl -L "$(curl -s https://api.github.com/repos/infracost/infracost/releases/latest | grep -o -E -m 1 "https://.+?-linux-amd64.tar.gz")" > infracost.tgz && tar -xzf infracost.tgz && rm infracost.tgz && sudo mv infracost-linux-amd64 /usr/bin/infracost && infracost register curl -L "$(curl -s https://api.github.com/repos/minamijoyo/tfupdate/releases/latest | grep -o -E -m 1 "https://.+?_linux_amd64.tar.gz")" > tfupdate.tar.gz && tar -xzf tfupdate.tar.gz tfupdate && rm tfupdate.tar.gz && sudo mv tfupdate /usr/bin/ @@ -274,6 +279,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terraform_providers_lock` | Updates provider signatures in [dependency lock files](https://www.terraform.io/docs/cli/commands/providers/lock.html). [Hook notes](#terraform_providers_lock) | - | | `terraform_tflint` | Validates all Terraform configuration files with [TFLint](https://github.com/terraform-linters/tflint). [Available TFLint rules](https://github.com/terraform-linters/tflint/tree/master/docs/rules#rules). [Hook notes](#terraform_tflint). | `tflint` | | `terraform_tfsec` | [TFSec](https://github.com/aquasecurity/tfsec) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_tfsec) | `tfsec` | +| `terraform_trivy` | [Trivy](https://github.com/aquasecurity/trivy) static analysis of terraform templates to spot potential security issues. [Hook notes](#terraform_trivy) | `trivy` | | `terraform_validate` | Validates all Terraform configuration files. [Hook notes](#terraform_validate) | `jq`, only for `--retry-once-with-cleanup` flag | | `terragrunt_fmt` | Reformat all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) to a canonical format. | `terragrunt` | | `terragrunt_validate` | Validates all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) | `terragrunt` | @@ -681,7 +687,9 @@ To replicate functionality in `terraform_docs` hook: ``` -### terraform_tfsec +### terraform_tfsec (deprecated) + +**DEPRECATED**. [tfsec was replaced by trivy](https://github.com/aquasecurity/tfsec/discussions/1994), so please use [`terraform_trivy`](#terraform_trivy). 1. `terraform_tfsec` will consume modified files that pre-commit passes to it, so you can perform whitelisting of directories @@ -738,6 +746,48 @@ To replicate functionality in `terraform_docs` hook: - --args=--config-file=.tfsec.json ``` +### terraform_trivy + +1. `terraform_trivy` will consume modified files that pre-commit + passes to it, so you can perform whitelisting of directories + or files to run against via [files](https://pre-commit.com/#config-files) + pre-commit flag + + Example: + + ```yaml + - id: terraform_trivy + files: ^prd-infra/ + ``` + + The above will tell pre-commit to pass down files from the `prd-infra/` folder + only such that the underlying `trivy` tool can run against changed files in this + directory, ignoring any other folders at the root level + +2. To ignore specific warnings, follow the convention from the +[documentation](https://aquasecurity.github.io/trivy/latest/docs/configuration/filtering/). + + Example: + + ```hcl + #trivy:ignore:AVD-AWS-0107 + #trivy:ignore:AVD-AWS-0124 + resource "aws_security_group_rule" "my-rule" { + type = "ingress" + cidr_blocks = ["0.0.0.0/0"] + } + ``` + +3. `terraform_trivy` supports custom arguments, so you can pass supported `--format` (output), `--skip-dirs` (exclude directories) and other flags: + + ```yaml + - id: terraform_trivy + args: + - > + --args=--format json + --skip-dirs="**/.terragrunt-cache" + ``` + ### terraform_validate 1. `terraform_validate` supports custom arguments so you can pass supported `-no-color` or `-json` flags: diff --git a/hooks/terraform_tfsec.sh b/hooks/terraform_tfsec.sh index 24b7d438f..d05ed4241 100755 --- a/hooks/terraform_tfsec.sh +++ b/hooks/terraform_tfsec.sh @@ -22,6 +22,9 @@ function main { ARGS+=("--no-color") fi + common::colorify "yellow" "tfsec tool was deprecated, and replaced by trivy. You can check trivy hook here:" + common::colorify "yellow" "https://github.com/antonbabenko/pre-commit-terraform/tree/master#terraform_trivy" + common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" } diff --git a/hooks/terraform_trivy.sh b/hooks/terraform_trivy.sh new file mode 100755 index 000000000..dc205601a --- /dev/null +++ b/hooks/terraform_trivy.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -eo pipefail + +# globals variables +# shellcheck disable=SC2155 # No way to assign to readonly variable in separate lines +readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +# shellcheck source=_common.sh +. "$SCRIPT_DIR/_common.sh" + +function main { + common::initialize "$SCRIPT_DIR" + common::parse_cmdline "$@" + common::export_provided_env_vars "${ENV_VARS[@]}" + common::parse_and_export_env_vars + # Support for setting PATH to repo root. + for i in "${!ARGS[@]}"; do + ARGS[i]=${ARGS[i]/__GIT_WORKING_DIR__/$(pwd)\/} + done + + common::per_dir_hook "$HOOK_ID" "${#ARGS[@]}" "${ARGS[@]}" "${FILES[@]}" +} + +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed in loop +# on each provided dir path. Run wrapped tool with specified arguments +# Arguments: +# dir_path (string) PATH to dir relative to git repo root. +# Can be used in error logging +# change_dir_in_unique_part (string/false) Modifier which creates +# possibilities to use non-common chdir strategies. +# Availability depends on hook. +# args (array) arguments that configure wrapped tool behavior +# Outputs: +# If failed - print out hook checks status +####################################################################### +function per_dir_hook_unique_part { + # shellcheck disable=SC2034 # Unused var. + local -r dir_path="$1" + # shellcheck disable=SC2034 # Unused var. + local -r change_dir_in_unique_part="$2" + shift 2 + local -a -r args=("$@") + + # pass the arguments to hook + trivy conf "$(pwd)" --exit-code=1 "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +####################################################################### +# Unique part of `common::per_dir_hook`. The function is executed one time +# in the root git repo +# Arguments: +# args (array) arguments that configure wrapped tool behavior +####################################################################### +function run_hook_on_whole_repo { + local -a -r args=("$@") + + # pass the arguments to hook + trivy conf "$(pwd)" "${args[@]}" + + # return exit code to common::per_dir_hook + local exit_code=$? + return $exit_code +} + +[ "${BASH_SOURCE[0]}" != "$0" ] || main "$@"