How we guard our infrastructure with Policy as Code

Interested in joining us?

Check out our current vacancies
How we guard our infrastructure with Policy as Code featured image

At Rightmove, moving to the cloud changed how we think about infrastructure. Instead of relying on central teams to provision resources, we’ve adopted a self-service model where engineers own their infrastructure. For the Cloud Foundation team, our job is to ensure that this approach is secure and compliant. It must also be efficient.

In this post, we’ll share our journey with Policy as Code, what worked for us, and how we implemented it using Checkov.

What is Policy as Code?

Policy as Code lets us define rules for infrastructure in code, rather than relying on manual checks. The rules are automated, version-controlled, and testable, which means they can be applied consistently and audited easily, just like Infrastructure as Code. Policy as Code can work with Terraform, catching misconfigurations early, keeping environments secure and compliant, while providing engineers with fast feedback.

Why Policy as Code?

Managing infrastructure at scale is challenging. We built internal Terraform modules and have organisational policies and runtime tools to protect our systems and enforce standards. They help, but they are not enough. Modules can be bypassed, and policies or tools that only run after deployment catch issues too late, slowing engineers down and increasing the risk of drift.

Policy as Code changes that. It enables us to define guardrails as executable rules that run automatically on Terraform plans within the CI/CD pipeline, providing engineers with immediate feedback before changes are applied.

Policies in code are versioned and auditable, enabling governance to evolve with infrastructure without slowing the team. Policy as Code catches mistakes early and embeds standards into workflows, reducing risk and boosting engineers’ confidence in building cloud resources safely at scale.

Why Checkov?

For the first phase of our Policy as Code journey, the goal was simple: scan Terraform changes and validate them against our defined policies directly in our GitLab pipelines.

When evaluating Policy as Code tools, we looked at Google Policy Validator, OPA with Conftest, and Checkov. Each has strengths, but we needed a solution that integrates well with our workflow, works with existing tools, and is easy for engineers to adopt.

CriteriaOPA + ConftestCheckovGoogle Policy Validator
Validation targetTerraform plan fileTerraform code and plan fileTerraform plan file
Language usedRegoYAML & PythonYAML (Rego under the hood)
How it worksCLI validates plan file against Rego policiesCLI validates code and plan files against Checkov policiesCLI validate plan file against Google policies, it requires IAM permissions to run
Prebuilt policiesNoneYes, includes CIS benchmarks and many security policiesSome prebuilt policies
Non-Terraform support / integrationKubernetes manifests, and can run as an operator in a Kubernetes clusterKubernetes manifests, and can integrate with Prisma Cloud for external security policiesNone
Familiarity & maturityWell-known, widely used, CNCF projectOpen source, well-known, supported by Prisma CloudBeta version, in early stages, supported by Google

While OPA is a well-known CNCF project, we found it challenging to get started with. It requires engineers to learn Rego and build supporting tools, and it doesn’t provide prebuilt policies out of the box. Google Policy Validator, on the other hand, is backed by Google and integrates with Google Cloud resources; however, it was still an early-stage product at the time. It also required additional permissions that added unnecessary complexity to our pipelines.

After our evaluation, Checkov stood out as the best fit. Our security team already uses Prisma Cloud, which Checkov can integrate with in the future. It also comes with many built-in policies, so we didn’t have to start from scratch. Additionally, Checkov allows us to write custom policies in YAML or Python, languages that engineers are already familiar with. YAML works well for simple rules, while Python handles the more complex cases, making our setup both simple and powerful.

How did we implement Checkov?

Getting started

Checkov can run anywhere; we can install it locally as a CLI tool and use its Docker image to run in a CI/CD pipeline. Refer to the guide for installation methods to try it out.

By default, Checkov uses the standard policies, which are usually the default CIS benchmarks. We disabled these rules to avoid being overwhelmed by errors in the pipeline and started with our custom policies first. Checkov will fetch the relevant policies depending on the cloud provider; for example, on Google Cloud, you can see the default policies in the link.

Checkov has global settings that can be set using CLI arguments and a configuration file. Any argument that can be found with the checkov --help command can also be set in the configuration file.

Below is an example of the configuration file, which instructs Checkov to softly fail on policies that have low or medium severity and read the external checks from the specified locations:

/.checkov.yaml:

soft-fail-on:
- LOW
- MEDIUM

external-checks-dir:
- yml-checks/plan

Custom policies

YAML vs Python

We can write custom policies in either YAML or Python. YAML works well for simple policies, while Python gives us the flexibility to handle more complex cases.

Scanning Terraform plan

Checkov can scan the Terraform plan file; it is ideal to run it before applying the plan to ensure the changes don’t affect our infrastructure.

Example for YAML policy for Terraform plan that checks for open firewall rules:

---
metadata:
  id: "RM_NET_001"
  name: "No open firewall rules to or from 0.0.0.0/0 (ingress or egress)"
  category: "NETWORKING"
  severity: "HIGH"
  guideline: "https://guide-url>"
  description: "Blocks open firewall rules to or from 0.0.0.0/0."
