Early release. The repo is not public and has no licence yet.

Explainable controls that run on Open Policy Agent and Rego.

ergo is an open source language for defining explainable controls. Instead of a binary result, every decision returns an audit report of what was checked, and why it passed or failed.

opa eval ... data.deploy.report
subjectcheckvalue readpassedcause
$well_formedcount(checks) = 1truesatisfied
$min_subjectscount(matching) = 2truesatisfied
d-1$appliesenvironment = "prod"truesatisfied
d-2$appliesenvironment = "prod"truesatisfied
d-3$appliesenvironment = "staging"falsevalue
d-1approvedapproved_by = "alice"truesatisfied
d-2approvedapproved_by = nullfalseabsent
compliant: false d-3 is out of scope. d-2 was never approved... and the report says so.

A decision nobody can inspect
isn't a control.

With Rego you can block a deployment, but you can't show later why. When you define controls with ergo you have records of why decisions were made.

In regulated settings, someone else has to be able to check that a control behaved as it should. A yes or no can't be validated. You don't just tell the auditor the decision. You hand over the workings.

Rego provides decisions

{
  "allow": false
}

Useful. But why was it false?

With ergo you get explanations

subject  check      value read               passed  cause
d-1      approved   approved_by = "alice"    true    satisfied
d-2      approved   approved_by = null       false   absent
d-3      $applies   environment = "staging"  false   value (out of scope, recorded)

Same decision. Now you can see why.

Write the control.
Not the sorcery.

# policy/requirements.yaml
requirements:
  prod_deploy:
    subject_type: deployment
    from: [deployments]
    id: [id]
    applies_to:
      is_prod:
        op: equals
        path: [environment]
        value: prod
    checks:
      approved:
        description: Someone approved the deployment
        op: non_empty_string
        path: [approved_by]
# policy/deploy.rego
package deploy

import data.ergo

report := ergo.report(input, data.requirements)
# policy/deploy.rego
package deploy

import data.ergo

requirements := {"prod_deploy": {
	"subject_type": "deployment",
	"from": ["deployments"],
	"id": ["id"],
	"applies_to": {"is_prod": {
		"op": "equals",
		"path": ["environment"],
		"value": "prod",
	}},
	"checks": {"approved": {
		"description": "Someone approved the deployment",
		"op": "non_empty_string",
		"path": ["approved_by"],
	}},
}}

report := ergo.report(input, requirements)

Defined in YAML or Rego

Write the requirement as a YAML file or as a Rego object. ergo reads both the same way and gives the same report.

  1. 1

    Define the subjects

    subject_type, from and id say what the subjects are and where to find them.

  2. 2

    Filter the scope

    applies_to keeps only the subjects this control is about. The rest are recorded, not dropped.

  3. 3

    Test the values

    checks are the rules each subject must pass. ergo does the looping, the evaluation and the report.

One report shape.
Every control.

A deployment control. A code-review control. A vulnerability control. Different policy, same evidence: one row per subject and check. Passing and failing rows have the same fields.

  1. requirement

    Which control produced the row.

  2. subject

    The thing that was judged, with its type and id.

  3. check

    The rule that ran.

  4. inputs

    The values the check actually read.

  5. passed

    The result. Always true or false.

  6. cause

    Why it reached that result.

Same policy + same input = the same report, byte for byte. Hash it. Diff it. Store it.

Failure isn't one state.

A missing field, a field set to null and a selector that matched nothing all read as null. They're different problems with different fixes. The cause tells them apart.

  • satisfied

    The check passed.

  • substituted

    The check failed, but its substitute passed.

  • ambiguous

    A selector matched more than one item.

  • unmatched

    A selector matched nothing, although the list was there.

  • absent

    A field the check reads isn't there.

  • null

    The field is there, but null.

  • value

    Everything was read fine. The values just don't pass.

approved_by = null
cause: absent

The evidence was never recorded.

approved_by = ""
cause: value

The evidence exists, and it says no.

Different problem. Different owner. Different fix.

  • Nothing disappears.
  • Nothing is implied.
  • Nothing is decorative.
  • Everything has a reason.
  • Everything has a state.
  • ergo.

You didn't write the checks that start with $. ergo adds them, so that whenever a requirement isn't met, at least one row explains why.

$well_formed
The requirement itself makes sense: it has at least one check and a valid require.
$min_subjects
At least one subject was found. A typo in from fails, instead of passing with nothing checked.
$applies
Whether each subject was in scope. Out-of-scope subjects stay in the report, with the reason.

Rego underneath.
ergo on top.

ergo doesn't replace OPA. It's a single Rego file you copy into your policies. The control is expressed as data, and ergo provides one evaluation and reporting model for all of them.

  1. inputdeployments.json
  2. controlrequirements
  3. libraryergo.rego
  4. engineOPA 1.19
  5. evidencereport
  6. compliant

Readable by machines.
Readable by people.

The vocabulary is small on purpose. Every check gets a plain-language expression, like approved_by is a non-empty string, rendered from the requirement itself. Controls are data, so they can be validated, diffed and tested like code.

Every field, operator and cause

One file. No lock-in.

  1. 01

    Copy the library

    Copy ergo.rego next to your policies. That's the install.

    policy/
      ergo.rego
  2. 02

    Write a requirement

    Say what you're judging, what's in scope and what must hold.

    report := ergo.report(input, data.requirements)
  3. 03

    Read the report

    Ask OPA for the report, or just the violations.

    opa eval -d policy -i deployments.json \
      'data.deploy.report'

ergo /ˈɜː.ɡəʊ/

Therefore. As a result. A conclusion that comes with its premises.

Not computer says no. Here is the decision, and here is what produced it.

Show the working.