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]


Reply via email to