Skip to content

Commit 8202151

Browse files
init codebase
0 parents  commit 8202151

13 files changed

+738
-0
lines changed

Dockerfile

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
FROM scratch
2+
3+
COPY main.wasm ./plugin.wasm

Makefile

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
goimports := golang.org/x/tools/cmd/[email protected]
2+
golangci_lint := github.com/golangci/golangci-lint/cmd/[email protected]
3+
4+
5+
.PHONY: build.example
6+
build.example:
7+
@find ./examples -type f -name "main.go" | grep ${name}\
8+
| xargs -I {} bash -c 'dirname {}' \
9+
| xargs -I {} bash -c 'cd {} && tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go'
10+
11+
12+
.PHONY: build.examples
13+
build.examples:
14+
@find ./examples -mindepth 1 -type f -name "main.go" \
15+
| xargs -I {} bash -c 'dirname {}' \
16+
| xargs -I {} bash -c 'cd {} && tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go'
17+
18+
.PHONY: test
19+
test:
20+
@go test $(shell go list ./... | grep -v e2e)
21+
@go test -tags "proxywasm_timing" ./proxywasm/proxytest
22+
23+
.PHONY: test.examples
24+
test.examples:
25+
@find ./examples -mindepth 1 -type f -name "main.go" \
26+
| xargs -I {} bash -c 'dirname {}' \
27+
| xargs -I {} bash -c 'cd {} && go test ./...'
28+
29+
.PHONY: test.e2e
30+
test.e2e:
31+
@go test -v ./e2e -count=1
32+
33+
.PHONY: test.e2e.single
34+
test.e2e.single:
35+
@go test -v ./e2e -run '/${name}' -count=1
36+
37+
.PHONY: run
38+
run:
39+
@envoy -c ./examples/${name}/envoy.yaml --concurrency 2 --log-format '%v'
40+
41+
.PHONY: lint
42+
lint:
43+
@go run $(golangci_lint) run
44+
45+
.PHONY: format
46+
format:
47+
@find . -type f -name '*.go' | xargs gofmt -s -w
48+
@for f in `find . -name '*.go'`; do \
49+
awk '/^import \($$/,/^\)$$/{if($$0=="")next}{print}' $$f > /tmp/fmt; \
50+
mv /tmp/fmt $$f; \
51+
done
52+
@go run $(goimports) -w -local github.com/tetratelabs/proxy-wasm-go-sdk `find . -name '*.go'`
53+
54+
.PHONY: check
55+
check:
56+
@$(MAKE) format
57+
@go mod tidy
58+
@if [ ! -z "`git status -s`" ]; then \
59+
echo "The following differences will fail CI until committed:"; \
60+
git diff --exit-code; \
61+
fi
62+
63+
# Build docker images of *compat* variant of Wasm Image Specification with built example binaries,
64+
# and push to ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples.
65+
# See https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md for details.
66+
# Only-used in github workflow on the main branch, and not for developers.
67+
.PHONY: wasm_image.build_push
68+
wasm_image.build_push:
69+
@for f in `find ./examples -type f -name "main.go"`; do \
70+
name=`echo $$f | sed -e 's/\\//-/g' | sed -e 's/\.-examples-//g' -e 's/\-main\.go//g'` ; \
71+
ref=ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples:$$name; \
72+
docker build -t $$ref . -f examples/wasm-image.Dockerfile --build-arg WASM_BINARY_PATH=$$(dirname $$f)/main.wasm; \
73+
docker push $$ref; \
74+
done
75+
76+
# Build OCI images of *compat* variant of Wasm Image Specification with built example binaries,
77+
# and push to ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples.
78+
# See https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md for details.
79+
# Only-used in github workflow on the main branch, and not for developers.
80+
# Requires "buildah" CLI.
81+
.PHONY: wasm_image.build_push_oci
82+
wasm_image.build_push_oci:
83+
@for f in `find ./examples -type f -name "main.go"`; do \
84+
name=`echo $$f | sed -e 's/\\//-/g' | sed -e 's/\.-examples-//g' -e 's/\-main\.go//g'` ; \
85+
ref=ghcr.io/tetratelabs/proxy-wasm-go-sdk-examples:$$name-oci; \
86+
buildah bud -f examples/wasm-image.Dockerfile --build-arg WASM_BINARY_PATH=$$(dirname $$f)/main.wasm -t $$ref .; \
87+
buildah push $$ref; \
88+
done
89+
90+
.PHONY: tidy
91+
tidy: ## Runs go mod tidy on every module
92+
@find . -name "go.mod" \
93+
| grep go.mod \
94+
| xargs -I {} bash -c 'dirname {}' \
95+
| xargs -I {} bash -c 'echo "=> {}"; cd {}; go mod tidy -v; '

