Bringing WAF to Envoy and Istio with WebAssembly and... Go?
Anuraag (ラグ) Agrawal - tetrate.io
Self-introduction
Allow developers to solve real problems with WebAssembly without creating more
Drink a new type of craft beer
Envoy
Web Application Firewall (WAF)
One rule
SecRule REQUEST_LINE|ARGS|ARGS_NAMES|REQUEST_COOKIES|REQUEST_COOKIES_NAMES|REQUEST_HEADERS|XML:/*|XML://@* "@rx (?i)(?:\$|$?)(?:\{|&(?:lbrace|lcub);?)(?:[^}]{0,15}(?:\$|$?)(?:\{|&(?:lbrace|lcub);?)|(?:jndi|ctx))" \
"id:944150,\
phase:2,\
block,\
t:none,t:urlDecodeUni,t:jsDecode,t:htmlEntityDecode,\
log,\
msg:'Potential Remote Command Execution: Log4j / Log4shell',\
tag:'application-multi',\
tag:'language-java',\
tag:'platform-multi',\
tag:'attack-rce',\
tag:'OWASP_CRS',\
tag:'capec/1000/152/137/6',\
tag:'PCI/6.5.2',\
tag:'paranoia-level/1',\
ver:'OWASP_CRS/4.0.0-rc1',\
severity:'CRITICAL',\
setvar:'tx.rce_score=+%{tx.critical_anomaly_score}',\
setvar:'tx.inbound_anomaly_score_pl1=+%{tx.critical_anomaly_score}'"
Language Reference
Why WAF?
Why Envoy WAF?
WebAssembly
WebAssembly
WebAssembly is not Assembly
Common hardware are register machines, not stack machines
WebAssembly bytecode
C source code | WebAssembly .wat text format | WebAssembly .wasm binary format |
int factorial(int n) { if (n == 0) return 1; else return n * factorial(n-1); } | (func (param i64) (result i64) local.get 0 i64.eqz if (result i64) i64.const 1 else local.get 0 local.get 0 i64.const 1 i64.sub call 0 i64.mul end) | 00 61 73 6D 01 00 00 00 01 00 01 60 01 73 01 73 06 03 00 01 00 02 0A 00 01 00 00 20 00 50 04 7E 42 01 05 20 00 20 00 42 01 7D 10 00 7E 0B 0B 15 17 |
So not fast?
WebAssembly has a bias but is not only for web
What is WebAssembly?
Compile existing binary for loading in web (not as fast as native but maybe enough)
Allow extending apps in a safe way (or only way, e.g. Go)
Host
WebAssembly Runtime
Sandbox Memory
Compiled wasm
str
ld
Wasm binary
Read
Compile
Host function
Guest function
What is an ABI?
What is a WebAssembly ABI
ABI parts
proxy-wasm ABI
proxy-wasm-go-sdk
https://github.com/tetratelabs/proxy-wasm-go-sdk
WAF Flow
tx := waf.NewTransaction()
tx.ProcessConnection(clientIP, clientPort)
tx.ProcessURI(url, method)
for header := range request.Header { tx.AddRequestHeader(header) }
if tx.ProcessRequestHeaders() { response.WriteStatus(403); return }
tx.RequestBodyWriter().Write(request.Body)
If tx.ProcessRequestBody() { response.WriteStatus(403); return }
// Repeat for response
WAF Flow in Envoy
proxy_on_http_request_headers:
tx := waf.NewTransaction()
tx.ProcessConnection(clientIP, clientPort)
tx.ProcessURI(url, method)
for header := range request.Header { tx.AddRequestHeader(header) }
if tx.ProcessRequestHeaders() { response.WriteStatus(403); return }
process_on_http_request_body:
tx.RequestBodyWriter().Write(request.Body)
If tx.ProcessRequestBody() { response.WriteStatus(403); return }
process_on_http_response_headers: / process_on_http_response_body:
// Repeat for response
coraza-proxy-wasm
Overhead
WAF:
All done 1000 calls (plus 0 warmup) 4.487 ms avg, 99.9 qps
No WAF:
All done 1000 calls (plus 0 warmup) 2.900 ms avg, 100.0 qps
Polyglot binary
TinyGo compiler more for embedded systems than Wasm. Swapped components for performance
Now possible to write high performance Wasm with Go
Try it now
ghcr.io/corazawaf/coraza-proxy-wasm:main
https://istio.io/latest/docs/reference/config/proxy_extensions/wasm-plugin/
Future
https://github.com/mosn/mosn/pull/2168
https://github.com/dapr/components-contrib/blob/master/middleware/http/wasm/httpwasm.go
One WAF Wasm binary for all the servers
Towards a future where experts work in one language and apply to all
Real-time WAF in wasm with Go (and others)