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 definitions

Regardless of the mapping type used, the routes to which they should apply are defined as a map of paths to allowed HTTP methods.

All route extensions are served under the /ext/ prefix of Synapse server. This is implicitly prepended to the paths defined in the routes section.
extensions:
  routeExtensions:
    foo:
      routes:
        "/team-a/foo": [] (1)
        "/team-b/{baz}": ["GET"] (2)
1 Activate the extension for requests sent via any HTTP method to /ext/team-a/foo. This is an exact match.
2 Activate the extension for GET requests to /ext/team-b/<ANY VALUE>. The curly braces around the second path component defines a wildcard that matches any value.

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 the pop-out section above for more information about defining routes.

Mapping reference

Documentation about Cerbos request and response formats can be found at https://docs.cerbos.dev/cerbos/latest/api/#_request_and_response_formats.
  • 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, and action: 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 of cerbos.schema.v1.ValidationError messages.

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).

Minimal configuration
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"
Customizing the response
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 the pop-out section above for more information about defining routes.

Building route extensions