Skip to content

Commit

Permalink
feat: supprot OPA plugin complex response (#5779)
Browse files Browse the repository at this point in the history
  • Loading branch information
bzp2010 authored Dec 15, 2021
1 parent c567cf3 commit 6ce20fc
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 29 deletions.
29 changes: 27 additions & 2 deletions apisix/plugins/opa.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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

Expand Down
14 changes: 7 additions & 7 deletions apisix/plugins/opa/helper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
11 changes: 0 additions & 11 deletions ci/linux-ci-init-service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}'
9 changes: 8 additions & 1 deletion ci/pod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down
25 changes: 25 additions & 0 deletions ci/pod/opa/data.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
}
45 changes: 45 additions & 0 deletions ci/pod/opa/example.rego
Original file line number Diff line number Diff line change
@@ -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"]
}
88 changes: 80 additions & 8 deletions t/plugin/opa.t
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand All @@ -80,7 +80,7 @@ property "host" validation failed: wrong type: expected string, got number
},
"type": "roundrobin"
},
"uri": "/hello"
"uris": ["/hello", "/test"]
}]]
)
Expand All @@ -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"}

0 comments on commit 6ce20fc

Please sign in to comment.