definition:
  or:
    - and:
        - cond_type: attribute
          resource_types:
            - google_compute_firewall
          attribute: allow
          operator: is_empty
        - cond_type: attribute
          resource_types:
            - google_compute_firewall
          attribute: deny
          operator: not_is_empty
    - and:
        - cond_type: attribute
          resource_types:
            - google_compute_firewall
          attribute: source_ranges
          operator: not_contains
          value: 0.0.0.0/0
        - cond_type: attribute
          resource_types:
            - google_compute_firewall
          attribute: destination_ranges
          operator: not_contains
          value: 0.0.0.0/0

Scanning Terraform code

Checkov can also scan our Terraform code to ensure that it follows our standards.

Example of a YAML policy for Terraform code that prevents using local-exec and remote-exec:

---
metadata:
  id: "RM_TF_002"
  name: "Disallow use of local-exec and remote-exec provisioners"
  category: "Supply Chain"
  severity: "HIGH"
  guideline: "https://guide-url>"
  description: "Blocks use of local-exec and remote-exec provisioners in Terraform code to prevent insecure or non-idempotent actions."
definition:
  cond_type: "attribute"
  resource_types:
    - "*"
  attribute: "provisioner"
  operator: "not_within"
  value:
    - "local-exec"
    - "remote-exec"

Policy as Code repository

To manage our policies, we created a centralised repository that stores all policies in one place. Our Cloud Foundation team owns this repository and is responsible for defining and maintaining the policies as code.

Below is the policy-as-code repository structure:

.
├── code.checkov.yaml	# Checkov Config file for code scan job
├── plan.checkov.yaml	# Checkov Config file for plan scan job
│
├── py-checks	# policies define using Python
│   ├── code    # policies that scan TF code
│   │   └── __init__.py
│   │   └── RM_TF_002.py
│   └── plan    # policies that scan TF plan
│       └── __init__.py
├── yml-checks	# policies define using YAML 
│    ├── code	# policies that scan TF code
│    │   └── RM_TF_001.yaml
│    └── plan	# policies that scan TF plan
│        ├── iam
│        │   ├── RM_IAM_001.yaml
│        │   └── ...............
│        └── networking
│        │    └── RM_NET_001.yaml
│        │    └── ............... 
│        │.............................   
├── tests	# all test cases for each policy 
│   ├── code	# test cases for the code policies
│   │   └── modules
│   │       ├── fail   # test a fail scenarios
│   │       │   └── RM_TF_001.tf.   # test the RM_TF_001 pocliy 
│   │       └── pass   # test a pass scenarios 
│   │           ├── RM_TF_001.tf    # test the RM_TF_001 pocliy
│   │    .............................  
│   │
│   └── plan    # test cases for the plan policies
│        .............................   

Testing

Changing the Policy as Code repository is risky, as every update can affect all our Terraform pipelines. To avoid breaking changes slipping through, we need to test new or updated policies before they are rolled out. Checkov doesn’t come with a built-in testing framework, so we had to create something ourselves.

Our solution is a simple test setup. We maintain two folders: one containing Terraform examples that should pass, and another with examples that should fail. A test script runs `terraform plan` on these files and then executes all Checkov policies against them. The results are automatically verified so that files in the “pass” folder succeed and files in the “fail” folder fail. This provides a safety net that catches mistakes early and prevents broken policies from impacting engineers’ workflows.

Checkov pipeline

We configured our GitLab pipelines to run Checkov scans on Terraform changes. When the infrastructure repositories that hold the Terraform code are updated, the pipeline triggers a Checkov job. This job clones the Policy-as-Code repository, which contains all our policies and configurations, and scans both the Terraform code and plans. Engineers get fast feedback, helping maintain standards before any changes are merged.

What’s next?

Our Checkov setup was built with scalability in mind, and this first iteration scans both Terraform code and Terraform plans. We’ll continue to expand it by adding more policies as our needs grow.

  • Looking ahead, we plan to explore integration between Checkov and Prisma Cloud. This will enable us to integrate policies defined by the security team in Prisma and enforce them directly in our pipelines, providing engineers with early feedback while ensuring alignment across teams.
  • We’re also considering enabling Checkov for Kubernetes. This would allow us to validate Kubernetes manifests against policies before they are applied, providing more control and helping protect our clusters from misconfigurations.

More broadly, we see Policy as Code as a key capability for the future of our platform. It helps us enforce security practices, ensures that standards are consistently followed, and maintains the compliance and safety of our infrastructure. At the same time, it gives engineers confidence to contribute without the risk of accidentally breaking something.

Cloud
Hussein Karamanhee author photo

Post written by

Senior Platform Engineer in the Cloud Foundation team at Rightmove

View all posts by Hussein Karamanhee

Discover more from Rightmove Tech Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading