Envoy extension

Synapse implements the Envoy external authorization API natively and its behaviour can be configured either declratively or programmatically.

A typical Envoy configuration to use Synapse as the external authorization service is as follows.

Envoy configuration
static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 8080
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                codec_type: AUTO
                http_filters:
                  - name: envoy.filters.http.ext_authz
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
                      transport_api_version: V3
                      grpc_service:
                        envoy_grpc:
                          cluster_name: synapse
                  - name: envoy.filters.http.router
                    typed_config:
                      "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
                route_config:
                  ...

  clusters:
    - name: synapse
      type: STATIC
      typed_extension_protocol_options:
        envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
          "@type": type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
          explicit_http_config:
            http2_protocol_options: {}
      load_assignment:
        cluster_name: synapse
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: 127.0.0.1
                      port_value: 3594

      transport_socket:
        name: envoy.transport_sockets.tls
        typed_config:
          "@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext
          sni: synapse.localhost
          common_tls_context:
            validation_context:
              trust_chain_verification: ACCEPT_UNTRUSTED

Declarative mapping

Map Envoy Check requests to Cerbos CheckResources and back using CEL expressions.

extensions:
  envoyExternalAuthz:
    enabled: true (1)
    mapping: (2)
      request:
        requestID: 'request.attributes.request.http.headers["x-request-id"]'
        principal:
          id: 'request.attributes.request.http.headers["x-user-id"]'
          roles: '["user"]'
        resource:
          id: '"foo"'
          kind: '"request"'
        action: "request.attributes.request.http.method"
1 Enable the Envoy Check endpoint on the server
2 Declarative mapping of request and response. See below for mapping reference.

Mapping reference

Documentation about Cerbos request and response formats can be found at https://docs.cerbos.dev/cerbos/latest/api/#_request_and_response_formats. For Envoy Check request and response formats, refer to https://buf.build/envoyproxy/envoy/docs/main:envoy.service.auth.v3#envoy.service.auth.v3.CheckRequest.
  • 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:
  envoyExternalAuthz:
    enabled: true
    mapping:
      request:
        requestID: 'request.attributes.request.http.headers["x-request-id"]'
        principal:
          id: 'request.attributes.request.http.headers["x-user-id"]'
          roles: '["user"]'
        resource:
          id: '"foo"'
          kind: '"request"'
        action: "request.attributes.request.http.method"
Customizing the response
extensions:
  envoyExternalAuthz:
    enabled: true
    mapping:
      request:
        requestID: 'request.attributes.request.http.headers["x-request-id"]'
        principal:
          id: 'request.attributes.request.http.headers["x-user-id"]'
          roles: '["user"]'
        resource:
          id: '"foo"'
          kind: '"request"'
        action: "request.attributes.request.http.method"
      response: >
        check.allow ? {"status": {"code": google.rpc.Code.OK }} : {
            "status": {"code": google.rpc.Code.OK },
            "deniedResponse": {
               "status": {"code": 401 },
               "body": json.encode(check.outputs["resource.example.v1#route"].body)
             }
        }

Programmatic mapping

extensions:
  envoyExternalAuthz:
    enabled: true (1)
    extension:
      extensionURL: /extensions/envoy.wasm (2)
      configuration: (3)
        environment: staging
1 Enable the Envoy Check endpoint.
2 URL to fetch the extension from. See extension URL format for more information.
3 Configuration values needed by the extension. [Optional]

Building an Envoy extension