This is an automated email from the ASF dual-hosted git repository.

bzp2010 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 6ce20fc  feat: supprot OPA plugin complex response (#5779)
6ce20fc is described below

commit 6ce20fcd238d207c44915f321d0e183443af0dd2
Author: Zeping Bai <bzp2...@apache.org>
AuthorDate: Wed Dec 15 16:19:26 2021 +0800

    feat: supprot OPA plugin complex response (#5779)
---
 apisix/plugins/opa.lua        | 29 +++++++++++++-
 apisix/plugins/opa/helper.lua | 14 +++----
 ci/linux-ci-init-service.sh   | 11 ------
 ci/pod/docker-compose.yml     |  9 ++++-
 ci/pod/opa/data.json          | 25 ++++++++++++
 ci/pod/opa/example.rego       | 45 ++++++++++++++++++++++
 t/plugin/opa.t                | 88 +++++++++++++++++++++++++++++++++++++++----
 7 files changed, 192 insertions(+), 29 deletions(-)

diff --git a/apisix/plugins/opa.lua b/apisix/plugins/opa.lua
index f33c3d0..cfe0b58 100644
--- a/apisix/plugins/opa.lua
+++ b/apisix/plugins/opa.lua
@@ -18,6 +18,7 @@
 local core   = require("apisix.core")
 local http   = require("resty.http")
 local helper = require("apisix.plugins.opa.helper")
+local type   = type
 
 local schema = {
     type = "object",
@@ -89,13 +90,37 @@ function _M.access(conf, ctx)
     -- parse the results of the decision
     local data, err = core.json.decode(res.body)
 
-    if err then
+    if err or not data then
         core.log.error("invalid response body: ", res.body, " err: ", err)
         return 503
     end
 
     if not data.result then
-        return 403
+        core.log.error("invalid OPA decision format: ", res.body,
+                       " err: `result` field does not exist")
+        return 503
+    end
+
+    local result = data.result
+
+    if not result.allow then
+        if result.headers then
+            core.response.set_header(result.headers)
+        end
+
+        local status_code = 403
+        if result.status_code then
+            status_code = result.status_code
+        end
+
+        local reason = nil
+        if result.reason then
+            reason = type(result.reason) == "table"
+                and core.json.encode(result.reason)
+                or result.reason
+        end
+
+        return status_code, reason
     end
 end
 
diff --git a/apisix/plugins/opa/helper.lua b/apisix/plugins/opa/helper.lua
index 2a8cf94..059ea08 100644
--- a/apisix/plugins/opa/helper.lua
+++ b/apisix/plugins/opa/helper.lua
@@ -34,13 +34,13 @@ end
 
 local function build_http_request(conf, ctx)
     return {
-        scheme = core.request.get_scheme(ctx),
-        method = core.request.get_method(ctx),
-        host   = core.request.get_host(ctx),
-        port   = core.request.get_port(ctx),
-        path   = core.request.get_path(ctx),
-        header = core.request.headers(ctx),
-        query  = core.request.get_uri_args(ctx),
+        scheme  = core.request.get_scheme(ctx),
+        method  = core.request.get_method(ctx),
+        host    = core.request.get_host(ctx),
+        port    = core.request.get_port(ctx),
+        path    = core.request.get_path(ctx),
+        headers = core.request.headers(ctx),
+        query   = core.request.get_uri_args(ctx),
     }
 end
 
diff --git a/ci/linux-ci-init-service.sh b/ci/linux-ci-init-service.sh
index 765c115..6a7ffbb 100755
--- a/ci/linux-ci-init-service.sh
+++ b/ci/linux-ci-init-service.sh
@@ -34,14 +34,3 @@ docker exec -i rmqnamesrv 
/home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic
 
 # prepare vault kv engine
 docker exec -i vault sh -c "VAULT_TOKEN='root' 
VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv"
-
-# prepare OPA env
-curl -XPUT 'http://localhost:8181/v1/policies/example' \
---header 'Content-Type: text/plain' \
---data-raw 'package example
-
-default allow = false
-
-allow {
-  input.request.header["test-header"] == "only-for-test"
-}'
diff --git a/ci/pod/docker-compose.yml b/ci/pod/docker-compose.yml
index b632a59..b5d8062 100644
--- a/ci/pod/docker-compose.yml
+++ b/ci/pod/docker-compose.yml
@@ -402,7 +402,14 @@ services:
     restart: unless-stopped
     ports:
       - 8181:8181
-    command: run -s
+    command: run -s /example.rego /data.json
+    volumes:
+      - type: bind
+        source: ./ci/pod/opa/example.rego
+        target: /example.rego
+      - type: bind
+        source: ./ci/pod/opa/data.json
+        target: /data.json
     networks:
       opa_net:
 
diff --git a/ci/pod/opa/data.json b/ci/pod/opa/data.json
new file mode 100644
index 0000000..3356559
--- /dev/null
+++ b/ci/pod/opa/data.json
@@ -0,0 +1,25 @@
+{
+    "users": {
+        "alice": {
+            "headers": {
+                "Location": "http://example.com/auth";
+            },
+            "status_code": 302
+        },
+        "bob": {
+            "headers": {
+                "test": "abcd",
+                "abcd": "test"
+            }
+        },
+        "carla": {
+            "reason": "Give you a string reason"
+        },
+        "dylon": {
+            "reason": {
+                "code": 40001,
+                "desc": "Give you a object reason"
+            }
+        }
+    }
+}
diff --git a/ci/pod/opa/example.rego b/ci/pod/opa/example.rego
new file mode 100644
index 0000000..2eb912e
--- /dev/null
+++ b/ci/pod/opa/example.rego
@@ -0,0 +1,45 @@
+#
+# 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 example
+
+import input.request
+import data.users
+
+default allow = false
+
+allow {
+    request.headers["test-header"] == "only-for-test"
+    request.method == "GET"
+    startswith(request.path, "/hello")
+    request.query["test"] != "abcd"
+    request.query["user"]
+}
+
+reason = users[request.query["user"]].reason {
+    not allow
+    request.query["user"]
+}
+
+headers = users[request.query["user"]].headers {
+    not allow
+    request.query["user"]
+}
+
+status_code = users[request.query["user"]].status_code {
+    not allow
+    request.query["user"]
+}
diff --git a/t/plugin/opa.t b/t/plugin/opa.t
index 3592f68..064661a 100644
--- a/t/plugin/opa.t
+++ b/t/plugin/opa.t
@@ -71,7 +71,7 @@ property "host" validation failed: wrong type: expected 
string, got number
                         "plugins": {
                             "opa": {
                                 "host": "http://127.0.0.1:8181";,
-                                "policy": "example/allow"
+                                "policy": "example"
                             }
                         },
                         "upstream": {
@@ -80,7 +80,7 @@ property "host" validation failed: wrong type: expected 
string, got number
                             },
                             "type": "roundrobin"
                         },
-                        "uri": "/hello"
+                        "uris": ["/hello", "/test"]
                 }]]
                 )
 
