Build Access Control with Rego
Common Model
While authorization models are varied, the basic building blocks of access control decisions commonly refer to subject, action, and resource.
- Subject
The actor of an access control decision: a human operating a browser, or a program using an API (e.g. "alice", "bob").
- Resource
The object that is acted on (e.g. "picture", "profile", "bank account").
- Action
The act of the access control decision: how a resource is referred to (e.g. "read", "delete", "update").
In the how-tos of the various policy models (RBAC, ABAC, ReBAC), we'll assume that data.common
contains subject, action, and resource.
How the subject/action/resource relates to the policy input depends on the deployment scenario, see these examples:
- Direct
- HTTP request (JWT)
If the access tuple is provided as-is in the policy input JSON
{
"subject": "alice",
"action": "delete",
"resource": "dog"
}
it can be used as-is from input
:
package common
import rego.v1
subject := input.subject
action := input.action
resource := input.resource
The input to OPA's policy evaluation could also be a JSON representation of an HTTP request including a JSON Web Token (JWT).
This example shows the input provided by Envoy (via opa-envoy-plugin
):
{
"request": {
"http": {
"headers": {
":authority": "example-app",
":method": "POST",
":path": "/pets/dogs",
"accept": "*/*",
"authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiQWxpY2lhIFNtaXRoc29uaWFuIiwicm9sZXMiOlsicmVhZGVyIiwid3JpdGVyIl0sInVzZXJuYW1lIjoiYWxpY2UifQ.md2KPJFH9OgBq-N0RonGdf5doGYRO_1miN8ugTSeTYc",
"content-length": "0",
"user-agent": "curl/7.68.0-DEV",
"x-ext-auth-allow": "yes",
"x-forwarded-proto": "http",
"x-request-id": "1455bbb0-0623-4810-a2c6-df73ffd8863a"
},
"host": "example-app",
"id": "8306787481883314548",
"method": "POST",
"path": "/pets/dogs",
"protocol": "HTTP/1.1"
}
}
}
This indirection can be resolved via Rego, too:
package common
import rego.v1
req := input.request.http # for shorter definitions
subject := jwt_claim(req.headers.authorization, "username")
actions := {
"POST": "create",
"PUT": "update",
"DELETE": "delete",
}
action := actions[req.method]
resource := "dogs" if startswith(req.path, "/pets/dogs")
jwt_claim(authorization, key) := value if {
bearer_token := token(authorization)
# Verify the signature on the Bearer token. In this example the secret is
# hardcoded into the policy however it could also be loaded via data or
# an environment variable.
io.jwt.verify_hs256(bearer_token, "B41BD5F462719C6D6118E673A2389")
[_, payload, _] := io.jwt.decode(bearer_token)
value := payload[key]
}
token(value) := substring(value, 7, -1) # cut off "bearer "
For illustration purposes, only some potential "actions" are spelled out.