Rego Basics
Learn the fundamentals of the Rego policy language
Rego is the policy language used by Open Policy Agent (OPA) and Denied Platform. It's a declarative language designed specifically for expressing authorization policies. This tutorial covers the fundamentals you need to write effective policies.
What is Rego?
Rego (pronounced "ray-go") is purpose-built for policy decisions. Unlike general-purpose languages, Rego:
- Is declarative — describe what should be true, not how to compute it
- Handles unknown data gracefully — missing fields don't cause errors
- Returns deterministic results — same input always produces same output
- Is designed for querying structured data — JSON-like data navigation
Basic Structure
In Denied Platform, you write rule conditions — the platform handles the OPA wrapper structure automatically. A policy is simply the conditions that must be true for the rule to match:
input.subject.properties.role == "admin"
input.action.name == "read"Each line is a condition. All conditions must be true for the rule to match (implicit AND).
How It Works
When you create a policy in Denied:
- You write the conditions (the rule body)
- The platform wraps your conditions in the proper OPA structure
- Your decision node evaluates requests against these conditions
Rules
Rules define when to allow or deny access. Simply list the conditions that must all be true:
input.subject.properties.role == "admin"This rule matches when the subject's role property is "admin".
The Input Object
Access fields with dot notation:
input.subject.properties.user_id # "user-123"
input.subject.properties.role # "editor"
input.action.name # "read"
input.resource.properties.owner # "user-789"Data Types
Strings
Strings use double quotes:
input.action.name == "read"
input.subject.properties.name == "Alice"Numbers
Numbers work as expected:
input.resource.properties.size < 1000
input.subject.properties.age >= 18
input.context.request_count == 5Booleans
Boolean values:
input.subject.properties.verified == true
input.resource.properties.public == falseArrays
Arrays are ordered lists:
# Check if value is in array using local binding
allowed_roles := ["admin", "editor"]
input.subject.properties.role == allowed_roles[_]
# Access by index (0-based)
input.subject.properties.permissions[0]
# Iterate over array
some role in input.subject.properties.roles
role == "editor"Objects
Objects are key-value maps:
# Access nested values
input.resource.properties.metadata.owner
# Check if key exists
input.resource.properties.tags["priority"]Null and Undefined
Rego handles missing values gracefully:
# This won't error if department is missing
# It will simply not match
input.subject.properties.department == "engineering"If a field doesn't exist, the comparison evaluates to undefined (not true or false), which means the rule body doesn't match. This is a key feature for handling optional fields.
Rules and Conditions
Basic Rules
A rule with one condition:
input.subject.properties.role == "admin"Multiple Conditions (AND)
All conditions in a rule must be true (implicit AND):
input.action.name == "read"
input.subject.properties.verified == true
input.resource.properties.public == trueThis matches when:
- The action is "read" AND
- The subject is verified AND
- The resource is public
Multiple Rules (OR)
In Denied Platform, you create separate policies for OR logic — if any policy matches, access is allowed:
Policy 1: Admin Access
input.subject.properties.role == "admin"Policy 2: Owner Access
input.resource.properties.owner == input.subject.properties.user_idPolicy 3: Public Read Access
input.action.name == "read"
input.resource.properties.visibility == "public"Access is allowed if Policy 1 OR Policy 2 OR Policy 3 matches.
Negation (NOT)
Use not to negate conditions:
not input.resource.properties.restrictedFor deny policies:
not input.subject.properties.verifiedComparison Operators
| Operator | Description | Example |
|---|---|---|
== | Equal | input.action.name == "read" |
!= | Not equal | input.subject.properties.role != "guest" |
< | Less than | input.subject.properties.age < 18 |
> | Greater than | input.subject.properties.level > 5 |
<= | Less than or equal | input.context.count <= 100 |
>= | Greater than or equal | input.subject.properties.score >= 80 |
Working with Arrays
Membership
Check if a value is in an array using local bindings:
# Define allowed values and check membership
allowed_roles := ["admin", "editor"]
input.subject.properties.role == allowed_roles[_]# Multiple allowed actions
allowed_actions := ["read", "list", "view"]
input.action.name == allowed_actions[_]Iteration
Iterate over array elements:
# Check if any role matches
some role in input.subject.properties.roles
role == "editor"# With index
some i, role in input.subject.properties.roles
role == "admin"Array Operations
# Count elements
count(input.subject.properties.roles) > 0# Check all elements match
every role in input.subject.properties.roles {
role != "banned"
}Working with Objects
Accessing Values
# Direct access
input.resource.properties.owner
# Nested access
input.resource.properties.metadata.classification
# With bracket notation
input.resource.properties["owner"]
input.resource.properties.tags["priority"]Checking Keys
# Check if key exists and matches value
input.resource.properties.metadata.classification
input.resource.properties.metadata.classification == "public"Iterating Objects
# Iterate key-value pairs
some key, value in input.resource.properties.permissions
key == input.subject.properties.user_id
value == "write"Built-in Functions
Rego provides many built-in functions:
String Functions
# Contains
contains(input.resource.properties.path, "/admin")
# Starts/ends with
startswith(input.resource.properties.path, "/api/")
endswith(input.resource.properties.name, ".pdf")
# Lower/upper case
lower(input.subject.properties.name) == "alice"
# String formatting
sprintf("user-%s", [input.subject.properties.user_id])Numeric Functions
# Absolute value
abs(input.context.difference)
# Min/max
max([input.resource.properties.a, input.resource.properties.b])
min([1, 2, 3])
# Rounding
round(input.subject.properties.score)
ceil(input.resource.properties.value)
floor(input.resource.properties.amount)Collection Functions
# Count
count(input.resource.properties.items) > 0
# Sum
sum(input.resource.properties.values)
# Sort
sort(input.resource.properties.items)
# Intersection
intersection({input.resource.properties.required_roles}, {input.subject.properties.roles})Type Checking
# Check types
is_string(input.subject.properties.name)
is_number(input.subject.properties.age)
is_boolean(input.subject.properties.verified)
is_array(input.subject.properties.roles)
is_object(input.resource.properties.metadata)
is_null(input.resource.properties.optional)Variables and Assignment
Local Variables
Use := to assign variables:
user_role := input.subject.properties.role
user_role == "admin"Computed Values
required_level := input.resource.properties.min_level
user_level := input.subject.properties.clearance
user_level >= required_levelComprehensions
Array Comprehension
Build arrays from iterations:
# Get all admin users from a list
admin_users := [user |
some user in input.context.users
user.role == "admin"
]
count(admin_users) > 0Set Comprehension
Build sets (unique values):
# Get unique departments
departments := {dept |
some user in input.context.users
dept := user.department
}
input.subject.properties.department == departments[_]Object Comprehension
Build objects:
# Map user IDs to roles
user_roles := {user.id: user.role |
some user in input.context.users
}
user_roles[input.subject.properties.user_id] == "admin"Helper Rules
For complex logic, you can define helper conditions using local bindings:
# Check if user is admin
is_admin := input.subject.properties.role == "admin"
# Check if user owns resource
is_owner := input.resource.properties.owner == input.subject.properties.user_id
# Use helpers in your conditions
is_adminOr for owner access with action restrictions:
is_owner := input.resource.properties.owner == input.subject.properties.user_id
allowed_actions := ["read", "update", "delete"]
is_owner
input.action.name == allowed_actions[_]Common Patterns
Role Check
required_role := "editor"
input.subject.properties.role == required_rolePermission Check
permission := sprintf("%s:%s", [input.resource.properties.type, input.action])
allowed_permissions := input.subject.properties.permissions
permission == allowed_permissions[_]Ownership Check
input.resource.properties.owner == input.subject.properties.user_idProperty Match
input.subject.properties.department == input.resource.properties.departmentDebugging Tips
Use Print Statements
During development, use print to debug:
print("Checking user:", input.subject.properties.user_id)
print("Has role:", input.subject.properties.role)
input.subject.properties.role == "admin"Remove print statements before deploying to production. They're useful for debugging but add overhead.
Test Incrementally
Build policies step by step:
- Start with a simple rule that matches everything
- Add one condition at a time
- Test after each change in the Playground
- Refine until the policy is complete
Handle Missing Fields
Be defensive about optional fields:
# This might not work if department is missing
input.subject.properties.department == "engineering"# More defensive version using object.get
dept := object.get(input.subject.properties, "department", "")
dept == "engineering"Next Steps
Now that you understand Rego basics:
- Common Patterns — RBAC, ABAC, and more
- Testing Policies — Validate in the Playground
- Policies Guide — Manage policies in Denied