This guide walks through creating a complete PolicyDomain from scratch, starting with the essentials and building up gradually.
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.
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
:::info[Operation Policies Are Different] Operation policies use tri-level integer output instead of simple true/false:
-1): DENY0): GRANT (other phases still evaluated)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.
:::
:::tip[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.
:::
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
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
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.
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.
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
}
:::tip[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. :::
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.
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.
Once you're comfortable with the basics, explore these advanced features:
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |