OPA Integration
Open Policy Agent (OPA) is a general-purpose policy engine that uses the Rego language for policy authoring. It's widely adopted in cloud-native environments, particularly for Kubernetes admission control, API gateway authorization, and microservice policy enforcement. The EDK integrates with OPA via its REST API, sending authorization requests to OPA's data API and interpreting the response.
How It Works
When a command triggers authorization, the EDK builds an OPA input document from the PolicyRequest and sends it as a POST to OPA's policy path. The input follows a normalized structure so that Rego policies always see the same shape regardless of which command triggered the evaluation.
The request sent to OPA:
{
"input": {
"principal": {
"type": "user",
"id": "usr_abc123",
"attributes": { "roles": ["admin"], "department": "engineering" }
},
"action": {
"name": "generate",
"module": "kms",
"service": "keys",
"command": "kms.keys.generate"
},
"resource": {
"type": "Key",
"id": "key-456"
},
"context": {
"tenantId": "acme-corp",
"sessionId": "sess_789"
}
}
}
OPA evaluates the Rego policy at the configured path and returns a response. The EDK looks for result.allow, if it's true, the decision is PERMIT. If false or missing, the decision is DENY. This fail-closed interpretation means a misconfigured policy that doesn't set allow will deny rather than accidentally grant access.
Rego Policy Examples
A basic RBAC policy:
package sphereon.authz
default allow = false
# Admins can do anything
allow {
input.principal.attributes.roles[_] == "admin"
}
# Users can read keys in their own tenant
allow {
input.action.module == "kms"
input.action.name == "get"
input.principal.attributes.tenantId == input.context.tenantId
}
# Deny cross-tenant access explicitly
deny {
input.principal.attributes.tenantId != input.context.tenantId
}
A more granular policy with reasons:
package sphereon.authz
default allow = false
allow {
has_permission
not deny
}
has_permission {
input.principal.attributes.roles[_] == "key-manager"
input.action.module == "kms"
}
deny {
input.action.name == "delete"
not input.principal.attributes.roles[_] == "admin"
}
reasons[msg] {
not has_permission
msg := sprintf("principal %s lacks required role for %s", [input.principal.id, input.action.command])
}
Configuration
sphereon:
authzen:
enabled: true
pdp:
type: opa
base-url: http://opa:8181
policy-path: /v1/data/sphereon/authz
timeout-ms: 5000
health-path: /health
| Property | Default | Description |
|---|---|---|
base-url | - | OPA server URL |
policy-path | /v1/data/sphereon/authz | Rego package path in OPA's data API |
timeout-ms | 5000 | Request timeout |
health-path | /health | Health check endpoint |
Deployment
OPA is typically deployed as a sidecar or a shared service. As a sidecar, it runs alongside your application and serves policy decisions with minimal network latency. As a shared service, multiple applications query the same OPA instance.
Policies can be loaded from local files, fetched from a bundle server, or pushed via OPA's management API. For the EDK, the policy must be available at the configured policy-path when the first authorization request arrives.
# docker-compose example
services:
opa:
image: openpolicyagent/opa:latest
command: run --server --addr 0.0.0.0:8181 /policies
volumes:
- ./policies:/policies
ports:
- "8181:8181"