diff --git a/kong/plugins/ip-restriction/handler.lua b/kong/plugins/ip-restriction/handler.lua index 3fa5596f4ff..2ba1f206d0c 100644 --- a/kong/plugins/ip-restriction/handler.lua +++ b/kong/plugins/ip-restriction/handler.lua @@ -3,9 +3,10 @@ local ipmatcher = require "resty.ipmatcher" local kong_meta = require "kong.meta" -local ngx_var = ngx.var -local kong = kong local error = error +local kong = kong +local log = kong.log +local ngx_var = ngx.var local IPMATCHER_COUNT = 512 @@ -29,6 +30,13 @@ do end +local function do_exit(status, message) + log.warn(message) + + return kong.response.error(status, message) +end + + local function match_bin(list, binary_remote_addr) local matcher, err @@ -52,31 +60,41 @@ local function match_bin(list, binary_remote_addr) end -function IpRestrictionHandler:access(conf) +local function do_restrict(conf) local binary_remote_addr = ngx_var.binary_remote_addr if not binary_remote_addr then - return kong.response.error(403, "Cannot identify the client IP address, unix domain sockets are not supported.") + return do_exit(403, "Cannot identify the client IP address, unix domain sockets are not supported.") end local deny = conf.deny local allow = conf.allow local status = conf.status or 403 - local message = conf.message or "Your IP address is not allowed" + local message = conf.message or string.format("IP address not allowed: %s", ngx_var.remote_addr) if not isempty(deny) then local blocked = match_bin(deny, binary_remote_addr) if blocked then - return kong.response.error(status, message) + return do_exit(status, message) end end if not isempty(allow) then local allowed = match_bin(allow, binary_remote_addr) if not allowed then - return kong.response.error(status, message) + return do_exit(status, message) end end end +function IpRestrictionHandler:access(conf) + return do_restrict(conf) +end + + +function IpRestrictionHandler:preread(conf) + return do_restrict(conf) +end + + return IpRestrictionHandler diff --git a/kong/plugins/ip-restriction/schema.lua b/kong/plugins/ip-restriction/schema.lua index a03cf4b494c..2a853791fa9 100644 --- a/kong/plugins/ip-restriction/schema.lua +++ b/kong/plugins/ip-restriction/schema.lua @@ -4,7 +4,7 @@ local typedefs = require "kong.db.schema.typedefs" return { name = "ip-restriction", fields = { - { protocols = typedefs.protocols_http }, + { protocols = typedefs.protocols { default = { "http", "https", "tcp", "tls", "grpc", "grpcs" } }, }, { config = { type = "record", fields = { diff --git a/spec/03-plugins/17-ip-restriction/01-schema_spec.lua b/spec/03-plugins/17-ip-restriction/01-schema_spec.lua index 265475de91b..35856efe275 100644 --- a/spec/03-plugins/17-ip-restriction/01-schema_spec.lua +++ b/spec/03-plugins/17-ip-restriction/01-schema_spec.lua @@ -4,24 +4,24 @@ local v = require("spec.helpers").validate_plugin_config_schema describe("Plugin: ip-restriction (schema)", function() it("should accept a valid allow", function() - assert(v({ allow = { "127.0.0.1", "127.0.0.2" } }, schema_def)) + assert(v({ allow = { "127.0.0.1/32", "127.0.0.2/32" } }, schema_def)) end) it("should accept a valid allow and status/message", function() - assert(v({ allow = { "127.0.0.1", "127.0.0.2" }, status = 403, message = "Forbidden" }, schema_def)) + assert(v({ allow = { "127.0.0.1/32", "127.0.0.2/32" }, status = 403, message = "Forbidden" }, schema_def)) end) it("should accept a valid cidr range", function() assert(v({ allow = { "127.0.0.1/8" } }, schema_def)) end) it("should accept a valid deny", function() - assert(v({ deny = { "127.0.0.1", "127.0.0.2" } }, schema_def)) + assert(v({ deny = { "127.0.0.1/32", "127.0.0.2/32" } }, schema_def)) end) it("should accept both non-empty allow and deny", function() local schema = { deny = { - "127.0.0.2" + "127.0.0.2/32" }, allow = { - "127.0.0.1" + "127.0.0.1/32" }, } assert(v(schema, schema_def)) @@ -40,7 +40,7 @@ describe("Plugin: ip-restriction (schema)", function() allow = { "invalid ip or cidr range: 'hello'" } }, err.config) - ok, err = v({ allow = { "127.0.0.1", "127.0.0.2", "hello" } }, schema_def) + ok, err = v({ allow = { "127.0.0.1/32", "127.0.0.2/32", "hello" } }, schema_def) assert.falsy(ok) assert.same({ allow = { [3] = "invalid ip or cidr range: 'hello'" } @@ -58,7 +58,7 @@ describe("Plugin: ip-restriction (schema)", function() deny = { "invalid ip or cidr range: 'hello'" } }, err.config) - ok, err = v({ deny = { "127.0.0.1", "127.0.0.2", "hello" } }, schema_def) + ok, err = v({ deny = { "127.0.0.1/32", "127.0.0.2/32", "hello" } }, schema_def) assert.falsy(ok) assert.same({ deny = { [3] = "invalid ip or cidr range: 'hello'" } diff --git a/spec/03-plugins/17-ip-restriction/02-access_spec.lua b/spec/03-plugins/17-ip-restriction/02-access_spec.lua index 8ed01faf5b8..e1af8734813 100644 --- a/spec/03-plugins/17-ip-restriction/02-access_spec.lua +++ b/spec/03-plugins/17-ip-restriction/02-access_spec.lua @@ -1,6 +1,7 @@ local helpers = require "spec.helpers" local cjson = require "cjson" +local MESSAGE = "echo, ping, pong. echo, ping, pong. echo, ping, pong.\n" for _, strategy in helpers.each_strategy() do describe("Plugin: ip-restriction (access) [#" .. strategy .. "]", function() @@ -91,6 +92,45 @@ for _, strategy in helpers.each_strategy() do service = grpc_service, }) + -- tcp services/routes + local tcp_srv = bp.services:insert({ + name = "tcp", + host = helpers.mock_upstream_host, + port = helpers.mock_upstream_stream_port, + protocol = "tcp" + }) + + local tls_srv = bp.services:insert({ + name = "tls", + host = helpers.mock_upstream_host, + port = helpers.mock_upstream_stream_ssl_port, + protocol = "tls" + }) + + local route_tcp_allow = bp.routes:insert { + destinations = { + { + port = 19000, + }, + }, + protocols = { + "tcp", + }, + service = tcp_srv, + } + + local route_tcp_deny = bp.routes:insert { + destinations = { + { + port = 19443, + }, + }, + protocols = { + "tls", + }, + service = tls_srv, + } + bp.plugins:insert { name = "ip-restriction", route = { id = route1.id }, @@ -186,17 +226,33 @@ for _, strategy in helpers.each_strategy() do name = "ip-restriction", route = { id = route12.id }, config = { - deny = { "127.0.0.1", "127.0.0.2" }, + deny = { "127.0.0.0/24" }, status = 401, message = "Forbidden" }, } + assert(db.plugins:insert { + name = "ip-restriction", + route = { id = route_tcp_allow.id }, + config = { + allow = { "127.0.0.0/24" }, + }, + }) + + assert(db.plugins:insert { + name = "ip-restriction", + route = { id = route_tcp_deny.id }, + config = { + deny = { "127.0.0.0/24" }, + }, + }) + assert(db.plugins:insert { name = "ip-restriction", route = { id = route_grpc_deny.id }, config = { - deny = { "127.0.0.1", "127.0.0.2" } + deny = { "127.0.0.0/24" }, }, }) @@ -204,7 +260,7 @@ for _, strategy in helpers.each_strategy() do name = "ip-restriction", route = { id = route_grpc_allow.id }, config = { - deny = { "127.0.0.2" } + deny = { "127.0.0.2/32" } }, }) @@ -212,7 +268,7 @@ for _, strategy in helpers.each_strategy() do name = "ip-restriction", route = { id = route_grpc_xforwarded_deny.id }, config = { - allow = { "127.0.0.4" }, + allow = { "127.0.0.4/32" }, }, }) @@ -222,6 +278,8 @@ for _, strategy in helpers.each_strategy() do real_ip_recursive = "on", trusted_ips = "0.0.0.0/0, ::/0", nginx_conf = "spec/fixtures/custom_nginx.template", + stream_listen = helpers.get_proxy_ip(false) .. ":19000," .. + helpers.get_proxy_ip(false) .. ":19443 ssl" }) proxy_client = helpers.proxy_client() @@ -247,8 +305,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("blocks a request when the IP is denied with status/message", function() @@ -276,6 +333,17 @@ for _, strategy in helpers.each_strategy() do assert.matches("Code: PermissionDenied", err) end) + it("blocks a request when the IP is denied #tcp", function() + local tcp = ngx.socket.tcp() + assert(tcp:connect(helpers.get_proxy_ip(true), 19443)) + assert(tcp:sslhandshake(nil, nil, false)) + assert(tcp:send(MESSAGE)) + assert(tcp:receive("*a")) + tcp:close() + + assert.logfile().has.line("IP address not allowed", true) + end) + it("allows a request when the IP is not denied", function() local res = assert(proxy_client:send { method = "GET", @@ -300,6 +368,16 @@ for _, strategy in helpers.each_strategy() do assert.truthy(ok) end) + it("allows a request when the IP is not denied #tcp", function() + local tcp = ngx.socket.tcp() + local ip = helpers.get_proxy_ip(false) + assert(tcp:connect(ip, 19000)) + assert(tcp:send(MESSAGE)) + local body = assert(tcp:receive("*a")) + assert.equal(MESSAGE, body) + tcp:close() + end) + it("blocks IP with CIDR", function() local res = assert(proxy_client:send { method = "GET", @@ -309,8 +387,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("blocks an IP on a allowed CIDR range", function() local res = assert(proxy_client:send { @@ -321,8 +398,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("takes precedence over an allowed IP", function() local res = assert(proxy_client:send { @@ -333,8 +409,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("takes precedence over an allowed CIDR range", function() local res = assert(proxy_client:send { @@ -345,8 +420,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) describe("X-Forwarded-For", function() @@ -385,8 +459,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) end) end) @@ -401,8 +474,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("allows a allowed IP", function() local res = assert(proxy_client:send { @@ -425,8 +497,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("block with not allowed X-Forwarded-For header", function() local res = assert(proxy_client:send { @@ -438,8 +509,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("block with not allowed X-Forwarded-For header #grpc", function() local ok, err = helpers.proxy_client_grpc(){ @@ -521,8 +591,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) res = assert(admin_client:send { method = "PATCH", @@ -721,8 +790,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("allows a request when the IPv6 is not denied", function() local res = assert(proxy_client:send { @@ -747,8 +815,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("blocks an IPv6 on a allowed IPv6 CIDR range", function() local res = assert(proxy_client:send { @@ -760,8 +827,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("takes precedence over an allowed IPv6", function() local res = assert(proxy_client:send { @@ -773,8 +839,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("takes precedence over an allowed IPv6 CIDR range", function() local res = assert(proxy_client:send { @@ -785,8 +850,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) end) @@ -801,8 +865,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("allows a allowed IPv6", function() local res = assert(proxy_client:send { @@ -864,8 +927,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) res = assert(admin_client:send { method = "PATCH", @@ -1001,8 +1063,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("blocks with blocked complex X-Forwarded-For header", function() local res = assert(proxy_client:send { @@ -1014,8 +1075,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("allows with allowed complex X-Forwarded-For header", function() local res = assert(proxy_client:send { @@ -1043,8 +1103,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) it("allows with allowed X-Forwarded-For header", function() local res = assert(proxy_client:send { @@ -1082,8 +1141,7 @@ for _, strategy in helpers.each_strategy() do } }) local body = assert.res_status(403, res) - local json = cjson.decode(body) - assert.same({ message = "Your IP address is not allowed" }, json) + assert.matches("IP address not allowed", body) end) end) end)