Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(trivy): Add terraform_trivy hook and deprecate terraform_tfsec #606

Merged
merged 12 commits into from
Dec 15, 2023
5 changes: 5 additions & 0 deletions .github/.container-structure-test-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
Expand Down
8 changes: 8 additions & 0 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,14 @@
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.
Expand Down
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand All @@ -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 \
Expand Down Expand Up @@ -136,6 +138,16 @@ RUN . /.env && \
) && chmod +x tfsec \
; fi

# Trivy
RUN . /.env && \
if [ "$TRIVY_VERSION" != "false" ]; then \
( \
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}-${TARGETARCH}.tar.gz")" > trivy.tar.gz \
|| curl -L "$(curl -s ${TRIVY_RELEASES} | grep -o -E -i -m 1 "https://.+?/v${TRIVY_VERSION}/trivy_.+?_${TARGETOS}-${TARGETARCH}.tar.gz")" > trivy.tar.gz \
) && tar -xzf trivy.tar.gz trivy && rm -rf trivy.tar.gz \
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
; fi

# TFUpdate
RUN . /.env && \
if [ "$TFUPDATE_VERSION" != "false" ]; then \
Expand Down
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,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/aquasec/trivy) required for `terraform_trivy` hook.
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
* [`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.
Expand Down Expand Up @@ -125,6 +126,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 \
.
Expand All @@ -138,7 +140,7 @@ Set `-e PRE_COMMIT_COLOR=never` to disable the color output in `pre-commit`.
<details><summary><b>MacOS</b></summary><br>

```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
```

</details>
Expand All @@ -156,6 +158,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 -xzv trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
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
Expand All @@ -178,6 +181,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 -xzv trivy.tar.gz trivy && rm trivy.tar.gz && sudo mv trivy /usr/bin
MaxymVlasov marked this conversation as resolved.
Show resolved Hide resolved
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/
Expand Down Expand Up @@ -274,6 +278,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` |
Expand Down Expand Up @@ -738,6 +743,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:
Expand Down
69 changes: 69 additions & 0 deletions hooks/terraform_trivy.sh
Original file line number Diff line number Diff line change
@@ -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 "$@"