Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] IP Restriction Plugin #384

Merged
merged 1 commit into from
Jul 10, 2015
Merged
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
5 changes: 5 additions & 0 deletions .luacheckrc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ files["kong/vendor/resty_http.lua"] = {
unused = false
}

files["kong/vendor/resty-lrucache/lib/resty/"] = {
global = false,
unused = false
}

files["spec/"] = {
globals = {"describe", "it", "before_each", "setup", "after_each", "teardown", "stub", "mock", "spy", "finally", "pending"}
}
7 changes: 7 additions & 0 deletions kong-0.4.0-1.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies = {
"lua-cjson ~> 2.1.0-1",
"ansicolors ~> 1.0.2-3",
"lbase64 ~> 20120820-1",
"lua-resty-iputils ~> 0.1.0-1",

"luasocket ~> 2.0.2-5",
"lrexlib-pcre ~> 2.7.2-1",
Expand All @@ -39,6 +40,7 @@ build = {
["lapp"] = "kong/vendor/lapp.lua",
["ngx.ssl"] = "kong/vendor/ssl.lua",
["resty_http"] = "kong/vendor/resty_http.lua",
["resty.lrucache"] = "kong/vendor/resty-lrucache/lib/resty/lrucache.lua",

["kong.constants"] = "kong/constants.lua",

Expand Down Expand Up @@ -160,6 +162,11 @@ build = {
["kong.plugins.ssl.ssl_util"] = "kong/plugins/ssl/ssl_util.lua",
["kong.plugins.ssl.schema"] = "kong/plugins/ssl/schema.lua",

["kong.plugins.ip-restriction.handler"] = "kong/plugins/ip-restriction/handler.lua",
["kong.plugins.ip-restriction.init_worker"] = "kong/plugins/ip-restriction/init_worker.lua",
["kong.plugins.ip-restriction.access"] = "kong/plugins/ip-restriction/access.lua",
["kong.plugins.ip-restriction.schema"] = "kong/plugins/ip-restriction/schema.lua",

["kong.api.app"] = "kong/api/app.lua",
["kong.api.crud_helpers"] = "kong/api/crud_helpers.lua",
["kong.api.routes.kong"] = "kong/api/routes/kong.lua",
Expand Down
1 change: 1 addition & 0 deletions kong.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ plugins_available:
- response_transformer
- requestsizelimiting
- analytics
- ip-restriction

## The Kong working directory
## (Make sure you have read and write permissions)
Expand Down
2 changes: 1 addition & 1 deletion kong/dao/cassandra/base_dao.lua
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ end
-- the other should be preserved. Of course this only applies in partial update.
local function fix_tables(t, old_t, schema)
for k, v in pairs(schema.fields) do
if v.schema then
if t[k] ~= nil and v.schema then
local s = type(v.schema) == "function" and v.schema(t) or v.schema
for s_k, s_v in pairs(s.fields) do
if not t[k][s_k] and old_t[k] then
Expand Down
2 changes: 1 addition & 1 deletion kong/dao/schemas_validation.lua
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ function _M.validate_entity(t, schema, options)
is_valid_type = bool == "true" or bool == "false"
t[column] = bool == "true"
elseif v.type == "array" then
t[column] = stringy.split(t[column], ",")
t[column] = stringy.strip(t[column]) == "" and {} or stringy.split(t[column], ",") -- Handling empty arrays
for arr_k, arr_v in ipairs(t[column]) do
t[column][arr_k] = stringy.strip(arr_v)
end
Expand Down
30 changes: 30 additions & 0 deletions kong/plugins/ip-restriction/access.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
local iputils = require "resty.iputils"
local responses = require "kong.tools.responses"
local utils = require "kong.tools.utils"

local _M = {}

function _M.execute(conf)
local block = false

if conf.blacklist and utils.table_size(conf.blacklist) > 0 then
if iputils.ip_in_cidrs(ngx.var.remote_addr, conf._blacklist_cache) then
block = true
end
end

if conf.whitelist and utils.table_size(conf.whitelist) > 0 then
if iputils.ip_in_cidrs(ngx.var.remote_addr, conf._whitelist_cache) then
block = false
else
block = true
end
end

if block then
ngx.ctx.stop_phases = true -- interrupt other phases of this request
return responses.send_HTTP_FORBIDDEN("Your IP address is not allowed")
end
end

return _M
23 changes: 23 additions & 0 deletions kong/plugins/ip-restriction/handler.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
local BasePlugin = require "kong.plugins.base_plugin"
local init_worker = require "kong.plugins.ip-restriction.init_worker"
local access = require "kong.plugins.ip-restriction.access"

local IpRestrictionHandler = BasePlugin:extend()

function IpRestrictionHandler:new()
IpRestrictionHandler.super.new(self, "ip-restriction")
end

function IpRestrictionHandler:init_worker()
IpRestrictionHandler.super.init_worker(self)
init_worker.execute()
end

function IpRestrictionHandler:access(conf)
IpRestrictionHandler.super.access(self)
access.execute(conf)
end

IpRestrictionHandler.PRIORITY = 990

return IpRestrictionHandler
9 changes: 9 additions & 0 deletions kong/plugins/ip-restriction/init_worker.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
local iputils = require "resty.iputils"

local _M = {}

function _M.execute()
iputils.enable_lrucache()
end

return _M
26 changes: 26 additions & 0 deletions kong/plugins/ip-restriction/schema.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
local iputils = require "resty.iputils"

local function validate_ips(v, t, column)
local new_fields
if v and type(v) == "table" then
for _, ip in ipairs(v) do
local _, err = iputils.parse_cidr(ip)
if type(err) == "string" then -- It's an error only if the second variable is a string
return false, err
end
end
new_fields = { ["_"..column.."_cache"] = iputils.parse_cidrs(v) }
end
return true, nil, new_fields
end

return {
fields = {
whitelist = { type = "array", func = validate_ips },
blacklist = { type = "array", func = validate_ips },

-- Internal use
_whitelist_cache = { type = "array" },
_blacklist_cache = { type = "array" }
}
}
229 changes: 229 additions & 0 deletions kong/vendor/resty-lrucache/lib/resty/lrucache.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
-- Copyright (C) Yichun Zhang (agentzh)


local ffi = require "ffi"
local ffi_new = ffi.new
local ffi_sizeof = ffi.sizeof
local ffi_cast = ffi.cast
local ffi_fill = ffi.fill
local ngx_now = ngx.now
local uintptr_t = ffi.typeof("uintptr_t")
local setmetatable = setmetatable
local tonumber = tonumber


-- queue data types
--
-- this queue is a double-ended queue and the first node
-- is reserved for the queue itself.
-- the implementation is mostly borrowed from nginx's ngx_queue_t data
-- structure.

ffi.cdef[[
typedef struct lrucache_queue_s lrucache_queue_t;
struct lrucache_queue_s {
double expire; /* in seconds */
lrucache_queue_t *prev;
lrucache_queue_t *next;
};
]]

local queue_arr_type = ffi.typeof("lrucache_queue_t[?]")
local queue_ptr_type = ffi.typeof("lrucache_queue_t*")
local queue_type = ffi.typeof("lrucache_queue_t")
local NULL = ffi.null


-- queue utility functions

local function queue_insert_tail(h, x)
local last = h[0].prev
x.prev = last
last.next = x
x.next = h
h[0].prev = x
end


local function queue_init(size)
if not size then
size = 0
end
local q = ffi_new(queue_arr_type, size + 1)
ffi_fill(q, ffi_sizeof(queue_type, size + 1), 0)

if size == 0 then
q[0].prev = q
q[0].next = q

else
local prev = q[0]
for i = 1, size do
local e = q[i]
prev.next = e
e.prev = prev
prev = e
end

local last = q[size]
last.next = q
q[0].prev = last
end

return q
end


local function queue_is_empty(q)
-- print("q: ", tostring(q), "q.prev: ", tostring(q), ": ", q == q.prev)
return q == q[0].prev
end


local function queue_remove(x)
local prev = x.prev
local next = x.next

next.prev = prev
prev.next = next

-- for debugging purpose only:
x.prev = NULL
x.next = NULL
end


local function queue_insert_head(h, x)
x.next = h[0].next
x.next.prev = x
x.prev = h
h[0].next = x
end


local function queue_last(h)
return h[0].prev
end


local function queue_head(h)
return h[0].next
end


-- true module stuffs

local _M = {
_VERSION = '0.04'
}
local mt = { __index = _M }


local function ptr2num(ptr)
return tonumber(ffi_cast(uintptr_t, ptr))
end


function _M.new(size)
if size < 1 then
return nil, "size too small"
end

local self = {
keys = {},
hasht = {},
free_queue = queue_init(size),
cache_queue = queue_init(),
key2node = {},
node2key = {},
}
return setmetatable(self, mt)
end


function _M.get(self, key)
local hasht = self.hasht
local val = hasht[key]
if not val then
return nil
end

local node = self.key2node[key]

-- print(key, ": moving node ", tostring(node), " to cache queue head")
local cache_queue = self.cache_queue
queue_remove(node)
queue_insert_head(cache_queue, node)

if node.expire >= 0 and node.expire < ngx_now() then
-- print("expired: ", node.expire, " > ", ngx_now())
return nil, val
end
return val
end


function _M.delete(self, key)
self.hasht[key] = nil

local key2node = self.key2node
local node = key2node[key]

if not node then
return false
end

key2node[key] = nil
self.node2key[ptr2num(node)] = nil

queue_remove(node)
queue_insert_tail(self.free_queue, node)
return true
end


function _M.set(self, key, value, ttl)
local hasht = self.hasht
hasht[key] = value

local key2node = self.key2node
local node = key2node[key]
if not node then
local free_queue = self.free_queue
local node2key = self.node2key

if queue_is_empty(free_queue) then
-- evict the least recently used key
-- assert(not queue_is_empty(self.cache_queue))
node = queue_last(self.cache_queue)

local oldkey = node2key[ptr2num(node)]
-- print(key, ": evicting oldkey: ", oldkey, ", oldnode: ",
-- tostring(node))
if oldkey then
hasht[oldkey] = nil
key2node[oldkey] = nil
end

else
-- take a free queue node
node = queue_head(free_queue)
-- print(key, ": get a new free node: ", tostring(node))
end

node2key[ptr2num(node)] = key
key2node[key] = node
end

queue_remove(node)
queue_insert_head(self.cache_queue, node)

if ttl then
node.expire = ngx_now() + ttl
else
node.expire = -1
end
end


return _M
Loading