Static code analysis is a crucial component of quality assurance for software projects. It allows errors to be detected early in a short feedback loop and addressed promptly. Source code is examined against defined rules to identify errors and potential vulnerabilities, and depending on the tool used, possible solutions are often suggested.

With the emergence of “Infrastructure as Code” approaches, infrastructure descriptions are increasingly being stored in the form of code or at least structured text files. This enables the advantages of static code analysis to be applied to infrastructure definitions as well.

Analysis of Terraform Files

For analyzing Terraform definition files, there is the CLI tool tfsec. It is available under the MIT license as an executable file for Windows, Linux, and macOS, or as a Docker image for download. tfsec comes with an extensive set of rules for major hyperscalers like AWS, Azure, and Google Cloud. However, there are fewer rules available for a self-hosted Kubernetes environment or an OpenStack cluster. While you can define custom rules using Custom Checks, using tfsec in a managed environment is probably the most practical approach.

tfsec

In the simplest case, you can run tfsec in the folder containing your Terraform files or pass the path as the first command-line argument. tfsec recursively scans all files with the extensions .tf and .tfvars and displays the results in the console.

./tfsec ./terraform

If no rule violations are detected, tfsec will output some statistics and provide an “no problems” message.

  timings
  ──────────────────────────────────────────
  disk i/o             5.3983ms
  parsing              0s
  adaptation           610.2µs
  checks               3.2634ms
  total                9.2719ms

  counts
  ──────────────────────────────────────────
  modules downloaded   0
  modules processed    1
  blocks processed     11
  files read           1

  results
  ──────────────────────────────────────────
  passed               6
  ignored              0
  critical             0
  high                 0
  medium               0
  low                  0

No problems detected!

When issues are detected, tfsec provides details about the problematic file, the exact location of the issue, as well as hints and links to further information. Using the --concise-output parameter reduces the output to the most essential information.

> ./tfsec ./terraform --concise-output

Result #1 CRITICAL Storage account uses an insecure TLS version. 
────────────────────────────────────────────────────────────────────────────────
  ./Terraform/main.tf:55
────────────────────────────────────────────────────────────────────────────────
          ID azure-storage-use-secure-tls-policy
      Impact The TLS version being outdated and has known vulnerabilities
  Resolution Use a more recent TLS/SSL policy for the load balancer

  More Information
  - https://aquasecurity.github.io/tfsec/v1.28.1/checks/azure/storage/use-secure-tls-policy/
  - https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/storage_account#min_tls_version
────────────────────────────────────────────────────────────────────────────────

  5 passed, 1 potential problem(s) detected.

In the example, tfsec is flagging an outdated TLS version for an Azure Storage Account.

If you want to exclude specific rules from being applied, you can use the --exclude [RULE-ID] parameter to exclude them from the analysis. You can also ignore issues related to individual resources or options within your Terraform definitions by using comments in your code.

resource "azurerm_storage_account" "storage_account" {
  account_kind    = "StorageV2"
  account_tier    = "Standard"
  min_tls_version = "TLS1_1" #tfsec:ignore:azure-storage-use-secure-tls-policy
  ...
}

CI Pipeline

A useful feature of tfsec is its ability to output the results of an analysis in various formats. Using the --format parameter, you can select the desired formats, including options like Markdown, HTML, and JUnit. By using the --out parameter, you specify the directory (which must exist) and the filename prefix for the output files. The first format specified will also be displayed on the console.

The following command generates the files results.lovely, results.junit, and results.markdown in the directory ./terraform/tfsec-results.

./tfsec ./terraform --concise-output \
    --format lovely,junit,markdown \
    --out ./terraform/tfsec-results/results

The results can now be further processed in a CI pipeline, and the build can fail in case of rule violations.

Azure DevOps Pipeline

If you are using Azure DevOps Pipelines, you can publish the JUnit file as test results. This way, you can easily integrate tfsec into existing test and build workflows and visualize errors directly within the pipeline.

For simpler adaptation to other CI systems, I will demonstrate how to use tfsec as a shell command. Additionally, there is a pipeline extension available in the Visual Studio Marketplace and a GitHub Action.

First, download the appropriate binary for your system, set the execute bit if necessary, and create the output folder. Then, run tfsec. If you want to see the result in Markdown format as part of the pipeline run in the web interface, you can upload it using a logging command like echo "##vso[task.uploadsummary]....

In a second task, you can then publish the JUnit file as test results.

- task: CmdLine@2
  displayName: "Run tfsec"
  inputs:
    workingDirectory: "$(System.DefaultWorkingDirectory)/Infrastructure/Terraform"
    script: |
      echo "Download tfsec v1.28.1 ..."
      wget -q https://github.com/aquasecurity/tfsec/releases/download/v1.28.1/tfsec-linux-amd64 -O tfsec
      chmod +x tfsec
      mkdir ./tfsec-results

      ./tfsec . --concise-output \
          --format lovely,junit,markdown \
          --out ./tfsec-results/results

      echo "##vso[task.uploadsummary]$(System.DefaultWorkingDirectory)/Infrastructure/Terraform/tfsec-results/results.markdown"

- task: PublishTestResults@2
  displayName: "Publish tfsec results"
  condition: succeededOrFailed()
  inputs:
    testResultsFormat: JUnit
    searchFolder: $(System.DefaultWorkingDirectory)/Infrastructure/Terraform/tfsec-results
    testResultsFiles: "results.junit"
    failTaskOnFailedTests: true
    testRunTitle: tfsec

The tfsec results are included in the test statistics.

Azure Pipeline Test

The result of the Markdown file is displayed in the Extensions tab.

Azure Pipeline Extensions Tab

With this, you have integrated automatic and continuous validation of Terraform files into your CI pipeline.