Skip to main content

Testing Policies

The mpe test command provides several ways to test your policies during development.

Test Commands Overview

CommandDescription
mpe test decisionTest policy decisions with PORC input
mpe test mapperTest mapper transformations
mpe test envoyTest full Envoy-to-decision pipeline

Understanding Test Output

Before diving into the commands, it helps to understand what each command outputs:

Decision and Envoy Output: AccessRecord

The mpe test decision and mpe test envoy commands output an AccessRecord—a JSON document that captures everything about the policy evaluation. The most important field is decision, which will be either "GRANT" or "DENY".

{
"decision": "GRANT",
"principal": { "subject": "user123", "realm": "" },
"operation": "api:resource:read",
"resource": "mrn:app:resource:123",
"references": [ ... ],
"porc": "{ ... }"
}

Key fields you'll see:

FieldDescription
decisionThe final outcome: "GRANT" or "DENY"
principalWho made the request (extracted from PORC)
operationWhat action was attempted
resourceWhat resource was accessed
referencesDetails about each policy evaluated (useful for debugging)

To quickly extract just the decision, pipe the output through jq:

mpe test decision -b my-domain.yml -i input.json | jq .decision
# Output: "GRANT" or "DENY"

For a deeper understanding of AccessRecords and how they support auditing and debugging, see Audit & Access Records.

Mapper Output: PORC Expression

The mpe test mapper command outputs a PORC expression—the standardized format that policies evaluate. This shows how your mapper transforms external input (like an Envoy request) into the Principal, Operation, Resource, and Context structure:

{
"principal": { "sub": "user@example.com", "mroles": [...] },
"operation": "my-service:http:get",
"resource": { "id": "mrn:http:my-service/api/users/123", ... },
"context": { ... }
}

This is useful for verifying that your mapper correctly extracts identity information and constructs the operation and resource fields.

Testing Policy Decisions

Basic Decision Test

Test a policy with a PORC expression:

# Create a PORC input file
cat > input.json << 'EOF'
{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:admin"]
},
"operation": "api:resource:read",
"resource": {
"id": "mrn:app:resource:123",
"group": "mrn:iam:resource-group:default"
},
"context": {}
}
EOF

# Test the decision
mpe test decision -b my-domain.yml -i input.json

Testing Different Scenarios

Authenticated User with Admin Role:

{
"principal": {
"sub": "admin-user",
"mroles": ["mrn:iam:role:admin"],
"mgroups": ["mrn:iam:group:admins"]
},
"operation": "api:users:delete",
"resource": {
"id": "mrn:app:user:456",
"group": "mrn:iam:resource-group:default"
}
}

Unauthenticated Request (should be denied):

{
"principal": {},
"operation": "api:data:read",
"resource": {
"id": "mrn:app:data:secret",
"group": "mrn:iam:resource-group:default"
}
}

Resource with Classification:

{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:analyst"],
"mclearance": "HIGH"
},
"operation": "vault:secret:read",
"resource": {
"id": "mrn:vault:secret:123",
"group": "mrn:iam:resource-group:classified",
"classification": "MODERATE",
"owner": "user456"
}
}

Testing Mappers

Mappers transform external inputs into PORC expressions. Test them separately:

# Create an Envoy-style input
cat > envoy-input.json << 'EOF'
{
"request": {
"http": {
"method": "GET",
"path": "/api/users/123",
"headers": {
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
},
"destination": {
"principal": "spiffe://cluster.local/ns/default/sa/my-service"
}
}
EOF

# Test the mapper
mpe test mapper -b my-domain.yml -i envoy-input.json

The output is a PORC expression showing how the mapper transformed the input. You can inspect specific fields with jq:

# Check what operation was generated
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .operation

# Verify the principal was extracted correctly
mpe test mapper -b my-domain.yml -i envoy-input.json | jq .principal

Testing Full Pipeline

Test the complete Envoy input to decision pipeline:

mpe test envoy -b my-domain.yml -i envoy-input.json

This runs both stages and outputs an AccessRecord (same format as mpe test decision):

  1. Mapper: Transform Envoy input → PORC
  2. Decision: Evaluate PORC against policies → AccessRecord
# Get the final decision
mpe test envoy -b my-domain.yml -i envoy-input.json | jq .decision

Using Multiple Bundles

You can load multiple PolicyDomain bundles:

mpe test decision \
-b base-policies.yml \
-b app-specific.yml \
-i input.json

Policies from all bundles are available for evaluation.

Enabling Trace Output

For debugging, enable OPA trace output:

mpe --trace test decision -b my-domain.yml -i input.json

This shows detailed evaluation steps.

Testing with stdin

Read input from stdin:

echo '{"principal": {"sub": "test", "mroles": ["mrn:iam:role:user"]}, "operation": "test:op", "resource": {"id": "test", "group": "mrn:iam:resource-group:default"}}' | \
mpe test decision -b my-domain.yml -i -
stdin is the default

You may omit '-i -' when you wish to use stdin-based input because it is the default

Common Test Patterns

Test JWT Validation

{
"principal": {},
"operation": "api:secure:read",
"resource": {
"id": "mrn:app:data:secret",
"group": "mrn:iam:resource-group:default"
}
}

Expected:

DENY
(no principal)

Test Role-Based Access

{
"principal": {
"sub": "user1",
"mroles": ["mrn:iam:role:viewer"]
},
"operation": "api:data:write",
"resource": {
"id": "mrn:app:data:123",
"group": "mrn:iam:resource-group:default"
}
}

Expected:

DENY
(viewer role typically lacks write permissions)

Test Resource Ownership

{
"principal": {
"sub": "user123",
"mroles": ["mrn:iam:role:user"]
},
"operation": "api:item:delete",
"resource": {
"id": "mrn:app:item:456",
"owner": "user123",
"group": "mrn:iam:resource-group:owner-access"
}
}

Expected:

GRANT
(owner access, assuming role and resource-group policies permit owner operations)

Best Practices

  1. Create a test suite: Maintain JSON files for common scenarios
  2. Test edge cases: Empty principals, missing fields, invalid data
  3. Test both GRANT and DENY: Verify policies correctly reject unauthorized access
  4. Use trace for debugging: When tests fail unexpectedly, enable --trace
  5. Test libraries independently: Verify library functions work as expected

Next Steps