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

Reply via email to