IaC CI/CD integration for Terraform Vet

Ethan Han
Google Cloud - Community
4 min readJun 23, 2023

Co-author: Ran Zhang

Background

One of the biggest concern about Infrastructure as code(IaC) is about configuration errors that could lead to security and governance violations. Security and Cloud administrators need to be able to set up organization level constraints that enforce security best practices. And it must also be compatible with tools across the entire application lifecycle, from development to deployment to auditing of deployed resources.

gcloud beta terraform vet is a tool that can be used to enforce policy compliance as part of an infrastructure CI/CD pipeline. Please refer our articles about how the gcloud beta terraform vet works and how to setup Policy Library in your organization.

This article we will talk about how integrate gcloud beta terraform vet with your CI/CD pipeline.

Assumptions

  1. The CI/CD is based on Cloud Build but the idea works similar in tools like GitLab CI/CD and GitHub Actions
  2. The dummy terraform code and Policy Library is stored in Cloud Source Repositories but you can change to your preferred tools.
  3. This article will not cover all the permissions instruction
  4. GCP project is created and necessary resources, such as bucket

How to integrate with IaC CI/CD pipeline?

Step1: Create your Policy Library

Create your Policy Library and push it to Cloud Source Repositories. Cloud Source Repositories for the details, please refer to Policy Validation — Preventive Control with Terraform Vet.

Copy storage_location.yaml file into policies/constraints folder and push the code.

$ cd path/to/your/policy-library
$ cp samples/storage_location.yaml policies/constraints

Step2: Create Terraform Dummy Code

Let’s create a repo folder with dummy terraform code contains backend.tf and main.tf files.

# backend.tf
terraform {
backend "gcs" {
bucket = "test-demo-tfstate" # make sure you have the bucket for terraform state
prefix = "env/dev"
}
}
# main.tf
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 4.36.0"
}
}
}
provider "google" {
project = "my-proj" # change it to your project id
}
resource "google_storage_bucket" "auto-expire" {
name = "my-proj-simple-demo-0"
# location = "asia-southeast1" # allowed location in policy library, the pipeline will success
location = "us-central2" # disallowed location in policy library, the pipeline will fail
force_destroy = true
}

Step3: Define cloudbuild.yaml

Create a new file cloudbuild.yaml, the file defines the IaC pipeline process. We are going to integrate the terraform vet commend before the terraform apply.

# Copyright 2023 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

timeout: 2700s
steps:
# [START tf init and plan]
- id: 'tf init and plan'
name: 'hashicorp/terraform:1.3.3'
entrypoint: 'sh'
args:
- '-c'
- |
terraform init
terraform plan -out=test.tfplan
terraform show -json ./test.tfplan > ./tfplan.json
ls -al tfplan.json
# [END tf init and plan]

# [START terraform vet]
- id: 'terraform vet and apply'
name: 'gcr.io/google.com/cloudsdktool/google-cloud-cli'
entrypoint: 'sh'
args:
- '-c'
- |
gcloud auth list
apt-get install google-cloud-sdk-terraform-tools
# clone policy library
gcloud source repos clone $_POLICY_LIBRARY --project=$PROJECT_ID
cd /workspace/$_POLICY_LIBRARY
git checkout main
echo "CNCac Policy list:"
ls -al /workspace/$_POLICY_LIBRARY/policies/constraints
policy_library_dir="/workspace/${_POLICY_LIBRARY}"

cd /workspace/
terra_vet_violations=$(gcloud beta terraform vet tfplan.json --policy-library=$policy_library_dir --format=json)
ret_val=$?
if [ $ret_val -eq 2 ]; then
# Optional: parse the terra_vet_violations variable as json and check the severity level
echo "$terra_vet_violations"
echo "Violations found; not proceeding with terraform apply"
exit 1
fi
if [ $ret_val -ne 0]; then
echo "Error during gcloud beta terraform vet; not proceeding with terraform apply"
exit 1
fi
echo "No policy violations detected; proceeding with terraform apply"

# [END terraform vet]
# [START tf apply]
- id: 'tf apply'
name: 'hashicorp/terraform:1.3.3'
entrypoint: 'sh'
args:
- '-c'
- |
terraform apply -auto-approve

# [END tf apply]


logsBucket: 'gs://${_BUILD_LOG_BUCKET}'
options:
logging: GCS_ONLY
substitutions:
# change it to your policy library repo name
_POLICY_LIBRARY: cncac-policy-library
# make sure you have created the bucket for cloud build log
_BUILD_LOG_BUCKET: cncac-build-log-bucket

Now your terraform dummy repo will look like this below, and push your repo to the Cloud Source Repositories.

my-tf-repo
├── backend.tf
├── main.tf
├── cloudbuil.yaml

Step4: Define Cloud Build Trigger

Here provides detailed introduction about Cloud Build trigger. We can simply trigger for all push for demo purpose like below.

Step5: Change Code and Commit

$ cat main.tf
resource "google_storage_bucket" "auto-expire" {
name = "my-proj-cncac-simple-demo-0"
# location = "asia-southeast1" # allowed location, the pipeline will success
location = "us-central2" # disallowed location, the pipeline will fail
force_destroy = true
}
$ git add --all .
$ git commit -m "feat: create bucket in disallowed location"
$ git push

Congratulations!

You can see the terraform vet will fail as it managed to detect the policy violation as shown in below picture. So that you can successfully avoid provisioning non-compliance resource into production environment.

--

--