Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: basic support OPA plugin #5734

Merged
merged 22 commits into from
Dec 13, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ install: runtime
$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/zipkin
$(ENV_INSTALL) apisix/plugins/zipkin/*.lua $(ENV_INST_LUADIR)/apisix/plugins/zipkin/

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/plugins/opa
$(ENV_INSTALL) apisix/plugins/opa/*.lua $(ENV_INST_LUADIR)/apisix/plugins/opa/

$(ENV_INSTALL) -d $(ENV_INST_LUADIR)/apisix/ssl/router
$(ENV_INSTALL) apisix/ssl/router/*.lua $(ENV_INST_LUADIR)/apisix/ssl/router/

Expand Down
17 changes: 13 additions & 4 deletions apisix/core/request.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ local io = require("apisix.core.io")
local ngx = ngx
local get_headers = ngx.req.get_headers
local clear_header = ngx.req.clear_header
local tonumber = tonumber
local error = error
local type = type
local str_fmt = string.format
local tonumber = tonumber
local error = error
local type = type
local str_fmt = string.format
local str_lower = string.lower
local req_read_body = ngx.req.read_body
local req_get_body_data = ngx.req.get_body_data
Expand Down Expand Up @@ -269,6 +269,15 @@ function _M.get_port(ctx)
end


function _M.get_path(ctx)
if not ctx then
ctx = ngx.ctx.api_ctx
end

return ctx.var.uri or ''
end


function _M.get_http_version()
return ngx.req.http_version()
end
Expand Down
104 changes: 104 additions & 0 deletions apisix/plugins/opa.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
--
-- 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.
--

local core = require("apisix.core")
local http = require("resty.http")
local helper = require("apisix.plugins.opa.helper")

local schema = {
type = "object",
properties = {
host = {type = "string"},
ssl_verify = {
type = "boolean",
default = true,
},
package = {type = "string"},
decision = {type = "string", maxLength = 256},
timeout = {
type = "integer",
minimum = 1,
maximum = 60000,
default = 3000,
description = "timeout in milliseconds",
},
keepalive = {type = "boolean", default = true},
keepalive_timeout = {type = "integer", minimum = 1000, default = 60000},
keepalive_pool = {type = "integer", minimum = 1, default = 5}
},
required = {"host", "package", "decision"}
}


local _M = {
version = 0.1,
priority = 2001,
name = "opa",
schema = schema,
}


function _M.check_schema(conf)
return core.schema.check(schema, conf)
end


function _M.access(conf, ctx)
local body = helper.build_opa_input(conf, ctx, "http")
local params = {
method = "POST",
body = body,
headers = {
["Content-Type"] = "application/json",
},
keepalive = conf.keepalive,
ssl_verify = conf.ssl_verify
}

if conf.keepalive then
params.keepalive_timeout = conf.keepalive_timeout
params.keepalive_pool = conf.keepalive_pool
end

local endpoint = conf.host .. "/v1/data/" .. conf.package .. "/" .. conf.decision

local httpc = http.new()
httpc:set_timeout(conf.timeout)

local res, err = httpc:request_uri(endpoint, params)

-- block by default when decision is unavailable
if not res or err then
core.log.error("failed to process OPA decision, err: ", err)
return 403
end

-- parse the results of the decision
local data, err = core.json.decode(res.body)

if err then
core.log.error("invalid response body: ", res.body, " err: ", err)
return 403
bzp2010 marked this conversation as resolved.
Show resolved Hide resolved
end

if not data.result then
return 403
end
end


return _M
61 changes: 61 additions & 0 deletions apisix/plugins/opa/helper.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
--
-- 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.
--

local core = require("apisix.core")
local ngx_time = ngx.time

local _M = {}


local function build_var(conf, ctx)
return {
server_addr = ctx.var.server_addr,
server_port = ctx.var.server_port,
remote_addr = ctx.var.remote_addr,
remote_port = ctx.var.remote_port,
timestamp = ngx_time(),
}
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, true),
header = core.request.headers(ctx),
query = core.request.get_uri_args(ctx),
}
end


function _M.build_opa_input(conf, ctx, subsystem)
local request = build_http_request(conf, ctx)

local data = {
type = subsystem,
request = request,
var = build_var(conf, ctx)
}

return core.json.encode({input = data})
end


return _M
11 changes: 11 additions & 0 deletions ci/linux-ci-init-service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,14 @@ docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic
docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic -n rocketmq_namesrv:9876 -t test2 -c DefaultCluster
docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic -n rocketmq_namesrv:9876 -t test3 -c DefaultCluster
docker exec -i rmqnamesrv /home/rocketmq/rocketmq-4.6.0/bin/mqadmin updateTopic -n rocketmq_namesrv:9876 -t test4 -c DefaultCluster

# 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"
}'
11 changes: 11 additions & 0 deletions ci/pod/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -379,10 +379,21 @@ services:
networks:
rocketmq_net:

# Open Policy Agent
opa:
image: openpolicyagent/opa:0.35.0
restart: unless-stopped
ports:
- 8181:8181
command: run -s
networks:
opa_net:

networks:
apisix_net:
consul_net:
kafka_net:
nacos_net:
skywalk_net:
rocketmq_net:
opa_net:
1 change: 1 addition & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ plugins: # plugin list (sorted by priority)
- jwt-auth # priority: 2510
- key-auth # priority: 2500
- consumer-restriction # priority: 2400
- opa # priority: 2001
- authz-keycloak # priority: 2000
#- error-log-logger # priority: 1091
- proxy-mirror # priority: 1010
Expand Down
1 change: 1 addition & 0 deletions t/admin/plugins.t
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ basic-auth
jwt-auth
key-auth
consumer-restriction
opa
authz-keycloak
proxy-mirror
proxy-cache
Expand Down
27 changes: 27 additions & 0 deletions t/core/request.t
Original file line number Diff line number Diff line change
Expand Up @@ -464,3 +464,30 @@ POST /hello
POST
--- no_error_log
[error]



=== TEST 14: get_path
--- config
location /hello1 {
content_by_lua_block {
local core = require("apisix.core")
local ngx_ctx = ngx.ctx
local api_ctx = ngx_ctx.api_ctx
if api_ctx == nil then
api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
ngx_ctx.api_ctx = api_ctx
end

core.ctx.set_vars_meta(api_ctx)

local path = core.request.get_path(ngx.ctx.api_ctx)
ngx.say(path)
}
}
--- request
GET /hello1/test?a=b&b=a
--- response_body
/hello1/test
--- no_error_log
[error]
Loading