feature: added IPv4 whitelist/blacklist plugin ip-restriction (#398)

This commit is contained in:
chnliyong 2019-08-19 13:59:47 +08:00 committed by WenMing
parent d930b7b122
commit f26f8ba6da
9 changed files with 566 additions and 3 deletions

View File

@ -280,3 +280,30 @@ See the License for the specific language governing permissions and
limitations under the License.
%%%%%%%%%
lua-resty-iputils
https://github.com/hamishforbes/lua-resty-iputils
The MIT License (MIT)
Copyright (c) 2013 Hamish Forbes
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
%%%%%%%%%

View File

@ -52,10 +52,11 @@ For more detailed information, see the [White Paper](https://www.iresty.com/down
- **Scalability**
- **High performance**: The single-core QPS reaches 24k with an average delay of less than 0.6 milliseconds.
- **Anti-ReDoS(Regular expression Denial of Service)**
- **IP whitelist/blacklist**
- **OAuth2.0**: TODO.
- **ACL**: TODO.
- **Bot detection**: TODO.
- **IP blacklist**: TODO.
## Install

View File

@ -49,10 +49,10 @@ APISIX 通过插件机制,提供动态负载平衡、身份验证、限流限
- **可扩展**
- **高性能**:在单核上 QPS 可以达到 24k同时延迟只有 0.6 毫秒。
- **防御 ReDoS(正则表达式拒绝服务)**
- **IP 黑名单**
- **OAuth2.0**: TODO.
- **ACL**: TODO.
- **Bot detection**: TODO.
- **IP 黑名单**: TODO.
## 安装

View File

@ -32,3 +32,4 @@ plugins: # plugin list
- node-status
- jwt-auth
- zipkin
- ip-restriction

View File

@ -0,0 +1,118 @@
local ipairs = ipairs
local core = require("apisix.core")
local iputils = require("resty.iputils")
local schema = {
type = "object",
properties = {
whitelist = {
type = "array",
items = {type = "string"},
minItems = 1
},
blacklist = {
type = "array",
items = {type = "string"},
minItems = 1
}
},
oneOf = {
{required = {"whitelist"}},
{required = {"blacklist"}}
}
}
local plugin_name = "ip-restriction"
local _M = {
version = 0.1,
priority = 3000, -- TODO: add a type field, may be a good idea
name = plugin_name,
schema = schema,
}
-- TODO: support IPv6
local function validate_cidr_v4(ip)
local lower, err = iputils.parse_cidr(ip)
if not lower and err then
return nil, "invalid cidr range: " .. err
end
return true
end
function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
if not ok then
return false, err
end
if conf.whitelist and #conf.whitelist > 0 then
for _, cidr in ipairs(conf.whitelist) do
ok, err = validate_cidr_v4(cidr)
if not ok then
return false, err
end
end
end
if conf.blacklist and #conf.blacklist > 0 then
for _, cidr in ipairs(conf.blacklist) do
ok, err = validate_cidr_v4(cidr)
if not ok then
return false, err
end
end
end
return true
end
local function create_cidrs(ip_list)
local parsed_cidrs = core.table.new(#ip_list, 0)
for i, cidr in ipairs(ip_list) do
local lower, upper = iputils.parse_cidr(cidr)
if not lower and upper then
local err = upper
return nil, "invalid cidr range: " .. err
end
parsed_cidrs[i] = {lower, upper}
end
return parsed_cidrs
end
function _M.access(conf, ctx)
local block = false
local binary_remote_addr = ctx.var.binary_remote_addr
if conf.blacklist and #conf.blacklist > 0 then
local name = plugin_name .. 'black'
local parsed_cidrs = core.lrucache.plugin_ctx(name, ctx, create_cidrs,
conf.blacklist)
block = iputils.binip_in_cidrs(binary_remote_addr, parsed_cidrs)
end
if conf.whitelist and #conf.whitelist > 0 then
local name = plugin_name .. 'white'
local parsed_cidrs = core.lrucache.plugin_ctx(name, ctx, create_cidrs,
conf.whitelist)
block = not iputils.binip_in_cidrs(binary_remote_addr, parsed_cidrs)
end
if block then
return 403, { message = "Your IP address is not allowed" }
end
end
return _M

View File

@ -28,6 +28,7 @@ dependencies = {
"lua-resty-session = 2.24",
"opentracing-openresty = 0.1",
"lua-resty-radixtree = 0.4",
"lua-resty-iputils = 0.3.0-1",
}
build = {

View File

@ -14,6 +14,6 @@ __DATA__
--- request
GET /apisix/admin/plugins/list
--- response_body_like eval
qr/\["limit-req","limit-count","limit-conn","key-auth","prometheus","node-status","jwt-auth","zipkin"\]/
qr/\["limit-req","limit-count","limit-conn","key-auth","prometheus","node-status","jwt-auth","zipkin","ip-restriction"\]/
--- no_error_log
[error]

1
t/debug-mode.t vendored
View File

@ -39,6 +39,7 @@ done
--- grep_error_log eval
qr/loaded plugin and sort by priority: [-\d]+ name: [\w-]+/
--- grep_error_log_out
loaded plugin and sort by priority: 3000 name: ip-restriction
loaded plugin and sort by priority: 2510 name: jwt-auth
loaded plugin and sort by priority: 2500 name: key-auth
loaded plugin and sort by priority: 1003 name: limit-conn

414
t/plugin/ip-restriction.t Normal file
View File

@ -0,0 +1,414 @@
BEGIN {
if ($ENV{TEST_NGINX_CHECK_LEAK}) {
$SkipReason = "unavailable for the hup tests";
} else {
$ENV{TEST_NGINX_USE_HUP} = 1;
undef $ENV{TEST_NGINX_USE_STAP};
}
}
use t::APISix 'no_plan';
repeat_each(1);
no_long_string();
no_shuffle();
no_root_location();
run_tests;
__DATA__
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local conf = {
whitelist = {
"10.255.254.0/24",
"192.168.0.0/16"
}
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("cjson").encode(conf))
}
}
--- request
GET /t
--- response_body
{"whitelist":["10.255.254.0\/24","192.168.0.0\/16"]}
--- no_error_log
[error]
=== TEST 2: wrong CIDR v4 format
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local conf = {
whitelist = {
"10.255.256.0/24",
"192.168.0.0/16"
}
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("cjson").encode(conf))
}
}
--- request
GET /t
--- response_body_like eval
qr/invalid cidr range: Invalid octet: 256/
--- no_error_log
[error]
=== TEST 3: wrong CIDR v4 format
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local conf = {
whitelist = {
"10.255.254.0/38",
"192.168.0.0/16"
}
}
local ok, err = plugin.check_schema(conf)
if not ok then
ngx.say(err)
end
ngx.say(require("cjson").encode(conf))
}
}
--- request
GET /t
--- response_body_like eval
qr@invalid cidr range: Invalid prefix: /38@
--- no_error_log
[error]
=== TEST 4: empty conf
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local ok, err = plugin.check_schema({})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
invalid "oneOf" in docuement at pointer "#"
done
--- no_error_log
[error]
=== TEST 5: empty CIDRs
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local ok, err = plugin.check_schema({blacklist={}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
invalid "type" in docuement at pointer "#/blacklist"
done
--- no_error_log
[error]
=== TEST 6: whitelist and blacklist mutual exclusive
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.ip-restriction")
local ok, err = plugin.check_schema({whitelist={"172.17.40.0/24"}, blacklist={"10.255.0.0/16"}})
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
invalid "oneOf" in docuement at pointer "#"
done
--- no_error_log
[error]
=== TEST 7: set whitelist
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ip-restriction": {
"whitelist": [
"127.0.0.0/24",
"113.74.26.106"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 8: hit route and ip cidr in the whitelist
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]
=== TEST 9: hit route and ip in the whitelist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 113.74.26.106
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]
=== TEST 10: hit route and ip not in the whitelist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 114.114.114.114
--- request
GET /hello
--- error_code: 403
--- response_body
{"message":"Your IP address is not allowed"}
--- no_error_log
[error]
=== TEST 11: hit route and ipv6 not not in the whitelist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 2001:db8::2
--- request
GET /hello
--- error_code: 403
--- response_body
{"message":"Your IP address is not allowed"}
--- no_error_log
[error]
=== TEST 12: set blacklist
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"ip-restriction": {
"blacklist": [
"127.0.0.0/24",
"113.74.26.106"
]
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 13: hit route and ip cidr in the blacklist
--- request
GET /hello
--- error_code: 403
--- response_body
{"message":"Your IP address is not allowed"}
--- no_error_log
[error]
=== TEST 14: hit route and ip in the blacklist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 113.74.26.106
--- request
GET /hello
--- error_code: 403
--- response_body
{"message":"Your IP address is not allowed"}
--- no_error_log
[error]
=== TEST 15: hit route and ip not not in the blacklist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 114.114.114.114
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]
=== TEST 16: hit route and ipv6 not not in the blacklist
--- http_config
set_real_ip_from 127.0.0.1;
real_ip_header X-Forwarded-For;
--- more_headers
X-Forwarded-For: 2001:db8::2
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]
=== TEST 17: remove ip-restriction
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- request
GET /t
--- response_body
passed
--- no_error_log
[error]
=== TEST 18: hit route
--- request
GET /hello
--- response_body
hello world
--- no_error_log
[error]