feat(apisix): add Cloudron package
- Implements Apache APISIX packaging for Cloudron platform. - Includes Dockerfile, CloudronManifest.json, and start.sh. - Configured to use Cloudron's etcd addon. 🤖 Generated with Gemini CLI Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
--
|
||||
-- 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 consumer = require("apisix.consumer")
|
||||
local json = require("apisix.core.json")
|
||||
local sleep = core.sleep
|
||||
local ngx_re = require("ngx.re")
|
||||
local http = require("resty.http")
|
||||
local ngx = ngx
|
||||
local rawget = rawget
|
||||
local rawset = rawset
|
||||
local setmetatable = setmetatable
|
||||
local type = type
|
||||
local string = string
|
||||
local req_read_body = ngx.req.read_body
|
||||
local req_get_body_data = ngx.req.get_body_data
|
||||
|
||||
local plugin_name = "wolf-rbac"
|
||||
|
||||
|
||||
local schema = {
|
||||
type = "object",
|
||||
properties = {
|
||||
appid = {
|
||||
type = "string",
|
||||
default = "unset"
|
||||
},
|
||||
server = {
|
||||
type = "string",
|
||||
default = "http://127.0.0.1:12180"
|
||||
},
|
||||
header_prefix = {
|
||||
type = "string",
|
||||
default = "X-"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
local _M = {
|
||||
version = 0.1,
|
||||
priority = 2555,
|
||||
type = 'auth',
|
||||
name = plugin_name,
|
||||
schema = schema,
|
||||
}
|
||||
|
||||
|
||||
local token_version = 'V1'
|
||||
local function create_rbac_token(appid, wolf_token)
|
||||
return token_version .. "#" .. appid .. "#" .. wolf_token
|
||||
end
|
||||
|
||||
local function fail_response(message, init_values)
|
||||
local response = init_values or {}
|
||||
response.message = message
|
||||
return response
|
||||
end
|
||||
|
||||
local function success_response(message, init_values)
|
||||
local response = init_values or {}
|
||||
response.message = message
|
||||
return response
|
||||
end
|
||||
|
||||
local function parse_rbac_token(rbac_token)
|
||||
local res, err = ngx_re.split(rbac_token, "#", nil, nil, 3)
|
||||
if not res then
|
||||
return nil, err
|
||||
end
|
||||
|
||||
if #res ~= 3 or res[1] ~= token_version then
|
||||
return nil, 'invalid rbac token: version'
|
||||
end
|
||||
local appid = res[2]
|
||||
local wolf_token = res[3]
|
||||
|
||||
return {appid = appid, wolf_token = wolf_token}
|
||||
end
|
||||
|
||||
local function new_headers()
|
||||
local t = {}
|
||||
local lt = {}
|
||||
local _mt = {
|
||||
__index = function(t, k)
|
||||
return rawget(lt, string.lower(k))
|
||||
end,
|
||||
__newindex = function(t, k, v)
|
||||
rawset(t, k, v)
|
||||
rawset(lt, string.lower(k), v)
|
||||
end,
|
||||
}
|
||||
return setmetatable(t, _mt)
|
||||
end
|
||||
|
||||
-- timeout in ms
|
||||
local function http_req(method, uri, body, myheaders, timeout)
|
||||
if not myheaders then
|
||||
myheaders = new_headers()
|
||||
end
|
||||
|
||||
local httpc = http.new()
|
||||
if timeout then
|
||||
httpc:set_timeout(timeout)
|
||||
end
|
||||
|
||||
local res, err = httpc:request_uri(uri, {
|
||||
method = method,
|
||||
headers = myheaders,
|
||||
body = body,
|
||||
ssl_verify = false
|
||||
})
|
||||
|
||||
if not res then
|
||||
core.log.error("FAIL REQUEST [ ",core.json.delay_encode(
|
||||
{method = method, uri = uri, body = body, headers = myheaders}),
|
||||
" ] failed! res is nil, err:", err)
|
||||
return nil, err
|
||||
end
|
||||
|
||||
return res
|
||||
end
|
||||
|
||||
local function http_get(uri, myheaders, timeout)
|
||||
return http_req("GET", uri, nil, myheaders, timeout)
|
||||
end
|
||||
|
||||
|
||||
function _M.check_schema(conf)
|
||||
local check = {"server"}
|
||||
core.utils.check_https(check, conf, plugin_name)
|
||||
core.log.info("input conf: ", core.json.delay_encode(conf))
|
||||
|
||||
local ok, err = core.schema.check(schema, conf)
|
||||
if not ok then
|
||||
return false, err
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function fetch_rbac_token(ctx)
|
||||
if ctx.var.arg_rbac_token then
|
||||
return ngx.unescape_uri(ctx.var.arg_rbac_token)
|
||||
end
|
||||
|
||||
if ctx.var.http_authorization then
|
||||
return ctx.var.http_authorization
|
||||
end
|
||||
|
||||
if ctx.var.http_x_rbac_token then
|
||||
return ctx.var.http_x_rbac_token
|
||||
end
|
||||
|
||||
return ctx.var['cookie_x-rbac-token']
|
||||
end
|
||||
|
||||
|
||||
local function check_url_permission(server, appid, action, resName, client_ip, wolf_token)
|
||||
local retry_max = 3
|
||||
local errmsg
|
||||
local userInfo
|
||||
local res
|
||||
local err
|
||||
local access_check_url = server .. "/wolf/rbac/access_check"
|
||||
local headers = new_headers()
|
||||
headers["x-rbac-token"] = wolf_token
|
||||
headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
local args = { appID = appid, resName = resName, action = action, clientIP = client_ip}
|
||||
local url = access_check_url .. "?" .. ngx.encode_args(args)
|
||||
local timeout = 1000 * 10
|
||||
|
||||
for i = 1, retry_max do
|
||||
-- TODO: read apisix info.
|
||||
res, err = http_get(url, headers, timeout)
|
||||
if err then
|
||||
break
|
||||
else
|
||||
core.log.info("check permission request:", url, ", status:", res.status,
|
||||
",body:", core.json.delay_encode(res.body))
|
||||
if res.status < 500 then
|
||||
break
|
||||
else
|
||||
core.log.info("request [curl -v ", url, "] failed! status:", res.status)
|
||||
if i < retry_max then
|
||||
sleep(0.1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if err then
|
||||
core.log.error("fail request: ", url, ", err:", err)
|
||||
return {
|
||||
status = 500,
|
||||
err = "request to wolf-server failed, err:" .. err
|
||||
}
|
||||
end
|
||||
|
||||
if res.status ~= 200 and res.status >= 500 then
|
||||
return {
|
||||
status = 500,
|
||||
err = 'request to wolf-server failed, status:' .. res.status
|
||||
}
|
||||
end
|
||||
|
||||
local body, err = json.decode(res.body)
|
||||
if not body then
|
||||
errmsg = 'check permission failed! parse response json failed!'
|
||||
core.log.error( "json.decode(", res.body, ") failed! err:", err)
|
||||
return {status = res.status, err = errmsg}
|
||||
else
|
||||
if body.data then
|
||||
userInfo = body.data.userInfo
|
||||
end
|
||||
errmsg = body.reason
|
||||
return {status = res.status, err = errmsg, userInfo = userInfo}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
function _M.rewrite(conf, ctx)
|
||||
local url = ctx.var.uri
|
||||
local action = ctx.var.request_method
|
||||
local client_ip = ctx.var.http_x_real_ip or core.request.get_ip(ctx)
|
||||
local perm_item = {action = action, url = url, clientIP = client_ip}
|
||||
core.log.info("hit wolf-rbac rewrite")
|
||||
|
||||
local rbac_token = fetch_rbac_token(ctx)
|
||||
if rbac_token == nil then
|
||||
core.log.info("no permission to access ",
|
||||
core.json.delay_encode(perm_item), ", need login!")
|
||||
return 401, fail_response("Missing rbac token in request")
|
||||
end
|
||||
|
||||
local tokenInfo, err = parse_rbac_token(rbac_token)
|
||||
core.log.info("token info: ", core.json.delay_encode(tokenInfo),
|
||||
", err: ", err)
|
||||
if err then
|
||||
return 401, fail_response('invalid rbac token: parse failed')
|
||||
end
|
||||
|
||||
local appid = tokenInfo.appid
|
||||
local wolf_token = tokenInfo.wolf_token
|
||||
perm_item.appid = appid
|
||||
perm_item.wolf_token = wolf_token
|
||||
|
||||
local consumer_conf = consumer.plugin(plugin_name)
|
||||
if not consumer_conf then
|
||||
return 401, fail_response("Missing related consumer")
|
||||
end
|
||||
|
||||
local consumers = consumer.consumers_kv(plugin_name, consumer_conf, "appid")
|
||||
|
||||
core.log.info("------ consumers: ", core.json.delay_encode(consumers))
|
||||
local cur_consumer = consumers[appid]
|
||||
if not cur_consumer then
|
||||
core.log.error("consumer [", appid, "] not found")
|
||||
return 401, fail_response("Invalid appid in rbac token")
|
||||
end
|
||||
core.log.info("consumer: ", core.json.delay_encode(cur_consumer))
|
||||
local server = cur_consumer.auth_conf.server
|
||||
|
||||
local res = check_url_permission(server, appid, action, url,
|
||||
client_ip, wolf_token)
|
||||
core.log.info(" check_url_permission(", core.json.delay_encode(perm_item),
|
||||
") res: ",core.json.delay_encode(res))
|
||||
|
||||
local username = nil
|
||||
local nickname = nil
|
||||
if type(res.userInfo) == 'table' then
|
||||
local userInfo = res.userInfo
|
||||
ctx.userInfo = userInfo
|
||||
local userId = userInfo.id
|
||||
username = userInfo.username
|
||||
nickname = userInfo.nickname or userInfo.username
|
||||
local prefix = cur_consumer.auth_conf.header_prefix or ''
|
||||
core.response.set_header(prefix .. "UserId", userId)
|
||||
core.response.set_header(prefix .. "Username", username)
|
||||
core.response.set_header(prefix .. "Nickname", ngx.escape_uri(nickname))
|
||||
core.request.set_header(ctx, prefix .. "UserId", userId)
|
||||
core.request.set_header(ctx, prefix .. "Username", username)
|
||||
core.request.set_header(ctx, prefix .. "Nickname", ngx.escape_uri(nickname))
|
||||
end
|
||||
|
||||
if res.status ~= 200 then
|
||||
-- no permission.
|
||||
core.log.error(" check_url_permission(",
|
||||
core.json.delay_encode(perm_item),
|
||||
") failed, res: ",core.json.delay_encode(res))
|
||||
return res.status, fail_response(res.err, { username = username, nickname = nickname })
|
||||
end
|
||||
consumer.attach_consumer(ctx, cur_consumer, consumer_conf)
|
||||
core.log.info("wolf-rbac check permission passed")
|
||||
end
|
||||
|
||||
local function get_args()
|
||||
local ctx = ngx.ctx.api_ctx
|
||||
local args, err
|
||||
req_read_body()
|
||||
if string.find(ctx.var.http_content_type or "","application/json",
|
||||
1, true) then
|
||||
local req_body = req_get_body_data()
|
||||
args, err = json.decode(req_body)
|
||||
if not args then
|
||||
core.log.error("json.decode(", req_body, ") failed! ", err)
|
||||
end
|
||||
else
|
||||
args = core.request.get_post_args(ctx)
|
||||
end
|
||||
|
||||
return args
|
||||
end
|
||||
|
||||
local function get_consumer(appid)
|
||||
local consumer_conf = consumer.plugin(plugin_name)
|
||||
if not consumer_conf then
|
||||
core.response.exit(500)
|
||||
end
|
||||
|
||||
local consumers = consumer.consumers_kv(plugin_name, consumer_conf, "appid")
|
||||
|
||||
core.log.info("------ consumers: ", core.json.delay_encode(consumers))
|
||||
local consumer = consumers[appid]
|
||||
if not consumer then
|
||||
core.log.info("request appid [", appid, "] not found")
|
||||
core.response.exit(400,
|
||||
fail_response("appid not found")
|
||||
)
|
||||
end
|
||||
return consumer
|
||||
end
|
||||
|
||||
local function request_to_wolf_server(method, uri, headers, body)
|
||||
headers["Content-Type"] = "application/json; charset=utf-8"
|
||||
local timeout = 1000 * 5
|
||||
local request_debug = core.json.delay_encode(
|
||||
{
|
||||
method = method, uri = uri, body = body,
|
||||
headers = headers,timeout = timeout
|
||||
}
|
||||
)
|
||||
|
||||
core.log.info("request [", request_debug, "] ....")
|
||||
local res, err = http_req(method, uri, core.json.encode(body), headers, timeout)
|
||||
if not res then
|
||||
core.log.error("request [", request_debug, "] failed! err: ", err)
|
||||
return core.response.exit(500,
|
||||
fail_response("request to wolf-server failed!")
|
||||
)
|
||||
end
|
||||
core.log.info("request [", request_debug, "] status: ", res.status,
|
||||
", body: ", res.body)
|
||||
|
||||
if res.status ~= 200 then
|
||||
core.log.error("request [", request_debug, "] failed! status: ",
|
||||
res.status)
|
||||
return core.response.exit(500,
|
||||
fail_response("request to wolf-server failed!")
|
||||
)
|
||||
end
|
||||
local body, err = json.decode(res.body)
|
||||
if not body then
|
||||
core.log.error("request [", request_debug, "] failed! err:", err)
|
||||
return core.response.exit(500, fail_response("request to wolf-server failed!"))
|
||||
end
|
||||
if not body.ok then
|
||||
core.log.error("request [", request_debug, "] failed! response body:",
|
||||
core.json.delay_encode(body))
|
||||
return core.response.exit(200, fail_response("request to wolf-server failed!"))
|
||||
end
|
||||
|
||||
core.log.info("request [", request_debug, "] success! response body:",
|
||||
core.json.delay_encode(body))
|
||||
return body
|
||||
end
|
||||
|
||||
local function wolf_rbac_login()
|
||||
local args = get_args()
|
||||
if not args then
|
||||
return core.response.exit(400, fail_response("invalid request"))
|
||||
end
|
||||
if not args.appid then
|
||||
return core.response.exit(400, fail_response("appid is missing"))
|
||||
end
|
||||
|
||||
local appid = args.appid
|
||||
local consumer = get_consumer(appid)
|
||||
core.log.info("consumer: ", core.json.delay_encode(consumer))
|
||||
|
||||
local uri = consumer.auth_conf.server .. '/wolf/rbac/login.rest'
|
||||
local headers = new_headers()
|
||||
local body = request_to_wolf_server('POST', uri, headers, args)
|
||||
|
||||
local userInfo = body.data.userInfo
|
||||
local wolf_token = body.data.token
|
||||
|
||||
local rbac_token = create_rbac_token(appid, wolf_token)
|
||||
core.response.exit(200, success_response(nil, {rbac_token = rbac_token, user_info = userInfo}))
|
||||
end
|
||||
|
||||
local function get_wolf_token(ctx)
|
||||
core.log.info("hit wolf-rbac change_password api")
|
||||
local rbac_token = fetch_rbac_token(ctx)
|
||||
if rbac_token == nil then
|
||||
local url = ctx.var.uri
|
||||
local action = ctx.var.request_method
|
||||
local client_ip = core.request.get_ip(ctx)
|
||||
local perm_item = {action = action, url = url, clientIP = client_ip}
|
||||
core.log.info("no permission to access ",
|
||||
core.json.delay_encode(perm_item), ", need login!")
|
||||
return core.response.exit(401, fail_response("Missing rbac token in request"))
|
||||
end
|
||||
|
||||
local tokenInfo, err = parse_rbac_token(rbac_token)
|
||||
core.log.info("token info: ", core.json.delay_encode(tokenInfo),
|
||||
", err: ", err)
|
||||
if err then
|
||||
return core.response.exit(401, fail_response('invalid rbac token: parse failed'))
|
||||
end
|
||||
return tokenInfo
|
||||
end
|
||||
|
||||
local function wolf_rbac_change_pwd()
|
||||
local args = get_args()
|
||||
|
||||
local ctx = ngx.ctx.api_ctx
|
||||
local tokenInfo = get_wolf_token(ctx)
|
||||
local appid = tokenInfo.appid
|
||||
local wolf_token = tokenInfo.wolf_token
|
||||
local consumer = get_consumer(appid)
|
||||
core.log.info("consumer: ", core.json.delay_encode(consumer))
|
||||
|
||||
local uri = consumer.auth_conf.server .. '/wolf/rbac/change_pwd'
|
||||
local headers = new_headers()
|
||||
headers['x-rbac-token'] = wolf_token
|
||||
request_to_wolf_server('POST', uri, headers, args)
|
||||
core.response.exit(200, success_response('success to change password', { }))
|
||||
end
|
||||
|
||||
local function wolf_rbac_user_info()
|
||||
local ctx = ngx.ctx.api_ctx
|
||||
local tokenInfo = get_wolf_token(ctx)
|
||||
local appid = tokenInfo.appid
|
||||
local wolf_token = tokenInfo.wolf_token
|
||||
local consumer = get_consumer(appid)
|
||||
core.log.info("consumer: ", core.json.delay_encode(consumer))
|
||||
|
||||
local uri = consumer.auth_conf.server .. '/wolf/rbac/user_info'
|
||||
local headers = new_headers()
|
||||
headers['x-rbac-token'] = wolf_token
|
||||
local body = request_to_wolf_server('GET', uri, headers, {})
|
||||
local userInfo = body.data.userInfo
|
||||
core.response.exit(200, success_response(nil, {user_info = userInfo}))
|
||||
end
|
||||
|
||||
function _M.api()
|
||||
return {
|
||||
{
|
||||
methods = {"POST"},
|
||||
uri = "/apisix/plugin/wolf-rbac/login",
|
||||
handler = wolf_rbac_login,
|
||||
},
|
||||
{
|
||||
methods = {"PUT"},
|
||||
uri = "/apisix/plugin/wolf-rbac/change_pwd",
|
||||
handler = wolf_rbac_change_pwd,
|
||||
},
|
||||
{
|
||||
methods = {"GET"},
|
||||
uri = "/apisix/plugin/wolf-rbac/user_info",
|
||||
handler = wolf_rbac_user_info,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
return _M
|
Reference in New Issue
Block a user