Envoy Integration

This guide explains how to integrate heimdall with Envoy.

Envoy is a high performance distributed proxy designed for single services and applications, as well as a communication bus and “universal data plane” designed for large microservice “service mesh” architectures.

Prerequisites

Integration Options

Envoy makes integration with external authorization services, like heimdall possible via an External Authorization filter in two ways

  • either via HTTP

  • or via GRPC

In both cases, the filter calls an external gRPC or HTTP service to check whether an incoming HTTP request is authorized or not. If the request is deemed unauthorized, then the request will be denied normally with 403 (Forbidden) response.

Even envoy allows the authorization service sending additional custom metadata to the upstream when GRPC is used, heimdall does not make use of this option.

Global Configuration

To integrate heimdall with Envoy globally, you need to configure two things:

  • a cluster entry referencing the actual heimdall address, and

  • the aforesaid filter in the http filter chain

The following snippet provides an example on how to create a cluster instance referencing heimdall. It assumes, you have just one heimdall instance deployed, which is also available via heimdall DNS name.

clusters:
  # other cluster entries
  - name: ext-authz (1)
    type: strict_dns
    typed_extension_protocol_options: (2)
      envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
        "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions"
        explicit_http_config:
          http2_protocol_options: {}
    load_assignment: (3)
      cluster_name: ext-authz
      endpoints:
        - lb_endpoints:
            - endpoint:
                address:
                  socket_address:
                    address: heimdall
                    port_value: 4456
  # other cluster entries
1The name of our cluster
2Here, we configure envoy to communicate with heimdall by making use of HTTP 2. This configuration is only required if you want to integrate heimdall via Envoy’s grpc_service (see below), as otherwise envoy will use HTTP 1 for GRPC communication, which is actually not allowed by GRPC (only HTTP 2 is supported).
3The endpoints in the cluster. In this example, only one endpoint is configured.

You can now include an External Authorization HTTP filter in the definition of the HTTP connection manager and depending on the used configuration, either configure the http_service and let it contain the required header name(s), heimdall sets in the HTTP responses (depends on your Contextualizers and Finalizers configuration), or configure the grpc_service.

Using HTTP protocol

The following snipped shows, how an External Authorization can be defined using http_service to let Envoy communicating with heimdall by making use of the previously defined cluster (see snippet from above) as well as forwarding all request headers to heimdall and to let it forward headers, set by heimdall in its responses (here the Authorization header) to the upstream services.

http_filters:
  # other http filter
  - name: envoy.filters.http.ext_authz
    typed_config:
      "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz" (1)
      transport_api_version: V3 (2)
      http_service:
        server_uri: (3)
          uri: heimdall:4456
          cluster: ext-authz
          timeout: 0.25s
        authorization_request:
          allowed_headers: (4)
            patterns:
              - safe_regex:
                  google_re2: {}
                  regex: ".*"
        authorization_response: (5)
          allowed_upstream_headers:
            patterns:
              - exact: authorization
  # other http filter
1The type of the filter, we’re going to configure
2Heimdall supports only the version 3 of the GRPC protocol defined by Envoy for that filter. So we set the required version here.
3The reference to our previously configured cluster
4Here, we say envoy to forward all headers from the received request to heimdall
5And here, we instruct envoy to forward the Authorization header set bei heimdall in its response to envoy to the upstream service

Envoy does not set X-Forwarded-* headers, as long as the envoy.filters.http.dynamic_forward_proxy is not configured. In such cases matching of URLs happens based on those URLs, used by Envoy while communicating with heimdall. That means your rules should ignore the scheme and host parts, respectively use the values specific for heimdall and not of the domain. Please follow Security Considerations if your rules rely on any of the X-Forwarded-* headers, and you integrate heimdall with envoy using http_service.

If you integrate heimdall with envoy via grpc_service (see below), spoofing of the aforesaid headers is not possible.

Using GRPC protocol

The following snipped shows, how an External Authorization can be defined using grpc_service to let Envoy communicating with heimdall by making use of the previously defined cluster (see snippet from above). In that configuration envoy by default forwards all request header to heimdall and also forwards headers, set by heimdall in its responses to the upstream services.

http_filters:
  # other http filter
  - name: envoy.filters.http.ext_authz
    typed_config:
      "@type": "type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz" (1)
      transport_api_version: V3 (2)
      grpc_service: (3)
        envoy_grpc:
          cluster_name: ext-authz
  # other http filter
1The type of the filter, we’re going to configure. Same filter is used for both approaches, communication via HTTP and GRPC
2Heimdall supports only the version 3 of the GRPC protocol defined by Envoy for that filter. So we set the required version here
3The reference to our previously configured cluster

Route-based Configuration

Route base configuration happens exactly the same way as globally. There is also an option to fine tune or disable the external authorization service if required by making use of the ExtAuthzPerRoute filter. You can find an example in the official Envoy documentation.

Demo Setup

The Envoy configuration file shown below can be used to create a fully working setup based on the quickstart described in Protect an Application and set up to implement Edge-level Authorization Architecture. Just update the docker-compose.yaml file used in that guide and replace the entry for proxy service, with the one shown below. You can also remove all labels configurations, as these will have no effect.

# docker-compose.yaml

services:
  proxy:
    image: envoyproxy/envoy:v1.24.1
    volumes:
      - ./envoy.yaml:/envoy.yaml:ro
    ports:
      - 9090:9090
    command: -c /envoy.yaml

  # other services from the guide
# envoy.yaml

static_resources:
  listeners:
    - name: listener_0
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 9090
      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"
              stat_prefix: edge
              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
                    http_service:
                      server_uri:
                        uri: heimdall:4456
                        cluster: ext-authz
                        timeout: 0.25s
                      authorization_request:
                        allowed_headers:
                          patterns:
                            - safe_regex:
                                google_re2: {}
                                regex: ".*"
                      authorization_response:
                        allowed_upstream_headers:
                          patterns:
                            - exact: authorization
                - name: envoy.filters.http.router
                  typed_config:
                    "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router"
              route_config:
                virtual_hosts:
                  - name: direct_response_service
                    domains: ["*"]
                    routes:
                      - match:
                          prefix: "/"
                        route:
                          cluster: services

  clusters:
    - name: ext-authz
      type: strict_dns
      load_assignment:
        cluster_name: ext-authz
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: heimdall
                      port_value: 4456
    - name: services
      connect_timeout: 5s
      type: strict_dns
      dns_lookup_family: V4_ONLY
      load_assignment:
        cluster_name: services
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: upstream
                      port_value: 80

After starting the docker compose environment, you can run the curl commands shown in the referenced guide. This time however against envoy by using port 9090. E.g. $ curl -v 127.0.0.1:9090/anonymous.

Additional Resources

The demo setup shown above is also available on GitHub.

Last updated on Mar 22, 2024