Start Building for Free
CircleCI.comAcademyBlogCommunitySupport

Config policies overview - Open preview

Cloud
On This Page

Use config policies to create organization-level policies to impose rules and scopes to govern which configuration elements are required, allowed, not allowed etc.

Introduction

Config policies allow you to create policies for governing CircleCI project configurations. In its strictest case, if a project configuration does not comply with the rules set out in the associated policy, that project’s pipelines cannot be triggered until it does comply.

CircleCI uses config.yml files to define CI/CD pipelines at the project level. This is a convenient way of developing and iterating quickly, as each pipeline can be made to meet the needs of the project as it grows. However, it can be difficult to manage and enforce organization-wide conventions and security policies. Config policies adds in this layer of control.

Config policy decisions are stored, and can be audited. This provides useful data about the pipeline definitions being run within your organization.

Quickstart

If you already have a CircleCI account connected to your VCS, and you would like to get started with config policies right away, check out the steps in the Create a policy how-to guide.

How config policies work

Config policies use a decision engine based on Open Policy Agent (OPA). Policies are written in the Rego query language, as defined by OPA. Rule violations are surfaced when pipeline triggers are applied.

Policies can be developed locally and pushed to CircleCI using the CircleCI CLI. Policies can be saved to a repository within your VCS. For more information see the Create and manage config policies page.

CircleCI uses the result of OPA output to generate decisions from policy executions. Those decisions have the following outputs:

status:           PASS | SOFT_FAIL | HARD_FAIL | ERROR
reason:           string                                      (optional, only present when status is ERROR)
enabled_rules:    Array<string>
soft_failures:    Array<{ rule: string, reason: string }>
hard_failures:    Array<{ rule: string, reason: string }>

The decision result tracks which rules were included in the making of the decision, and any violations, both soft and hard, that were found.

Use the CLI with config policies

Use the CircleCI CLI to manage your organization’s policies programmatically. Config policies sub-commands are grouped under the circleci policy command.

The following config policies sub-commands are currently supported within the CLI:

  • diff - shows the difference between local and remote policy bundles

  • fetch - fetches the policy bundle (or one policy, based on name) from remote

  • push - pushes the policy bundle (activate policy bundle)

    For example, running the following command activates a policy bundle stored at ./policy_bundle_dir_path:

    circleci policy push ./policy_bundle_dir_path --owner-id <your-organization-ID>
  • settings - used to modify config policies settings as required.

    When the settings sub-command is called without any flags, current settings are fetched and printed on console.

    circleci policy settings --owner-id <your-organization-ID>

    Example output:

    {
      "enabled": false
    }
  • test - used to run tests on your policies.

    For example, running the following command runs all defined tests in the policies directory:

    circleci policy test ./policies

    Example output:

    ok    policies    0.001s
    
    3/3 tests passed (0.001s)

Writing Rego policies using CircleCI domain-specific language

Policies are written in Rego, a purpose-built declarative policy language that supports OPA. You can find more about Rego in the rego language docs.

In order for CircleCI to make decisions about your configs, it needs to be able to interpret the output generated when your policies are evaluated. Therefore, policies must be written to meet the CircleCI specifications detailed on this page.

Package and name

All policies must belong to the org package and declare the policy name as the first rule. All policy Rego files should include:

package org

policy_name["unique_policy_name"]

The policy_name is an alphanumeric string with a maximum length of 80 characters. It is used to uniquely identify the policy by name within the system (similar to a Kubernetes named resource). Policy names must be unique. Two policies cannot have the same name within an organization.

The policy_name must be declared using a partial rule and declare the name as a rego key: policy_name["NAME"].

Rules

After declaring the org package and policy_name rule, policies can then be defined as a list of rules. Each rule is composed of three parts:

  • Evaluation - Evaluates whether the config contains the policy violation.

  • Enforcement status - Determines how a violation should be enforced.

  • Enablement - Determines if a policy violation should be enabled.

Using this format allows policy writers to create custom helper functions without impacting CircleCI’s ability to parse policy evaluation output. You can create your own helper functions, but also CircleCI provides a set of helpers by importing data.circleci.config in your policies. For more information, see the Config policy reference.

Policies all have access to config data through the input variable. The input is the project config being evaluated. Since the input matches the CircleCI config, you can write rules to enforce a desired state on any available config element, for example, jobs or workflows.

input.workflows     # an array of nested structures mirroring workflows in the CircleCI config
input.jobs          # an array of nested structures mirroring jobs in the CircleCI config

Define a rule

In OPA, rules can produce any type of output. At CircleCI, rules that produce violations must have outputs of the following types:

  • String

  • String array

  • Map of string to string

This is because rule violations must produce error messages that individual contributors and SecOps can act upon. Helper rules that produce differently typed outputs can still be defined, but rules that will be considered when making CircleCI decisions must have the output types specified above. For more information see the Enablement section below.

Evaluation

This is how the decision engine determines if a config violates the given policy. The evaluation defines the name and ID of the rule, checks a condition, and returns a user-friendly string describing the violation. Rule evaluations include the rule name and an optional rule ID. The rule name will be used to enable and set the enforcement level for a rule.

RULE_NAME = reason {
  ... # some comparison
  reason := "..."
}
RULE_NAME[RULE_ID] = reason {
  ... # some comparison
  reason := "..."
}

Here is an example of a simple evaluation that checks that a config includes at least one workflow:

