TL;DR

Google Cloud Platform hosts 9% of global cloud workloads, and infrastructure misconfigurations are responsible for 65% of cloud security incidents, according to the Gartner Cloud Security report 2024. GCP infrastructure testing prevents these incidents by validating Terraform modules, IAM policies, and network configurations before they reach production. Unlike application testing, infrastructure testing requires real cloud resources: a unit test for a GKE cluster must actually create the cluster, verify its configuration, and destroy it — Terratest makes this reproducible in CI/CD pipelines. According to the ThoughtWorks Technology Radar, infrastructure testing with Terratest is now considered a core DevOps practice for organizations operating at scale on Google Cloud. The GCP Policy Library provides 100+ pre-built Rego policies for security and compliance validation, covering everything from storage bucket public access to IAM privilege escalation prevention. This guide covers the complete GCP infrastructure testing stack: Terratest for Terraform module testing, Config Validator for policy enforcement, and gcloud CLI integration for automated compliance reporting.

AI-Assisted Approaches

AI tools accelerate GCP infrastructure test development, especially for generating constraint templates and Terratest assertions.

Generating Terratest assertions for GCP resources:

I have a Terraform module that creates:

- GCP Compute Instance with custom metadata
- VPC network with firewall rules
- Cloud Storage bucket with lifecycle policies

Generate Terratest assertions in Go that verify:

1. Instance is running and has correct machine type
2. Firewall rules allow only ports 443 and 22
3. Bucket has versioning enabled and lifecycle rule for 30-day deletion
4. All resources have required labels: environment, team, cost-center

Include proper error handling and use google.golang.org/api packages.

Creating Config Validator constraints:

Write a Rego constraint template for Config Validator that enforces:

1. All GCS buckets must have uniform bucket-level access enabled
2. All Compute instances must not have external IP addresses
3. All VPCs must have flow logs enabled

Use the GCP Policy Library format with proper CAI asset types
and include sample constraint YAML files.

Debugging failed terraform vet validations:

My terraform vet command fails with this output:
[paste vet output]

The constraint is:
[paste constraint YAML]

My Terraform plan includes:
[paste relevant terraform plan output]

Explain why the validation fails and how to fix either
the Terraform code or the constraint.

When to Use Each Testing Approach

Testing Strategy Decision Framework

Test TypeToolWhen to UseGCP Resources Required
Syntax validationterraform validateEvery commitNone
Static analysistflint-ruleset-googleEvery commitNone
Policy pre-checkgcloud terraform vetBefore plan/applyAPI access only
Unit testingTerraform test (native)Module logicNone (mocked)
Integration testingTerratestReal resource validationFull project access
End-to-endTerratest + application testsProduction-like environmentsDedicated test project

Use Terratest When

  • You need to verify actual GCP resource properties: Metadata, labels, network configs
  • Testing cross-resource interactions: IAM bindings, firewall rules affecting instances
  • Validating GCP-specific behaviors: Managed instance group scaling, Cloud SQL failover
  • CI/CD requires deployment verification: Resources must exist before proceeding

Use Config Validator/terraform vet When

  • Enforcing organizational policies: No public IPs, encryption required, specific regions
  • Pre-deployment compliance checks: Block non-compliant plans before apply
  • Standardizing across teams: Consistent tagging, naming conventions, resource configurations

Terratest for GCP Modules

Basic Module Test Structure

package test

import (
    "testing"
    "github.com/gruntwork-io/terratest/modules/gcp"
    "github.com/gruntwork-io/terratest/modules/terraform"
    "github.com/stretchr/testify/assert"
)

