How to Add Header Security to an Istio Application with an Envoy WASM Filter (without changing a single line of code!)

Bryant Hagadorn
4 min readJun 16, 2020

--

The rise of Istio has taken the cloud native world by storm with the ability to add observability, routing, and security directly to your microservice applications without tweaking any bit of application code. All of it is possible with the creation of the dynamic Envoy proxy, which is API-driven and quickly configured. However, changes to the Envoy filters require (1) changes to its code (in C++) and redeployment of a new binary (and resetting your sidecars) or (2) dynamically loading new filters. Until now, option 2 was quite difficult. However a new wave of interest in dynamically loading new filters through Envoy WASM with the advent of technologies such as WASME and the docker-esque WebAssemblyHub has opened a plethora of opportunities to tune the service mesh.

Envoy is the sidecar proxy and magic behind Istio and WASM Filter capabilitiees

Security headers are a common method for layering in security inside of a web application, and best practices are laid out by OWASP’s Project Secure Headers. In this tutorial, I’m going to deploy the Istio BookInfo demo and the WASM filter onto a blank GKE cluster. Using https://securityheaders.com I’m going to measure our success and hopefully upgrade your BookInfo from failing to passing. Currently, adding header security is done manually or more likely through middleware piece of software that is framework specific. For example, Express has helmet, Django has django-csp, PHP has secure-headers, and there are many others out there. The great thing about using a WASM filter is it is language/framework agnostic and works without even altering a single piece of application code!

Prerequisites: A blank Kubernetes cluster and istioctl 1.5.x only. *At the time of this writing, 1.6 is not supported!

GitHub for reference: https://github.com/blhagadorn/kronos/tree/master/example

Step 1: Create your kubernetes cluster and set your local kubectl credentials to that cluster

Step 2: Install the WASME CRD’s

kubectl apply -f https://github.com/solo-io/wasme/releases/latest/download/wasme.io_v1_crds.yaml

Step 3: Install the WASME Operator

kubectl apply -f https://github.com/solo-io/wasme/releases/latest/download/wasme-default.yaml

Step 4: Install Istio on the cluster with istioctl

istioctl manifest apply --set profile=demo

Step 5: Enable sidecar injection in the default namespace

kubectl label namespace default istio-injection=enabled

Step 6: Install the BookInfo Application and BookInfo Gateway

kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.5/samples/bookinfo/platform/kube/bookinfo.yamlkubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.5/samples/bookinfo/networking/bookinfo-gateway.yaml

A simple kubectl get pods should verify (1) that BookInfo has been installed and (2) the Envoy sidecar is attached

➜  kubectl get pods
NAME READY STATUS RESTARTS AGE
details-v1-57b865bb97-sxt7z 2/2 Running 0 59s
productpage-v1-5545944459-89vsv 2/2 Running 0 57s
ratings-v1-64f9dffdf7-ks9mr 2/2 Running 0 59s
reviews-v1-cf8c55bb7-7p7zr 2/2 Running 0 58s
reviews-v2-79c69bc9c9-mshtd 2/2 Running 0 58s
reviews-v3-75d9664847-z2xbm 2/2 Running 0 58s

Step 7: View your application from the browser to be sure it exists, and then check its grade on https://securityheaders.com. This will vary depending on how your cloud provider, but for me on GKE to get the URL of BookInfo I ran these commands:

export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.status.loadBalancer.ingress[0].ip}’)export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.spec.ports[?(@.name=="http2")].port}')export GATEWAY_URL=$INGRESS_HOST:$INGRESS_PORT

After running those my Gateway URL is:

➜  echo $GATEWAY_URL
34.71.43.64:80

The product page should be located at his URL

34.71.43.64:80/productpage

After figuring out my Gateway URL, I plugged the URL into https://securityheaders.com, which results in a dismal F.

See these docs for information unique to your Kubernetes networking setup: Istio Ingress IP and Ports

To upgrade our report (and our security), we are going to use the Kronos filter!

Step 8: Apply the filter to your cluster (file also found here)

cat << EOF | kubectl apply -f -
apiVersion: wasme.io/v1
kind: FilterDeployment
metadata:
name: bookinfo-kronos
spec:
deployment:
istio:
kind: Deployment
filter:
image: webassemblyhub.io/haggs/kronos:latest
EOF

This applies a few out of the box security headers, here’s a quick list from the README in Kronos:

X-XSS_Protection: 1 X-XSS-Protection

X-Frame-Options: SAMEORIGIN X-Frame-Options

X-Content-Type-Options: nosniff X-Content-Type-Options

X-Download-Options: noopen

Strict-Transport-Security: max-age=5184000. Strict Transport Security

Step 9: Rerun the https://securityheaders.com check to see what changed:

After applying Kronos

Congratulations! We have gone from an F to a D

Summary: Without even altering a single line of BookInfo code, we were able to add response headers that made the web page more secure. This was possible through WebAssemblyHub and Envoy filters that dynamically intercept the API request and add custom filters. As always, please feel free to reach out to me with any questions or comments! If you’d like to contribute, please feel free to raise an issue or create a pull request to https://github.com/blhagadorn/kronos.

--

--

Bryant Hagadorn

Kubernetes, security, and other ramblings! I love learning and writing about what I learn. LinkedIn: https://www.linkedin.com/in/bryanthagadorn/