Run WASM at Ingress to Validate, Verify and Transform a request

getenroute.io/yastack.io
ITNEXT
Published in
5 min readApr 30, 2022

--

Introduction

When developers get tools they like, they tend to be more productive. Extensibility in a language of choice provides the convenience of extensibility without learning a new language. WebAssembly runtime executes bytecode, generated by compiling code written in high level languages — javascript, C, C++, Python, Rust or Golang.

We explore WebAssembly support in EnRoute gateway, and how we can load bytecode generated from custom logic written in a language of choice. into the WebAssembly runtime using EnRoute.

We start with a goal to verify a request, validate its schema and transform the request. We use custom code that is compiled and loaded into the WebAssembly runtime, to achieve this

WASM support is a part of EnRoute Community Edition which is free to use without any enterprise license.

What this article covers

The following steps walk through what’s needed to work with a request

  • Install EnRoute with WASM support
  • Install example workload
  • Program EnRoute to make the service externally available
  • Create a WebAssembly filter
  • Enable WebAssembly filter for service
  • Ensure request MAC is validated and transform request to redact sensitive information
  • Ensure JSON schema is validated

Next, we go through the above-mentioned steps in detail. We also look at how to build a WebAssembly image in compat variant which is then loaded into the Envoy WebAssembly runtime by EnRoute

Install EnRoute with WASM support

WASM support is provided in a separate EnRoute image. It can be installed using the following helm command after adding the helm repo and installing the enroute image with wasm tag

helm repo add saaras https://getenroute.io 
helm install enroute-demo saaras/enroute \
--set serviceAccount.create=true \
--create-namespace \
--namespace enroutedemo \
--set images.enrouteService.tag=wasm

Install example workload

kubectl create namespace httpbin kubectl apply -f https://raw.githubusercontent.com/saarasio/enroute/master/helm-chart/httpbin-bin-service.yaml

Program EnRoute to make the service externally available

helm install httpbin-service-policy saaras/service-policy
--set service.name=httpbin
--set service.prefix=/post
--set service.port= 80
--namespace httpbin

Send request to External-IP of LoadBalancer service to verify it works as expected. The getting started guide has details on setting this up.

Get the public IP of EnRoute LoadBalancer service (212.2.241.255) and send a request

curl -X POST -H 'Content-Type: application/json' -d '{}' 212.2.241.255/post{
"args": {},
"data": "{}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "2",
"Content-Type": "application/json",
"Host": "212.2.241.255",
"User-Agent": "curl/7.68.0",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000",
"X-Envoy-External-Address": "192.168.1.5"
},
"json": {},
"origin": "152.70.114.65,192.168.1.5",
"url": "http://212.2.241.255/post"
}

Create a WebAssembly filter

cat <<EOF | kubectl apply -f -
apiVersion: enroute.saaras.io/v1
kind: HttpFilter
metadata:
name: httpbin-wasm-wasmfilter-oci
namespace: httpbin
spec:
httpFilterConfig:
config: |
{
"url" : "oci://saarasio/vvx-json"
}
name: httpbin-wasm-wasmfilter-oci
type: http_filter_wasm
EOF

Enable the WebAssembly filter for service

kubectl edit -n httpbin gatewayhosts.enroute.saaras.io httpbin-80-gatewayhost  5 apiVersion: enroute.saaras.io/v1
6 kind: GatewayHost
7 metadata:
8 annotations:
9 meta.helm.sh/release-name: httpbin-service-policy
10 meta.helm.sh/release-namespace: httpbin
11 creationTimestamp: "2022-04-26T14:56:48Z"
12 generation: 3
13 labels:
14 app: httpbin
15 app.kubernetes.io/managed-by: Helm
16 name: httpbin-80-gatewayhost
17 namespace: httpbin
18 resourceVersion: "3736"
19 uid: b2e39054-a959-40c3-9754-365b518b8ee6
20 spec:
21 routes:
22 - conditions:
23 - prefix: /post
24 filters:
25 - name: httpbin-80-rl2
26 type: route_filter_ratelimit
27 services:
28 - healthCheck:
29 healthyThresholdCount: 3
30 host: hc
31 intervalSeconds: 5
32 path: /
33 timeoutSeconds: 3
34 unhealthyThresholdCount: 3
35 name: httpbin
36 port: 80
37 virtualhost:
38 filters:
39 - name: httpbin-80-luatestfilter
40 type: http_filter_lua
41 - name: httpbin-wasm-wasmfilter-oci
42 type: http_filter_wasm
43 fqdn: '*'