@@ -95,19 +95,91 @@ passed
 
 
 
-=== TEST 3: hit route (with wrong header request)
+=== TEST 3: hit route (with correct request)
 --- request
-GET /hello
+GET /hello?test=1234&user=none
+--- more_headers
+test-header: only-for-test
+--- response_body
+hello world
+
+
+
+=== TEST 4: hit route (with wrong header request)
+--- request
+GET /hello?test=1234&user=none
 --- more_headers
 test-header: not-for-test
 --- error_code: 403
 
 
 
-=== TEST 4: hit route (with correct request)
+=== TEST 5: hit route (with wrong query request)
 --- request
-GET /hello
+GET /hello?test=abcd&user=none
 --- more_headers
 test-header: only-for-test
---- response_body
-hello world
+--- error_code: 403
+
+
+
+=== TEST 6: hit route (with wrong method request)
+--- request
+POST /hello?test=1234&user=none
+--- more_headers
+test-header: only-for-test
+--- error_code: 403
+
+
+
+=== TEST 7: hit route (with wrong path request)
+--- request
+GET /test?test=1234&user=none
+--- more_headers
+test-header: only-for-test
+--- error_code: 403
+
+
+
+=== TEST 8: hit route (response status code and header)
+--- request
+GET /test?test=abcd&user=alice
+--- more_headers
+test-header: only-for-test
+--- error_code: 302
+--- response_headers
+Location: http://example.com/auth
+
+
+
+=== TEST 9: hit route (response multiple header reason)
+--- request
+GET /test?test=abcd&user=bob
+--- more_headers
+test-header: only-for-test
+--- error_code: 403
+--- response_headers
+test: abcd
+abcd: test
+
+
+
+=== TEST 10: hit route (response string reason)
+--- request
+GET /test?test=abcd&user=carla
+--- more_headers
+test-header: only-for-test
+--- error_code: 403
+--- response
+Give you a string reason
+
+
+
+=== TEST 11: hit route (response json reason)
+--- request
+GET /test?test=abcd&user=dylon
+--- more_headers
+test-header: only-for-test
+--- error_code: 403
+--- response
+{"code":40001,"desc":"Give you a object reason"}

Reply via email to