Get started Edit

The Open Policy Agent (OPA) is an open source, general-purpose policy engine that enables unified, context-aware policy enforcement across the entire stack.

OPA provides a high-level declarative language for authoring policies and simple APIs to answer policy queries. Using OPA, you can offload policy decisions from your service such as:

  • Should this API call be allowed? E.g., true or false.
  • How much quota remains for this user? E.g., 1048.
  • Which hosts can this container be deployed on? E.g., ["host1", "host40", ..., "host329"].
  • What updates must be applied to this resource? E.g., {"labels": {"team": "products}}.

This tutorial shows how to get started with OPA using an interactive shell or REPL (read-eval-print loop).

Goals

REPLs are great for learning new languages and running quick experiments. You can use OPA’s REPL to experiment with policies and prototype new ones.

To introduce the REPL, you will use dummy data and an example policy. In English, the policy can be stated as follows:

  • Servers that open an unencrypted HTTP port must not be connected to a public network.

Inside the REPL, you will define rules that codify the policy stated above.

Once you finish this tutorial, you will be familiar with:

  • Running OPA as an interactive shell/REPL.
  • Writing ad-hoc queries in Rego.

Prerequisites

If this is your first time using OPA, download the latest executable for your system.

On macOS (64-bit):

curl -L -o opa https://github.com/open-policy-agent/opa/releases/download/v0.12.2/opa_darwin_amd64

On Linux (64-bit):

curl -L -o opa https://github.com/open-policy-agent/opa/releases/download/v0.12.2/opa_linux_amd64

Windows users can obtain the OPA executable from GitHub Releases. The steps below are the same for Windows users except the executable name will be different.

Set permissions on the OPA executable:

chmod 755 ./opa

Steps

1. Make sure you can run the REPL on your machine.

./opa run

Without any data, you can experiment with simple expressions to get the hang of it:

> true
true
> 3.14
3.14
> ["hello", "world"]
[
  "hello",
  "world"
]

You can also test simple boolean expressions:

> true == false
false
> 3.14 > 3
true
> "hello" != "goodbye"
true

Most REPLs let you define variables that you can reference later on. OPA allows you to do something similar. For example, you can define a pi constant as follows:

> pi := 3.14

Once “pi” is defined, you query for the value and write expressions in terms of it:

> pi
3.14
> pi > 3
true

In addition to running queries, the REPL also lets you define rules:

> items := ["pizza", "apples", "bread", "coffee"]
> users := {"bob": {"likes": [0, 2]}, "alice": {"likes": [1, 2, 3]}}
> likes[[name, item]] { index := users[name].likes[_]; item := items[index] }

The likes rule above defines a set of tuples specifying what each user likes.

> likes[["alice", item]] # what does alice like?
+----------+------------------------+
|   item   | likes[["alice", item]] |
+----------+------------------------+
| "apples" | ["alice","apples"]     |
| "bread"  | ["alice","bread"]      |
| "coffee" | ["alice","coffee"]     |
+----------+------------------------+
> likes[[name, "bread"]] # who likes bread?
+---------+------------------------+
|  name   | likes[[name, "bread"]] |
+---------+------------------------+
| "bob"   | ["bob","bread"]        |
| "alice" | ["alice","bread"]      |
+---------+------------------------+

When you enter expressions into the OPA REPL, you are effectively running queries. The REPL output shows the values of variables in the expression that make the query true. If there is no set of variables that would make the query true, the REPL prints undefined. If there are no variables in the query and the query evaluates successfully, then the REPL just prints true.

Quit out of the REPL by pressing Control-D or typing exit:

> exit
Exiting

2. Create a data file and a policy module.

Let’s define a bit of JSON data that will be used in the tutorial:

cat >data.json <<EOF
{
    "servers": [
        {"id": "s1", "name": "app", "protocols": ["https", "ssh"], "ports": ["p1", "p2", "p3"]},
        {"id": "s2", "name": "db", "protocols": ["mysql"], "ports": ["p3"]},
        {"id": "s3", "name": "cache", "protocols": ["memcache"], "ports": ["p3"]},
        {"id": "s4", "name": "dev", "protocols": ["http"], "ports": ["p1", "p2"]}
    ],
    "networks": [
        {"id": "n1", "public": false},
        {"id": "n2", "public": false},
        {"id": "n3", "public": true}
    ],
    "ports": [
        {"id": "p1", "networks": ["n1"]},
        {"id": "p2", "networks": ["n3"]},
        {"id": "p3", "networks": ["n2"]}
    ]
}
EOF

Also, let’s include a rule that defines a set of servers that are attached to public networks:

cat >example.rego <<EOF
package opa.example

import data.servers
import data.networks
import data.ports

public_servers[s] {
    some i, j
    s := servers[_]
    s.ports[_] == ports[i].id
    ports[i].networks[_] == networks[j].id
    networks[j].public == true
}
EOF

3. Run the REPL with the data file and policy module as input.

./opa run data.json example.rego

You can now run queries against the various documents:

> data.servers[_].id
+--------------------+
| data.servers[_].id |
+--------------------+
| "s1"               |
| "s2"               |
| "s3"               |
| "s4"               |
+--------------------+
> data.opa.example.public_servers[x]
+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+
|                                       x                                       |                      data.opa.example.public_servers[x]                       |
+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+
| {"id":"s1","name":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} | {"id":"s1","name":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} |
| {"id":"s4","name":"dev","ports":["p1","p2"],"protocols":["http"]}             | {"id":"s4","name":"dev","ports":["p1","p2"],"protocols":["http"]}             |
+-------------------------------------------------------------------------------+-------------------------------------------------------------------------------+

One powerful thing about Rego and the REPL is that you can run queries using the same syntax that you would use to lookup values.

For example if i has value 0 then data.servers[i] returns the first value in the data.servers array:

> i := 0
> data.servers[i]
{
  "id": "s1",
  "name": "app",
  "ports": [
      "p1",
      "p2",
      "p3"
  ],
  "protocols": [
      "https",
      "ssh"
  ]
}

That same expression data.servers[i] when i has no value defines a query that returns all the values of i and data.servers[i]:

> unset i
> data.servers[i]
+---+-------------------------------------------------------------------------------+
| i |                                data.servers[i]                                |
+---+-------------------------------------------------------------------------------+
| 0 | {"id":"s1","name":"app","ports":["p1","p2","p3"],"protocols":["https","ssh"]} |
| 1 | {"id":"s2","name":"db","ports":["p3"],"protocols":["mysql"]}                  |
| 2 | {"id":"s3","name":"cache","ports":["p3"],"protocols":["memcache"]}            |
| 3 | {"id":"s4","name":"dev","ports":["p1","p2"],"protocols":["http"]}             |
+---+-------------------------------------------------------------------------------+

4. Import and export documents.

The REPL also understands the Import and Package directives.

> import data.servers
> servers[i].ports[_] == "p2"; id := servers[i].id
+---+------+
| i |  id  |
+---+------+
| 0 | "s1" |
| 3 | "s4" |
+---+------+
> package opa.example
> public_servers[x].protocols[_] == "http"
+-------------------------------------------------------------------+
|                                 x                                 |
+-------------------------------------------------------------------+
| {"id":"s4","name":"dev","ports":["p1","p2"],"protocols":["http"]} |
+-------------------------------------------------------------------+

5. Define a rule to identify servers in violation of our security policy.

> import data.servers
> violations[s] {
  s := servers[_]
  s.protocols[_] == "http"
  public_servers[s]
}

> violations[server] = _
+-------------------------------------------------------------------+
|                              server                               |
+-------------------------------------------------------------------+
| {"id":"s4","name":"dev","ports":["p1","p2"],"protocols":["http"]} |
+-------------------------------------------------------------------+

The REPL accepts multi-line input and will change appearance when it detects multi-line input. You can end multi-line input by inserting a blank line.