From 70587167e3e04c0233f31ec7b32593e74c2fc0ff Mon Sep 17 00:00:00 2001 From: thefosk Date: Tue, 17 Mar 2015 19:16:35 -0700 Subject: [PATCH 1/6] Starting to separate authentication plugins --- kong-0.0.1beta-1.rockspec | 14 +- src/kong/plugins/authentication/handler.lua | 19 --- src/kong/plugins/authentication/schema.lua | 21 --- src/kong/plugins/basicauth/access.lua | 96 +++++++++++++ src/kong/plugins/basicauth/handler.lua | 19 +++ src/kong/plugins/basicauth/schema.lua | 5 + .../{authentication => headerauth}/access.lua | 62 +-------- src/kong/plugins/headerauth/handler.lua | 19 +++ src/kong/plugins/headerauth/schema.lua | 6 + src/kong/plugins/queryauth/access.lua | 128 ++++++++++++++++++ src/kong/plugins/queryauth/handler.lua | 19 +++ src/kong/plugins/queryauth/schema.lua | 4 + 12 files changed, 311 insertions(+), 101 deletions(-) delete mode 100644 src/kong/plugins/authentication/handler.lua delete mode 100644 src/kong/plugins/authentication/schema.lua create mode 100644 src/kong/plugins/basicauth/access.lua create mode 100644 src/kong/plugins/basicauth/handler.lua create mode 100644 src/kong/plugins/basicauth/schema.lua rename src/kong/plugins/{authentication => headerauth}/access.lua (68%) create mode 100644 src/kong/plugins/headerauth/handler.lua create mode 100644 src/kong/plugins/headerauth/schema.lua create mode 100644 src/kong/plugins/queryauth/access.lua create mode 100644 src/kong/plugins/queryauth/handler.lua create mode 100644 src/kong/plugins/queryauth/schema.lua diff --git a/kong-0.0.1beta-1.rockspec b/kong-0.0.1beta-1.rockspec index 85cb59933bc..629c6882c43 100644 --- a/kong-0.0.1beta-1.rockspec +++ b/kong-0.0.1beta-1.rockspec @@ -64,9 +64,17 @@ build = { ["kong.dao.cassandra.accounts"] = "src/kong/dao/cassandra/accounts.lua", ["kong.dao.cassandra.applications"] = "src/kong/dao/cassandra/applications.lua", - ["kong.plugins.authentication.handler"] = "src/kong/plugins/authentication/handler.lua", - ["kong.plugins.authentication.access"] = "src/kong/plugins/authentication/access.lua", - ["kong.plugins.authentication.schema"] = "src/kong/plugins/authentication/schema.lua", + ["kong.plugins.basicauth.handler"] = "src/kong/plugins/basicauth/handler.lua", + ["kong.plugins.basicauth.access"] = "src/kong/plugins/basicauth/access.lua", + ["kong.plugins.basicauth.schema"] = "src/kong/plugins/basicauth/schema.lua", + + ["kong.plugins.queryauth.handler"] = "src/kong/plugins/queryauth/handler.lua", + ["kong.plugins.queryauth.access"] = "src/kong/plugins/queryauth/access.lua", + ["kong.plugins.queryauth.schema"] = "src/kong/plugins/queryauth/schema.lua", + + ["kong.plugins.headerauth.handler"] = "src/kong/plugins/headerauth/handler.lua", + ["kong.plugins.headerauth.access"] = "src/kong/plugins/headerauth/access.lua", + ["kong.plugins.headerauth.schema"] = "src/kong/plugins/headerauth/schema.lua", ["kong.plugins.networklog.handler"] = "src/kong/plugins/networklog/handler.lua", ["kong.plugins.networklog.log"] = "src/kong/plugins/networklog/log.lua", diff --git a/src/kong/plugins/authentication/handler.lua b/src/kong/plugins/authentication/handler.lua deleted file mode 100644 index cc7184a12af..00000000000 --- a/src/kong/plugins/authentication/handler.lua +++ /dev/null @@ -1,19 +0,0 @@ --- Copyright (C) Mashape, Inc. - -local BasePlugin = require "kong.base_plugin" -local access = require "kong.plugins.authentication.access" - -local AuthenticationHandler = BasePlugin:extend() - -function AuthenticationHandler:new() - AuthenticationHandler.super.new(self, "authentication") -end - -function AuthenticationHandler:access(conf) - AuthenticationHandler.super.access(self) - access.execute(conf) -end - -AuthenticationHandler.PRIORITY = 1000 - -return AuthenticationHandler diff --git a/src/kong/plugins/authentication/schema.lua b/src/kong/plugins/authentication/schema.lua deleted file mode 100644 index e67ac817003..00000000000 --- a/src/kong/plugins/authentication/schema.lua +++ /dev/null @@ -1,21 +0,0 @@ -local constants = require "kong.constants" -local utils = require "kong.tools.utils" - -local function check_authentication_key_names(names, plugin_value) - if plugin_value.authentication_type == constants.AUTHENTICATION.BASIC and names then - return false, "This field is not available for \""..constants.AUTHENTICATION.BASIC.."\" authentication" - elseif plugin_value.authentication_type ~= constants.AUTHENTICATION.BASIC then - if not names or type(names) ~= "table" or utils.table_size(names) == 0 then - return false, "You need to specify an array with at least one value" - end - end - return true -end - -return { - authentication_type = { required = true, immutable = true, enum = { constants.AUTHENTICATION.QUERY, - constants.AUTHENTICATION.BASIC, - constants.AUTHENTICATION.HEADER }}, - authentication_key_names = { type = "table", func = check_authentication_key_names }, - hide_credentials = { type = "boolean", default = false } -} diff --git a/src/kong/plugins/basicauth/access.lua b/src/kong/plugins/basicauth/access.lua new file mode 100644 index 00000000000..22bb8bbd600 --- /dev/null +++ b/src/kong/plugins/basicauth/access.lua @@ -0,0 +1,96 @@ +local constants = require "kong.constants" +local stringy = require "stringy" +local cjson = require "cjson" +local cache = require "kong.tools.cache" + +local _M = {} + +-- Fast lookup for credential retrieval depending on the type of the authentication +-- +-- All methods must respect: +-- +-- @param request ngx request object +-- @param {table} conf Plugin configuration (value property) +-- @return {string} public_key +-- @return {string} private_key +local retrieve_credentials = { + [constants.AUTHENTICATION.BASIC] = function(request, conf) + local username, password + local authorization_header = request.get_headers()["authorization"] + + if authorization_header then + local iterator, iter_err = ngx.re.gmatch(authorization_header, "\\s*[Bb]asic\\s*(.+)") + if not iterator then + ngx.log(ngx.ERR, iter_err) + return + end + + local m, err = iterator() + if err then + ngx.log(ngx.ERR, err) + return + end + + if m and table.getn(m) > 0 then + local decoded_basic = ngx.decode_base64(m[1]) + local basic_parts = stringy.split(decoded_basic, ":") + username = basic_parts[1] + password = basic_parts[2] + end + end + + if conf.hide_credentials then + request.clear_header("authorization") + end + + return username, password + end +} + +-- Fast lookup for credential validation depending on the type of the authentication +-- +-- All methods must respect: +-- +-- @param {table} application The retrieved application from the public_key passed in the request +-- @param {string} public_key +-- @param {string} private_key +-- @return {boolean} Success of authentication +local validate_credentials = { + [constants.AUTHENTICATION.BASIC] = function(application, username, password) + if application then + -- TODO: No encryption yet + return application.secret_key == password + end + end +} + +function _M.execute(conf) + if not conf then return end + + local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.BASIC](ngx.req, conf) + local application + + -- Make sure we are not sending an empty table to find_by_keys + if public_key then + application = cache.get_and_set(cache.application_key(public_key), function() + local applications, err = dao.applications:find_by_keys { public_key = public_key } + local result + if err then + ngx.log(ngx.ERR, err) + utils.show_error(500) + elseif #applications > 0 then + result = applications[1] + end + return result + end) + end + + if not validate_credentials[constants.AUTHENTICATION.BASIC](application, public_key, secret_key) then + utils.show_error(403, "Your authentication credentials are invalid") + end + + ngx.req.set_header(constants.HEADERS.ACCOUNT_ID, application.account_id) + ngx.ctx.authenticated_entity = application +end + +return _M diff --git a/src/kong/plugins/basicauth/handler.lua b/src/kong/plugins/basicauth/handler.lua new file mode 100644 index 00000000000..99c2be66e89 --- /dev/null +++ b/src/kong/plugins/basicauth/handler.lua @@ -0,0 +1,19 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.base_plugin" +local access = require "kong.plugins.basicauth.access" + +local BasicAuthHandler = BasePlugin:extend() + +function BasicAuthHandler:new() + BasicAuthHandler.super.new(self, "basicauth") +end + +function BasicAuthHandler:access(conf) + BasicAuthHandler.super.access(self) + access.execute(conf) +end + +BasicAuthHandler.PRIORITY = 1000 + +return BasicAuthHandler diff --git a/src/kong/plugins/basicauth/schema.lua b/src/kong/plugins/basicauth/schema.lua new file mode 100644 index 00000000000..0e2060e3e46 --- /dev/null +++ b/src/kong/plugins/basicauth/schema.lua @@ -0,0 +1,5 @@ +local constants = require "kong.constants" + +return { + hide_credentials = { type = "boolean", default = false } +} diff --git a/src/kong/plugins/authentication/access.lua b/src/kong/plugins/headerauth/access.lua similarity index 68% rename from src/kong/plugins/authentication/access.lua rename to src/kong/plugins/headerauth/access.lua index c78d425809c..719d0980125 100644 --- a/src/kong/plugins/authentication/access.lua +++ b/src/kong/plugins/headerauth/access.lua @@ -70,8 +70,8 @@ local retrieve_credentials = { local public_key local headers = request.get_headers() - if conf.authentication_key_names then - for _,key_name in ipairs(conf.authentication_key_names) do + if conf.header_names then + for _,key_name in ipairs(conf.header_names) do if headers[key_name] ~= nil then public_key = headers[key_name] @@ -83,51 +83,6 @@ local retrieve_credentials = { end end end - end, - [constants.AUTHENTICATION.QUERY] = function(request, conf) - local public_key - - if conf.authentication_key_names then - for _,key_name in ipairs(conf.authentication_key_names) do - public_key = get_key_from_query(key_name, request, conf) - - if public_key then - return public_key - end - - end - end - end, - [constants.AUTHENTICATION.BASIC] = function(request, conf) - local username, password - local authorization_header = request.get_headers()["authorization"] - - if authorization_header then - local iterator, iter_err = ngx.re.gmatch(authorization_header, "\\s*[Bb]asic\\s*(.+)") - if not iterator then - ngx.log(ngx.ERR, iter_err) - return - end - - local m, err = iterator() - if err then - ngx.log(ngx.ERR, err) - return - end - - if m and table.getn(m) > 0 then - local decoded_basic = ngx.decode_base64(m[1]) - local basic_parts = stringy.split(decoded_basic, ":") - username = basic_parts[1] - password = basic_parts[2] - end - end - - if conf.hide_credentials then - request.clear_header("authorization") - end - - return username, password end } @@ -142,22 +97,13 @@ local retrieve_credentials = { local validate_credentials = { [constants.AUTHENTICATION.HEADER] = function(application, public_key) return application ~= nil - end, - [constants.AUTHENTICATION.QUERY] = function(application, public_key) - return application ~= nil - end, - [constants.AUTHENTICATION.BASIC] = function(application, username, password) - if application then - -- TODO: No encryption yet - return application.secret_key == password - end end } function _M.execute(conf) if not conf then return end - local public_key, secret_key = retrieve_credentials[conf.authentication_type](ngx.req, conf) + local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.HEADER](ngx.req, conf) local application -- Make sure we are not sending an empty table to find_by_keys @@ -175,7 +121,7 @@ function _M.execute(conf) end) end - if not validate_credentials[conf.authentication_type](application, public_key, secret_key) then + if not validate_credentials[constants.AUTHENTICATION.HEADER](application, public_key, secret_key) then utils.show_error(403, "Your authentication credentials are invalid") end diff --git a/src/kong/plugins/headerauth/handler.lua b/src/kong/plugins/headerauth/handler.lua new file mode 100644 index 00000000000..3f802b4d64f --- /dev/null +++ b/src/kong/plugins/headerauth/handler.lua @@ -0,0 +1,19 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.base_plugin" +local access = require "kong.plugins.headerauth.access" + +local HeaderAuthHandler = BasePlugin:extend() + +function HeaderAuthHandler:new() + HeaderAuthHandler.super.new(self, "headerauth") +end + +function HeaderAuthHandler:access(conf) + HeaderAuthHandler.super.access(self) + access.execute(conf) +end + +HeaderAuthHandler.PRIORITY = 1000 + +return HeaderAuthHandler diff --git a/src/kong/plugins/headerauth/schema.lua b/src/kong/plugins/headerauth/schema.lua new file mode 100644 index 00000000000..96b48dcecab --- /dev/null +++ b/src/kong/plugins/headerauth/schema.lua @@ -0,0 +1,6 @@ +local constants = require "kong.constants" + +return { + header_names = { required = true, type = "table" }, + hide_credentials = { type = "boolean", default = false } +} diff --git a/src/kong/plugins/queryauth/access.lua b/src/kong/plugins/queryauth/access.lua new file mode 100644 index 00000000000..2a8b68d8c0e --- /dev/null +++ b/src/kong/plugins/queryauth/access.lua @@ -0,0 +1,128 @@ +local constants = require "kong.constants" +local stringy = require "stringy" +local cjson = require "cjson" +local cache = require "kong.tools.cache" + +local _M = {} + +local function get_key_from_query(key_name, request, conf) + local public_key, parameters + local found_in = {} + + -- First, try with querystring + parameters = request.get_uri_args() + + if parameters[key_name] ~= nil then + found_in.querystring = true + -- If missing from querystring, try to get it from the body + elseif request.get_headers()["content-type"] then + -- Lowercase content-type for easier comparison + local content_type = string.lower(request.get_headers()["content-type"]) + + -- Call ngx.req.read_body to read the request body first + -- or turn on the lua_need_request_body directive to avoid errors. + request.read_body() + + if content_type == "application/x-www-form-urlencoded" or stringy.startswith(content_type, "multipart/form-data") then + parameters = request.get_post_args() + found_in.post = parameters[key_name] ~= nil + elseif content_type == "application/json" then + parameters = request.get_body_data() + if parameters and string.len(parameters) > 0 then + parameters = cjson.decode(parameters) + found_in.body = parameters[key_name] ~= nil + end + end + end + + -- At this point, we know where the key is supposed to be + public_key = parameters[key_name] + + if conf.hide_credentials then + if found_in.querystring then + parameters[key_name] = nil + ngx.vars.querystring = ngx.encode_args(parameters) + elseif found_in.post then + parameters[key_name] = nil + request.set_header("content-length", string.len(parameters)) + request.set_body_data(parameters) + elseif found_in.body then + parameters[key_name] = nil + parameters = cjson.encode(parameters) + request.set_header("content-length", string.len(parameters)) + request.set_body_data(parameters) + end + end + + return public_key +end + +-- Fast lookup for credential retrieval depending on the type of the authentication +-- +-- All methods must respect: +-- +-- @param request ngx request object +-- @param {table} conf Plugin configuration (value property) +-- @return {string} public_key +-- @return {string} private_key +local retrieve_credentials = { + [constants.AUTHENTICATION.QUERY] = function(request, conf) + local public_key + + if conf.key_names then + for _,key_name in ipairs(conf.key_names) do + public_key = get_key_from_query(key_name, request, conf) + + if public_key then + return public_key + end + + end + end + end +} + +-- Fast lookup for credential validation depending on the type of the authentication +-- +-- All methods must respect: +-- +-- @param {table} application The retrieved application from the public_key passed in the request +-- @param {string} public_key +-- @param {string} private_key +-- @return {boolean} Success of authentication +local validate_credentials = { + [constants.AUTHENTICATION.QUERY] = function(application, public_key) + return application ~= nil + end +} + +function _M.execute(conf) + if not conf then return end + + local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.QUERY](ngx.req, conf) + local application + + -- Make sure we are not sending an empty table to find_by_keys + if public_key then + application = cache.get_and_set(cache.application_key(public_key), function() + local applications, err = dao.applications:find_by_keys { public_key = public_key } + local result + if err then + ngx.log(ngx.ERR, err) + utils.show_error(500) + elseif #applications > 0 then + result = applications[1] + end + return result + end) + end + + if not validate_credentials[constants.AUTHENTICATION.QUERY](application, public_key, secret_key) then + utils.show_error(403, "Your authentication credentials are invalid") + end + + ngx.req.set_header(constants.HEADERS.ACCOUNT_ID, application.account_id) + ngx.ctx.authenticated_entity = application +end + +return _M diff --git a/src/kong/plugins/queryauth/handler.lua b/src/kong/plugins/queryauth/handler.lua new file mode 100644 index 00000000000..7f35dfb7363 --- /dev/null +++ b/src/kong/plugins/queryauth/handler.lua @@ -0,0 +1,19 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.base_plugin" +local access = require "kong.plugins.queryauth.access" + +local QueryAuthHandler = BasePlugin:extend() + +function QueryAuthHandler:new() + QueryAuthHandler.super.new(self, "queryauth") +end + +function QueryAuthHandler:access(conf) + QueryAuthHandler.super.access(self) + access.execute(conf) +end + +QueryAuthHandler.PRIORITY = 1000 + +return QueryAuthHandler diff --git a/src/kong/plugins/queryauth/schema.lua b/src/kong/plugins/queryauth/schema.lua new file mode 100644 index 00000000000..3924fa394a1 --- /dev/null +++ b/src/kong/plugins/queryauth/schema.lua @@ -0,0 +1,4 @@ +return { + key_names = { required = true, type = "table" }, + hide_credentials = { type = "boolean", default = false } +} From 3a53b8cccf2ac29764b5fea0e28fd5ef9e22c661 Mon Sep 17 00:00:00 2001 From: thefosk Date: Wed, 18 Mar 2015 20:49:19 -0700 Subject: [PATCH 2/6] making tests work --- kong.yml | 4 ++- spec/integration/server/server_spec.lua | 8 ++--- spec/unit/base_controller_spec.lua | 46 ++++++++++++------------- spec/unit/dao/cassandra_spec.lua | 16 +++++---- src/kong/tools/faker.lua | 14 ++++---- 5 files changed, 45 insertions(+), 43 deletions(-) diff --git a/kong.yml b/kong.yml index 8302b963a33..338cdc4a61b 100644 --- a/kong.yml +++ b/kong.yml @@ -1,6 +1,8 @@ # Available plugins on this server plugins_available: - - authentication + - queryauth + - headerauth + - basicauth - ratelimiting - networklog diff --git a/spec/integration/server/server_spec.lua b/spec/integration/server/server_spec.lua index 324ef157b55..c553151fa5c 100644 --- a/spec/integration/server/server_spec.lua +++ b/spec/integration/server/server_spec.lua @@ -67,21 +67,21 @@ describe("#server-cli", function() end) it("should not fail when an existing plugin is being enabled", function() - replace_conf_property("plugins_available", {"authentication"}) + replace_conf_property("plugins_available", {"queryauth"}) local result, exit_code = spec_helper.start_kong(SERVER_CONF, true) assert.are.same(0, exit_code) end) it("should not work when an unexisting plugin is being enabled along with an existing one", function() - replace_conf_property("plugins_available", {"authentication", "wot-wat"}) + replace_conf_property("plugins_available", {"queryauth", "wot-wat"}) assert.has_error(function() spec_helper.start_kong(SERVER_CONF, true) end, "The following plugin has been enabled in the configuration but is not installed on the system: wot-wat") end) it("should not work when a plugin is being used in the DB but it's not in the configuration", function() - replace_conf_property("plugins_available", {"authentication"}) + replace_conf_property("plugins_available", {"queryauth"}) spec_helper.prepare_db(true) assert.has_error(function() spec_helper.start_kong(SERVER_CONF, true) @@ -89,7 +89,7 @@ describe("#server-cli", function() end) it("should work the used plugins are enabled", function() - replace_conf_property("plugins_available", {"ratelimiting", "authentication"}) + replace_conf_property("plugins_available", {"ratelimiting", "queryauth", "headerauth", "basicauth"}) spec_helper.prepare_db(true) local result, exit_code = spec_helper.start_kong(SERVER_CONF, true) assert.are.same(0, exit_code) diff --git a/spec/unit/base_controller_spec.lua b/spec/unit/base_controller_spec.lua index f53de3d36c4..c0d1c157cd4 100644 --- a/spec/unit/base_controller_spec.lua +++ b/spec/unit/base_controller_spec.lua @@ -46,79 +46,77 @@ describe("Base Controller", function() end) it("should parse tables without invalid sub-schema values", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "wot", ["value.authentication_type"] = "query" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "wot", ["value.key_names"] = "apikey" }) assert.are.same({ name = "wot", value = {} }, result) - result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "authentication", wot = "query" }) + result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "queryauth", wot = "query" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = {} }, result) end) it("should parse tables with valid sub-schema values", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "authentication", ["value.authentication_type"] = "query" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "queryauth", ["value.key_names"] = "apikey" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = { - authentication_type = "query" + key_names = { "apikey" } } }, result) end) it("should not parse tables with invalid subschema prefix", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "authentication", ["asd.authentication_type"] = "query" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "queryauth", ["asd.key_names"] = "apikey" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = {} }, result) - result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "authentication", ["authentication_type"] = "query" }) + result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "queryauth", ["key_names"] = "apikey" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = {} }, result) end) it("should parse tables with skippig invalid values", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "authentication", ["value.authentication_type"] = "query", ["value.wot"] = "ciao" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {name = "queryauth", ["value.key_names"] = "apikey", ["value.wot"] = "ciao" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = { - authentication_type = "query" + key_names = { "apikey" } } }, result) end) it("should parse reversed-order tables with valid sub-schema values", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.authentication_type"] = "query", name = "authentication" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.key_names"] = "query", name = "queryauth" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = { - authentication_type = "query" + key_names = { "query" } } }, result) end) it("should parse arrays with a correct delimitator", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.authentication_type"] = "query", name = "authentication", ["value.authentication_key_names"] = "wot,wat" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.key_names"] = "wot,wat", name = "queryauth" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = { - authentication_type = "query", - authentication_key_names = { "wot", "wat" } + key_names = { "wot", "wat" } } }, result) end) it("should parse arrays with a incorrect delimitator", function() - local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.authentication_type"] = "query", name = "authentication", ["value.authentication_key_names"] = "wot;wat" }) + local result = base_controller.parse_params(spec_helper.dao_factory.plugins._schema, {["value.key_names"] = "wot;wat", name = "queryauth" }) assert.are.same({ - name = "authentication", + name = "queryauth", value = { - authentication_type = "query", - authentication_key_names = { "wot;wat" } + key_names = { "wot;wat" } } }, result) end) diff --git a/spec/unit/dao/cassandra_spec.lua b/spec/unit/dao/cassandra_spec.lua index 05045abe066..22ba8cc79c4 100644 --- a/spec/unit/dao/cassandra_spec.lua +++ b/spec/unit/dao/cassandra_spec.lua @@ -314,10 +314,9 @@ describe("Cassandra DAO #dao #cassandra", function() local plugin_t = { api_id = api.id, application_id = apps[#apps].id, - name = "authentication", + name = "queryauth", value = { - authentication_type = "query", - authentication_key_names = { "x-kong-key" } + key_names = { "x-kong-key" } } } @@ -330,12 +329,13 @@ describe("Cassandra DAO #dao #cassandra", function() assert.falsy(err) -- Failure - plugin_t.value.authentication_type = "hello" + plugin_t.name = "ratelimiting" + plugin_t.value = { period = "hello" } local plugin, err = dao_factory.plugins:insert(plugin_t) assert.truthy(err) assert.is_daoError(err) assert.truthy(err.schema) - assert.are.same("\"hello\" is not allowed. Allowed values are: \"query\", \"basic\", \"header\"", err.message["value.authentication_type"]) + assert.are.same("\"hello\" is not allowed. Allowed values are: \"second\", \"minute\", \"hour\", \"day\", \"month\", \"year\"", err.message["value.period"]) assert.falsy(plugin) end) @@ -807,8 +807,10 @@ describe("Cassandra DAO #dao #cassandra", function() assert.falsy(err) assert.truthy(res) - assert.are.same(2, #res) - assert.truthy(utils.array_contains(res, "authentication")) + assert.are.same(4, #res) + assert.truthy(utils.array_contains(res, "queryauth")) + assert.truthy(utils.array_contains(res, "headerauth")) + assert.truthy(utils.array_contains(res, "basicauth")) assert.truthy(utils.array_contains(res, "ratelimiting")) end) diff --git a/src/kong/tools/faker.lua b/src/kong/tools/faker.lua index 13efc441554..5853c838816 100644 --- a/src/kong/tools/faker.lua +++ b/src/kong/tools/faker.lua @@ -41,10 +41,10 @@ Faker.FIXTURES = { { public_key = "username", secret_key = "password", __account = 1 }, }, plugin = { - { name = "authentication", value = { authentication_type = "query", authentication_key_names = { "apikey" }}, __api = 1 }, - { name = "authentication", value = { authentication_type = "query", authentication_key_names = { "apikey" }}, __api = 6 }, - { name = "authentication", value = { authentication_type = "header", authentication_key_names = { "apikey" }}, __api = 2 }, - { name = "authentication", value = { authentication_type = "basic" }, __api = 3 }, + { name = "queryauth", value = { key_names = { "apikey" }}, __api = 1 }, + { name = "queryauth", value = { key_names = { "apikey" }}, __api = 6 }, + { name = "headerauth", value = { header_names = { "apikey" }}, __api = 2 }, + { name = "basicauth", value = {}, __api = 3 }, { name = "ratelimiting", value = { period = "minute", limit = 2 }, __api = 5 }, { name = "ratelimiting", value = { period = "minute", limit = 2 }, __api = 6 }, { name = "ratelimiting", value = { period = "minute", limit = 4 }, __api = 6, __application = 2 } @@ -74,10 +74,10 @@ function Faker:fake_entity(type) secret_key = "private_random"..r } elseif type == "plugin" then - local plugin_type = random_from_table({ "authentication", "ratelimiting" }) + local plugin_type = random_from_table({ "queryauth", "ratelimiting" }) local plugin_value - if plugin_type == "authentication" then - plugin_value = { authentication_type = "query", authentication_key_names = { "apikey"..r }} + if plugin_type == "queryauth" then + plugin_value = { key_names = { "apikey"..r }} else plugin_value = { period = "minute", limit = r } end From d131dc61acfe8addfdbfb2680140dbe292baf2d3 Mon Sep 17 00:00:00 2001 From: thefosk Date: Wed, 18 Mar 2015 22:22:07 -0700 Subject: [PATCH 3/6] Authentication plugins refactoring --- src/plugins/basicauth/access.lua | 68 +++++++++++------------ src/plugins/headerauth/access.lua | 92 +++++++------------------------ src/plugins/queryauth/access.lua | 32 +++++------ 3 files changed, 66 insertions(+), 126 deletions(-) diff --git a/src/plugins/basicauth/access.lua b/src/plugins/basicauth/access.lua index 22bb8bbd600..4e87cf24bf9 100644 --- a/src/plugins/basicauth/access.lua +++ b/src/plugins/basicauth/access.lua @@ -13,39 +13,38 @@ local _M = {} -- @param {table} conf Plugin configuration (value property) -- @return {string} public_key -- @return {string} private_key -local retrieve_credentials = { - [constants.AUTHENTICATION.BASIC] = function(request, conf) - local username, password - local authorization_header = request.get_headers()["authorization"] - - if authorization_header then - local iterator, iter_err = ngx.re.gmatch(authorization_header, "\\s*[Bb]asic\\s*(.+)") - if not iterator then - ngx.log(ngx.ERR, iter_err) - return - end +local function retrieve_credentials(request, conf) + local username, password + local authorization_header = request.get_headers()["authorization"] - local m, err = iterator() - if err then - ngx.log(ngx.ERR, err) - return - end + if authorization_header then + local iterator, iter_err = ngx.re.gmatch(authorization_header, "\\s*[Bb]asic\\s*(.+)") + if not iterator then + ngx.log(ngx.ERR, iter_err) + return + end - if m and table.getn(m) > 0 then - local decoded_basic = ngx.decode_base64(m[1]) - local basic_parts = stringy.split(decoded_basic, ":") - username = basic_parts[1] - password = basic_parts[2] - end + local m, err = iterator() + if err then + ngx.log(ngx.ERR, err) + return end - if conf.hide_credentials then - request.clear_header("authorization") + if m and table.getn(m) > 0 then + local decoded_basic = ngx.decode_base64(m[1]) + local basic_parts = stringy.split(decoded_basic, ":") + username = basic_parts[1] + password = basic_parts[2] end + end - return username, password + if conf.hide_credentials then + request.clear_header("authorization") end -} + + return username, password +end + -- Fast lookup for credential validation depending on the type of the authentication -- @@ -55,19 +54,18 @@ local retrieve_credentials = { -- @param {string} public_key -- @param {string} private_key -- @return {boolean} Success of authentication -local validate_credentials = { - [constants.AUTHENTICATION.BASIC] = function(application, username, password) - if application then - -- TODO: No encryption yet - return application.secret_key == password - end +local function validate_credentials(application, username, password) + if application then + -- TODO: No encryption yet + return application.secret_key == password end -} +end + function _M.execute(conf) if not conf then return end - local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.BASIC](ngx.req, conf) + local public_key, secret_key = retrieve_credentials(ngx.req, conf) local application -- Make sure we are not sending an empty table to find_by_keys @@ -85,7 +83,7 @@ function _M.execute(conf) end) end - if not validate_credentials[constants.AUTHENTICATION.BASIC](application, public_key, secret_key) then + if not validate_credentials(application, public_key, secret_key) then utils.show_error(403, "Your authentication credentials are invalid") end diff --git a/src/plugins/headerauth/access.lua b/src/plugins/headerauth/access.lua index 719d0980125..4bbd4f82cce 100644 --- a/src/plugins/headerauth/access.lua +++ b/src/plugins/headerauth/access.lua @@ -5,58 +5,6 @@ local cache = require "kong.tools.cache" local _M = {} -local function get_key_from_query(key_name, request, conf) - local public_key, parameters - local found_in = {} - - -- First, try with querystring - parameters = request.get_uri_args() - - if parameters[key_name] ~= nil then - found_in.querystring = true - -- If missing from querystring, try to get it from the body - elseif request.get_headers()["content-type"] then - -- Lowercase content-type for easier comparison - local content_type = string.lower(request.get_headers()["content-type"]) - - -- Call ngx.req.read_body to read the request body first - -- or turn on the lua_need_request_body directive to avoid errors. - request.read_body() - - if content_type == "application/x-www-form-urlencoded" or stringy.startswith(content_type, "multipart/form-data") then - parameters = request.get_post_args() - found_in.post = parameters[key_name] ~= nil - elseif content_type == "application/json" then - parameters = request.get_body_data() - if parameters and string.len(parameters) > 0 then - parameters = cjson.decode(parameters) - found_in.body = parameters[key_name] ~= nil - end - end - end - - -- At this point, we know where the key is supposed to be - public_key = parameters[key_name] - - if conf.hide_credentials then - if found_in.querystring then - parameters[key_name] = nil - ngx.vars.querystring = ngx.encode_args(parameters) - elseif found_in.post then - parameters[key_name] = nil - request.set_header("content-length", string.len(parameters)) - request.set_body_data(parameters) - elseif found_in.body then - parameters[key_name] = nil - parameters = cjson.encode(parameters) - request.set_header("content-length", string.len(parameters)) - request.set_body_data(parameters) - end - end - - return public_key -end - -- Fast lookup for credential retrieval depending on the type of the authentication -- -- All methods must respect: @@ -65,26 +13,25 @@ end -- @param {table} conf Plugin configuration (value property) -- @return {string} public_key -- @return {string} private_key -local retrieve_credentials = { - [constants.AUTHENTICATION.HEADER] = function(request, conf) - local public_key - local headers = request.get_headers() +local function retrieve_credentials(request, conf) + local public_key + local headers = request.get_headers() - if conf.header_names then - for _,key_name in ipairs(conf.header_names) do - if headers[key_name] ~= nil then - public_key = headers[key_name] + if conf.header_names then + for _,key_name in ipairs(conf.header_names) do + if headers[key_name] ~= nil then + public_key = headers[key_name] - if conf.hide_credentials then - request.clear_header(key_name) - end - - return public_key + if conf.hide_credentials then + request.clear_header(key_name) end + + return public_key end end end -} +end + -- Fast lookup for credential validation depending on the type of the authentication -- @@ -94,16 +41,15 @@ local retrieve_credentials = { -- @param {string} public_key -- @param {string} private_key -- @return {boolean} Success of authentication -local validate_credentials = { - [constants.AUTHENTICATION.HEADER] = function(application, public_key) - return application ~= nil - end -} +local function validate_credentials(application, public_key) + return application ~= nil +end + function _M.execute(conf) if not conf then return end - local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.HEADER](ngx.req, conf) + local public_key, secret_key = retrieve_credentials(ngx.req, conf) local application -- Make sure we are not sending an empty table to find_by_keys @@ -121,7 +67,7 @@ function _M.execute(conf) end) end - if not validate_credentials[constants.AUTHENTICATION.HEADER](application, public_key, secret_key) then + if not validate_credentials(application, public_key, secret_key) then utils.show_error(403, "Your authentication credentials are invalid") end diff --git a/src/plugins/queryauth/access.lua b/src/plugins/queryauth/access.lua index 2a8b68d8c0e..55c006ea540 100644 --- a/src/plugins/queryauth/access.lua +++ b/src/plugins/queryauth/access.lua @@ -65,22 +65,20 @@ end -- @param {table} conf Plugin configuration (value property) -- @return {string} public_key -- @return {string} private_key -local retrieve_credentials = { - [constants.AUTHENTICATION.QUERY] = function(request, conf) - local public_key +local function retrieve_credentials(request, conf) + local public_key - if conf.key_names then - for _,key_name in ipairs(conf.key_names) do - public_key = get_key_from_query(key_name, request, conf) - - if public_key then - return public_key - end + if conf.key_names then + for _,key_name in ipairs(conf.key_names) do + public_key = get_key_from_query(key_name, request, conf) + if public_key then + return public_key end + end end -} +end -- Fast lookup for credential validation depending on the type of the authentication -- @@ -90,16 +88,14 @@ local retrieve_credentials = { -- @param {string} public_key -- @param {string} private_key -- @return {boolean} Success of authentication -local validate_credentials = { - [constants.AUTHENTICATION.QUERY] = function(application, public_key) - return application ~= nil - end -} +local function validate_credentials(application, public_key) + return application ~= nil +end function _M.execute(conf) if not conf then return end - local public_key, secret_key = retrieve_credentials[constants.AUTHENTICATION.QUERY](ngx.req, conf) + local public_key, secret_key = retrieve_credentials(ngx.req, conf) local application -- Make sure we are not sending an empty table to find_by_keys @@ -117,7 +113,7 @@ function _M.execute(conf) end) end - if not validate_credentials[constants.AUTHENTICATION.QUERY](application, public_key, secret_key) then + if not validate_credentials(application, public_key, secret_key) then utils.show_error(403, "Your authentication credentials are invalid") end From adfe1648220eedade67f6659328906167f8e73ab Mon Sep 17 00:00:00 2001 From: thefosk Date: Wed, 18 Mar 2015 23:47:55 -0700 Subject: [PATCH 4/6] splitting logging plugins --- kong-0.0.1beta-1.rockspec | 14 +++++++-- kong.yml | 4 ++- src/plugins/basicauth/access.lua | 2 -- src/plugins/filelog/handler.lua | 17 ++++++++++ src/plugins/filelog/log.lua | 18 +++++++++++ src/plugins/filelog/schema.lua | 1 + src/plugins/headerauth/access.lua | 2 -- src/plugins/networklog/handler.lua | 17 ---------- src/plugins/networklog/log.lua | 50 ------------------------------ src/plugins/networklog/schema.lua | 14 --------- src/plugins/tcplog/handler.lua | 17 ++++++++++ src/plugins/tcplog/log.lua | 42 +++++++++++++++++++++++++ src/plugins/tcplog/schema.lua | 6 ++++ src/plugins/udplog/handler.lua | 17 ++++++++++ src/plugins/udplog/log.lua | 40 ++++++++++++++++++++++++ src/plugins/udplog/schema.lua | 5 +++ 16 files changed, 177 insertions(+), 89 deletions(-) create mode 100644 src/plugins/filelog/handler.lua create mode 100644 src/plugins/filelog/log.lua create mode 100644 src/plugins/filelog/schema.lua delete mode 100644 src/plugins/networklog/handler.lua delete mode 100644 src/plugins/networklog/log.lua delete mode 100644 src/plugins/networklog/schema.lua create mode 100644 src/plugins/tcplog/handler.lua create mode 100644 src/plugins/tcplog/log.lua create mode 100644 src/plugins/tcplog/schema.lua create mode 100644 src/plugins/udplog/handler.lua create mode 100644 src/plugins/udplog/log.lua create mode 100644 src/plugins/udplog/schema.lua diff --git a/kong-0.0.1beta-1.rockspec b/kong-0.0.1beta-1.rockspec index 3244d99e2c3..2f185d8fbde 100644 --- a/kong-0.0.1beta-1.rockspec +++ b/kong-0.0.1beta-1.rockspec @@ -76,9 +76,17 @@ build = { ["kong.plugins.headerauth.access"] = "src/plugins/headerauth/access.lua", ["kong.plugins.headerauth.schema"] = "src/plugins/headerauth/schema.lua", - ["kong.plugins.networklog.handler"] = "src/plugins/networklog/handler.lua", - ["kong.plugins.networklog.log"] = "src/plugins/networklog/log.lua", - ["kong.plugins.networklog.schema"] = "src/plugins/networklog/schema.lua", + ["kong.plugins.tcplog.handler"] = "src/plugins/tcplog/handler.lua", + ["kong.plugins.tcplog.log"] = "src/plugins/tcplog/log.lua", + ["kong.plugins.tcplog.schema"] = "src/plugins/tcplog/schema.lua", + + ["kong.plugins.udplog.handler"] = "src/plugins/udplog/handler.lua", + ["kong.plugins.udplog.log"] = "src/plugins/udplog/log.lua", + ["kong.plugins.udplog.schema"] = "src/plugins/udplog/schema.lua", + + ["kong.plugins.filelog.handler"] = "src/plugins/filelog/handler.lua", + ["kong.plugins.filelog.log"] = "src/plugins/filelog/log.lua", + ["kong.plugins.filelog.schema"] = "src/plugins/filelog/schema.lua", ["kong.plugins.ratelimiting.handler"] = "src/plugins/ratelimiting/handler.lua", ["kong.plugins.ratelimiting.access"] = "src/plugins/ratelimiting/access.lua", diff --git a/kong.yml b/kong.yml index 7b7da50204d..109fb2e92e5 100644 --- a/kong.yml +++ b/kong.yml @@ -4,7 +4,9 @@ plugins_available: - headerauth - basicauth - ratelimiting - - networklog + - tcplog + - udplog + - filelog # Uncomment the following line to setup a custom output directory # output: /var/log/kong diff --git a/src/plugins/basicauth/access.lua b/src/plugins/basicauth/access.lua index 4e87cf24bf9..460b6b81a65 100644 --- a/src/plugins/basicauth/access.lua +++ b/src/plugins/basicauth/access.lua @@ -45,7 +45,6 @@ local function retrieve_credentials(request, conf) return username, password end - -- Fast lookup for credential validation depending on the type of the authentication -- -- All methods must respect: @@ -61,7 +60,6 @@ local function validate_credentials(application, username, password) end end - function _M.execute(conf) if not conf then return end diff --git a/src/plugins/filelog/handler.lua b/src/plugins/filelog/handler.lua new file mode 100644 index 00000000000..70ce61cd6d9 --- /dev/null +++ b/src/plugins/filelog/handler.lua @@ -0,0 +1,17 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.plugins.base_plugin" +local log = require "kong.plugins.filelog.log" + +local FileLogHandler = BasePlugin:extend() + +function FileLogHandler:new() + FileLogHandler.super.new(self, "filelog") +end + +function FileLogHandler:log(conf) + FileLogHandler.super.log(self) + log.execute(conf) +end + +return FileLogHandler diff --git a/src/plugins/filelog/log.lua b/src/plugins/filelog/log.lua new file mode 100644 index 00000000000..253b94886bd --- /dev/null +++ b/src/plugins/filelog/log.lua @@ -0,0 +1,18 @@ +-- Copyright (C) Mashape, Inc. + +local cjson = require "cjson" + +local _M = {} + +local function log(premature, message) + ngx.log(ngx.INFO, cjson.encode(message)) +end + +function _M.execute() + local ok, err = ngx.timer.at(0, log, ngx.ctx.log_message) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + end +end + +return _M diff --git a/src/plugins/filelog/schema.lua b/src/plugins/filelog/schema.lua new file mode 100644 index 00000000000..1ab17e9b997 --- /dev/null +++ b/src/plugins/filelog/schema.lua @@ -0,0 +1 @@ +return {} -- No schema \ No newline at end of file diff --git a/src/plugins/headerauth/access.lua b/src/plugins/headerauth/access.lua index 4bbd4f82cce..7b9c1dc66f5 100644 --- a/src/plugins/headerauth/access.lua +++ b/src/plugins/headerauth/access.lua @@ -32,7 +32,6 @@ local function retrieve_credentials(request, conf) end end - -- Fast lookup for credential validation depending on the type of the authentication -- -- All methods must respect: @@ -45,7 +44,6 @@ local function validate_credentials(application, public_key) return application ~= nil end - function _M.execute(conf) if not conf then return end diff --git a/src/plugins/networklog/handler.lua b/src/plugins/networklog/handler.lua deleted file mode 100644 index dce96e004a7..00000000000 --- a/src/plugins/networklog/handler.lua +++ /dev/null @@ -1,17 +0,0 @@ --- Copyright (C) Mashape, Inc. - -local BasePlugin = require "kong.plugins.base_plugin" -local log = require "kong.plugins.networklog.log" - -local NetworkLogHandler = BasePlugin:extend() - -function NetworkLogHandler:new() - NetworkLogHandler.super.new(self, "networklog") -end - -function NetworkLogHandler:log(conf) - NetworkLogHandler.super.log(self) - log.execute(conf) -end - -return NetworkLogHandler diff --git a/src/plugins/networklog/log.lua b/src/plugins/networklog/log.lua deleted file mode 100644 index 3d9a0ccc45a..00000000000 --- a/src/plugins/networklog/log.lua +++ /dev/null @@ -1,50 +0,0 @@ --- Copyright (C) Mashape, Inc. - -local cjson = require "cjson" - -local _M = {} - -local function log(premature, conf, message) - local lower_type = string.lower(conf.type) - if lower_type == "nginx_log" then - ngx.log(ngx.INFO, cjson.encode(message)) - elseif lower_type == "tcp" then - local ok, err - local host = conf.host - local port = conf.port - local timeout = conf.timeout - if not timeout then timeout = 60000 end - - local keepalive = conf.keepalive - if not keepalive then keepalive = 10000 end - - local sock = ngx.socket.tcp() - sock:settimeout(timeout) - - ok, err = sock:connect(host, port) - if not ok then - ngx.log(ngx.ERR, "failed to connect to " .. host .. ":" .. tostring(port) .. ": ", err) - return - end - - ok, err = sock:send(cjson.encode(message) .. "\r\n") - if not ok then - ngx.log(ngx.ERR, "failed to send data to " .. host .. ":" .. tostring(port) .. ": ", err) - end - - ok, err = sock:setkeepalive(keepalive) - if not ok then - ngx.log(ngx.ERR, "failed to keepalive to " .. host .. ":" .. tostring(port) .. ": ", err) - return - end - end -end - -function _M.execute(conf) - local ok, err = ngx.timer.at(0, log, conf, ngx.ctx.log_message) - if not ok then - ngx.log(ngx.ERR, "failed to create timer: ", err) - end -end - -return _M diff --git a/src/plugins/networklog/schema.lua b/src/plugins/networklog/schema.lua deleted file mode 100644 index f38a7412eb4..00000000000 --- a/src/plugins/networklog/schema.lua +++ /dev/null @@ -1,14 +0,0 @@ -local function check_tcp(v, t) - if t and t.type == "tcp" and v == nil then - return false, "This property is required for the \"tcp\" type" - end - return true -end - -return { - type = { required = true, enum = { "tcp", "nginx_log" }}, - host = { func = check_tcp }, - port = { func = check_tcp }, - timeout = { func = check_tcp }, - keepalive = { func = check_tcp } -} diff --git a/src/plugins/tcplog/handler.lua b/src/plugins/tcplog/handler.lua new file mode 100644 index 00000000000..74560c8d18b --- /dev/null +++ b/src/plugins/tcplog/handler.lua @@ -0,0 +1,17 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.plugins.base_plugin" +local log = require "kong.plugins.tcplog.log" + +local TcpLogHandler = BasePlugin:extend() + +function TcpLogHandler:new() + TcpLogHandler.super.new(self, "tcplog") +end + +function TcpLogHandler:log(conf) + TcpLogHandler.super.log(self) + log.execute(conf) +end + +return TcpLogHandler diff --git a/src/plugins/tcplog/log.lua b/src/plugins/tcplog/log.lua new file mode 100644 index 00000000000..c54a245a481 --- /dev/null +++ b/src/plugins/tcplog/log.lua @@ -0,0 +1,42 @@ +-- Copyright (C) Mashape, Inc. + +local cjson = require "cjson" + +local _M = {} + +local function log(premature, conf, message) + local ok, err + local host = conf.host + local port = conf.port + local timeout = conf.timeout + local keepalive = conf.keepalive + + local sock = ngx.socket.tcp() + sock:settimeout(timeout) + + ok, err = sock:connect(host, port) + if not ok then + ngx.log(ngx.ERR, "failed to connect to "..host..":"..tostring(port)..": ", err) + return + end + + ok, err = sock:send(cjson.encode(message).."\r\n") + if not ok then + ngx.log(ngx.ERR, "failed to send data to ".. host..":"..tostring(port)..": ", err) + end + + ok, err = sock:setkeepalive(keepalive) + if not ok then + ngx.log(ngx.ERR, "failed to keepalive to "..host..":"..tostring(port)..": ", err) + return + end +end + +function _M.execute(conf) + local ok, err = ngx.timer.at(0, log, conf, ngx.ctx.log_message) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + end +end + +return _M diff --git a/src/plugins/tcplog/schema.lua b/src/plugins/tcplog/schema.lua new file mode 100644 index 00000000000..4482b913701 --- /dev/null +++ b/src/plugins/tcplog/schema.lua @@ -0,0 +1,6 @@ +return { + host = { required = true }, + port = { required = true }, + timeout = { required = false, default = 10000 }, + keepalive = { required = false, default = 60000 } +} diff --git a/src/plugins/udplog/handler.lua b/src/plugins/udplog/handler.lua new file mode 100644 index 00000000000..be1fd5bad75 --- /dev/null +++ b/src/plugins/udplog/handler.lua @@ -0,0 +1,17 @@ +-- Copyright (C) Mashape, Inc. + +local BasePlugin = require "kong.plugins.base_plugin" +local log = require "kong.plugins.udplog.log" + +local UdpLogHandler = BasePlugin:extend() + +function UdpLogHandler:new() + UdpLogHandler.super.new(self, "udplog") +end + +function UdpLogHandler:log(conf) + UdpLogHandler.super.log(self) + log.execute(conf) +end + +return UdpLogHandler diff --git a/src/plugins/udplog/log.lua b/src/plugins/udplog/log.lua new file mode 100644 index 00000000000..6ba58fc547e --- /dev/null +++ b/src/plugins/udplog/log.lua @@ -0,0 +1,40 @@ +-- Copyright (C) Mashape, Inc. + +local cjson = require "cjson" + +local _M = {} + +local function log(premature, conf, message) + local host = conf.host + local port = conf.port + local timeout = conf.timeout + + local sock = ngx.socket.udp() + sock:settimeout(timeout) + + local ok, err = sock:setpeername(host, port) + if not ok then + ngx.log(ngx.ERR, "failed to connect to "..host..":"..tostring(port)..": ", err) + return + end + + local ok, err = sock:send(cjson.encode(message)) + if not ok then + ngx.log(ngx.ERR, "failed to send data to ".. host..":"..tostring(port)..": ", err) + end + + local ok, err = sock:close() + if not ok then + ngx.log(ngx.ERR, "failed to close connection from "..host..":"..tostring(port)..": ", err) + return + end +end + +function _M.execute(conf) + local ok, err = ngx.timer.at(0, log, conf, ngx.ctx.log_message) + if not ok then + ngx.log(ngx.ERR, "failed to create timer: ", err) + end +end + +return _M diff --git a/src/plugins/udplog/schema.lua b/src/plugins/udplog/schema.lua new file mode 100644 index 00000000000..fa8c204afa5 --- /dev/null +++ b/src/plugins/udplog/schema.lua @@ -0,0 +1,5 @@ +return { + host = { required = true }, + port = { required = true }, + timeout = { required = false, default = 10000 } +} From bca2da6f4ba97835d321ea54567401cdf527676d Mon Sep 17 00:00:00 2001 From: thefosk Date: Thu, 19 Mar 2015 18:24:23 -0700 Subject: [PATCH 5/6] tests for log --- kong-0.0.1beta-1.rockspec | 1 + spec/integration/proxy/log_spec.lua | 107 ++++++++++++++++++++++++ spec/integration/server/server_spec.lua | 4 +- spec/unit/dao/cassandra_spec.lua | 5 +- spec/unit/faker_spec.lua | 2 +- src/plugins/filelog/log.lua | 9 +- src/tools/faker.lua | 5 +- 7 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 spec/integration/proxy/log_spec.lua diff --git a/kong-0.0.1beta-1.rockspec b/kong-0.0.1beta-1.rockspec index 2f185d8fbde..1c6f41c6151 100644 --- a/kong-0.0.1beta-1.rockspec +++ b/kong-0.0.1beta-1.rockspec @@ -28,6 +28,7 @@ dependencies = { "stringy ~> 0.2-1", "inspect ~> 3.0-1", "luasocket ~> 2.0.2-5", + "lua-llthreads2 ~> 0.1.3-1", "lua_cliargs ~> 2.3-3", "lua-path ~> 0.2.3-1", "luatz ~> 0.3-1" diff --git a/spec/integration/proxy/log_spec.lua b/spec/integration/proxy/log_spec.lua new file mode 100644 index 00000000000..154d7044316 --- /dev/null +++ b/spec/integration/proxy/log_spec.lua @@ -0,0 +1,107 @@ +local spec_helper = require "spec.spec_helpers" +local http_client = require "kong.tools.http_client" +local cjson = require "cjson" +local Threads = require "llthreads2.ex" + +local STUB_GET_URL = spec_helper.STUB_GET_URL + +local function start_tcp_server() + local thread = Threads.new({ + function() + local socket = require "socket" + local server = assert(socket.bind("*", 7777)) + local client = server:accept() + local line, err = client:receive() + if not err then client:send(line .. "\n") end + client:close() + return line + end; + }) + + thread:start() + return thread; +end + +local function start_udp_server() + local thread = Threads.new({ + function() + local socket = require("socket") + udp = socket.udp() + udp:setsockname("*", 8888) + data, ip, port = udp:receivefrom() + return data + end; + }) + + thread:start() + return thread; +end + +describe("Logging Plugins #proxy", function() + + setup(function() + spec_helper.prepare_db() + spec_helper.start_kong() + end) + + teardown(function() + spec_helper.stop_kong() + spec_helper.reset_db() + end) + + describe("Invalid API", function() + + it("should log to TCP", function() + local thread = start_tcp_server() -- Starting the mock TCP server + + -- Making the request + local response, status, headers = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "test.com"}) + assert.are.equal(200, status) + + -- Getting back the TCP server input + local ok, res = thread:join() + assert.truthy(ok) + assert.truthy(res) + + -- Making sure it's alright + local log_message = cjson.decode(res) + assert.are.same("127.0.0.1", log_message.ip) + end) + + it("should log to UDP", function() + local thread = start_udp_server() -- Starting the mock TCP server + + -- Making the request + local response, status, headers = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "test.com"}) + assert.are.equal(200, status) + + -- Getting back the TCP server input + local ok, res = thread:join() + assert.truthy(ok) + assert.truthy(res) + + -- Making sure it's alright + local log_message = cjson.decode(res) + assert.are.same("127.0.0.1", log_message.ip) + end) + + it("should log to file", function() + local thread = start_udp_server() -- Starting the mock TCP server + + -- Making the request + local response, status, headers = http_client.get(STUB_GET_URL, {apikey = "apikey123"}, {host = "test.com"}) + assert.are.equal(200, status) + + -- Getting back the TCP server input + local ok, res = thread:join() + assert.truthy(ok) + assert.truthy(res) + + -- Making sure it's alright + local log_message = cjson.decode(res) + assert.are.same("127.0.0.1", log_message.ip) + end) + + end) + +end) diff --git a/spec/integration/server/server_spec.lua b/spec/integration/server/server_spec.lua index ae1cecd4e50..45e6f6c1a62 100644 --- a/spec/integration/server/server_spec.lua +++ b/spec/integration/server/server_spec.lua @@ -98,7 +98,7 @@ describe("Server", function() end) it("should not work when a plugin is being used in the DB but it's not in the configuration", function() - replace_conf_property("plugins_available", {"queryauth"}) + replace_conf_property("plugins_available", {"queryauth", "basicauth", "headerauth", "tcplog", "udplog", "filelog"}) spec_helper.prepare_db() assert.has_error(function() spec_helper.start_kong(SERVER_CONF, true) @@ -106,7 +106,7 @@ describe("Server", function() end) it("should work the used plugins are enabled", function() - replace_conf_property("plugins_available", {"ratelimiting", "queryauth", "headerauth", "basicauth"}) + replace_conf_property("plugins_available", {"ratelimiting", "queryauth", "headerauth", "basicauth", "tcplog", "udplog", "filelog"}) spec_helper.prepare_db() local result, exit_code = spec_helper.start_kong(SERVER_CONF, true) assert.are.same(0, exit_code) diff --git a/spec/unit/dao/cassandra_spec.lua b/spec/unit/dao/cassandra_spec.lua index b6b8bf385b8..23e364d23f8 100644 --- a/spec/unit/dao/cassandra_spec.lua +++ b/spec/unit/dao/cassandra_spec.lua @@ -808,11 +808,14 @@ describe("Cassandra DAO #dao #cassandra", function() assert.falsy(err) assert.truthy(res) - assert.are.same(4, #res) + assert.are.same(7, #res) assert.truthy(utils.array_contains(res, "queryauth")) assert.truthy(utils.array_contains(res, "headerauth")) assert.truthy(utils.array_contains(res, "basicauth")) assert.truthy(utils.array_contains(res, "ratelimiting")) + assert.truthy(utils.array_contains(res, "tcplog")) + assert.truthy(utils.array_contains(res, "udplog")) + assert.truthy(utils.array_contains(res, "filelog")) end) it("should insert a plugin and set the application_id to a 'null' uuid if none is specified", function() diff --git a/spec/unit/faker_spec.lua b/spec/unit/faker_spec.lua index 00d7c38d846..d0cbf57e0e0 100644 --- a/spec/unit/faker_spec.lua +++ b/spec/unit/faker_spec.lua @@ -78,7 +78,7 @@ describe("Faker #tools", function() it("should be possible to add some random entities complementing the default hard-coded ones", function() faker:seed(2000) assert.spy(faker.insert_from_table).was.called(2) - assert.spy(insert_spy).was.called(6018) -- 3*2000 + 18 base entities + assert.spy(insert_spy).was.called(6021) -- 3*2000 + 21 base entities end) it("should create relations between entities_to_insert and inserted entities", function() diff --git a/src/plugins/filelog/log.lua b/src/plugins/filelog/log.lua index 253b94886bd..be07d1aecb5 100644 --- a/src/plugins/filelog/log.lua +++ b/src/plugins/filelog/log.lua @@ -4,15 +4,8 @@ local cjson = require "cjson" local _M = {} -local function log(premature, message) - ngx.log(ngx.INFO, cjson.encode(message)) -end - function _M.execute() - local ok, err = ngx.timer.at(0, log, ngx.ctx.log_message) - if not ok then - ngx.log(ngx.ERR, "failed to create timer: ", err) - end + ngx.log(ngx.INFO, cjson.encode(ngx.ctx.log_message)) end return _M diff --git a/src/tools/faker.lua b/src/tools/faker.lua index 5853c838816..5420c26cd38 100644 --- a/src/tools/faker.lua +++ b/src/tools/faker.lua @@ -47,7 +47,10 @@ Faker.FIXTURES = { { name = "basicauth", value = {}, __api = 3 }, { name = "ratelimiting", value = { period = "minute", limit = 2 }, __api = 5 }, { name = "ratelimiting", value = { period = "minute", limit = 2 }, __api = 6 }, - { name = "ratelimiting", value = { period = "minute", limit = 4 }, __api = 6, __application = 2 } + { name = "ratelimiting", value = { period = "minute", limit = 4 }, __api = 6, __application = 2 }, + { name = "tcplog", value = { host = "127.0.0.1", port = 7777 }, __api = 1 }, + { name = "udplog", value = { host = "127.0.0.1", port = 8888 }, __api = 1 }, + { name = "filelog", value = { }, __api = 1 } } } From 0c3345150ed9146fdbc39c347bd6c41d62eaf3a7 Mon Sep 17 00:00:00 2001 From: thefosk Date: Thu, 19 Mar 2015 18:38:35 -0700 Subject: [PATCH 6/6] Improving the way we handled times --- src/kong.lua | 11 +++++------ src/resolver/header_filter.lua | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/kong.lua b/src/kong.lua index 683e55777ed..8dcba54bca4 100644 --- a/src/kong.lua +++ b/src/kong.lua @@ -27,6 +27,7 @@ utils = require "kong.tools.utils" local cache = require "kong.tools.cache" local constants = require "kong.constants" +local timestamp = require "kong.tools.timestamp" -- Define the plugins to load here, in the appropriate order local plugins = {} @@ -167,7 +168,7 @@ end -- @return nil function _M.exec_plugins_access() -- Setting a property that will be available for every plugin - ngx.ctx.start = ngx.now() + ngx.ctx.started_at = timestamp.get_utc() ngx.ctx.plugin_conf = {} -- Iterate over all the plugins @@ -189,13 +190,13 @@ function _M.exec_plugins_access() end end - ngx.ctx.proxy_start = ngx.now() -- Setting a property that will be available for every plugin + ngx.ctx.proxy_started_at = timestamp.get_utc() -- Setting a property that will be available for every plugin end -- Calls header_filter() on every loaded plugin -- @return nil function _M.exec_plugins_header_filter() - ngx.ctx.proxy_end = ngx.now() -- Setting a property that will be available for every plugin + ngx.ctx.proxy_ended_at = timestamp.get_utc() -- Setting a property that will be available for every plugin if not ngx.ctx.error then for _, plugin in ipairs(plugins) do @@ -225,8 +226,6 @@ end function _M.exec_plugins_log() if not ngx.ctx.error then - local now = ngx.now() - -- Creating the log variable that will be serialized local message = { request = { @@ -242,7 +241,7 @@ function _M.exec_plugins_log() ip = ngx.var.remote_addr, status = ngx.status, url = ngx.var.uri, - created_at = now + started_at = ngx.ctx.started_at } ngx.ctx.log_message = message diff --git a/src/resolver/header_filter.lua b/src/resolver/header_filter.lua index c441b4d004e..2dec78edff0 100644 --- a/src/resolver/header_filter.lua +++ b/src/resolver/header_filter.lua @@ -1,10 +1,11 @@ local constants = require "kong.constants" +local timestamp = require "kong.tools.timestamp" local _M = {} function _M.execute(conf) - local api_time = ngx.ctx.proxy_end - ngx.ctx.proxy_start - ngx.header[constants.HEADERS.PROXY_TIME] = ngx.now() - ngx.ctx.start - api_time + local api_time = ngx.ctx.proxy_ended_at - ngx.ctx.proxy_started_at + ngx.header[constants.HEADERS.PROXY_TIME] = timestamp.get_utc() - ngx.ctx.started_at - api_time ngx.header[constants.HEADERS.API_TIME] = api_time end