tfsec

IAC scanning tool

What is TFSec?

Installation

  • brew install tfsec

  • Other methods can be found in the official docs

Scanning

Local Scanning

  • With tfsec installed, we can easily run it to scan a directory of terraform code

  • tfsec /terraform-directory

# Partial output of results

Result #1 HIGH Bucket does not encrypt data with a customer managed key. 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  s3-cis-req.tf:3-15
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
    3resource "aws_s3_bucket_server_side_encryption_configuration" "s3-encryption-config" {
    4  │   bucket = aws_s3_bucket.s3-bucket-1.bucket # defines bucket name
    5
    6rule {
    7apply_server_side_encryption_by_default {
    8  │       sse_algorithm = var.sse-algorithm # defines encryption type
    9  │     }
   10  │   }
   11
   ..  
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
          ID aws-s3-encryption-customer-key
      Impact Using AWS managed keys does not allow for fine grained control
  Resolution Enable encryption using customer managed keys

  More Information
  - https://aquasecurity.github.io/tfsec/v1.28.4/checks/aws/s3/encryption-customer-key/
  - https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_bucket#enable-default-server-side-encryption
  
  [SNIP]

  results
  ──────────────────────────────────────────
  passed               9
  ignored              0
  critical             0
  high                 7
  medium               3
  low                  1

  9 passed, 11 potential problem(s) detected.

CI Job

tfsec can be added as a job to a CI pipeline. For example, the code below can be used for GitLab. Just add it to .gitlab-ci.yml.

# example leveraging the docker image

stages:
  - iac_scanning
  - prep
  - plan
  - deploy
  
code_scanning:
  stage: iac_scanning
  image:
    name: aquasec/tfsec-ci:latest
  script: tfsec . -f markdown --out $CI_PROJECT_DIR/tfsec_findings.md
  allow_failure: true
  artifacts:
    when: always
    expire_in: 1 day
    paths:
      - tfsec_findings.md
# example downloading the tool

stages:
  - iac_scanning
  - prep
  - plan
  - deploy

code_scanning:
  stage: iac_scanning
  image:
    name: ubuntu:latest
  script:
    - apt-get update && apt-get install -y curl
    - curl -s https://raw.githubusercontent.com/aquasecurity/tfsec/master/scripts/install_linux.sh | bash
    - tfsec . -f markdown --out $CI_PROJECT_DIR/tfsec_findings.md
  allow_failure: true
  artifacts:
    when: always
    expire_in: 1 day
    paths:
      - tfsec_findings.md

Creating Exceptions

We can easily make exceptions by adding this line of code directly above the resource. Hint, if you use the VS Code extension, you can just click a button to do this for you. When the scan runs again, this finding will be ignored.

#tfsec:ignore:aws-s3-encryption-customer-key
resource "aws_s3_bucket_server_side_encryption_configuration" "tpcrc" {

Additionally, we can set an expiration date to suppress the finding until after the date e.g., Mar 2, 2024.

#tfsec:ignore:aws-s3-encryption-customer-key:exp:2024-03-02
resource "aws_s3_bucket_server_side_encryption_configuration" "tpcrc" {

Config File

The tfsec config file enables us to modify a rule's severity level or exclude a rule from a scan completely. This is useful when a rule is not applicable or the severity should be lowered for a dev environment vs a prod environment.

Overriding the Severity

This rule below is being modified from the original "HIGH" finding to a "LOW" finding.

# tfsec.json
{
    "severity_overrides": {
        "AWS001": "ERROR",
        "aws-s3-encryption-customer-key": "LOW"
    }
}

The custom config file can be leveraged during a scan. The next time this finding is discovered, it will be reported with a LOW severity.

tfsec --config-file tfsec.json

Excluding a Rule from a Scan

Sometimes you may want to exclude a rule from a scan completely.

#tfsec.json
{
    "severity_overrides": {
        "AWS001": "ERROR",
        "aws-s3-encryption-customer-key": "LOW"
    },
    "exclude": [
        "AWS002",
        "aws-s3-enable-bucket-encryption"
    ]
}

Again, leveraging the custom config file will result in the rule aws-s3-enable-bucket-encryption being excluded from the findings.

Custom tfsec Rules

tfsec supports custom rules written in JSON, YAML, or Rego. Custom rules are useful when you want to enforce security policies specific to your organization or when out-of-the-box rules don't meet your requirements.

JSON Format

This rule ensures an S3 bucket has a business owner tag.

# _tfchecks.json

{
    "checks": [
        {
            "code": "No Business Owner Tag - CUS001",
            "description": "Custom check to ensure buckets are tagged with a business owner.",
            "impact": "By not having a business owner tag, it is difficult to determine who is responsible for the bucket.",
            "resolution": "Add the businessOwner tag.",
            "requiredTypes": [
                "resource"
            ],
            "requiredLabels": [
                "aws_s3_bucket"
            ],
            "severity": "CRITICAL",
            "matchSpec": {
                "name": "tags",
                "action": "contains",
                "value": "businessOwner"
            },
            "errorMessage": "The required businessOwner tag was missing."
        }
    ]
}

The custom rule can be leveraged during a scan. This assumes the terraform code and custom rule are in the same directory but this doesn't have to be true.

tfsec --custom-check-dir .

YAML Format

The same rule as above but in YAML.

---
checks:
  - code: No Business Owner Tag - CUS001
    description: Custom check to ensure buckets are tagged with a business owner.
    impact: By not having a business owner tag, it is difficult to determine who is responsible for the bucket.
    resolution: Add the businessOwner tag.
    requiredTypes:
      - resource
    requiredLabels:
      - aws_s3_bucket
    severity: CRITICAL
    matchSpec:
      name: tags
      action: contains
      value: businessOwner
    errorMessage: The required businessOwner tag was missing.

Rego Format

tfsec allows for writing policies in Rego format but not all arguments are supported. In this case, we cannot write a tfsec rego policy that checks for tags. Under the hood, tfsec is utilizing the open-source project defsec and tags are not supported for an S3 bucket. You can view what is supported here. Additionally, you can see all the values and their current configuration by running this command, tfsec --print-rego-input | jq '.aws.s3.buckets[0]' which will return an output similar to below.

An example of a supported check would be a Rego policy that checks S3 bucket versioning is enabled.

package custom.aws.s3.versioning_enabled

deny[res] {
    bucket := input.aws.s3.buckets[_]
    bucket.versioning.enabled.value == false
    msg := "Bucket should have versioning enabled."
    res := result.new(msg, bucket.versioning.enabled)
}

Useful Resources

Last updated