README.md

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# JSON Payload validation
2+
3+
This wasm plugin checks whether the request has JSON payload and has required keys in it.
4+
If not, the wasm plugin ceases the further process of the request and returns 403 immediately.
5+
6+
## Run it via Envoy
7+
8+
`envoy.yaml` is the example envoy config file that you can use for running the wasm plugin
9+
with standalone Envoy.
10+
11+
Envoy listens on `localhost:18000`, responding to any requests with static content "hello from server".
12+
However, the wasm plugin also runs to validate the requests' payload.
13+
14+
```bash
15+
make -C ../.. run name=json_validation
16+
```
17+
18+
The plugin intercepts the request and makes Envoy return 403 instead of the static content
19+
if the request has no JSON payload or the payload JSON doesn't have "id" or "token" keys.
20+
21+
```console
22+
# Returns the normal response when the request has the required keys, id and token.
23+
curl -X POST localhost:18000 -H 'Content-Type: application/json' --data '{"id": "xxx", "token": "xxx"}'
24+
hello from the server
25+
26+
# Returns 403 when the request has missing required keys.
27+
curl -v -X POST localhost:18000 -H 'Content-Type: application/json' --data '"required_keys_missing"'
28+
Note: Unnecessary use of -X or --request, POST is already inferred.
29+
* Trying 127.0.0.1:18000...
30+
* TCP_NODELAY set
31+
* Connected to localhost (127.0.0.1) port 18000 (#0)
32+
> POST / HTTP/1.1
33+
> Host: localhost:18000
34+
> User-Agent: curl/7.68.0
35+
> Accept: */*
36+
> Content-Type: application/json
37+
> Content-Length: 23
38+
>
39+
* upload completely sent off: 23 out of 23 bytes
40+
* Mark bundle as not supporting multiuse
41+
< HTTP/1.1 403 Forbidden
42+
< content-length: 15
43+
< content-type: text/plain
44+
< date: Tue, 01 Mar 2022 19:22:24 GMT
45+
< server: envoy
46+
<
47+
* Connection #0 to host localhost left intact
48+
invalid payload
49+
```
50+
51+
### Run it via Istio
52+
53+
This example details deploying to a kind cluster running the Istio httpbin sample app.
54+
55+
```console
56+
# Create a test cluster
57+
kind create cluster
58+
59+
# Install Istio and the httpbin sample app
60+
61+
istioctl install --set profile=demo -y
62+
kubectl label namespace default istio-injection=enabled
63+
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.12/samples/httpbin/httpbin.yaml
64+
kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.12/samples/httpbin/httpbin-gateway.yaml
65+
```
66+
67+
For Istio 1.12 and later the easiest way is to use a WasmPlugin resource. For older Istio
68+
versions an EnvoyFilter is needed.
69+
70+
#### Install using WasmPlugin resource
71+
72+
Build and push the wasm module to your container registry, then apply the WasmPlugin.
73+
74+
```console
75+
export HUB=your_registry # e.g. docker.io/tetrate
76+
make -C ../.. build.example name=json_validation
77+
docker build . -t ${HUB}/json-validation:v1
78+
docker push ${HUB}/json-validation:v1
79+
80+
sed "s|YOUR_CONTAINER_REGISTRY|$HUB|" wasmplugin.yaml | kubectl apply -f -
81+
```
82+
83+
#### Install using EnvoyFilter
84+
85+
To use an EnvoyFilter, create a config map containing the compiled wasm plugin, mount the config
86+
map into the gateway pod, and then configure Envoy via an EnvoyFilter to load the wasm plugin from
87+
a local file.
88+
89+
```console
90+
# Create the config map
91+
kubectl -n istio-system create configmap wasm-plugins --from-file=main.wasm
92+
93+
# Patch the gateway deployment to mount the config map
94+
kubectl -n istio-system patch deployment istio-ingressgateway --patch-file=gatewaydeploymentpatch.yaml
95+
96+
# Create the EnvoyFilter
97+
kubectl apply -f envoyfilter.yaml
98+
```
99+
100+
#### Test the plugin
101+
102+
Expose the ingress gateway on port 8080 on your local machine via.
103+
104+
```console
105+
kubectl port-forward -n istio-system svc/istio-ingressgateway 8080:80
106+
```
107+
108+
Requests without the required payload will fail:
109+
110+
```console
111+
% curl -i http://localhost:8080/post -H 'Content-Type: application/json' --data '{"id": "xxx", "not_token": "xxx"}'
112+
HTTP/1.1 403 Forbidden
113+
content-length: 15
114+
content-type: text/plain
115+
date: Tue, 15 Mar 2022 23:05:52 GMT
116+
server: istio-envoy
117+
118+
invalid payload
119+
```
120+
121+
But those with the payload will proceed:
122+
123+
```console
124+
% curl -i http://localhost:8080/post -H 'Content-Type: application/json' --data '{"id": "xxx", "token": "xxx"}'
125+
HTTP/1.1 200 OK
126+
server: istio-envoy
127+
date: Tue, 15 Mar 2022 23:06:29 GMT
128+
content-type: application/json
129+
content-length: 884
130+
access-control-allow-origin: *
131+
access-control-allow-credentials: true
132+
x-envoy-upstream-service-time: 3
133+
134+
{
135+
"args": {},
136+
"data": "{\"id\": \"xxx\", \"token\": \"xxx\"}",
137+
"files": {},
138+
"form": {},
139+
"headers": {
140+
"Accept": "*/*",
141+
"Content-Length": "29",
142+
"Content-Type": "application/json",
143+
"Host": "localhost:8080",
144+
"User-Agent": "curl/7.64.1",
145+
"X-B3-Parentspanid": "99a94908edd26592",
146+
"X-B3-Sampled": "1",
147+
"X-B3-Spanid": "e12fc7fd9aa74838",
148+
"X-B3-Traceid": "2b7375cda8bc98a299a94908edd26592",
149+
"X-Envoy-Attempt-Count": "1",
150+
"X-Envoy-Internal": "true",
151+
"X-Forwarded-Client-Cert": "By=spiffe://cluster.local/ns/default/sa/httpbin;Hash=5703a66dcdbc8cafc8c29e1ebee1174f4bc81234d8dc1ccc20fb9e3c26b320e1;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
152+
},
153+
"json": {
154+
"id": "xxx",
155+
"token": "xxx"
156+
},
157+
"origin": "10.244.0.9",
158+
"url": "http://localhost:8080/post"
159+
}
160+
```

