Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
test:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
strategy:
fail-fast: false
steps:
Expand All @@ -32,18 +32,18 @@ jobs:
nohup etcd &
- name: Install and configure APISIX
run: |
mkdir apisix-2.7
wget https://downloads.apache.org/apisix/2.7/apache-apisix-2.7-src.tgz
tar zxvf apache-apisix-2.7-src.tgz -C apisix-2.7
cd apisix-2.7
mkdir apisix-2.15
wget https://downloads.apache.org/apisix/2.15.3/apache-apisix-2.15.3-src.tgz
tar zxvf apache-apisix-2.15.3-src.tgz -C apisix-2.15 --strip-components=1
cd apisix-2.15
make deps
make init
cd ..
- name: Configure apisix-authz
run: |
cp apisix-authz.lua apisix-2.7/apisix/plugins/apisix-authz.lua
cp apisix-authz.lua apisix-2.15/apisix/plugins/apisix-authz.lua
# append plugins to config
cat <<EOT >> apisix-2.7/conf/config.yaml
cat <<EOT >> apisix-2.15/conf/config.yaml
plugins: # plugin list (sorted by priority)
- client-control # priority: 22000
- ext-plugin-pre-req # priority: 12000
Expand Down Expand Up @@ -100,7 +100,7 @@ jobs:
sudo luarocks install https://raw.githubusercontent.com/casbin/lua-casbin/master/casbin-1.16.1-1.rockspec
- name: Start the server
run: |
cd apisix-2.7
cd apisix-2.15
make run
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
Expand Down
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,42 @@ curl -X PUT \

As per the current configuration, if the request is authorized, the execution will proceed normally. While if it is not authorized, it will return "Access Denied" message with the 403 exit code and stop any further execution.

### Multiple Routes with Different Policies

The plugin supports using different policies for different routes. Each unique combination of `model_path` and `policy_path` creates a separate enforcer instance. For example:

```bash
# Route 1 with policy A
curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/api/v1/*",
"plugins": {
"apisix-authz": {
"model_path": "/path/to/model.conf",
"policy_path": "/path/to/policy_v1.csv",
"username": "user"
}
},
"upstream": {...}
}'

# Route 2 with policy B
curl http://127.0.0.1:9080/apisix/admin/routes/2 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
{
"uri": "/api/v2/*",
"plugins": {
"apisix-authz": {
"model_path": "/path/to/model.conf",
"policy_path": "/path/to/policy_v2.csv",
"username": "user"
}
},
"upstream": {...}
}'
```

Each route will use its own policy file and enforce authorization rules independently.

## Development

If you wish to customize this according to your scenario, you can do so by customizing the apisix-authz.lua file from your plugins directory.
Expand Down
61 changes: 45 additions & 16 deletions apisix-authz.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ local Enforcer = require("casbin")
local core = require("apisix.core")
local get_headers = ngx.req.get_headers

-- e is the Casbin Enforcer
local CasbinEnforcer
-- Table to store multiple Casbin Enforcers keyed by configuration
local CasbinEnforcers = {}
-- Reference to the last accessed enforcer (for API operations)
local CurrentEnforcer

local plugin_name = "apisix-authz"

Expand All @@ -41,18 +43,25 @@ local _M = {
}

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)
-- Create a unique key for this configuration
local conf_key = conf.model_path .. "|" .. conf.policy_path

-- creates an enforcer for this configuration if it doesn't exist
if not CasbinEnforcers[conf_key] then
CasbinEnforcers[conf_key] = Enforcer:new(conf.model_path, conf.policy_path)
end

local enforcer = CasbinEnforcers[conf_key]
-- Set as current enforcer for potential API operations
CurrentEnforcer = enforcer

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
if not enforcer:enforce(username, path, method) then
return 403, {message = "Access Denied"}
end
else
Expand All @@ -62,6 +71,10 @@ end

-- subject, object, action
local function addPolicy()
if not CurrentEnforcer then
return 400, {message = "No enforcer available. Send a request through a configured route first."}
end

local headers = get_headers()
local type = headers["type"]

Expand All @@ -74,7 +87,7 @@ local function addPolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:AddPolicy(subject, object, action) then
if CurrentEnforcer:AddPolicy(subject, object, action) then
return 200, {message = "Successfully added policy."}
else
return 400, {message = "Invalid policy request."}
Expand All @@ -87,7 +100,7 @@ local function addPolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:AddGroupingPolicy(user, role) then
if CurrentEnforcer:AddGroupingPolicy(user, role) then
return 200, {message = "Successfully added grouping policy."}
else
return 400, {message = "Invalid policy request."}
Expand All @@ -98,6 +111,10 @@ local function addPolicy()
end

local function removePolicy()
if not CurrentEnforcer then
return 400, {message = "No enforcer available. Send a request through a configured route first."}
end

local headers = get_headers()
local type = headers["type"]

Expand All @@ -110,7 +127,7 @@ local function removePolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:RemovePolicy(subject, object, action) then
if CurrentEnforcer:RemovePolicy(subject, object, action) then
return 200, {message = "Successfully removed policy."}
else
return 400, {message = "Invalid policy request."}
Expand All @@ -123,7 +140,7 @@ local function removePolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:RemoveGroupingPolicy(user, role) then
if CurrentEnforcer:RemoveGroupingPolicy(user, role) then
return 200, {message = "Successfully removed grouping policy."}
else
return 400, {message = "Invalid policy request."}
Expand All @@ -135,6 +152,10 @@ end

-- subject, object, action
local function hasPolicy()
if not CurrentEnforcer then
return 400, {message = "No enforcer available. Send a request through a configured route first."}
end

local headers = get_headers()
local type = headers["type"]

Expand All @@ -147,7 +168,7 @@ local function hasPolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:HasPolicy(subject, object, action) then
if CurrentEnforcer:HasPolicy(subject, object, action) then
return 200, {data = "true"}
else
return 200, {data = "false"}
Expand All @@ -160,7 +181,7 @@ local function hasPolicy()
return 400, {message = "Invalid policy request."}
end

if CasbinEnforcer:HasGroupingPolicy(user, role) then
if CurrentEnforcer:HasGroupingPolicy(user, role) then
return 200, {data = "true"}
else
return 200, {data = "false"}
Expand All @@ -171,18 +192,22 @@ local function hasPolicy()
end

local function getPolicy()
if not CurrentEnforcer then
return 400, {message = "No enforcer available. Send a request through a configured route first."}
end

local headers = get_headers()
local type = headers["type"]

if type == "p" then
local policy = CasbinEnforcer:GetPolicy()
local policy = CurrentEnforcer:GetPolicy()
if policy then
return 200, {data = policy}
else
return 400
end
elseif type == "g" then
local groupingPolicy = CasbinEnforcer:GetGroupingPolicy()
local groupingPolicy = CurrentEnforcer:GetGroupingPolicy()
if groupingPolicy then
return 200, {data = groupingPolicy}
else
Expand All @@ -194,8 +219,12 @@ local function getPolicy()
end

local function savePolicy()
if not CurrentEnforcer then
return 400, {message = "No enforcer available. Send a request through a configured route first."}
end

local _, err = pcall(function ()
CasbinEnforcer:savePolicy()
CurrentEnforcer:savePolicy()
end)
if not err then
return 200, {message = "Successfully saved policy."}
Expand Down Expand Up @@ -231,8 +260,8 @@ function _M.api()
methods = {"POST"},
uri = "/apisix/plugin/casbin/save",
handler = savePolicy,
},
}
}
end

function _M.check_schema(conf)
Expand Down
Loading