Building DevSecOps solutions using AWS, Terraform and Kubernetes

CircleCI OPA Policies

  • 17th November 2024

Introduction

An increasing and often overlooked risk posed to organisations is the “Insider Threat

These can take two forms:

  • Malicious Insiders
  • Unintentional Insiders

When it comes to pipelines, a common mistake is to give every developer full access to edit pipeline yml definitions. So that could be your .gitlab-ci.yml, .github/workflows/*.yml or .circle/config.yml file depending on your CICD tool.

This could potentially lead to data theft, sabotage, espionage, fraud or even just accidental damage.

During this short article we will explore the high level ideas behind using OPA (Open Policy Agent) policies within CircleCI to help mitigate the risk of insider threats.

Prerequisites

  • Access to the Scale Plan on CircleCI - Unfortunately this is an enterprise only feature currently!

OPA Configuration Policies

CircleCI’s config policies feature lets you create OPA (Open Policy Agent) policies to govern project configurations.

OPA provides a high-level declarative language that lets you specify policy as code. These allow you to enforce fine-grained controls against the .circle/config.yml file.

Example CircleCI Config

For the sake of this example, here is an example of a very bare bones .circle/config.yml file:

version: 2.1
jobs:
  build:
    docker:
      - image: alpine:3.15
    steps:
      - run:
          name: The First Step
          command: |
            echo 'Hello World!'
            echo 'This is the delivery pipeline'

Note the version number, currently it would be possible for a developer to switch this out for any value they like. What if an old version was unsecure?

We need a way to stop developers from intentionally or unintentionally changing key parts of the config file without blocking them from doing their day job.

Example OPA Policy

This is where OPA policies come into play. To give you a better idea on how OPA policies work, here is a basic example, ./config-policies/check-version.rego:

package org

policy_name["check_version_is_set"]

# enable_hard will block any pipeline from running that fails this step
enable_hard["check_version"]

# define the version check
check_version = reason {
    not input.version
    reason := "version must be defined"
} {
    not input.version >= 2.1
    reason := sprintf("version must be at least 2.1 but got %v", [input.version])
}

This is a simple check, but would enforce that the company will only allow pipelines to run if they are using the correct version number.

These customizable, code-based rules could also be written to:

  • Restrict access to production environments
  • Require specific approval jobs
  • Limit the use of untrusted tools or processes
  • Restrict SSH access to production pipelines
  • Restrict unauthorised custom bash scripts from runnning

Infact, this same idea can be taken further to validate nearly any facet of our .circle/config.yml file.

Example Test Case

If you’re going to move fast with the development of OPA policies it is integral that you develop these using a test driven development approach.

CircleCI has an excellent wrapper around OPA policies to allow these to be easily tested.

Here is an shortened example CircleCI provide to show tests working, ./config-policies/check-version-test.yml:

# Top level tests must have keys prefixed with `test_`
test_version_check:

  # The default values our tests are built on
  input:
    version: 2.1
  decision: &root
    status: PASS
    enabled_rules: [check_version]

  # The overrides for each specific test, along with the decision we expect to see
  cases:
    absent_version:
      input:
        version: null
      decision:
        <<: *root
        status: HARD_FAIL
        hard_failures:
          - rule: check_version
            reason: version must be defined

    inferior_version:
      input:
        version: 1.0
      decision:
        <<: *root
        status: HARD_FAIL
        hard_failures:
          - rule: check_version
            reason: version must be at least 2.1 but got 1

This can be as simple as running this locally:

circleci policy test ./config-policies --verbose

Using TDD lets us move fast, test locally and help confirm our policies will work as intended before deploying them to CircleCI.

Getting Started

Before you get started, please refer to the CircleCI documentation. This is an in-depth topic that can not be condensed into a single blog post unfortunately.

Once you have exhausted the CircleCI documentation, then checkout the Open Policy Agent site. They have tonnes of examples to help you progress further.

Summary

If insider threats are a risk to your business, then OPA policies can be an essential control for your pipelines.

A key tip is to use test driven development to make your life a lot easier implementing these restrictions.

OPA policies are not limited to CircleCI configuration files, and can also be used to protect all sorts of configuration files including in Kubernets and Envoy.

Rhuaridh

Please get in touch through my socials if you would like to ask any questions - I am always happy to speak tech!