Skip to content

OPA / Rego

Sluice can decide with Rego modules. Open Policy Agent (OPA) runs inside Sluice: you need no OPA server.

Example

Goal: analysts read the shop catalog, but only the rows of their tenant.

policies:
  # Data sources and subject bindings still come from this folder.
  directory: "./policies.d"
  engine: opa
  opa:
    moduleDir: "./rego"
    query: "data.sluice.main"

With engine: opa, Sluice evaluates no YAML policy. To combine both, see Policy engines.

package sluice

import rego.v1

default main := {
    "allow": false,
    "deny_reason": {"message": "only analysts read the shop catalog"},
}

# Analysts read the shop catalog. Each table gets a filter on the tenant of the subject.
main := {
    "allow": true,
    "row_filters": [tenant_filter(t.key) | some t in input.tables],
} if {
    "analysts" in input.subject.groups
    every t in input.tables {
        t.catalog == "shop"
    }
}

tenant_filter(table) := {
    "table": table,
    "predicate": {
        "column": "tenant_id",
        "op": "Equals",
        "value": input.subject.claims.tenantId,
    },
}

An analyst of the tenant acme reads the orders.

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_acme.supersecret" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, tenant_id, amount FROM shop.main.orders ORDER BY id"}'
{
  "query_id": "<query-id>",
  "columns": ["id", "tenant_id", "amount"],
  "rows": [
    [1, "acme", 120],
    [2, "acme", 80]
  ],
  "row_count": 2,
  "truncated": false
}

A guest sends the same query.

curl -s http://localhost:8080/v1/query \
  -H "X-Api-Key: sl_demo_guest.supersecret" \
  -H "Content-Type: application/json" \
  -d '{"sql": "SELECT id, tenant_id, amount FROM shop.main.orders ORDER BY id"}'
{
  "code": "ACL_DENIED",
  "message": "only analysts read the shop catalog",
  "query_id": "<query-id>"
}

What happened: Sluice evaluated data.sluice.main for each query. For the analyst, the module returned allow and a row filter. For the guest, the default applied.

Fields

input key Content
subject id, issuer, email, groups, auth_method, claims
action Statement kind, for example SELECT
tables[] catalog, schema, table, and key as catalog.schema.table
shape has_select_star, is_aggregate, has_cte, has_union, has_limit, limit, has_where, joins
request remote_ip, user_agent, and headers with names in lower case
Result field Type Default Meaning
allow boolean false true allows. false denies.
abstain boolean false true: the other composite members decide.
deny_reason object message, code, policy for the error.
row_filters[] list table (a key from the input), predicate as in a row filter, combine.
column_masks[] list table, column, and mask as in a column mask.
rejections[] list rule, message, code. One entry rejects the query.

An undefined result denies the query. The header X-Sluice-Applied-Policies and the audit record show the name opa.

Limits

  • policies.opa.moduleDir is required. Sluice loads the .rego files at its top level and does not start without one.
  • A module that does not compile fails the reload with ERR_POLICY_INVALID. The old modules and policies stay active.
  • An unknown output field, a bad predicate or mask, or a table outside the input gives 503 ERR_POLICY_UNAVAILABLE.
  • An unknown code becomes ACL_DENIED or ACL_REJECTED.

See also