diff --git a/.pre-commit-hooks.yaml b/.pre-commit-hooks.yaml index d7b2d61..8796c4b 100644 --- a/.pre-commit-hooks.yaml +++ b/.pre-commit-hooks.yaml @@ -82,3 +82,9 @@ files: \.tf$ exclude: \.+.terraform\/.*$ require_serial: true + +- id: terrascan + name: terrascan + description: Runs terrascan on Terraform templates. + language: script + entry: terrascan.sh diff --git a/README.md b/README.md index a939111..462e25f 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ * [`TFSec`](https://github.com/liamg/tfsec) required for `terraform_tfsec` hook. * [`coreutils`](https://formulae.brew.sh/formula/coreutils) required for `terraform_validate` hook on macOS (due to use of `realpath`). * [`checkov`](https://github.com/bridgecrewio/checkov) required for `checkov` hook. +* [`terrascan`](https://github.com/accurics/terrascan) required for `terrascan` hook. or build and use the Docker image locally as mentioned below in the `Run` section. ##### MacOS ```bash -brew install pre-commit gawk terraform-docs tflint tfsec coreutils checkov +brew install pre-commit gawk terraform-docs tflint tfsec coreutils checkov terrascan ``` ##### Ubuntu 18.04 @@ -32,6 +33,7 @@ pip3 install pre-commit curl -L "$(curl -s https://api.github.com/repos/terraform-docs/terraform-docs/releases/latest | grep -o -E "https://.+?-linux-amd64.tar.gz")" > terraform-docs.tgz && tar xzf 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 "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/tfsec/tfsec/releases/latest | grep -o -E "https://.+?tfsec-linux-amd64")" > tfsec && chmod +x tfsec && mv tfsec /usr/bin/ +curl -L "$(curl -s https://api.github.com/repos/accurics/terrascan/releases/latest | grep -o -E "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz && tar -xf terrascan.tar.gz terrascan && rm terrascan.tar.gz && sudo mv terrascan /usr/bin/ python3.7 -m pip install -U checkov ``` @@ -72,9 +74,9 @@ or you can also build and use the provided Docker container, which wraps all dep ```bash # first building it docker build -t pre-commit . -# and then running it in the folder +# and then running it in the folder # with the terraform code you want to check by executing -docker run -v $(pwd):/lint -w /lint pre-commit run -a +docker run -v $(pwd):/lint -w /lint pre-commit run -a ``` ## Available Hooks @@ -93,6 +95,7 @@ There are several [pre-commit](https://pre-commit.com/) hooks to keep Terraform | `terragrunt_validate` | Validates all [Terragrunt](https://github.com/gruntwork-io/terragrunt) configuration files (`*.hcl`) | | `terraform_tfsec` | [TFSec](https://github.com/liamg/tfsec) static analysis of terraform templates to spot potential security issues. | | `checkov` | [checkov](https://github.com/bridgecrewio/checkov) static analysis of terraform templates to spot potential security issues. | +| `terrascan` | [terrascan](https://github.com/accurics/terrascan) Detect compliance and security violations. | Check the [source file](https://github.com/antonbabenko/pre-commit-terraform/blob/master/.pre-commit-hooks.yaml) to know arguments used for each hook. diff --git a/terrascan.sh b/terrascan.sh new file mode 100755 index 0000000..d823306 --- /dev/null +++ b/terrascan.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +set -eo pipefail + +main() { + initialize_ + parse_cmdline_ "$@" + + # propagate $FILES to custom function + terrascan_ "$ARGS" "$FILES" +} + +terrascan_() { + # consume modified files passed from pre-commit so that + # terrascan runs against only those relevant directories + for file_with_path in $FILES; do + file_with_path="${file_with_path// /__REPLACED__SPACE__}" + paths[index]=$(dirname "$file_with_path") + + let "index+=1" + done + + for path_uniq in $(echo "${paths[*]}" | tr ' ' '\n' | sort -u); do + path_uniq="${path_uniq//__REPLACED__SPACE__/ }" + pushd "$path_uniq" > /dev/null + terrascan scan -i terraform $ARGS + popd > /dev/null + done +} + +initialize_() { + # get directory containing this script + local dir + local source + source="${BASH_SOURCE[0]}" + while [[ -L $source ]]; do # resolve $source until the file is no longer a symlink + dir="$(cd -P "$(dirname "$source")" > /dev/null && pwd)" + source="$(readlink "$source")" + # if $source was a relative symlink, we need to resolve it relative to the path where the symlink file was located + [[ $source != /* ]] && source="$dir/$source" + done + _SCRIPT_DIR="$(dirname "$source")" + + # source getopt function + # shellcheck source=lib_getopt + . "$_SCRIPT_DIR/lib_getopt" +} + +parse_cmdline_() { + declare argv + argv=$(getopt -o a: --long args: -- "$@") || return + eval "set -- $argv" + + for argv; do + case $argv in + -a | --args) + shift + ARGS+=("$1") + shift + ;; + --) + shift + FILES+=("$@") + break + ;; + esac + done +} + +# global arrays +declare -a ARGS=() +declare -a FILES=() + +[[ ${BASH_SOURCE[0]} != "$0" ]] || main "$@"