Envoy as an abstract layer for services

Rajith Karunarathne
API Integration Essentials
3 min readFeb 17, 2019

Envoy has been gaining lot of popularity recently due to its ability to adapt itself to cater the requirements of the micro services architecture (MSA). Envoy has become an integral part when creating a data plane of modern service meshes. In most cases envoy act as an edge proxy(network abstraction) for all the services, it runs as a side car proxy for the services. But envoy can be improved as an complete abstract layer for services.

We can abstract anything from the services that could be abstracted away and can be built into envoy. Envoy functionality can be extended in order to provide authentication, authorization, rate limiting analytics and etc. These are the key features provided by modern cloud native API gateways as well as legacy API gateways. The basic idea is that envoy can be used to built a fully fledged API gateway.

In this article I will be focusing on how we can abstract the authentication for services by enabling envoy to authorize with external auth server. Envoy can be extended using http filters (Application level filters, and also it has the capability write network level filters as well). I have used the capability of writing inline lua scripts in order perform authentication with external authentication server.

The “envoy_on_request” function of lua http filters can be used to intercepts the request and to perform additional checks. I have written inline lua script to call an authorization server before sending the request to actual back end.If authentication is failed, then request will be rejected.

In the following sample, I have added my authentication server as an upstream in the envoy configuration and performs http call to introspect endpoint of authentication server in order to validate the token provided by the client. (I have used wso2 Identity server as my auth server, to request tokens and validate tokens)

static_resources:
listeners:
- address:
socket_address:
address: 0.0.0.0
port_value: 8000
filter_chains:
- filters:
- name: envoy.http_connection_manager
config:
stat_prefix: ingress_http
access_log:
- config:
path: "/home/rajith/envoy/sample/access.log"
name: envoy.file_access_log
route_config:
name: local_route
virtual_hosts:
- name: backend
domains:
- "*"
routes:
- match:
prefix: "/"
route:
cluster: service_hello
http_filters:
- name: envoy.lua
config:
inline_code: |
function envoy_on_request(request_handle)
local oauth = request_handle:headers():get("Authorization")
words = {}
for word in oauth:gmatch("%S+") do table.insert(words, word) end
payload = "token=" .. words[2]
local headers, body = request_handle:httpCall(
"token",
{
[":method"] = "POST",
[":path"] = "/oauth2/introspect",
[":authority"] = "token"
},
payload,
5000)
local activeString
for word in body:gmatch('([^,]+)')
do
if string.match(word, "active") then
activeString = word
end
end
local resVal = {}
for word in activeString:gmatch("([^:]+)") do table.insert(resVal, word) end
if string.match(resVal[2], "false") then
request_handle:respond(
{[":status"] = "401"},
"Token Expired")
end
end
function envoy_on_response(response_handle)
body_size = response_handle:body():length()
response_handle:headers():add("response-body-size", tostring(body_size))
end
- name: envoy.router
clusters:
- name: service_hello
connect_timeout: 5s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 9090
- name: token
connect_timeout: 5s
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
lb_policy: ROUND_ROBIN
hosts:
- socket_address:
address: 127.0.0.1
port_value: 9763
admin:
access_log_path: "/dev/null"
address:
socket_address:
address: 0.0.0.0
port_value: 8001

The request will be sent to the actual back end by envoy only if valid token is provided by the client.

Note: The additional steps in the lua script is required to extract the token validity from the json response sent by auth server. Please forgive me for my bad coding in lua :)

--

--