Creating Your First PolicyDomain
This guide walks through creating a complete PolicyDomain from scratch, starting with the essentials and building up gradually.
PolicyDomain Structure
A PolicyDomain YAML file has this structure:
apiVersion: iamlite.manetu.io/v1alpha4
kind: PolicyDomain
metadata:
name: domain-name
spec:
policies: [] # Access control policies
roles: [] # Role-to-policy mappings
groups: [] # Group-to-role mappings
resource-groups: [] # Resource-to-policy mappings
operations: [] # Operation routing
resources: [] # Resource selector routing (v1alpha4+)
scopes: [] # Access-method constraint policies
mappers: [] # Input transformation (optional)
policy-libraries: [] # Reusable Rego code (optional)
For a detailed explanation of each component, see the Concepts section.
Step 1: Define Policies
Policies contain Rego code that makes access control decisions. Most policies define an allow rule that returns true (grant) or false (deny).
Let's start with three policies:
spec:
policies:
# Full access policy
- mrn: &allow-all "mrn:iam:policy:allow-all"
name: allow-all
description: "Grants full access"
rego: |
package authz
default allow = true
# Read-only policy
- mrn: &read-only "mrn:iam:policy:read-only"
name: read-only
description: "Read-only access - allows get, read, and list operations"
rego: |
package authz
import rego.v1
default allow = false
# Allow read-only operations
allow if {
ro_patterns := {"*:get", "*:read", "*:list"}
some pattern in ro_patterns
glob.match(pattern, [], input.operation)
}
# Operation phase policy (see note below about tri-level)
- mrn: &operation-default "mrn:iam:policy:operation-default"
name: operation-default
description: "Default operation policy - defers to identity and resource phases"
rego: |
package authz
# Operation policies use tri-level: negative=DENY, 0=GRANT, positive=GRANT Override
# Returning 0 defers the decision to identity and resource phases
default allow = 0
Operation policies use tri-level integer output instead of simple true/false:
- Negative (e.g.,
-1): DENY - Zero (
0): GRANT (other phases still evaluated) - Positive (e.g.,
1): GRANT Override (skip other phases)
Using default allow = 0 keeps this example simple by deferring all decisions to the identity and resource phases. In practice, operation policies often perform checks like verifying authentication (e.g., input.principal != {}). See Tri-Level Policies for complete semantics and real-world patterns.
YAML anchors, such as &allow-all, can be referenced later with *allow-all. This keeps your PolicyDomain DRY and ensures consistent MRN references.
Step 2: Define Roles
Roles map to policies and are assigned to principals (users). When a user has a role, their access is evaluated against that role's policy.
roles:
- mrn: &admin-role "mrn:iam:role:admin"
name: admin
description: "Administrator with full access"
policy: *allow-all # references the &allow-all anchor from Step 1
- mrn: &viewer-role "mrn:iam:role:viewer"
name: viewer
description: "Read-only access"
policy: *read-only
Step 3: Define Groups
Groups organize roles together. Principals can be members of groups, inheriting all roles assigned to that group.
groups:
- mrn: "mrn:iam:group:admins"
name: admins
description: "Administrator group"
roles:
- *admin-role
- mrn: "mrn:iam:group:readers"
name: readers
description: "Read-only users"
roles:
- *viewer-role
Step 4: Define Resource Groups
Resource groups associate policies with sets of resources. Every resource belongs to a resource group, and that group's policy is evaluated during authorization.
resource-groups:
- mrn: &default-resources "mrn:iam:resource-group:default"
name: default
description: "Default resource group"
default: true
policy: *allow-all
- mrn: "mrn:iam:resource-group:sensitive"
name: sensitive
description: "Sensitive resources requiring stricter access"
policy: *sensitive-data
The default: true flag designates mrn:iam:resource-group:default as the fallback group for resources that don't match any specific routing rules.
Step 5: Define Operations
Operations route incoming requests to policies based on the operation string. Selectors use regular expressions to match operations.
operations:
- name: all-operations
selector:
- ".*" # Match all operations
policy: *operation-default
This example routes all operations through the tri-level operation-default policy. See Operations for examples of more complex operation routing patterns.
Step 6: Define Mappers (Optional)
Mappers are only needed when integrating with systems that cannot construct PORC expressions directly, such as Envoy's ext_authz protocol. Most applications should build PORC expressions in their own code instead.
mappers:
- name: http-mapper
selector:
- ".*"
rego: |
package mapper
import rego.v1
default claims := {}
method := lower(input.request.http.method)
path := input.request.http.path
# Extract JWT claims
auth := input.request.http.headers.authorization
token := split(auth, "Bearer ")[1]
claims := io.jwt.decode(token)[1]
porc := {
"principal": claims,
"operation": sprintf("api:http:%s", [method]),
"resource": sprintf("mrn:api:%s", [path]),
"context": input
}
This uses the simple MRN string format, which is the recommended approach. See Resource Resolution for details on how the PolicyEngine enriches resources with metadata.
Complete Minimal Example
Here's a complete, minimal PolicyDomain that you can use as a starting point:
apiVersion: iamlite.manetu.io/v1alpha4
kind: PolicyDomain
metadata:
name: my-first-domain
spec:
policies:
- mrn: &operation-default "mrn:iam:policy:operation-default"
name: operation-default
description: "Defers to identity and resource phases"
rego: |
package authz
default allow = 0 # Tri-level: negative=DENY, 0=GRANT, positive=GRANT Override
- mrn: &allow-all "mrn:iam:policy:allow-all"
name: allow-all
description: "Grants full access"
rego: |
package authz
default allow = true
- mrn: &read-only "mrn:iam:policy:read-only"
name: read-only
description: "Read-only access"
rego: |
package authz
import rego.v1
default allow = false
allow if {
ro_patterns := {"*:get", "*:read", "*:list"}
some pattern in ro_patterns
glob.match(pattern, [], input.operation)
}
roles:
- mrn: &admin-role "mrn:iam:role:admin"
name: admin
policy: *allow-all
- mrn: &viewer-role "mrn:iam:role:viewer"
name: viewer
policy: *read-only
groups:
- mrn: "mrn:iam:group:admins"
name: admins
roles:
- *admin-role
resource-groups:
- mrn: "mrn:iam:resource-group:default"
name: default
default: true
policy: *allow-all
operations:
- name: all-operations
selector:
- ".*"
policy: *operation-default
For a deeper dive, see Examples.
Using External Rego Files
For better maintainability, you can use PolicyDomainReference with external .rego files:
apiVersion: iamlite.manetu.io/v1alpha4
kind: PolicyDomainReference
metadata:
name: my-domain
spec:
policies:
- mrn: "mrn:iam:policy:main"
name: main
rego_filename: policies/main.rego # External file
Then build:
mpe build -f my-domain-ref.yml
This creates a PolicyDomain with the Rego content inlined.
Advanced Topics
Once you're comfortable with the basics, explore these advanced features:
- Policy Libraries — Extract reusable Rego code into shared libraries that multiple policies can import
- Policy Conjunction — Understand how the evaluation phases work together, including tri-level policies for early grant/deny decisions
- Resource Routing — Route resources to groups based on MRN patterns
- Scopes — Add access-method constraints for scenarios like API keys vs. interactive sessions
Next Steps
- Testing Policies - Test your PolicyDomain
- Examples - Complete, production ready use-cases for a deeper understanding.
- PolicyDomain Schema Reference - Complete schema documentation