Interactively Debugging the Rego Policy Language with Fregot

Fugue performs more than 100 million policy validations a day in order to identify compliance violations for cloud infrastructure environments at scale. These policy-as-code validations are written in Rego, the policy language for the Open Policy Agent (OPA) engine. To enhance the process of writing and debugging Rego policies, we recently open-sourced fregot, the Fugue Rego Toolkit. You can think of fregot as an alternative to OPA's built-in interpreter -- the REPL allows you interactively debug Rego code with easy-to-understand error messages, and you can evaluate expressions and test policies. Read more about it in our blog post here.

 

This tutorial is an abbreviated version of the full walkthrough in fregot's GitHub repo. It shows how to use fregot to debug a Rego policy that checks whether AWS EC2 instances in a Terraform plan use AMIs from an approved list.

 

Prerequisites

  1. Clone the repo: git clone https://github.com/fugue/fregot.git
  2. Move to the demo directory: cd fregot/examples/demo
  3. Install fregot

 

Optional Steps

If you'd like to generate the Terraform plan JSON yourself:

  1. Install Terraform v0.12 or later
  2. Optional: Install jq

 

Steps

Generate Terraform Plan as JSON

Let's say your organization requires Amazon Web Services EC2 instances to only use hardened Linux Amazon Machine Images (AMIs) that are on a whitelist. Your boss wants to prevent any Terraform with a non-blessed AMI ID from being deployed, so you've installed fregot and have written a Rego policy to validate the Terraform plan before it is applied. You'll be working with these files:

 

demo.rego is a Rego policy that checks AWS AMI IDs in a Terraform plan against a whitelist. The policy contains an error that we'll debug in this tutorial.

 

demo.tf is a Terraform file that will deploy two EC2 instances. If you take a look, you'll see that ami-0b69ea66ff7391e80 is listed in approved_amis in the policy demo.rego, and ami-atotallyfakeamiid is (unsurprisingly) not.

 

repl_demo_input.json is the Terraform plan formatted as JSON so fregot can evaluate it. We've done this for you, but if you've installed Terraform v0.12 or later and you'd like to generate the output yourself, you can do so with the following commands:

 

Initialize Terraform directory:

terraform init

Create Terraform plan:

terraform plan -out=tfplan

Generate JSON representation of plan and pretty-print it with jq:

terraform show -json tfplan | jq . > repl_demo_input.json

 

Evaluate Terraform Plan with Fregot

Let's start by validating the Terraform plan JSON against the Rego policy. We'll use fregot eval to specify the input file (repl_demo_input.json), the function we want to evaluate (data.fregot.examples.demo.deny), and the Rego file it is in (demo.rego):

fregot eval --input \
repl_demo_input.json \
'data.fregot.examples.demo.deny' demo.rego

 

But wait, what's this...

 

Oh No, an Error!

Uh oh! There's an error in the Rego file. Evaluating deny produces this message:

fregot (eval error):
"demo.rego" (line 10, column 11):
index type error:

10| ami = input.resource_changes.change.after.ami
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

evalRefArg: cannot index array with a string

Stack trace:
rule fregot.examples.demo.amis at demo.rego:21:11
rule fregot.examples.demo.deny at cli:1:1

 

Well, it's a good thing we just installed fregot! Let's use the REPL to interactively debug the code.

 

Launch REPL

We'll start by launching the REPL:

fregot repl demo.rego --watch

 

The --watch flag tells fregot to automatically reload the loaded files (including input) when it detects a change.

 

(Handy, right? When used in conjunction with the :watch command, fregot monitors an expression and prints an updated evaluation whenever the policy and/or input files change! See the full walkthrough on GitHub for details.)

 

Load Policy and Input

First, load the policy:

:load demo.rego

 

Next, set the input:

:input repl_demo_input.json

 

We're going to take a closer look at data.fregot.examples.demo.deny. Let's set a breakpoint so we can investigate.

 

Set and Activate Breakpoint

To set the breakpoint at deny, you can use the :break command with the rule name. Since the policy has already been loaded in the REPL, rather than using the full name data.fregot.examples.demo.deny, you can simplify it like so:

:break deny

 

We set the breakpoint with the rule name here, but we could also have used the line number: :break demo.rego:20

Now, evaluate the rule to activate the breakpoint:

deny

 

You'll see this output:

21|     ami = amis[ami]
^^^^^^^^^^^^^^^

 

 

Great! We've entered debugging mode and fregot is showing us the first line of the rule body. Notice how the prompt shows the word debug now:

fregot.examples.demo(debug)%

 

Step Forward

Nothing seems out of order yet, so step forward into the next query with the :step command:

:step

 

You'll see this output:

10|     ami = input.resource_changes.change.after.ami
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

 

 

So far, so good. Step forward again:

:step

 

Look, there's the error message we saw earlier!

(debug) error
fregot (eval error):
"demo.rego" (line 10, column 11):
index type error:

10| ami = input.resource_changes.change.after.ami
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

evalRefArg: cannot index array with a string

Stack trace:
rule fregot.examples.demo.amis at demo.rego:21:11
rule fregot.examples.demo.deny at deny:1:1

 

 

Error Mode

fregot automatically puts you into error mode, indicated by the REPL prompt:

fregot.examples.demo(error)%

 

Let's look at the error message closely. Something is wrong with the input, since line 10 is where we assign the AMI ID in the input to the variable ami:

    10|     ami = input.resource_changes.change.after.ami
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

 

 

Consider the error reason:

evalRefArg: cannot index array with a string

 

In this case, that means the policy is referencing an array incorrectly. There's something wrong with this syntax:

input.resource_changes.change.after.ami

 

We're dealing with nested documents here, so let's start with the entire input document and narrow down level by level until we get just the part we want, ami, which is the AMI ID for each AMI in the input.

 

Evaluate Expressions

We can see the entire input document by evaluating input:

input

 

We get the expected result, which is the information in repl_demo_input.json. No problems yet, so evaluate the next nested level:

input.resource_changes

 

There's a lot less JSON now. Everything seems OK so far -- no errors. Narrow it down again:

input.resource_changes.change

 

Looks like we found the problem! You should see this error message:

fregot (eval error):
"input.resource_changes.change" (line 1, column 1):
index type error:

1| input.resource_changes.change
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

evalRefArg: cannot index array with a string

Stack trace:
rule fregot.examples.demo.amis at demo.rego:21:11
rule fregot.examples.demo.deny at deny:1:1

 

 

Diagnose Error

If you look at repl_demo_input.json, you'll see the JSON follows this basic structure:

{
"resource_changes": [
{
"change": {
"after": {
"ami": "ami-0b69ea66ff7391e80"
},
}
},
{
"change": {
"after": {
"ami": "ami-atotallyfakeamiid"
}
}
}
]
}

 

 

As you can see, resource_changes is an array with two items, and we should account for this when we assign a value to the ami variable. We need to iterate through resource_changes so we can assign each change.after.ami value to the ami variable. We can do this with [_], so let's add that to the previous expression and evaluate it again:

input.resource_changes[_].change

 

Success! You should see the two items in the array. Let's narrow it all the way down and confirm we get just the two AMI IDs:

input.resource_changes[_].change.after.ami

 

The output looks great:

= "ami-0b69ea66ff7391e80"
= "ami-atotallyfakeamiid"

 

 

Fix Error in Policy

Now that we know how to fix the policy, let's add [_] to line 10 of demo.rego. It should look like this now:

ami = input.resource_changes[_].change.after.ami

 

Save your changes and go back to fregot -- you'll see that the updated policy has automatically been reloaded:

Reloaded demo.rego

 

(Note: If you didn't launch the REPL with --watch, you can manually reload all modified files with :reload.)

 

You're still in error mode, so quit error mode to return to normal REPL mode:

:quit

 

Then quit the REPL so we can test out the policy:

 

:quit

Now we can evaluate the policy as we did before -- this time, it should work properly:

fregot eval --input \
repl_demo_input.json \
'data.fregot.examples.demo.deny' demo.rego

 

 

And it does!

[true]

 

deny returns true, which means we've successfully tested the Terraform plan against the Rego policy and determined that the plan fails validation.

 

Now that you've solved the code error, you may use the same policy to evaluate your own JSON Terraform plans. Output the plan as JSON and evaluate it with fregot and the demo.rego policy, then execute the same fregot eval command from earlier, substituting your_input_file_here for your own input:

fregot eval --input \
your_input_file_here \
'data.fregot.examples.demo.deny' demo.rego

 

 

If the AMIs in the Terraform plan are whitelisted and the plan passes validation, you'll see output like this because deny returns no results -- concretely meaning that the request will not be denied:

 

[]

 

If the AMI IDs in the plan aren't whitelisted, the output looks like this because deny returns true, as you saw earlier:

 

[true]

What's Next?

This is an abbreviated version of the full walkthrough in fregot's GitHub repo. To learn how to use the :watch command to automatically print the updated value of deny whenever the policy or input changes, and to see a demo of the :next command, check out the walkthrough.

 

And if you want to try your hand at debugging on your own, you'll find an alternative version of this policy at fregot/examples/ami_id/, where you can introduce an error and debug with fregot to your heart's content.

 

Fregot is still in active development, so if you encounter any issues, please file a report. For more information about fregot, see the README on GitHub.

Secure Your Cloud

Find Security and compliance violations in your cloud infrastructure and ensure they never happen again.