A really basic implementation of envoy External Processing Filter. This capability allows you to define an external gRPC server which can selectively process headers and payload/body of requests (see External Processing Filter PRD. Basically, your own unrestricted filter.
ext_proc
^
|
client -> envoy -> upstream
NOTE, this filter is really early and has a lot of features to implement!
- Source: ext_proc.cc
All we will demonstrate in this repo is the most basic functionality: manipulate headers and body-content on the request/response. I know, there are countless other ways to do this with envoy but just as a demonstration of writing the external gRPC server that this functionality uses. If interested, pls read on:
The scenario is like this
A) Manipulate outbound headers and body
ext_proc (delete specific header from client to upstream; append body content sent to upstream)
^
|
client -> envoy -> upstream
B) Manipulate response headers and body
ext_proc (delete specific header from upstream to client; append body content sent to client)
^
|
client <- envoy <- upstream
Specifically for (A), if a header key= "user" is sent by the client AND if the request is a POST, the external processing filter will
- redact that header
- append 'foo' to the body and send that to
httpbin.org/post
If A is triggered, the couple of headers from httpbin are removed and the content type is set to text. Finally, the response body has the text qux appended to it.
If the request type is GET or if the header 'user' is not present no modifications are made
First this code was just committed in PR 14385 so we will need envoy from latest:
docker cp `docker create envoyproxy/envoy:v1.33-latest`:/usr/local/bin/envoy /tmpNow start the external gRPC server
go run grpc_server.goThis will start the gRPC server which will receive the requests from envoy.
Now start envoy
./envoy -c server.yaml -l debugThe config w'ere using allows the external processor to progressively override headers sent over
http_filters:
- name: envoy.filters.http.ext_proc
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.ext_proc.v3.ExternalProcessor
failure_mode_allow: false
allow_mode_override: true
allowed_override_modes:
- request_header_mode: "SEND"
response_header_mode: "SEND"
request_body_mode: "BUFFERED"
response_body_mode: "BUFFERED"
request_trailer_mode: "SKIP"
response_trailer_mode: "SKIP"
processing_mode:
request_header_mode: "SEND"
response_header_mode: "SEND"
request_body_mode: "BUFFERED"
response_body_mode: "BUFFERED"
request_trailer_mode: "SKIP"
response_trailer_mode: "SKIP"
grpc_service:
envoy_grpc:
cluster_name: ext_proc_clusterSend in some requests
note, in each of tests, the upstream is
httpbin.orgwhich will just echo back the inbound request to the caller and display the headers and body it got back as the json response (i.,e the json response below is what httpbin saw)
- GET Request
In this case, we should not expect any modifications to take place.
$ curl -v -H "host: http.domain.com" --resolve http.domain.com:8080:127.0.0.1 http://http.domain.com:8080/get
> GET /get HTTP/1.1
> Host: http.domain.com
> User-Agent: curl/7.74.0
> Accept: */*
< HTTP/1.1 200 OK
< date: Wed, 31 Mar 2021 16:59:50 GMT
< content-type: application/json
< content-length: 311
< server: envoy
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 31
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "http.domain.com",
"User-Agent": "curl/7.74.0",
"X-Amzn-Trace-Id": "Root=1-6064aa86-1e8e99652e2c7ee003a2750f",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000"
},
"origin": "108.51.98.171",
"url": "https://http.domain.com/get"
}- GET Request with user header
Here we're also not expecting changes
$ curl -v -H "host: http.domain.com" --resolve http.domain.com:8080:127.0.0.1 -H "user: sal" http://http.domain.com:8080/get
> GET /get HTTP/1.1
> Host: http.domain.com
> User-Agent: curl/7.74.0
> Accept: */*
> user: sal
< HTTP/1.1 200 OK
< date: Wed, 31 Mar 2021 17:00:37 GMT
< content-type: application/json
< content-length: 331
< server: envoy
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 24
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "http.domain.com",
"User": "sal",
"User-Agent": "curl/7.74.0",
"X-Amzn-Trace-Id": "Root=1-6064aab5-1c5e1204091c69600d45b6ba",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000"
},
"origin": "108.51.98.171",
"url": "https://http.domain.com/get"
}- POST Request with user header
In this case,we send in a POST but no user header so also no difference
$ curl -v -H "host: http.domain.com" -H "content-type: text/plain" --resolve http.domain.com:8080:127.0.0.1 -d 'foo' http://http.domain.com:8080/post
> POST /post HTTP/1.1
> Host: http.domain.com
> User-Agent: curl/7.74.0
> Accept: */*
> content-type: text/plain
> Content-Length: 3
< HTTP/1.1 200 OK
< date: Wed, 31 Mar 2021 17:03:06 GMT
< content-type: application/json
< content-length: 441
< server: envoy
< access-control-allow-origin: *
< access-control-allow-credentials: true
< x-envoy-upstream-service-time: 8
{
"args": {},
"data": "foo",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "3",
"Content-Type": "text/plain",
"Host": "http.domain.com",
"User-Agent": "curl/7.74.0",
"X-Amzn-Trace-Id": "Root=1-6064ab4a-6df7e0d437a8ad2637c35fce",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000"
},
"json": null,
"origin": "108.51.98.171",
"url": "https://http.domain.com/post"
}- Finally,
We send a post request and the 'user' header below
What happens is that the external processing filter will
- In
*pb.ProcessingRequest_RequestHeaders,
- detect and remove
userheader - instruct further processing of the request body
- In
*pb.ProcessingRequest_RequestBody,
- append
barto the inbound request body - add a new header
added-header
- In
*pb.ProcessingRequest_ResponseHeaders,
- remove the following headers sent by httpbin:
"access-control-allow-origin", "access-control-allow-credentials" - update the content-length value by addin in the byte-length contained in the data we're going to later add to the body (i.e, add by #bytes in
qux)
- In
*pb.ProcessingRequest_ResponseBody
- Append
quxto the response body sent by httpbin
$ curl -v -H "host: http.domain.com" -H "content-type: text/plain" \
--resolve http.domain.com:8080:127.0.0.1 -H "user: sal" -d 'foo' http://http.domain.com:8080/post
> POST /post HTTP/1.1
> Host: http.domain.com
> User-Agent: curl/8.14.1
> Accept: */*
> content-type: text/plain
> user: sal
> Content-Length: 3
* upload completely sent off: 3 bytes
< HTTP/1.1 200 OK
< date: Fri, 04 Jul 2025 13:12:11 GMT
< server: envoy
< x-envoy-upstream-service-time: 177
< content-type: text/plain
< content-length: 490
{
"args": {},
"data": "foo baaar ",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Added-Header": "my new header",
"Content-Length": "10",
"Content-Type": "text/plain",
"Host": "http.domain.com",
"User-Agent": "curl/8.14.1",
"X-Amzn-Trace-Id": "Root=1-6867d32b-600054140d75cd4d13d6dfc6",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000"
},
"json": null,
"origin": "66.44.26.235",
"url": "https://http.domain.com/post"
}
quxThats it, i'll be adding on more features as they become available to this repo.
docker build --tag extproc .
docker run -it -p 18080:18080 extproc
Other reference envoy samples