build.sh

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/bash
2+
3+
tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go

envoy.yaml

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# This config has Envoy listen on localhost:18000, responding to any requests with static content "hello from server".
2+
# In addition, the example wasm plugin to validate the requests payload runs.
3+
# The plugin intercepts the request and makes Envoy return 403 instead of the static content
4+
# if the request has no JSON payload or the payload JSON doesn't have "id" or "token" keys.
5+
static_resources:
6+
listeners:
7+
- name: main
8+
address:
9+
socket_address:
10+
address: 0.0.0.0
11+
port_value: 18000
12+
filter_chains:
13+
- filters:
14+
- name: envoy.http_connection_manager
15+
typed_config:
16+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
17+
stat_prefix: ingress_http
18+
codec_type: auto
19+
route_config:
20+
name: local_route
21+
virtual_hosts:
22+
- name: local_service
23+
domains:
24+
- "*"
25+
routes:
26+
- match:
27+
prefix: "/"
28+
route:
29+
cluster: web_service
30+
31+
http_filters:
32+
- name: envoy.filters.http.wasm
33+
typed_config:
34+
"@type": type.googleapis.com/udpa.type.v1.TypedStruct
35+
type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
36+
value:
37+
config:
38+
configuration:
39+
"@type": type.googleapis.com/google.protobuf.StringValue
40+
value: |
41+
{ "requiredKeys": ["id", "token"] }
42+
vm_config:
43+
runtime: "envoy.wasm.runtime.v8"
44+
code:
45+
local:
46+
filename: "./examples/json_validation/main.wasm"
47+
- name: envoy.filters.http.router
48+
typed_config:
49+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
50+
51+
- name: staticreply
52+
address:
53+
socket_address:
54+
address: 127.0.0.1
55+
port_value: 8099
56+
filter_chains:
57+
- filters:
58+
- name: envoy.http_connection_manager
59+
typed_config:
60+
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
61+
stat_prefix: ingress_http
62+
codec_type: auto
63+
route_config:
64+
name: local_route
65+
virtual_hosts:
66+
- name: local_service
67+
domains:
68+
- "*"
69+
routes:
70+
- match:
71+
prefix: "/"
72+
direct_response:
73+
status: 200
74+
body:
75+
inline_string: "hello from the server\n"
76+
http_filters:
77+
- name: envoy.filters.http.router
78+
typed_config:
79+
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
80+
81+
clusters:
82+
- name: web_service
83+
connect_timeout: 0.25s
84+
type: STATIC
85+
lb_policy: ROUND_ROBIN
86+
load_assignment:
87+
cluster_name: mock_service
88+
endpoints:
89+
- lb_endpoints:
90+
- endpoint:
91+
address:
92+
socket_address:
93+
address: 127.0.0.1
94+
port_value: 8099
95+
96+
admin:
97+
access_log_path: "/dev/null"
98+
address:
99+
socket_address:
100+
address: 0.0.0.0
101+
port_value: 8001

envoyfilter.yaml

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
apiVersion: networking.istio.io/v1alpha3
2+
kind: EnvoyFilter
3+
metadata:
4+
name: json-validation
5+
namespace: istio-system
6+
spec:
7+
configPatches:
8+
- applyTo: HTTP_FILTER
9+
match:
10+
context: GATEWAY
11+
patch:
12+
operation: INSERT_BEFORE
13+
value:
14+
name: json-validation
15+
typed_config:
16+
"@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm
17+
config:
18+
configuration:
19+
"@type": type.googleapis.com/google.protobuf.StringValue
20+
value: |
21+
{ "requiredKeys": ["id", "token"] }
22+
vm_config:
23+
code:
24+
local:
25+
filename: /var/local/lib/wasm-plugins/main.wasm
26+
runtime: envoy.wasm.runtime.v8
27+
vm_id: json-validation

0 commit comments

Comments
 (0)