Every policy decision generates a normalized AccessRecord that captures the complete evaluation context. This audit trail is fundamental to understanding, debugging, and analyzing access control behavior across your system.
In distributed systems, access control decisions happen across many services, making it difficult to answer questions like:
The AccessRecord stream solves these challenges by providing a consistent, structured record of every decision.
The PolicyEngine's comprehensive audit capabilities are what make the Principle of Least Privilege practical at scale. Traditional systems often fail at least privilege because it's too difficult to know what access is actually needed—so administrators grant broad permissions "just in case."
With AccessRecords, you can take a different approach:
This transforms access control from guesswork into an evidence-based practice. See Iterative Policy Refinement for a detailed workflow.
Just as PORC normalizes authorization inputs, the AccessRecord normalizes authorization outputs. This symmetry allows you to reason about access control decisions consistently, regardless of where they originated in your system.
Every AccessRecord contains:
| Field | Description |
|---|---|
| Metadata | Timestamp, unique ID, and optional environment context |
| Principal | The subject and realm from the PORC |
| Operation | The operation being attempted |
| Resource | The resource MRN being accessed |
| Decision | The top-level outcome: GRANT or DENY |
| References | Details about each policy bundle evaluated |
| PORC | The complete PORC expression for replay/debugging |
Each evaluated policy bundle is recorded with:
GRANT or DENY)The fingerprint is particularly valuable—it uniquely identifies the exact policy version evaluated, enabling precise forensic analysis even after policies are updated.
| Feature | Availability | Description |
|---|---|---|
| JSON to stdout | Stream AccessRecords as JSON for custom processing | |
| ElasticSearch Integration | Durable storage with indexing, dashboards, and alerting |
In the Community PolicyEngine, AccessRecords are emitted as JSON to stdout:
{
"metadata": {
"timestamp": "2024-01-15T10:30:00Z",
"id": "550e8400-e29b-41d4-a716-446655440000",
"env": {
"service": "api-gateway",
"pod": "api-gw-7d9f8b6c4-x2m9k"
}
},
"principal": {
"subject": "alice@example.com",
"realm": "employees"
},
"operation": "api:documents:read",
"resource": "mrn:app:document:12345",
"decision": "GRANT",
"references": [
{
"id": "mrn:iam:policy:require-auth",
"fingerprint": "a3f2b8c1...",
"decision": "GRANT",
"phase": "OPERATION",
"reason_code": "POLICY_OUTCOME"
},
{
"id": "mrn:iam:role:editor",
"fingerprint": "d4e5f6a7...",
"decision": "GRANT",
"phase": "IDENTITY",
"reason_code": "POLICY_OUTCOME"
}
],
"porc": "{...}"
}
You can pipe this output to your logging infrastructure, message queue, or analysis tools.
The Premium PolicyEngine integrates directly with ElasticSearch, providing:
AccessRecords provide the evidence trail required for compliance audits:
When users report access problems, AccessRecords reveal exactly what happened:
# Find recent denials for a specific user (Community example)
mpe serve ... 2>&1 | jq 'select(.principal.subject == "alice@example.com" and .decision == "DENY")'
The detailed policy references show which policy caused the denial and why.
Build alerting on unusual patterns:
Because AccessRecords include the complete PORC, you can replay decisions against different policy versions:
This enables safe policy updates by understanding the impact before deployment.
The combination of comprehensive audit logging and policy replay creates a powerful workflow for implementing the Principle of Least Privilege:
Phase 1: Deploy Strict Policies
Start with policies that are intentionally restrictive. It's easier to safely expand access than to identify and close security gaps:
# Start with deny-by-default policies that only grant essential access
roles:
- mrn: "mrn:iam:role:new-service"
name: new-service
policy: "mrn:iam:policy:minimal-access" # Only the operations you're certain are needed
Phase 2: Observe Access Patterns
Monitor AccessRecords to understand actual usage. Look for denied requests that represent legitimate access needs:
# Find denied requests for analysis (Community example)
mpe serve ... 2>&1 | jq 'select(.decision == "DENY")' > denied-requests.jsonl
# Analyze denial patterns by operation
cat denied-requests.jsonl | jq -r '.operation' | sort | uniq -c | sort -rn
Phase 3: Validate Policy Changes
Before expanding access, use policy replay to understand the impact of proposed changes:
Phase 4: Deploy and Monitor
After validating that the expanded policy only grants intended access:
This approach is effective because:
When onboarding a new service, rather than guessing what permissions it needs:
This evidence-based approach results in tightly scoped permissions that precisely match actual needs.
Aggregate AccessRecords to understand your access patterns:
Use the audit.env configuration option to capture deployment context in every AccessRecord. Configure it in your mpe-config.yaml:
audit:
env:
- name: service
type: env
value: SERVICE_NAME
- name: environment
type: string
value: production
- name: region
type: env
value: AWS_REGION
- name: pod
type: env
value: HOSTNAME
Each entry specifies:
metadata.env fieldenv, string, k8s-label, or k8s-annot)Values are resolved once at PolicyEngine startup.
For example, with the above configuration and SERVICE_NAME=api-gateway, AWS_REGION=us-east-1, HOSTNAME=api-gw-7d9f8b6c4-x2m9k, every AccessRecord will include:
{
"metadata": {
"env": {
"service": "api-gateway",
"environment": "production",
"region": "us-east-1",
"pod": "api-gw-7d9f8b6c4-x2m9k"
}
}
}
You can include Kubernetes pod labels and annotations using the k8s-label and k8s-annot types. These read from the Kubernetes Downward API files:
audit:
env:
- name: app
type: k8s-label
value: app.kubernetes.io/name
- name: version
type: k8s-annot
value: deployment.kubernetes.io/revision
This requires a Downward API volume mount in your pod spec:
volumes:
- name: podinfo
downwardAPI:
items:
- path: "labels"
fieldRef:
fieldPath: metadata.labels
- path: "annotations"
fieldRef:
fieldPath: metadata.annotations
volumeMounts:
- name: podinfo
mountPath: /etc/podinfo
By default, the PolicyEngine reads from /etc/podinfo. If your volume is mounted elsewhere, set audit.k8s.podinfo in your config:
audit:
k8s:
podinfo: /custom/path/podinfo
Outside of Kubernetes (or without the volume mount), k8s-label and k8s-annot entries resolve to empty strings.
See the Configuration Reference for complete details.
Balance storage costs with compliance and debugging needs:
Track the ratio of GRANT to DENY decisions. Sudden changes may indicate:
For the complete AccessRecord schema including all fields, types, and enumeration values, see the AccessRecord Schema Reference.
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 |