Skip to main content

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/v1beta1
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 Are Different

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

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
}
Resource Format

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/v1beta1
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/v1beta1
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​