Route extensions
Route extensions are a way to register arbitrary HTTP handlers on Synapse to provide external authorization endpoints. Many systems such as gateways, proxies, query engines and queues have support for delegating access control to another system by calling into a specific endpoint with information about the access decision to be made. Route extensions enable using Cerbos policies to make those decisions by defining how to map the requests and responses to the format required by each component.
Synapse route extensions can be defined in two ways.
-
Declarative mapping
-
Programmatic mapping using pluggable implementations provided by Cerbos or developed by the user
Route patterns
Synapse matches the paths in a routes map using the gorilla/mux pattern syntax. Literal paths are exact matches and do not include subpaths.
Pattern syntax
| Pattern | Matches | Notes |
|---|---|---|
|
The literal path only |
Exact match. |
|
A single path segment |
Default regex is |
|
A segment matching an inline regex |
Accepts a Go regular expression, for example |
|
Zero or more characters, including |
Multi-segment wildcard. |
Use \{name:.*} for a multi-segment wildcard. The form \{name…} matches a single segment.
|
Methods and path variables
Specify methods in the value of the map, for example "/foo": ["GET"]. An empty list allows all methods. Host patterns and method prefixes in the path are not supported.
The /ext/ prefix is prepended to every pattern and is not stripped before the extension runs, so the path the extension sees includes it (for example /ext/orders/v1).
Matched path variables are not included separately in the extension input. Use req.path when the extension needs a value from the path.
Overlapping routes and registration order
Routes from all route extensions share one router. If patterns overlap, the first registered pattern handles the request; the most specific pattern is not selected automatically. Registration order is not guaranteed, so configure non-overlapping patterns. Exact duplicate route strings are rejected at startup.
Catch-all routes
When teams own separate API prefixes, each prefix can delegate to one catch-all route. The extension then owns the endpoint mapping within that prefix:
extensions:
routeExtensions:
orders:
extension:
extensionURL: /extensions/orders.star
routes:
"/orders": [] (1)
"/orders/{path:.*}": [] (2)
| 1 | Handles the bare /ext/orders path. |
| 2 | Handles /ext/orders/ and paths below it. The extension validates the path and method. |
Use a disjoint prefix for each catch-all route. An empty method list allows all methods, so the extension must reject unsupported path and method combinations.
Declarative mapping
This extension allows users to define mappings for the request/response conversion using CEL expressions.
extensions:
routeExtensions:
foo: (1)
mapping:
request: (2)
requestID: 'request.header["X-Request-Id"]'
principal:
id: '"abc"'
roles: '["user"]'
attr: "request.body.json"
resource:
id: '"test"'
kind: '"request"'
action: "request.method"
response: (3)
status: "check.allow ? 200 : 403"
body: "check.requestID"
routes: (4)
"/bar": ["GET", "POST"]
"/baz/{baz}": []
| 1 | Unique name for the instance |
| 2 | Section for defining how to map the incoming HTTP request into a Cerbos CheckResources request |
| 3 | Section for defining how to map the Cerbos response into the HTTP response expected by the caller |
| 4 | Routes to which this mapping applies. See Route patterns. |
Mapping reference
-
request(required):-
requestID: a CEL expression returning a string. -
principal(required):-
id(required): a CEL expression returning a string. -
roles(required): a CEL expression returning a list of strings, or a list of CEL expressions each returning a string. -
attr: a CEL expression returning a map of strings to values, or a map of strings to CEL expressions each returning a value. -
policyVersion: a CEL expression returning a string. -
scope: a CEL expression returning a string.
-
-
resource(required):-
kind(required): a CEL expression returning a string. -
id(required): a CEL expression returning a string. -
attr: a CEL expression returning a map of strings to values, or a map of strings to CEL expressions each returning a value. -
policyVersion: a CEL expression returning a string. -
scope: a CEL expression returning a string.
-
-
action(required): a CEL expression returning a string. -
auxData:-
jwt:-
token: a CEL expression returning a string. -
keySetID: a CEL expression returning a string.
-
-
-
-
response:-
status: a CEL expression returning an HTTP status code as an integer. Defaults to 200 when allowed and 403 when denied. -
headers: a CEL expression returning a map of strings to strings, or a map of strings to CEL expressions each returning a string. -
body: a CEL expression returning bytes or a string.
-
Request expressions have access to the incoming HTTP request in the CEL variable request, which has the following fields:
-
method: the request’s HTTP method as an uppercase string. -
header: map of HTTP request headers. Keys are header names in canonical form and values are strings (comma-separated when multiple headers with the same name are present in the request). -
headers: map of HTTP request headers. Keys are header names in canonical form and values are lists of strings. -
body: object with the following fields:-
bytes: the raw request body -
text: the request body as a string -
json: the JSON-decoded request body
-
Response expressions have access to the CheckResources result in the CEL variable check which has the following fields:
-
requestId,principal,resource, andaction: the inputs produced by the request expressions. -
cerbosCallId: the ID for audit logging as a string. -
allow: whether the action is allowed as a boolean. -
outputs: the output from the policy as a map of source (policy-id#rule-name) to value. -
validationErrors: errors encountered when validating the principal and resource against the corresponding schemas as a list ofcerbos.schema.v1.ValidationErrormessages.
Using the YAML scalar block syntax > for CEL expressions is recommended to avoid quoting issues. action: "view" would be interpreted as the CEL expression view (which would fail to compile because a variable named view is not defined), when the desired setting was the CEL expression "view" (which just returns the intended string literal).
extensions:
routeExtensions:
foo:
mapping:
request:
principal:
id: >
request.header["X-User-Id"]
roles:
- >
request.header["X-User-Role"]
resource:
kind: >
request.body.json.kind
id: >
request.body.json.id
action: >
"view"
extensions:
routeExtensions:
foo:
mapping:
request:
# ...
response:
status: >
check.allow ? 204 : 404
headers:
X-Cerbos-Call-Id: >
check.cerbosCallId
body: >
json.encode({"outputs": check.outputs})
Programmatic mapping
extensions:
routeExtensions:
infra: (1)
extension:
extensionURL: "/extensions/infra.wasm?checksum=sha256:2b2e0ddc150e8563127ba9bd13199519b357c85ca5b3de598c4bcd5729599c6c" (2)
configuration: (3)
environment: staging
routes: (4)
"/team-a/foo": []
"/team-b/{baz}": ["GET"]
| 1 | Unique name for the extension |
| 2 | URL to fetch the extension from. See extension URL format for more information. |
| 3 | Configuration values needed by the extension. [Optional] |
| 4 | Routes to which this mapping applies. See Route patterns. |