rushitote opened a new issue #4674: URL: https://github.com/apache/apisix/issues/4674
## Background Hi, we have been developing `casbin-authz` plugin for APISIX based on [Lua Casbin](https://github.com/casbin/lua-casbin/) which is the Lua implementation of the [Casbin](https://casbin.org/) library. Casbin is an authorization library which supports access control models like ACL, RBAC and ABAC. `casbin-authz` is a plugin for APISIX that enables authorization based on Casbin. The initial implementation is at [apisix-authz](https://github.com/casbin-lua/apisix-authz). ## Implementation and Usage This is what we have developed till now: ```lua local Enforcer = require("casbin") local core = require("apisix.core") local get_headers = ngx.req.get_headers local CasbinEnforcer local plugin_name = "apisix-authz" local schema = { type = "object", properties = { model_path = { type = "string" }, policy_path = { type = "string" }, username = { type = "string"} }, required = {"model_path", "policy_path", "username"}, additionalProperties = false } local _M = { version = 0.1, priority = 2560, type = 'auth', name = plugin_name, schema = schema } function _M.rewrite(conf) -- creates an enforcer when request sent for the first time if not CasbinEnforcer then CasbinEnforcer = Enforcer:new(conf.model_path, conf.policy_path) end local path = ngx.var.request_uri local method = ngx.var.request_method local username = get_headers()[conf.username] if not username then username = "anonymous" end if path and method and username then if not CasbinEnforcer:enforce(username, path, method) then return 403, {message = "Access Denied"} end else return 403, {message = "Access Denied"} end end local function addPolicy() local headers = get_headers() local type = headers["type"] if type == "p" then local subject = headers["subject"] local object = headers["object"] local action = headers["action"] if not subject or not object or not action then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:AddPolicy(subject, object, action) then return 200, {message = "Successfully added policy."} else return 400, {message = "Invalid policy request."} end elseif type == "g" then local user = headers["user"] local role = headers["role"] if not user or not role then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:AddGroupingPolicy(user, role) then return 200, {message = "Successfully added grouping policy."} else return 400, {message = "Invalid policy request."} end else return 400, {message = "Invalid policy type."} end end local function removePolicy() local headers = get_headers() local type = headers["type"] if type == "p" then local subject = headers["subject"] local object = headers["object"] local action = headers["action"] if not subject or not object or not action then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:RemovePolicy(subject, object, action) then return 200, {message = "Successfully removed policy."} else return 400, {message = "Invalid policy request."} end elseif type == "g" then local user = headers["user"] local role = headers["role"] if not user or not role then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:RemoveGroupingPolicy(user, role) then return 200, {message = "Successfully removed grouping policy."} else return 400, {message = "Invalid policy request."} end else return 400, {message = "Invalid policy type."} end end -- subject, object, action local function hasPolicy() local headers = get_headers() local type = headers["type"] if type == "p" then local subject = headers["subject"] local object = headers["object"] local action = headers["action"] if not subject or not object or not action then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:HasPolicy(subject, object, action) then return 200, {data = "true"} else return 200, {data = "false"} end elseif type == "g" then local user = headers["user"] local role = headers["role"] if not user or not role then return 400, {message = "Invalid policy request."} end if CasbinEnforcer:HasGroupingPolicy(user, role) then return 200, {data = "true"} else return 200, {data = "false"} end else return 400, {message = "Invalid policy type."} end end local function getPolicy() local headers = get_headers() local type = headers["type"] if type == "p" then local policy = CasbinEnforcer:GetPolicy() if policy then return 200, {data = policy} else return 400 end elseif type == "g" then local groupingPolicy = CasbinEnforcer:GetGroupingPolicy() if groupingPolicy then return 200, {data = groupingPolicy} else return 400 end else return 400, {message = "Invalid policy type."} end end local function savePolicy() local _, err = pcall(function () CasbinEnforcer:savePolicy() end) if not err then return 200, {message = "Successfully saved policy."} else core.log.error("Save Policy error: " .. err) return 400, {message = "Failed to save policy, see logs."} end end function _M.api() return { { methods = {"POST"}, uri = "/apisix/plugin/casbin/add", handler = addPolicy, }, { methods = {"POST"}, uri = "/apisix/plugin/casbin/remove", handler = removePolicy, }, { methods = {"GET"}, uri = "/apisix/plugin/casbin/has", handler = hasPolicy, }, { methods = {"GET"}, uri = "/apisix/plugin/casbin/get", handler = getPolicy, }, { methods = {"POST"}, uri = "/apisix/plugin/casbin/save", handler = savePolicy, }, } end function _M.check_schema(conf) return core.schema.check(schema, conf) end return _M ``` The user can send send a request to configure the plugin on a route by: ```bash curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' { "uri": "/*", "plugins": { "apisix-authz": { "model_path": "/path/to/authz_model.conf", "policy_path": "/path/to/authz_policy.csv", "username": "user" } }, "upstream": { "type": "roundrobin", "nodes": { "example.com": 1 } }, "host": "example.com" }' ``` This will use the model in `model_path` and policy in `policy_path` of the configuration to create a Casbin Enforcer when run for the first time. (Example model file and policy file [here](https://github.com/casbin-lua/apisix-authz/tree/master/examples)). The plugin checks whether the username (as passed in header), the object (the path URL) and the HTTP request method are authorized or not. If the username header is not present, it assumes it to be `anonymous` whose permissions can be set in the model/policy files. If such request is authorized, it will proceed normally as it would and if not it would return a 403 code (for now). It also features an API to get the policies, configure the policies and save all of them again(if updated) to the policy file path. The API is in initial stage and may support more functions as needed. What do you think about this? Will this be helpful? If so, I can start with an initial PR. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
