This is an automated email from the ASF dual-hosted git repository.
spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new e59bd91 docs(wasm): add forward-auth example (#6178)
e59bd91 is described below
commit e59bd91b9b041a3e663aa7c637f373a0d46222d8
Author: 罗泽轩 <[email protected]>
AuthorDate: Sun Jan 23 19:29:00 2022 +0800
docs(wasm): add forward-auth example (#6178)
---
.github/workflows/build.yml | 2 +-
docs/en/latest/wasm.md | 8 ++
t/cli/test_admin.sh | 2 +-
t/wasm/forward-auth.go | 219 +++++++++++++++++++++++++++++++++++++
t/wasm/forward-auth.t | 257 ++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 486 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index a99778a..5e5891e 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -84,7 +84,7 @@ jobs:
export TINYGO_VER=0.20.0
wget
https://github.com/tinygo-org/tinygo/releases/download/v${TINYGO_VER}/tinygo_${TINYGO_VER}_amd64.deb
2>/dev/null
sudo dpkg -i tinygo_${TINYGO_VER}_amd64.deb
- cd t/wasm && find . -type f -name "main.go" | xargs -Ip tinygo build
-o p.wasm -scheduler=none -target=wasi p
+ cd t/wasm && find . -type f -name "*.go" | xargs -Ip tinygo build -o
p.wasm -scheduler=none -target=wasi p
- name: Linux Before install
run: sudo ./ci/${{ matrix.os_name }}_runner.sh before_install
diff --git a/docs/en/latest/wasm.md b/docs/en/latest/wasm.md
index 31e9ca4..bc55022 100644
--- a/docs/en/latest/wasm.md
+++ b/docs/en/latest/wasm.md
@@ -102,3 +102,11 @@ Here is the mapping between Proxy WASM callbacks and
APISIX's phases:
For example, when the first request hits the route which has WASM plugin
configured.
* `proxy_on_http_request_headers`: run in the access/rewrite phase, depends on
the configuration of `http_request_phase`.
* `proxy_on_http_response_headers`: run in the header_filter phase.
+
+## Example
+
+We have reimplemented some Lua plugin via Wasm, under `t/wasm/` of this repo:
+
+* fault-injection
+* forward-auth
+* response-rewrite
diff --git a/t/cli/test_admin.sh b/t/cli/test_admin.sh
index ecfbffc..0ab1c6e 100755
--- a/t/cli/test_admin.sh
+++ b/t/cli/test_admin.sh
@@ -339,4 +339,4 @@ fi
make stop
-echo "pass: ccept changes to /apisix/plugins successfully"
+echo "pass: accept changes to /apisix/plugins successfully"
diff --git a/t/wasm/forward-auth.go b/t/wasm/forward-auth.go
new file mode 100644
index 0000000..10eaada
--- /dev/null
+++ b/t/wasm/forward-auth.go
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package main
+
+import (
+ "net/url"
+ "strconv"
+ "strings"
+
+ "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm"
+ "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types"
+ "github.com/valyala/fastjson"
+)
+
+func main() {
+ proxywasm.SetVMContext(&vmContext{})
+}
+
+type vmContext struct {
+ types.DefaultVMContext
+}
+
+func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext {
+ return &pluginContext{
+ contextID: contextID,
+ upstreamHeaders: map[string]struct{}{},
+ clientHeaders: map[string]struct{}{},
+ requestHeaders: map[string]struct{}{},
+ }
+}
+
+type pluginContext struct {
+ types.DefaultPluginContext
+ contextID uint32
+
+ host string
+ path string
+ scheme string
+ upstreamHeaders map[string]struct{}
+ clientHeaders map[string]struct{}
+ requestHeaders map[string]struct{}
+ timeout uint32
+}
+
+func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int)
types.OnPluginStartStatus {
+ data, err := proxywasm.GetPluginConfiguration()
+ if err != nil {
+ proxywasm.LogErrorf("error reading plugin configuration: %v",
err)
+ return types.OnPluginStartStatusFailed
+ }
+
+ var p fastjson.Parser
+ v, err := p.ParseBytes(data)
+ if err != nil {
+ proxywasm.LogErrorf("erorr decoding plugin configuration: %v",
err)
+ return types.OnPluginStartStatusFailed
+ }
+
+ ctx.timeout = uint32(v.GetUint("timeout"))
+ if ctx.timeout == 0 {
+ ctx.timeout = 3000
+ }
+
+ // schema check
+ if ctx.timeout < 1 || ctx.timeout > 60000 {
+ proxywasm.LogError("bad timeout")
+ return types.OnPluginStartStatusFailed
+ }
+
+ s := string(v.GetStringBytes("uri"))
+ if s == "" {
+ proxywasm.LogError("bad uri")
+ return types.OnPluginStartStatusFailed
+ }
+
+ uri, err := url.Parse(s)
+ if err != nil {
+ proxywasm.LogErrorf("bad uri: %v", err)
+ return types.OnPluginStartStatusFailed
+ }
+
+ ctx.host = uri.Host
+ ctx.path = uri.Path
+ ctx.scheme = uri.Scheme
+
+ arr := v.GetArray("upstream_headers")
+ for _, a := range arr {
+
ctx.upstreamHeaders[strings.ToLower(string(a.GetStringBytes()))] = struct{}{}
+ }
+
+ arr = v.GetArray("request_headers")
+ for _, a := range arr {
+ ctx.requestHeaders[string(a.GetStringBytes())] = struct{}{}
+ }
+
+ arr = v.GetArray("client_headers")
+ for _, a := range arr {
+ ctx.clientHeaders[strings.ToLower(string(a.GetStringBytes()))]
= struct{}{}
+ }
+
+ return types.OnPluginStartStatusOK
+}
+
+func (pluginCtx *pluginContext) NewHttpContext(contextID uint32)
types.HttpContext {
+ ctx := &httpContext{contextID: contextID, pluginCtx: pluginCtx}
+ return ctx
+}
+
+type httpContext struct {
+ types.DefaultHttpContext
+ contextID uint32
+ pluginCtx *pluginContext
+}
+
+func (ctx *httpContext) dispatchHttpCall(elem *fastjson.Value) {
+ method, _ := proxywasm.GetHttpRequestHeader(":method")
+ uri, _ := proxywasm.GetHttpRequestHeader(":path")
+ scheme, _ := proxywasm.GetHttpRequestHeader(":scheme")
+ host, _ := proxywasm.GetHttpRequestHeader("host")
+ addr, _ := proxywasm.GetProperty([]string{"remote_addr"})
+
+ pctx := ctx.pluginCtx
+ hs := [][2]string{}
+ hs = append(hs, [2]string{":scheme", pctx.scheme})
+ hs = append(hs, [2]string{"host", pctx.host})
+ hs = append(hs, [2]string{":path", pctx.path})
+ hs = append(hs, [2]string{"X-Forwarded-Proto", scheme})
+ hs = append(hs, [2]string{"X-Forwarded-Method", method})
+ hs = append(hs, [2]string{"X-Forwarded-Host", host})
+ hs = append(hs, [2]string{"X-Forwarded-Uri", uri})
+ hs = append(hs, [2]string{"X-Forwarded-For", string(addr)})
+
+ for k := range pctx.requestHeaders {
+ h, err := proxywasm.GetHttpRequestHeader(k)
+
+ if err != nil && err != types.ErrorStatusNotFound {
+ proxywasm.LogErrorf("httpcall failed: %v", err)
+ return
+ }
+ hs = append(hs, [2]string{k, h})
+ }
+
+ calloutID, err := proxywasm.DispatchHttpCall(pctx.host, hs, nil, nil,
+ pctx.timeout, ctx.httpCallback)
+ if err != nil {
+ proxywasm.LogErrorf("httpcall failed: %v", err)
+ return
+ }
+ proxywasm.LogInfof("httpcall calloutID %d, pluginCtxID %d", calloutID,
ctx.pluginCtx.contextID)
+}
+
+func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool)
types.Action {
+ data, err := proxywasm.GetPluginConfiguration()
+ if err != nil {
+ proxywasm.LogErrorf("error reading plugin configuration: %v",
err)
+ return types.ActionContinue
+ }
+
+ var p fastjson.Parser
+ v, err := p.ParseBytes(data)
+ if err != nil {
+ proxywasm.LogErrorf("erorr decoding plugin configuration: %v",
err)
+ return types.ActionContinue
+ }
+
+ ctx.dispatchHttpCall(v)
+ return types.ActionContinue
+}
+
+func (ctx *httpContext) httpCallback(numHeaders int, bodySize int, numTrailers
int) {
+ hs, err := proxywasm.GetHttpCallResponseHeaders()
+ if err != nil {
+ proxywasm.LogErrorf("callback err: %v", err)
+ return
+ }
+
+ var status int
+ for _, h := range hs {
+ if h[0] == ":status" {
+ status, _ = strconv.Atoi(h[1])
+ }
+
+ if _, ok := ctx.pluginCtx.upstreamHeaders[h[0]]; ok {
+ err := proxywasm.ReplaceHttpRequestHeader(h[0], h[1])
+ if err != nil {
+ proxywasm.LogErrorf("set header failed: %v",
err)
+ }
+ }
+ }
+
+ if status >= 300 {
+ chs := [][2]string{}
+ for _, h := range hs {
+ if _, ok := ctx.pluginCtx.clientHeaders[h[0]]; ok {
+ chs = append(chs, [2]string{h[0], h[1]})
+ }
+ }
+
+ if err := proxywasm.SendHttpResponse(403, chs, nil, -1); err !=
nil {
+ proxywasm.LogErrorf("send http failed: %v", err)
+ return
+ }
+ }
+}
diff --git a/t/wasm/forward-auth.t b/t/wasm/forward-auth.t
new file mode 100644
index 0000000..ed9866b
--- /dev/null
+++ b/t/wasm/forward-auth.t
@@ -0,0 +1,257 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+ plan(skip_all => "apisix-nginx-module not installed");
+} else {
+ plan('no_plan');
+}
+
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+ $block->set_value("no_error_log", "[error]");
+ }
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+ my $extra_yaml_config = <<_EOC_;
+wasm:
+ plugins:
+ - name: wasm-forward-auth
+ priority: 7997
+ file: t/wasm/forward-auth.go.wasm
+_EOC_
+ $block->set_value("extra_yaml_config", $extra_yaml_config);
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: setup route with plugin
+--- config
+ location /t {
+ content_by_lua_block {
+ local datas = {
+ {
+ url = "/apisix/admin/upstreams/u1",
+ data = [[{
+ "nodes": {
+ "127.0.0.1:1984": 1
+ },
+ "type": "roundrobin"
+ }]],
+ },
+ {
+ url = "/apisix/admin/routes/auth",
+ data = [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": [
+ "return function(conf, ctx)
+ local core = require(\"apisix.core\");
+ if core.request.header(ctx,
\"Authorization\") == \"111\" then
+ core.response.exit(200);
+ end
+ end",
+ "return function(conf, ctx)
+ local core = require(\"apisix.core\");
+ if core.request.header(ctx,
\"Authorization\") == \"222\" then
+
core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
+ core.response.exit(200);
+ end
+ end",]] .. [[
+ "return function(conf, ctx)
+ local core = require(\"apisix.core\");
+ if core.request.header(ctx,
\"Authorization\") == \"333\" then
+
core.response.set_header(\"X-User-ID\", \"i-am-an-user\");
+ core.response.exit(401);
+ end
+ end",
+ "return function(conf, ctx)
+ local core = require(\"apisix.core\");
+ if core.request.header(ctx,
\"Authorization\") == \"444\" then
+ local auth_headers = {
+ 'X-Forwarded-Proto',
+ 'X-Forwarded-Method',
+ 'X-Forwarded-Host',
+ 'X-Forwarded-Uri',
+ 'X-Forwarded-For',
+ }
+ for _, k in ipairs(auth_headers) do
+ core.log.warn('get header ',
string.lower(k), ': ', core.request.header(ctx, k))
+ end
+ core.response.exit(403);
+ end
+ end"
+ ]
+ }
+ },
+ "uri": "/auth"
+ }]],
+ },
+ {
+ url = "/apisix/admin/routes/echo",
+ data = [[{
+ "plugins": {
+ "serverless-pre-function": {
+ "phase": "rewrite",
+ "functions": [
+ "return function (conf, ctx)
+ local core = require(\"apisix.core\");
+ core.response.exit(200,
core.request.headers(ctx));
+ end"
+ ]
+ }
+ },
+ "uri": "/echo"
+ }]],
+ },
+ {
+ url = "/apisix/admin/routes/1",
+ data = [[{
+ "plugins": {
+ "wasm-forward-auth": {
+ "conf": "{
+ \"uri\": \"http://127.0.0.1:1984/auth\",
+ \"request_headers\": [\"Authorization\"],
+ \"client_headers\": [\"X-User-ID\"],
+ \"upstream_headers\": [\"X-User-ID\"]
+ }"
+ },
+ "proxy-rewrite": {
+ "uri": "/echo"
+ }
+ },
+ "upstream_id": "u1",
+ "uri": "/hello"
+ }]],
+ },
+ {
+ url = "/apisix/admin/routes/2",
+ data = [[{
+ "plugins": {
+ "wasm-forward-auth": {
+ "conf": "{
+ \"uri\": \"http://127.0.0.1:1984/auth\",
+ \"request_headers\": [\"Authorization\"]
+ }"
+ },
+ "proxy-rewrite": {
+ "uri": "/echo"
+ }
+ },
+ "upstream_id": "u1",
+ "uri": "/empty"
+ }]],
+ },
+ }
+
+ local t = require("lib.test_admin").test
+
+ for _, data in ipairs(datas) do
+ local code, body = t(data.url, ngx.HTTP_PUT, data.data)
+ ngx.say(body)
+ end
+ }
+ }
+--- response_body eval
+"passed\n" x 5
+
+
+
+=== TEST 2: hit route (test request_headers)
+--- request
+GET /hello
+--- more_headers
+Authorization: 111
+--- response_body_like eval
+qr/\"authorization\":\"111\"/
+
+
+
+=== TEST 3: hit route (test upstream_headers)
+--- request
+GET /hello
+--- more_headers
+Authorization: 222
+--- response_body_like eval
+qr/\"x-user-id\":\"i-am-an-user\"/
+
+
+
+=== TEST 4: hit route (test client_headers)
+--- request
+GET /hello
+--- more_headers
+Authorization: 333
+--- error_code: 403
+--- response_headers
+x-user-id: i-am-an-user
+
+
+
+=== TEST 5: hit route (check APISIX generated headers and ignore client
headers)
+--- request
+GET /hello
+--- more_headers
+Authorization: 444
+X-Forwarded-Host: apisix.apache.org
+--- error_code: 403
+--- grep_error_log eval
+qr/get header \S+: \S+/
+--- grep_error_log_out
+get header x-forwarded-proto: http,
+get header x-forwarded-method: GET,
+get header x-forwarded-host: localhost,
+get header x-forwarded-uri: /hello,
+get header x-forwarded-for: 127.0.0.1,
+
+
+
+=== TEST 6: hit route (not send upstream headers)
+--- request
+GET /empty
+--- more_headers
+Authorization: 222
+--- response_body_unlike eval
+qr/\"x-user-id\":\"i-am-an-user\"/
+
+
+
+=== TEST 7: hit route (not send client headers)
+--- request
+GET /empty
+--- more_headers
+Authorization: 333
+--- error_code: 403
+--- response_headers
+!x-user-id