In this post we demonstrate how to use the open source security and compliance tool called Checkov with Azure DevOps to verify your Azure infrastructure is secure.
Introducing Checkov
Checkov is a great tool for engineering teams to harness as part of their Cloud environment deployments.
Checkov currently supports scanning the following scanning capabilities:
- Terraform (for AWS, GCP, Azure and OCI)
- CloudFormation (including AWS SAM)
- Azure Resource Manager (ARM)
- Serverless framework
- Helm charts
- Kubernetes
- Docker
Setup
In this article we demonstrate using Checkov with Terraform code on Azure.
We will be using brew on mac to install it locally:
brew install checkov
Terraform runs against your terraform code located in a path.
checkov --directory /user/path/to/iac/code
Or against a tfplan file.
terraform init
terraform plan -out tf.plan
terraform show -json tf.plan > tf.json
checkov -f tf.json
Terraform code
Lets deploy a web app with VNET integration. Sample code block snippet below (full code not shown)
resource "azurerm_resource_group" "rg" {
name = "rg-demoapp-dev-001"
location = var.location
}
resource "azurerm_virtual_network" "vnet" {
name = "vnet"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
address_space = ["10.0.0.0/16"]
}
resource "azurerm_subnet" "integrationsubnet" {
name = "integrationsubnet"
resource_group_name = azurerm_resource_group.rg.name
virtual_network_name = azurerm_virtual_network.vnet.name
address_prefixes = ["10.0.1.0/24"]
delegation {
name = "delegation"
service_delegation {
name = "Microsoft.Web/serverFarms"
}
}
}
resource "azurerm_app_service" "backwebapp" {
name = "backwebapprom"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
app_service_plan_id = azurerm_app_service_plan.appserviceplan.id
}
Building a Azure pipeline
Now we have a sample Azure Terraform code to deploy. The next step is to use Checkov in a CI/CD pipeline.
What we want to do is use the output Checkov to report the failures in a unit test output format.
In terms of stages we want to visualize something like:
Terraform Validate -> Checkov compliance scan -> Terraform plan
Defining the pipeline
We want to use Azure DevOps unit test feature which can display unit test results. To do this we will use Junit as the output format and the following yaml task:
- task: PublishTestResults@2
inputs:
testRunTitle: "Checkov Results"
failTaskOnFailedTests: true
testResultsFormat: "JUnit"
testResultsFiles: "CheckovReport.xml"
searchFolder: "$(System.DefaultWorkingDirectory)"
displayName: "Publish > Checkov scan results"
Using this with Checkov will provide us with a really nice test result dashboard as you will see.
Next we define the whole pipeline. In this pipeline we make use of the Chechov docker image to run it on a Azure DevOps build agent.
Complete pipeline:
# Azure Pipeline that run basic continuous integration on a Terraform project
# This makes sure the pipeline is triggered every time code is pushed in the validation-testing example source, on all branches.
trigger:
branches:
include:
- '*'
paths:
include:
- 'src/terraform-azure-webapp'
variables:
# There must be an Azure Service Connection with that name defined in your Azure DevOps settings. See https://docs.microsoft.com/en-us/azure/devops/pipelines/library/connect-to-azure?view=azure-devops
serviceConnection: 'terraform-basic-testing-azure-connection'
azureLocation: 'westeurope'
# Terraform settings
terraformWorkingDirectory: '$(System.DefaultWorkingDirectory)/src/terraform-azure-webapp'
terraformVersion: '1.0.1'
pool:
vmImage: ubuntu-20.04
stages:
- stage: Validate
displayName: Terraform Validate
jobs:
- job: Validate
steps:
# Step 1: install Terraform on the Azure Pipelines agent
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
displayName: 'Install Terraform'
inputs:
terraformVersion: $(terraformVersion)
# Step 2: run Terraform init to initialize the workspace
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'Run terraform init'
inputs:
command: init
workingDirectory: $(terraformWorkingDirectory)
# Step 3 run Terraform validate
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'Run terraform validate'
inputs:
command: validate
workingDirectory: $(terraformWorkingDirectory)
- stage: Compliance
displayName: Checkov compliance scan
jobs:
- job: Compliance
displayName: Checkov compliance scan
steps:
- bash: |
docker run \
--volume $(pwd):/tf bridgecrew/checkov \
--directory /tf \
--output junitxml \
--soft-fail > $(pwd)/CheckovReport.xml
workingDirectory: $(System.DefaultWorkingDirectory)
displayName: "Run > checkov"
- task: PublishTestResults@2
inputs:
testRunTitle: "Checkov Results"
failTaskOnFailedTests: true
testResultsFormat: "JUnit"
testResultsFiles: "CheckovReport.xml"
searchFolder: "$(System.DefaultWorkingDirectory)"
displayName: "Publish > Checkov scan results"
# Step 4: run Terraform validate to validate HCL syntax
- stage: Plan
displayName: Terraform Plan
jobs:
- job: Plan
steps:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'init'
inputs:
command: init
workingDirectory: $(terraformWorkingDirectory)
environmentServiceName: $(serviceConnection)
# Step 5: run Terraform plan to validate HCL syntax
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'Run terraform plan'
inputs:
command: plan
workingDirectory: $(terraformWorkingDirectory)
environmentServiceName: $(serviceConnection)
commandOptions: -var location=$(azureLocation)
- stage: Apply
displayName: Terraform Apply
jobs:
- job: Apply
steps:
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'init'
inputs:
command: init
workingDirectory: $(terraformWorkingDirectory)
environmentServiceName: $(serviceConnection)
# Step 5: run Terraform apply to validate HCL syntax
- task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-cli.TerraformCLI@0
displayName: 'Run terraform plan'
inputs:
command: apply
workingDirectory: $(terraformWorkingDirectory)
environmentServiceName: $(serviceConnection)
commandOptions: -var location=$(azureLocation)
Seeing the results
Let’s dissect some parts of the pipeline:
Initialisation terraform
We initialize terraform using the following Azure pipeline task:
task: charleszipp.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller@0
displayName: 'Install Terraform'
inputs:
terraformVersion: $(terraformVersion)
Executing Checkov task
We make use of the Checkov docker container to actually run Checkov. This avoids us installing Checkov directly on each build agent run.
- stage: Compliance
displayName: Checkov compliance scan
jobs:
- job: Compliance
displayName: Checkov compliance scan
steps:
- bash: |
docker run \
--volume $(pwd):/tf bridgecrew/checkov \
--directory /tf \
--output junitxml \
--soft-fail > $(pwd)/CheckovReport.xml
workingDirectory: $(System.DefaultWorkingDirectory)
displayName: "Run > checkov"
- task: PublishTestResults@2
inputs:
testRunTitle: "Checkov Results"
failTaskOnFailedTests: true
testResultsFormat: "JUnit"
testResultsFiles: "CheckovReport.xml"
searchFolder: "$(System.DefaultWorkingDirecto
The key points from this stage and steps:
- output the result in JUnit format
- failTaskOnFailedTests:true – So that the compliance check fails the build
Pipeline run
Once you trigger the pipeline, Checkov should find issues and fail the pipeline:

Test results

Summary
Checkov is a great tool for for shifting security left. Additionally integrating it into Azure pipelines is seamless. You also get the added bonus of a great dashboard of results immediately.
The key takeways:
- Shift left your security scan of your Cloud infrastructure
- Use Checkov docker container. No need to install Checkov!!
- Use the JUnit to output results