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: a single JWT. Mutually exclusive with jwts.

        • token (required): a CEL expression returning a string.

        • keySetID: a CEL expression returning a string.

      • jwts: a map of token names to JWTs, each with the same token and keySetID fields as jwt. Mutually exclusive with jwt.

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

Use jwts to pass more than one token to the PDP. Policies read the claims of a named token as request.auxData.jwts.NAME.claims.CLAIM. Named JWTs require Cerbos PDP 0.55.0 or later. See Cerbos conditions for the expression syntax.

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)
             }
        }
Passing multiple JWTs
extensions:
  envoyExternalAuthz:
    enabled: true
    mapping:
      request:
        # ...
        auxData:
          jwts:
            access: (1)
              token: >
                request.attributes.request.http.headers["authorization"].replace("Bearer ", "", 1)
            id:
              token: >
                request.attributes.request.http.headers["x-id-token"]
1 Token name. Policies read the claims of this token as request.auxData.jwts.access.claims.

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