Liking cljdoc? Tell your friends :D

sidebar_position: 2

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

:::info[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. :::

:::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. :::

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
        }

:::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. :::

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

Can you improve this documentation?Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close