Ensure MAC is verified and request is transformed with personal information redacted

We first create a payload JSON that we send to the server using a POST request. Here is the json payload in a file called payload.json

cat payload.json{
"data": [{
"type": "articles",
"id": "1",
"attributes": {
"title": "JSON:API paints my bikeshed!",
"body": "The shortest article. Ever.",
"created": "2006-01-02 15:04",
"updated": "2011-01-19 22:15",
"dob": "2022-01-19 22:15"
},
"relationships": {
"author": {
"data": {"id": "42", "type": "people"}
}
}
}],
"included": [
{
"type": "people",
"id": "42",
"attributes": {
"name": "John",
"age": 80,
"gender": "male"
}
}
]
}

For verification, we calculate the SHA256 of the payload and compare it to the value we compute and send in the Digest header

cat payload.json | tr -d "\n" | openssl dgst -sha256 -binary | base64h3wEb7jrjmwD8O7TDvOD3WGG23lfnzsfmODcbwLmdlk =

To send a request

curl -vv 212.2.241.255/post -H 'Content-Type: application/json' -H "Digest: sha256=h3wEb7jrjmwD8O7TDvOD3WGG23lfnzsfmODcbwLmdlk=" --data @/home/ubuntu/payload.json{
"args": {},
"data": "{ \"data\": [{ \"type\": \"articles\", \"id\": \"1\", \"attributes\": { \"title\": \"JSON:API paints my bikeshed!\", \"body\": \"The shortest article. Ever.\", \"created\": \"2006-01-02 15:04\", \"updated\": \"2011-01-19 22:15\", \"dob\": \"0000-00-00 00:00\" }, \"relationships\": { \"author\": { \"data\": {\"id\": \"42\", \"type\": \"people\"} } } }], \"included\": [ { \"type\": \"people\", \"id\": \"42\", \"attributes\": { \"name\": \"John\", \"age\": 80, \"gender\": \"male\" } } ]}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "532",
"Content-Type": "application/json",
"Digest": "sha256=h3wEb7jrjmwD8O7TDvOD3WGG23lfnzsfmODcbwLmdlk=",
"Host": "212.2.241.255",
"User-Agent": "curl/7.68.0",
"X-Envoy-Expected-Rq-Timeout-Ms": "15000",
"X-Envoy-External-Address": "192.168.1.5"
},
"json": {
"data": [
{
"attributes": {
"body": "The shortest article. Ever.",
"created": "2006-01-02 15:04",
"dob": "0000-00-00 00:00",
"title": "JSON:API paints my bikeshed!",
"updated": "2011-01-19 22:15"
},
"id": "1",
"relationships": {
"author": {
"data": {
"id": "42",
"type": "people"
}
}
},
"type": "articles"
}
],
"included": [
{
"attributes": {
"age": 80,
"gender": "male",
"name": "John"
},
"id": "42",
"type": "people"
}
]
},
"origin": "152.70.114.65,192.168.1.5",
"url": "http://212.2.241.255/post"
}

The JSON validation checks for presence of a few JSON elements in the payload and the format of certain fields. In the above payload, it checks for type, id, title, body and verifies the format of created and updated files to ensure valid dates are provided.

Validate, Verify, Transform using WASM Demo

Conclusion

WebAssembly provides a flexible extension mechanism to customize request verification, validation and transformation. WebAssembly in a proxy implements the proxy-wasm abi spec, that is implemented by SDKs. The SDKs in different languages provide a mechanism to interface with WebAssembly runtime. We use the go sdk that implements the proxy-wasm abi spec to achieve this.

The code generated in the above step is compiled to WASM using the WASI libc when compiling using tinygo The bytecode generated after the compilation is packaged into a WASM artifact in compat variant that is then loaded into the Envoy WebAssembly runtime.

In the next article we cover how to build a compat variant of an image that can be loaded by EnRoute and details on proxy-wasm ABI that let’s us recieve callbacks for request processing.

Originally published at https://getenroute.io.

--

--