blob: 1756004bc98e3a22533fdbde21d75bd0c984cb4e [file] [log] [blame]
--
-- 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 ipairs = ipairs
local ngx = ngx
local tostring = tostring
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_post_args = ngx.req.get_post_args
local req_get_body_data = ngx.req.get_body_data
local plugin_name = "wolf-rbac"
local lrucache = core.lrucache.new({
type = "plugin",
})
local schema = {
type = "object",
properties = {
appid = {
type = "string",
default = "unset"
},
server = {
type = "string",
default = "http://127.0.0.1:10080"
},
header_prefix = {
type = "string",
default = "X-"
},
}
}
local _M = {
version = 0.1,
priority = 2555,
type = 'auth',
name = plugin_name,
schema = schema,
}
local create_consume_cache
do
local consumer_names = {}
function create_consume_cache(consumers)
core.table.clear(consumer_names)
for _, consumer in ipairs(consumers.nodes) do
core.log.info("consumer node: ", core.json.delay_encode(consumer))
consumer_names[consumer.auth_conf.appid] = consumer
end
return consumer_names
end
end -- do
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 myheaders == nil then myheaders = new_headers() end
local httpc = http.new()
if timeout then
httpc:set_timeout(timeout)
end
local params = {method = method, headers = myheaders, body = body,
ssl_verify = false}
local res, err = httpc:request_uri(uri, params)
if err 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)
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 ~= 401 then
return {
status = 500,
err = 'request to wolf-server failed, status:' .. res.status
}
end
local body, err = json.decode(res.body)
if err 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 = lrucache("consumers_key", consumer_conf.conf_version,
create_consume_cache, consumer_conf)
core.log.info("------ consumers: ", core.json.delay_encode(consumers))
local consumer = consumers[appid]
if not 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(consumer))
local server = 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 = 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, ctx)
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 401, fail_response(res.err,
{ username = username, nickname = nickname }
)
end
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 err then
core.log.error("json.decode(", req_body, ") failed! ", err)
end
else
args = req_get_post_args()
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 = lrucache("consumers_key", consumer_conf.conf_version,
create_consume_cache, consumer_conf)
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 [" .. tostring(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 err or not res then
core.log.error("request [", request_debug, "] failed! err: ", err)
return core.response.exit(500,
fail_response("request to wolf-server failed! " .. tostring(err))
)
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! status:"
.. tostring(res.status))
)
end
local body, err = json.decode(res.body)
if err or 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(body.reason))
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