func TestGCPComputeInstance(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    region := "us-central1"
    zone := "us-central1-a"

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/compute",
        Vars: map[string]interface{}{
            "project_id":    projectID,
            "region":        region,
            "zone":          zone,
            "instance_name": "test-instance-" + random.UniqueId(),
            "machine_type":  "e2-micro",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Verify instance properties
    instanceName := terraform.Output(t, terraformOptions, "instance_name")
    instance := gcp.FetchInstance(t, projectID, zone, instanceName)

    assert.Equal(t, "RUNNING", instance.Status)
    assert.Contains(t, instance.MachineType, "e2-micro")
}

Testing VPC and Firewall Rules

func TestGCPNetworkWithFirewall(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/network",
        Vars: map[string]interface{}{
            "project_id":   projectID,
            "network_name": "test-vpc-" + random.UniqueId(),
            "allowed_ports": []string{"443", "22"},
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    networkName := terraform.Output(t, terraformOptions, "network_name")

    // Verify firewall rules
    firewalls := gcp.GetFirewallRulesForNetwork(t, projectID, networkName)

    for _, fw := range firewalls {
        // Ensure no rule allows 0.0.0.0/0 on sensitive ports
        for _, allowed := range fw.Allowed {
            if contains(fw.SourceRanges, "0.0.0.0/0") {
                assert.NotContains(t, allowed.Ports, "3389",
                    "RDP should not be open to internet")
                assert.NotContains(t, allowed.Ports, "3306",
                    "MySQL should not be open to internet")
            }
        }
    }
}

Testing Cloud Storage with Lifecycle Rules

func TestGCSBucketCompliance(t *testing.T) {
    t.Parallel()

    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)
    bucketName := "test-bucket-" + strings.ToLower(random.UniqueId())

    terraformOptions := terraform.WithDefaultRetryableErrors(t, &terraform.Options{
        TerraformDir: "../modules/storage",
        Vars: map[string]interface{}{
            "project_id":  projectID,
            "bucket_name": bucketName,
            "location":    "US",
        },
    })

    defer terraform.Destroy(t, terraformOptions)
    terraform.InitAndApply(t, terraformOptions)

    // Use GCP client library for detailed bucket inspection
    ctx := context.Background()
    client, err := storage.NewClient(ctx)
    require.NoError(t, err)
    defer client.Close()

    bucket := client.Bucket(bucketName)
    attrs, err := bucket.Attrs(ctx)
    require.NoError(t, err)

    // Verify compliance requirements
    assert.True(t, attrs.UniformBucketLevelAccess.Enabled,
        "Uniform bucket-level access must be enabled")
    assert.True(t, attrs.VersioningEnabled,
        "Versioning must be enabled")
    assert.NotEmpty(t, attrs.Lifecycle.Rules,
        "Lifecycle rules must be configured")
}

Config Validator and Policy Library

Setting Up Policy Library

# Clone the GCP policy library
git clone https://github.com/GoogleCloudPlatform/policy-library.git
cd policy-library

# Structure
# policies/
#   constraints/    # Your constraint definitions
#   templates/      # Constraint templates (Rego rules)

Creating Custom Constraints

# policies/constraints/require_bucket_versioning.yaml
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: GCPStorageBucketVersioningConstraintV1
metadata:
  name: require-bucket-versioning
spec:
  severity: high
  match:
    ancestries:

      - "organizations/123456789"
    excludedAncestries: []
  parameters:
    versioning_enabled: true

Constraint Template Example

# policies/templates/gcp_storage_bucket_versioning.yaml
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
  name: gcpstoragebucketversioningconstraintv1
spec:
  crd:
    spec:
      names:
        kind: GCPStorageBucketVersioningConstraintV1
      validation:
        openAPIV3Schema:
          properties:
            versioning_enabled:
              type: boolean
  targets:

    - target: validation.gcp.forsetisecurity.org
      rego: |
        package templates.gcp.GCPStorageBucketVersioningConstraintV1

        violation[{
            "msg": message,
            "details": metadata,
        }] {
            asset := input.asset
            asset.asset_type == "storage.googleapis.com/Bucket"

            versioning := asset.resource.data.versioning
            not versioning.enabled

            message := sprintf("Bucket %v does not have versioning enabled", [asset.name])
            metadata := {"resource": asset.name}
        }

Using gcloud terraform vet

# Generate Terraform plan in JSON format
terraform plan -out=tfplan.binary
terraform show -json tfplan.binary > tfplan.json

# Validate against policy library
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --project=my-project

# In CI/CD pipeline
gcloud beta terraform vet tfplan.json \
    --policy-library=./policy-library \
    --format=json > violations.json

# Check for violations
if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
    echo "Policy violations found:"
    jq '.violations[] | .message' violations.json
    exit 1
fi

CI/CD Integration

GitHub Actions Workflow

name: GCP Infrastructure Tests

on:
  pull_request:
    paths:

      - 'terraform/**'
      - 'policies/**'

jobs:
  static-analysis:
    runs-on: ubuntu-latest
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: 1.7.0

      - name: Terraform Validate
        run: terraform validate
        working-directory: terraform/

      - name: TFLint with GCP ruleset
        uses: terraform-linters/setup-tflint@v4
        with:
          tflint_version: latest

      - run: |
          tflint --init
          tflint --format=compact
        working-directory: terraform/

  policy-validation:
    runs-on: ubuntu-latest
    needs: static-analysis
    steps:

      - uses: actions/checkout@v4

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Setup gcloud
        uses: google-github-actions/setup-gcloud@v2

      - name: Generate plan
        run: |
          terraform init
          terraform plan -out=tfplan.binary
          terraform show -json tfplan.binary > tfplan.json
        working-directory: terraform/

      - name: Policy validation
        run: |
          gcloud beta terraform vet tfplan.json \
            --policy-library=../policies \
            --format=json > violations.json

          if [ $(jq '.violations | length' violations.json) -gt 0 ]; then
            echo "::error::Policy violations detected"
            jq -r '.violations[] | "- \(.constraint): \(.message)"' violations.json
            exit 1
          fi
        working-directory: terraform/

  integration-tests:
    runs-on: ubuntu-latest
    needs: policy-validation
    if: github.event.pull_request.draft == false
    steps:

      - uses: actions/checkout@v4

      - name: Setup Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Authenticate to GCP
        uses: google-github-actions/auth@v2
        with:
          credentials_json: ${{ secrets.GCP_SA_KEY }}

      - name: Run Terratest
        run: |
          cd test
          go test -v -timeout 30m -parallel 4
        env:
          GOOGLE_PROJECT: ${{ secrets.GCP_TEST_PROJECT }}

Common GCP Testing Patterns

Testing IAM Bindings

func TestIAMBindings(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // After terraform apply...

    // Verify service account has expected roles
    saEmail := terraform.Output(t, terraformOptions, "service_account_email")

    policy, err := getProjectIAMPolicy(projectID)
    require.NoError(t, err)

    expectedRole := "roles/storage.objectViewer"
    member := "serviceAccount:" + saEmail

    found := false
    for _, binding := range policy.Bindings {
        if binding.Role == expectedRole {
            for _, m := range binding.Members {
                if m == member {
                    found = true
                    break
                }
            }
        }
    }
    assert.True(t, found, "Service account should have %s role", expectedRole)
}

Testing Private Google Access

func TestPrivateGoogleAccess(t *testing.T) {
    projectID := gcp.GetGoogleProjectIDFromEnvVar(t)

    // After creating subnet...

    subnetName := terraform.Output(t, terraformOptions, "subnet_name")
    region := "us-central1"

    subnet := gcp.FetchSubnet(t, projectID, region, subnetName)

    assert.True(t, subnet.PrivateIpGoogleAccess,
        "Private Google Access should be enabled for private subnets")
}

Measuring Success

MetricBefore TestingAfter TestingHow to Track
Policy violations in prodUnknown0Config Validator reports
Failed deployments15%/month<2%/monthCI/CD pipeline metrics
Compliance audit findings5-10/quarter0-1/quarterAudit reports
Mean time to detect issuesDaysMinutesAlert timestamps

Warning signs your testing isn’t working:

  • Policy violations still reaching production
  • Terratest passing but real issues in deployed resources
  • Tests taking >30 minutes (resource leaks likely)
  • Flaky tests due to eventual consistency

Conclusion

GCP infrastructure testing combines universal IaC testing practices with Google-specific tools:

  1. Use Terratest for integration tests requiring real GCP resources
  2. Implement Config Validator constraints for organizational policies
  3. Run gcloud terraform vet in CI/CD to catch violations early
  4. Leverage the Policy Library for common compliance patterns

The key is layering these tools—static analysis catches syntax issues, policy validation catches compliance issues, and integration tests catch functional issues.

Official Resources

“Infrastructure bugs are 10x more expensive than application bugs because they affect every service running on that infrastructure simultaneously. GCP policy validation catches these issues before deployment — when they cost minutes, not days of incident response.” — Yuri Kan, Senior QA Lead

FAQ

What is GCP infrastructure testing?

Validating Google Cloud resources — Terraform modules, Cloud SQL, GKE clusters, IAM policies — using Terratest for integration tests, Config Validator for policy compliance, and gcloud terraform vet for pre-deployment checks.

What is Terratest and how is it used with GCP?

Go library for infrastructure testing. Spins up real GCP resources, validates their configuration and connectivity, then destroys them. Enables true integration testing of Terraform modules in CI/CD pipelines.

What is GCP Config Validator?

GCP tool that checks Terraform plans against a Policy Library of Rego policies. Validates infrastructure configurations comply with security requirements before deployment — catches privilege escalation and public exposure.

How do you test IAM policies in GCP?

Use gcloud iam test-iam-permissions to verify permissions, Terratest assertions to validate bindings post-deployment, and Config Validator policies to enforce least-privilege across all GCP resources.

See Also