envoyproxy/envoy

Filter request: Deduplicate response header tokens

Open

#9,019 opened on Nov 14, 2019

View on GitHub
 (2 comments) (3 reactions) (0 assignees)C++ (5,373 forks)batch import
enhancementhelp wanted

Repository metrics

Stars
 (27,997 stars)
PR merge metrics
 (Avg merge 8d) (378 merged PRs in 30d)

Description

Title: Envoy concatenates values of duplicated Content-Type header resulting in an invalid response.

Description:

One of our clusters is returning a response with a duplicated Content-Type header, as the following:

HTTP/1.1 200 OK
Content-Length: 0
Content-Type: text/plain
Content-Type: text/plain

Which I believe is invalid according to the HTTP spec, since only one media type is allowed in a response, but it's somehow accepted by the client of our service. However, envoy does not only accept the response but also combines the values into a single header:

HTTP/1.1 200 OK
content-length: 0
content-type: text/plain,text/plain

Which is still wrong and probably even worse than the original upstream's response (and that one does get rejected by the client of our service).

I think envoy should treat the original response as a protocol error and return a 503 to the downstream.

Repro steps:

Tested with envoy v1.11.2

  1. Start this dummy netcat http upstream server:
# this is using netcat-openbsd
# on alpine: apk add netcat-openbsd
while true; do echo -e "HTTP/1.1 200 OK\nContent-Length: 0\nContent-Type: text/plain\nContent-Type: text/plain\n\n" | nc -l 0.0.0.0 8888; done
  1. Configure envoy to use that cluster. (see config section below)
  2. Make a request through envoy:
curl -v http://localhost:8080/
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.54.0
> Accept: */*
>
< HTTP/1.1 200 OK
< content-length: 0
< content-type: text/plain,text/plain
< x-envoy-upstream-service-time: 0
< date: Thu, 14 Nov 2019 04:19:59 GMT
< server: envoy
<

Admin and Stats Output: no special error counters seem to be incremented.

Config:

static_resources:
  listeners:
    - name: "ingress_listener"
      address:
        socket_address:
          protocol: "TCP"
          address: "0.0.0.0"
          port_value: 8080
      filter_chains:
        - filters:
            - name: "envoy.http_connection_manager"
              config:
                route_config:
                  name: "ingress_route"
                  virtual_hosts:
                    - name: "virtual_host_service"
                      domains:
                        - "*"
                      routes:
                        - match:
                            prefix: "/"
                          route:
                            cluster: "service"
                http_filters:
                  - name: "envoy.router"

  clusters:
    - name: "service"
      connect_timeout: "0.25s"
      type: "LOGICAL_DNS"
      load_assignment:
        cluster_name: "service"
        endpoints:
          - lb_endpoints:
              - endpoint:
                  address:
                    socket_address:
                      address: "localhost"
                      port_value: 8888

Contributor guide