# Terraform static code analysis

This is a wrapper around the Checkov IaC static code analysis tool. We use it to scan `terraform-infra` and any `terraform-*` repositories for possible security issues and best practices adherence. The tool uses Jenkins as a runtime and analyzes pull requests, publishing its findings as issue comments and commit status checks.

In production, the tool runs as the [Checkov Scan](https://github.com/organizations/theorchard/settings/apps/checkov-scan) Github app, where it has permissions to access private repositories, publish/hide comments, and create commit status checks. For local development, it can also run in a user context, unrelated to the Github app, by using a personal access token.

The tool can download private Github repositories that are referenced as terraform modules in the infrastructure. By default, our sources use the `git@github.com` scheme, which would typically necessitate a separate SSH key. However, Checkov provides functionality to automatically replace source schemes with HTTPS when using an access token. This _also_ means that when running locally, you may see an error similar to the following example:

```bash
2025-06-30 22:23:48,125 [MainThread  ] [WARNI]  failed to get git::ssh://git@github.com/theorchard/terraform-vpc-info.git?ref=3.0.2 in git loader because of Cmd('git') failed due to: exit code(128)
integration-test-1  |   cmdline: git clone -v --depth=1 -b 3.0.2 -- ssh://*****@*****hub.com/theorchard/terraform-vpc-info.git /repository/qa/daemon-youtube-ownership/.terraform-modules-15ai1eoe/git@github.com/theorchard/terraform-vpc-info/3.0.2
integration-test-1  |   stderr: 'Cloning into '/repository/qa/daemon-youtube-ownership/.terraform-modules-15ai1eoe/git@github.com/theorchard/terraform-vpc-info/3.0.2'...
integration-test-1  | git@github.com: Permission denied (publickey).
integration-test-1  | fatal: Could not read from remote repository.
```
This should be considered non-blocking behavior, since Checkov will first successfully download module sources on subsequent attempts using HTTPS.

The tool requires a target repository cloned locally to run a scan on it. It scans only the directories, which contain changes to `.tf`, `.tfvars` or `.json` files.

Additional documentation is available in [Notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e).

## Environment variables

Variable | Description | Required
------------ | ------------- | -------------
GITHUB_API_KEY | Github token with access to private repositories and Github APIs; this should only be used for local development and testing | False (set this OR `GITHUB_APP_` environment variables)
GITHUB_APP_CLIENT_ID | Client ID of Github app; this should be set in production in lieu of `GITHUB_API_KEY` | False (set this OR `GITHUB_API_KEY`)
GITHUB_APP_INSTALLATION_ID | Installation ID of Github app; this should be set in production in lieu of `GITHUB_API_KEY` | False (set this OR `GITHUB_API_KEY`)
GITHUB_APP_PRIVATE_KEY_FILE_LOCATION | Path on disk to SSH private key of Github app; this should be set in production in lieu of `GITHUB_API_KEY` | False (set this OR `GITHUB_API_KEY`)
GITHUB_REPO_NAME | Github repository name in the next format: `<org or username>/<repo name>`. | True
GITHUB_PR_NUM | Github pull-request number from the repository to run scan on. | True
RECURSIVE_SCAN | Set to `false` to disable nested directories scan. Default is `true`. | False
REPO_DIR | A local path to the cloned repository. Must be writeable. | True
BLOCKING_MODE | Set to `true` to request changes using the pull-request review process if any findings are found, or approve the pull-request in case of none. Default is `false` that will only add non-blocking comments with findings. | False
AWS_ROLES_PER_PREFIX | A list of IAM roles the tool will assume to run dynamic checks using AWS API. The list consists of comma-separated key-value pairs, where the key is a path prefix in terraform-infra, and the value is ARN of the role to use for projects under this path: `${terraform-infra-repo-prefix}=${role-arn-for-that-account}`. See an example below. | False

## Custom policies

The full list of custom policies added to Checkov:

ID | Name | Guide
--- | --- | ---
ORCD_AWS_1 | Avoid using subnets that are short on available IPs | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#46166c83eab648edb5ea9212d8197626)
ORCD_AWS_2 | Avoid using old terraform module versions that are non Checkov compliant | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#abec314942d149e4a13d9fc4c05b9b32)
ORCD_AWS_3 | Ensure CloudFront distribution has a response headers policy attached | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#2a109a24adb44b5dbe3b74d1085c661b)
ORCD_AWS_4 | Ensure CloudFront response headers policy has security headers configuration block | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#a17ea07e48d44d8790b052bfee2b03db)
ORCD_AWS_5 | Ensure CloudFront response headers policy has Strict Transport Security configured | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#ffacf1217e554431915daf1644e8ad8e)
ORCD_AWS_6 | Ensure CloudFront response headers policy has XSS Protection configured | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#821e6bf0bd384e409df408883ccb8c73)
ORCD_AWS_7 | Ensure CloudFront response headers policy has Content Type Options configured | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#1c2bdcc41464429ea82b22ae2cd246f9)
ORCD_AWS_8 | Ensure CloudFront response headers policy has Frame Options configured | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#b2afdcd11c4844179e3eddce1ca6263e)
ORCD_AWS_9 | Ensure CloudFront response headers policy has Referrer Policy configured | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#c13f9de9cd114d40a870f5c2f2d0ff9c)
ORCD_AWS_10 | Security Group is not specified for VPC connected Lambda Function in terraform-lambda module | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#a9279036f9de4e0c81942a2628a5bdf9)
ORCD_AWS_11 | Avoid using default VPC Security Group for VPC connected Lambda Function in terraform-lambda module | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#57af6d87726b47139ab2b17c3b31bb5c)
ORCD_AWS_12 | Ensure IAM User has proper tagging | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#6203b6e0751f40b1935db0c21d193273)
ORCD_SNOWFLAKE_1 | Ensure Snowflake resources are only in prod/snowflake directory | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e#2ea97177520f80f8b900f434d1605f66)
ORCD_SNOWFLAKE_3 | Ensure Snowflake provider account_name is not set to deprecated 'orchard' account | [notion](https://www.notion.so/Checkov-guide-e5c30d67d35248ebbb0806df59775e0e)

## Dynamic policies

We introduced AWS API access to make custom policies dynamic. As an example, you can check the [SubnetsWithIPShortage](https://github.com/theorchard/terraform-static-code-analysis/blob/master/policies/advanced/SubnetsWithIPShortage.py) policy that verifies the requested subnet has enough IP addresses available.

To grant AWS API access to the tool - we deployed IAM roles with necessary permissions in our AWS accounts (
  [theorchard/dev](https://github.com/theorchard/terraform-infra/tree/master/dev/checkov),
  [theorchard/qa+prod](https://github.com/theorchard/terraform-infra/tree/master/prod/checkov)
).

Pipeline Jenkins IAM role has been given [cross-account access](https://github.com/theorchard/terraform-infra/blob/master/prod/jenkins/variables.tf#L19) to these roles.

The tool uses the `AWS_ROLES_PER_PREFIX` environment variable to define which role should be used based on the path prefix:

```bash
export AWS_ROLES_PER_PREFIX='dev=<theorchard-dev-role-arn>,qa=<theorchard-qaprod-role-arn>,songwhip/dev=<songwhip-dev-role-arn>'
export AWS_ROLES_PER_PREFIX='dev=arn:aws:iam::103233932089:role/dev-checkov-access-role,qa=arn:aws:iam::437795906767:role/prod-checkov-access-role,prod=arn:aws:iam::437795906767:role/prod-checkov-access-role'
```

### Handling AWS exceptions

While using dynamic checks an exception may occur due to insufficient permissions or incorrect resource ID specified. All exceptions will be logged to `stderr` and shown in Jenkins job console logs. Nothing will be printed out to pull-request comments.

As for the custom policy that got an exception, we recommend skipping this particular check using the `CheckResult.SKIPPED` return code. Using the fail-open approach here will not impact scan results with false-positive findings.

## Usage example

```bash
awsume dev

# Run via docker compose
cp .env.shadow .env
docker-compose up --build module-integration-tests

# Or run the container directly
docker run -it --rm \
  -v '<path-to-terraform-git-repo>:/repository' \
  -e REPO_DIR='/repository' \
  -e GITHUB_API_KEY='<github-api-key>' \
  -e GITHUB_REPO_NAME='<github-repo-name>' \
  -e GITHUB_PR_NUM='<github-pr-number>' \
  terraform-scanner:latest
```
