diff --git a/apisix/plugins/multi-auth.lua b/apisix/plugins/multi-auth.lua index 3b9660d7b4e0..a01530296635 100644 --- a/apisix/plugins/multi-auth.lua +++ b/apisix/plugins/multi-auth.lua @@ -72,6 +72,15 @@ function _M.rewrite(conf, ctx) local status_code local errors = {} + for _, auth_plugin in pairs(auth_plugins) do + for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do + local auth = plugin.get(auth_plugin_name) + if auth.clear_auth_headers then + auth.clear_auth_headers(auth_plugin_conf, ctx) + end + end + end + for k, auth_plugin in pairs(auth_plugins) do for auth_plugin_name, auth_plugin_conf in pairs(auth_plugin) do local auth = plugin.get(auth_plugin_name) diff --git a/apisix/plugins/wolf-rbac.lua b/apisix/plugins/wolf-rbac.lua index f52faf0cdf3a..4956cc4d6673 100644 --- a/apisix/plugins/wolf-rbac.lua +++ b/apisix/plugins/wolf-rbac.lua @@ -18,6 +18,7 @@ local core = require("apisix.core") local consumer = require("apisix.consumer") local json = require("apisix.core.json") +local auth_utils = require("apisix.utils.auth") local sleep = core.sleep local ngx_re = require("ngx.re") local http = require("resty.http") @@ -31,22 +32,20 @@ local req_read_body = ngx.req.read_body local req_get_body_data = ngx.req.get_body_data local plugin_name = "wolf-rbac" +local default_server = "http://127.0.0.1:12180" +local default_header_prefix = "X-" local schema = { type = "object", properties = { - appid = { - type = "string", - default = "unset" - }, server = { type = "string", - default = "http://127.0.0.1:12180" + default = default_server }, header_prefix = { type = "string", - default = "X-" + default = default_header_prefix }, ssl_verify = { type = "boolean", @@ -54,16 +53,47 @@ local schema = { } } +local consumer_schema = { + type = "object", + properties = { + appid = { + type = "string", + }, + }, + required = {"appid"}, +} + local _M = { version = 0.1, priority = 2555, type = 'auth', name = plugin_name, schema = schema, + consumer_schema = consumer_schema, } local token_version = 'V1' +local public_api_uris = { + ["/apisix/plugin/wolf-rbac/login"] = true, + ["/apisix/plugin/wolf-rbac/change_pwd"] = true, + ["/apisix/plugin/wolf-rbac/user_info"] = true, +} + + +local function clear_identity_headers(conf, ctx) + local prefix = conf.header_prefix or default_header_prefix + core.request.set_header(ctx, prefix .. "UserId", nil) + core.request.set_header(ctx, prefix .. "Username", nil) + core.request.set_header(ctx, prefix .. "Nickname", nil) +end + + +function _M.clear_auth_headers(conf, ctx) + clear_identity_headers(conf, ctx) +end + + local function create_rbac_token(appid, wolf_token) return token_version .. "#" .. appid .. "#" .. wolf_token end @@ -142,7 +172,11 @@ local function http_get(uri, myheaders, timeout, ssl_verify) end -function _M.check_schema(conf) +function _M.check_schema(conf, schema_type) + if schema_type == core.schema.TYPE_CONSUMER then + return core.schema.check(consumer_schema, conf) + end + local ok, err = core.schema.check(schema, conf) if not ok then return false, err @@ -158,6 +192,29 @@ function _M.check_schema(conf) end +local function get_route_plugin_conf(ctx) + for i = 1, #ctx.plugins, 2 do + if ctx.plugins[i].name == plugin_name then + return ctx.plugins[i + 1] + end + end + + return core.response.exit(500, fail_response("Missing wolf-rbac configuration")) +end + + +local function is_wolf_public_api(ctx) + for i = 1, #ctx.plugins, 2 do + if ctx.plugins[i].name == "public-api" then + local conf = ctx.plugins[i + 1] + return public_api_uris[conf.uri or ctx.var.uri] == true + end + end + + return false +end + + local function fetch_rbac_token(ctx) if ctx.var.arg_rbac_token then return ngx.unescape_uri(ctx.var.arg_rbac_token) @@ -239,6 +296,14 @@ end function _M.rewrite(conf, ctx) + if not auth_utils.is_running_under_multi_auth(ctx) then + clear_identity_headers(conf, ctx) + end + + if is_wolf_public_api(ctx) then + return + end + local url = ctx.var.uri local action = ctx.var.request_method local client_ip = core.request.get_remote_client_ip(ctx) @@ -276,34 +341,19 @@ function _M.rewrite(conf, ctx) return 401, fail_response("Invalid appid in rbac token") end core.log.info("consumer appid: ", appid) - local server = cur_consumer.auth_conf.server - local ssl_verify = cur_consumer.auth_conf.ssl_verify - - local res = check_url_permission(server, appid, action, url, - client_ip, wolf_token, ssl_verify) + local res = check_url_permission(conf.server or default_server, appid, action, url, + client_ip, wolf_token, conf.ssl_verify) core.log.info(" check_url_permission(appid: ", appid, ", action: ", action, ", url: ", url, ") res status: ", res.status, ", err: ", res.err) local username = nil local nickname = nil - local prefix = cur_consumer.auth_conf.header_prefix or '' - -- drop client-supplied identity headers before trusting the auth response - core.request.set_header(ctx, prefix .. "UserId", nil) - core.request.set_header(ctx, prefix .. "Username", nil) - core.request.set_header(ctx, prefix .. "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 - 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 @@ -312,12 +362,21 @@ function _M.rewrite(conf, ctx) ") failed, res status: ", res.status, ", err: ", res.err) return res.status, fail_response(res.err, { username = username, nickname = nickname }) end + if type(res.userInfo) == 'table' then + local prefix = conf.header_prefix or default_header_prefix + local userId = res.userInfo.id + 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 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 function get_args(ctx) local args, err req_read_body() if string.find(ctx.var.http_content_type or "","application/json", @@ -396,8 +455,8 @@ local function request_to_wolf_server(method, uri, headers, body, ssl_verify) return body end -local function wolf_rbac_login() - local args = get_args() +local function wolf_rbac_login(ctx) + local args = get_args(ctx) if not args then return core.response.exit(400, fail_response("invalid request")) end @@ -406,12 +465,13 @@ local function wolf_rbac_login() end local appid = args.appid - local consumer = get_consumer(appid) + get_consumer(appid) core.log.info("consumer appid: ", appid) - local uri = consumer.auth_conf.server .. '/wolf/rbac/login.rest' + local conf = get_route_plugin_conf(ctx) + local uri = (conf.server or default_server) .. '/wolf/rbac/login.rest' local headers = new_headers() - local body = request_to_wolf_server('POST', uri, headers, args, consumer.auth_conf.ssl_verify) + local body = request_to_wolf_server('POST', uri, headers, args, conf.ssl_verify) local userInfo = body.data.userInfo local wolf_token = body.data.token @@ -442,35 +502,34 @@ local function get_wolf_token(ctx) return tokenInfo end -local function wolf_rbac_change_pwd() - local args = get_args() - - local ctx = ngx.ctx.api_ctx +local function wolf_rbac_change_pwd(ctx) + local args = get_args(ctx) local tokenInfo = get_wolf_token(ctx) local appid = tokenInfo.appid local wolf_token = tokenInfo.wolf_token - local consumer = get_consumer(appid) + get_consumer(appid) core.log.info("consumer appid: ", appid) - local uri = consumer.auth_conf.server .. '/wolf/rbac/change_pwd' + local conf = get_route_plugin_conf(ctx) + local uri = (conf.server or default_server) .. '/wolf/rbac/change_pwd' local headers = new_headers() headers['x-rbac-token'] = wolf_token - request_to_wolf_server('POST', uri, headers, args, consumer.auth_conf.ssl_verify) + request_to_wolf_server('POST', uri, headers, args, conf.ssl_verify) core.response.exit(200, success_response('success to change password', { })) end -local function wolf_rbac_user_info() - local ctx = ngx.ctx.api_ctx +local function wolf_rbac_user_info(ctx) local tokenInfo = get_wolf_token(ctx) local appid = tokenInfo.appid local wolf_token = tokenInfo.wolf_token - local consumer = get_consumer(appid) + get_consumer(appid) core.log.info("consumer appid: ", appid) - local uri = consumer.auth_conf.server .. '/wolf/rbac/user_info' + local conf = get_route_plugin_conf(ctx) + local uri = (conf.server or default_server) .. '/wolf/rbac/user_info' local headers = new_headers() headers['x-rbac-token'] = wolf_token - local body = request_to_wolf_server('GET', uri, headers, {}, consumer.auth_conf.ssl_verify) + local body = request_to_wolf_server('GET', uri, headers, {}, conf.ssl_verify) local userInfo = body.data.userInfo core.response.exit(200, success_response(nil, {user_info = userInfo})) end diff --git a/docs/en/latest/plugins/wolf-rbac.md b/docs/en/latest/plugins/wolf-rbac.md index 9ee62459c577..1fb83639623f 100644 --- a/docs/en/latest/plugins/wolf-rbac.md +++ b/docs/en/latest/plugins/wolf-rbac.md @@ -34,11 +34,12 @@ The `wolf-rbac` Plugin provides a [role-based access control](https://en.wikiped ## Attributes -| Name | Type | Required | Default | Description | -|---------------|--------|----------|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| server | string | False | "http://127.0.0.1:12180" | Service address of wolf server. | -| appid | string | False | "unset" | App id added in wolf console. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. | -| header_prefix | string | False | "X-" | Prefix for a custom HTTP header. After authentication is successful, three headers will be added to the request header (for backend) and response header (for frontend) namely: `X-UserId`, `X-Username`, and `X-Nickname`. | +| Name | Type | Required | Default | Description | +| --- | --- | --- | --- | --- | +| appid | string | True | N/A | Consumer application ID configured in Wolf. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. | +| server | string | False | "http://127.0.0.1:12180" | Route or Service address of the Wolf server. | +| header_prefix | string | False | "X-" | Route or Service prefix for the identity headers added after successful authentication: `{prefix}UserId`, `{prefix}Username`, and `{prefix}Nickname`. | +| ssl_verify | boolean | False | false | Whether to verify the Wolf server's TLS certificate. Configured on a Route or Service. | ## API @@ -79,7 +80,6 @@ curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X "username":"wolf_rbac", "plugins":{ "wolf-rbac":{ - "server":"http://127.0.0.1:12180", "appid":"restful" } }, @@ -101,7 +101,10 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X "methods": ["GET"], "uri": "/*", "plugins": { - "wolf-rbac": {} + "wolf-rbac": { + "server": "http://127.0.0.1:12180", + "header_prefix": "X-" + } }, "upstream": { "type": "roundrobin", @@ -114,6 +117,10 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X You can also use the [APISIX Dashboard](/docs/dashboard/USER_GUIDE) to complete the operation through a web UI. +## Upgrade + +Move `server`, `header_prefix`, and `ssl_verify` from each Consumer to every Route or Service that uses `wolf-rbac`. Keep only `appid` on the Consumer. If Consumers on one Route used different values, split them across Routes or Services, or standardize the values before upgrading. +