contains_workflows = reason {
    count(input.workflows) > 0
    reason := "config must contain at least one workflow"
}

The rule ID can be used to differentiate between multiple violations of the same rule. For example, if a config uses multiple unofficial Docker images, this might lead to multiple violations of a use_official_docker_image rule. Rule IDs should only be used when multiple violations are expected. In some cases, the customer may only need to know if a rule passes or not. In this case, the rule will not need a rule ID.

use_official_docker_image[image] = reason {
  some image in docker_images   # docker_images are parsed below
  not startswith(image, "circleci")
  not startswith(image, "cimg")
  reason := sprintf("%s is not an approved Docker image", [image])
}

# helper to parse docker images from the config
docker_images := {image | walk(input, [path, value])  # walk the entire config tree
                          path[_] == "docker"         # find any settings that match 'docker'
                          image := value[_].image}    # grab the images from that section
Enforcement

The policy service allows rules to be enforced at different levels.

ENFORCEMENT_STATUS["RULE_NAME"]

The two available enforcement levels are:

  • hard_fail - If the policy-service detects that the config violated a rule set as hard_fail, the pipeline will not be triggered.

  • soft_fail - If the policy-service detects that the config violated a rule set as soft_fail, the pipeline will be triggered and the violation will be logged in the policy-service decision log.

An example of setting the use_official_docker_image rule to hard_fail:

hard_fail["use_official_docker_image"]
Enablement

A rule must be enabled for it to be inspected for policy violations. Rules that are not enabled do not need to match CircleCI violation output formats, and can be used as helpers for other rules.

enable_rule["RULE_NAME"]

To enable a rule, add the rule as a key in the enable_rule object. For example, to enable the rule use_official_docker_image, use the following:

enable_rule["use_official_docker_image"]

Using pipeline metadata

When writing policies for circleci config, it is often desirable to have policies that vary slightly in behaviour by project or branch. This is possible using the data.meta Rego property.

When a policy is evaluated in the context of a triggered pipeline the following properties will be available on data.meta:

  • project_id (CircleCI Project UUID)

  • build_number (number)

  • vcs.branch (string)

  • vcs.release_tag (string)

  • vcs.origin_repository_url (string) - URL to the repository where the commit was made (this will only be different in the case of a forked pull request)

  • vcs.target_repository_url (string) - URL to the repository building the commit

This metadata can be used to activate/deactivate rules, modify enforcement statuses, and be part of the rule definitions themselves.

The following is an example of a policy that only runs its rule for a single project and enforces it as hard_fail only on branch main.

package org

policy_name["example"]

# specific project UUID
# use care to avoid naming collisions as assignments are global across the entire policy bundle
sample_project_id := "c2af7012-076a-11ed-84e6-f7fa45ad0fd1"

# this rule is enabled only if the body evaluates to true
enable_rule["custom_rule"] { data.meta.project_id == sample_project_id }

# "custom_rule" evaluates to a hard_failure condition only if run in the context of branch main
hard_fail["custom_rule"] { data.meta.vcs.branch == "main" }

The following is an example of a policy that blocks pull request builds from untrusted origins.

package org

import future.keywords

policy_name["forked_pull_requests"]

# this rule is enabled only if the body evaluates to true (origin_repository_url and target_repository_url will be different in case of a forked pull request)
enable_rule["check_forked_builds"] {
	data.meta.vcs.origin_repository_url != data.meta.vcs.target_repository_url
}

# enable hard failure
hard_fail["check_forked_builds"]

check_forked_builds = reason {
	not from_trusted_origin(data.meta.vcs.origin_repository_url)
	reason := sprintf("pipeline triggered from untrusted origin: %s", [data.meta.vcs.origin_repository_url])
}

from_trusted_origin(origin) {
	some trusted_origin in {
		"https://github.com/trusted_org/",
		"https://bitbucket.org/trusted_org/",
	}

	startswith(origin, trusted_origin)
}

Testing policies

It is important to be able to deploy new policies with confidence, knowing how they will be applied, and the decisions they will generate ahead of time. To enable this process, the circleci policy test command is available. The test subcommand is inspired by the golang and opa test commands. For more information on setting up testing, see the Test config policies guide.

Example policy

The following is an example of a complete policy with one rule, use_official_docker_image, which checks that all Docker images in a config are prefixed by circleci or cimg. It uses some helper code to find all the docker_images in the config. It then sets the enforcement status of use_official_docker_image to hard_fail and enables the rule.

This example also imports future.keywords, for more information see the OPA docs.

package org

import future.keywords

policy_name["example"]

use_official_docker_image[image] = reason {
  some image in docker_images   # docker_images are parsed below
  not startswith(image, "circleci")
  not startswith(image, "cimg")
  reason := sprintf("%s is not an approved Docker image", [image])
}

# helper to parse docker images from the config
docker_images := {image | walk(input, [path, value])  # walk the entire config tree
                          path[_] == "docker"         # find any settings that match 'docker'
                          image := value[_].image}    # grab the images from that section

hard_fail["use_official_docker_image"]

enable_rule["use_official_docker_image"]

Help make this document better

This guide, as well as the rest of our docs, are open source and available on GitHub. We welcome your contributions.

Need support?

Our support engineers are available to help with service issues, billing, or account related questions, and can help troubleshoot build configurations. Contact our support engineers by opening a ticket.

You can also visit our support site to find support articles, community forums, and training resources.