fix(core): upstream openresty fixes; lua global scope pollution
diff --git a/.dockerignore b/.dockerignore
index 1b5d582..b6643df 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,3 +5,4 @@
Dockerfile
Makefile
README.md
+tools/
diff --git a/Dockerfile b/Dockerfile
index 30a35ce..ce8d088 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,7 +17,7 @@
# apigateway
#
-# VERSION 1.15.8.3
+# VERSION 1.17.8.2
#
# From https://hub.docker.com/_/alpine/
#
@@ -37,7 +37,7 @@
&& rm -rf /var/cache/apk/*
# openresty build
-ENV OPENRESTY_VERSION=1.15.8.3 \
+ENV OPENRESTY_VERSION=1.17.8.2 \
PCRE_VERSION=8.37 \
TEST_NGINX_VERSION=0.24 \
OPM_VERSION=0.0.5 \
diff --git a/scripts/lua/lib/dataStore.lua b/scripts/lua/lib/dataStore.lua
index 57a86cc..453576a 100644
--- a/scripts/lua/lib/dataStore.lua
+++ b/scripts/lua/lib/dataStore.lua
@@ -34,7 +34,7 @@
function DataStore:setSnapshotId(tenant)
self.snapshotId = self.impl.getSnapshotId(self.ds, tenant)
- self:lockSnapshot(snapshotId)
+ self:lockSnapshot(self.snapshotId)
if self.snapshotId == ngx.null then
self.snapshotId = nil
end
@@ -159,7 +159,7 @@
function DataStore:getSubscriptions(artifactId, tenantId)
self:singleInit()
- return self.impl.deleteSubscription(self.ds, key, self.snapshotId)
+ return self.impl.getSubscriptions(self.ds, artifactId, tenantId, self.snapshotId)
end
function DataStore:healthCheck()
diff --git a/scripts/lua/lib/redis.lua b/scripts/lua/lib/redis.lua
index bd6701f..5c4da83 100644
--- a/scripts/lua/lib/redis.lua
+++ b/scripts/lua/lib/redis.lua
@@ -102,6 +102,169 @@
end
end
+-- LRU Caching methods
+
+--- Call function with retry logic
+-- @param func function to call
+-- @param args arguments to pass in to function
+local function call(func, args)
+ local res, err = func(unpack(args))
+ local retryCount = REDIS_RETRY_COUNT
+ while not res and retryCount > 0 do
+ res, err = func(unpack(args))
+ retryCount = retryCount - 1
+ end
+ return res, err
+end
+
+local function exists(red, key, snapshotId)
+ if snapshotId ~= nil then
+ key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
+ end
+ if CACHING_ENABLED then
+ local cached = c:get(key)
+ if cached ~= nil then
+ return 1
+ end
+ -- if it isn't in the cache, try and load it in there
+ if red == nil then
+ red = _M.init()
+ end
+ local result = red:get(key)
+ if result ~= ngx.null then
+ c:set(key, result, CACHE_TTL)
+ return 1, red
+ end
+ return 0
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ return call(red.exists, {red, key}), red
+ end
+end
+
+local function get(red, key)
+ if CACHING_ENABLED then
+ local cached, stale = c:get(key)
+ if cached ~= nil then
+ return cached
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ local result = red:get(key)
+ c:set(key, result, CACHE_TTL)
+ return result, red
+ end
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ return call(red.get, {red, key})
+ end
+end
+
+local function hget(red, key, id)
+ if CACHING_ENABLED then
+ local cachedmap, stale = c:get(key)
+ if cachedmap ~= nil then
+ local cached = cachedmap:get(id)
+ if cached ~= nil then
+ return cached
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ local result = red:hget(key, id)
+ cachedmap:set(id, result, CACHE_TTL)
+ c:set(key, cachedmap, CACHE_TTL)
+ return result, red
+ end
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ local result = red:hget(key, id)
+ local newcache = lrucache.new(CACHE_SIZE)
+ newcache:set(id, result, CACHE_TTL)
+ c:set(key, newcache, CACHE_TTL)
+ return result, red
+ end
+ else
+ if red == nil then
+ red = _M.init()
+ end
+ return call(red.hget, {red, key, id}), red
+ end
+end
+
+local function hgetall(red, key)
+ return call(red.hgetall, {red, key})
+end
+
+local function hset(red, key, id, value)
+ if CACHING_ENABLED then
+ local cachedmap = c:get(key)
+ if cachedmap ~= nil then
+ cachedmap:set(id, value, CACHE_TTL)
+ c:set(key, cachedmap, CACHE_TTL)
+ return red:hset(key, id, value)
+ else
+ local val = lrucache.new(CACHE_SIZE)
+ val:set(id, value, CACHE_TTL)
+ c:set(key, val, CACHE_TTL)
+ end
+ end
+ return call(red.hset, {red, key, id, value})
+end
+
+local function expire(red, key, ttl)
+ if CACHING_ENABLED then
+ local cached = c:get(key)
+ local value = ''
+ if cached ~= nil then -- just put it back in the cache with a ttl
+ value = cached
+ end
+ c:set(key, value, ttl)
+ end
+ return call(red.expire, {red, ttl})
+end
+
+local function del(red, key)
+ if CACHING_ENABLED then
+ c:delete(key)
+ end
+ return call(red.del, {red, key})
+end
+
+local function hdel(red, key, id)
+ if CACHING_ENABLED then
+ local cachecontents = c:get(key)
+ if cachecontents ~= nil then
+ cachecontents:del(id)
+ c:set(key, cachecontents, CACHE_TTL)
+ end
+ end
+ return call(red.hdel, {red, key, id})
+end
+
+local function set(red, key, value)
+ return call(red.set, {red, key, value})
+end
+
+local function smembers(red, key)
+ return call(red.smembers, {red, key})
+end
+
+local function srem(red, key, id)
+ return call(red.srem, {red, key, id})
+end
+
+local function sadd(red, key, id)
+ return call(red.sadd, {red, key, id})
+end
+
---------------------------
----------- APIs ----------
---------------------------
@@ -597,8 +760,8 @@
if get(red, startingString) == nil then
set(red, startingString, '')
end
- path = {}
- key = {}
+ local path = {}
+ local key = {}
for p in string.gmatch(pathStr, '[^/]*') do
if p ~= '' then
table.insert(path, p)
@@ -632,169 +795,6 @@
red:expire(utils.concatStrings({'lock:snapshots:', snapshotId}), 60)
end
--- LRU Caching methods
-
-function exists(red, key, snapshotId)
- if snapshotId ~= nil then
- key = utils.concatStrings({'snapshots:', snapshotId, ':', key})
- end
- if CACHING_ENABLED then
- local cached = c:get(key)
- if cached ~= nil then
- return 1
- end
- -- if it isn't in the cache, try and load it in there
- if red == nil then
- red = _M.init()
- end
- local result = red:get(key)
- if result ~= ngx.null then
- c:set(key, result, CACHE_TTL)
- return 1, red
- end
- return 0
- else
- if red == nil then
- red = _M.init()
- end
- return call(red.exists, {red, key}), red
- end
-end
-
-function get(red, key)
- if CACHING_ENABLED then
- local cached, stale = c:get(key)
- if cached ~= nil then
- return cached
- else
- if red == nil then
- red = _M.init()
- end
- local result = red:get(key)
- c:set(key, result, CACHE_TTL)
- return result, red
- end
- else
- if red == nil then
- red = _M.init()
- end
- return call(red.get, {red, key})
- end
-end
-
-function hget(red, key, id)
- if CACHING_ENABLED then
- local cachedmap, stale = c:get(key)
- if cachedmap ~= nil then
- local cached = cachedmap:get(id)
- if cached ~= nil then
- return cached
- else
- if red == nil then
- red = _M.init()
- end
- local result = red:hget(key, id)
- cachedmap:set(id, result, CACHE_TTL)
- c:set(key, cachedmap, CACHE_TTL)
- return result, red
- end
- else
- if red == nil then
- red = _M.init()
- end
- local result = red:hget(key, id)
- local newcache = lrucache.new(CACHE_SIZE)
- newcache:set(id, result, CACHE_TTL)
- c:set(key, newcache, CACHE_TTL)
- return result, red
- end
- else
- if red == nil then
- red = _M.init()
- end
- return call(red.hget, {red, key, id}), red
- end
-end
-
-function hgetall(red, key)
- return call(red.hgetall, {red, key})
-end
-
-function hset(red, key, id, value)
- if CACHING_ENABLED then
- local cachedmap = c:get(key)
- if cachedmap ~= nil then
- cachedmap:set(id, value, CACHE_TTL)
- c:set(key, cachedmap, CACHE_TTL)
- return red:hset(key, id, value)
- else
- local val = lrucache.new(CACHE_SIZE)
- val:set(id, value, CACHE_TTL)
- c:set(key, val, CACHE_TTL)
- end
- end
- return call(red.hset, {red, key, id, value})
-end
-
-function expire(red, key, ttl)
- if CACHING_ENABLED then
- local cached = c:get(key)
- local value = ''
- if cached ~= nil then -- just put it back in the cache with a ttl
- value = cached
- end
- c:set(key, value, ttl)
- end
- return call(red.expire, {red, ttl})
-end
-
-function del(red, key)
- if CACHING_ENABLED then
- c:delete(key)
- end
- return call(red.del, {red, key})
-end
-
-function hdel(red, key, id)
- if CACHING_ENABLED then
- local cachecontents = c:get(key)
- if cachecontents ~= nil then
- cachecontents:del(id)
- c:set(key, cachecontents, CACHE_TTL)
- end
- end
- return call(red.hdel, {red, key, id})
-end
-
-function set(red, key, value)
- return call(red.set, {red, key, value})
-end
-
-function smembers(red, key)
- return call(red.smembers, {red, key})
-end
-
-function srem(red, key, id)
- return call(red.srem, {red, key, id})
-end
-
-function sadd(red, key, id)
- return call(red.sadd, {red, key, id})
-end
-
---- Call function with retry logic
--- @param func function to call
--- @param args arguments to pass in to function
-function call(func, args)
- local res, err = func(unpack(args))
- local retryCount = REDIS_RETRY_COUNT
- while not res and retryCount > 0 do
- res, err = func(unpack(args))
- retryCount = retryCount - 1
- end
- return res, err
-end
-
_M.get = get
_M.set = set
_M.exists = exists
diff --git a/scripts/lua/lib/request.lua b/scripts/lua/lib/request.lua
index faa2caa..38f6eab 100644
--- a/scripts/lua/lib/request.lua
+++ b/scripts/lua/lib/request.lua
@@ -27,7 +27,7 @@
--- Error function to call when request is malformed
-- @param code error code
-- @param msg error message
-function err(code, msg)
+local function err(code, msg)
ngx.header.content_type = "application/json; charset=utf-8"
ngx.status = code
local errObj = cjson.encode({
@@ -41,7 +41,7 @@
--- Function to call when request is successful
-- @param code status code
-- @param obj JSON encoded object to return
-function success(code, obj)
+local function success(code, obj)
ngx.status = code
if obj ~= nil then
ngx.say(obj)
diff --git a/scripts/lua/lib/utils.lua b/scripts/lua/lib/utils.lua
index e9c2977..4c58f83 100644
--- a/scripts/lua/lib/utils.lua
+++ b/scripts/lua/lib/utils.lua
@@ -67,7 +67,7 @@
-- @return concatenated string of (?<path_pathParam>(\\w+))
function _Utils.convertTemplatedPathParam(m)
local x = m:gsub("{", ""):gsub("}", "")
- return concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"})
+ return _Utils.concatStrings({"(?<path_" , x , ">([a-zA-Z0-9\\-\\s\\_\\%]*))"})
end
--- Generate random uuid
diff --git a/scripts/lua/management/lib/apis.lua b/scripts/lua/management/lib/apis.lua
index 04161f2..b2efbd3 100644
--- a/scripts/lua/management/lib/apis.lua
+++ b/scripts/lua/management/lib/apis.lua
@@ -32,6 +32,33 @@
local _M = {}
+--- Filter APIs based on query parameters
+-- @param apis list of apis
+-- @param queryParams query parameters to filter tenants
+local function filterAPIs(apis, queryParams)
+ local basePath = queryParams['filter[where][basePath]']
+ basePath = basePath == nil and queryParams['basePath'] or basePath
+ local name = queryParams['filter[where][name]']
+ name = name == nil and queryParams['title'] or name
+ -- missing or invalid query parameters
+ if (basePath == nil and name == nil) then
+ return nil
+ end
+ -- filter tenants
+ local apiList = {}
+ for k, v in pairs(apis) do
+ if k%2 == 0 then
+ local api = cjson.decode(v)
+ if (basePath ~= nil and name == nil and api.basePath == basePath) or
+ (name ~= nil and basePath == nil and api.name == name) or
+ (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name) then
+ apiList[#apiList+1] = api
+ end
+ end
+ end
+ return apiList
+end
+
--- Get all APIs in redis
-- @param ds dataStore.client
-- @param queryParams object containing optional query parameters
@@ -137,31 +164,4 @@
return {}
end
---- Filter APIs based on query parameters
--- @param apis list of apis
--- @param queryParams query parameters to filter tenants
-function filterAPIs(apis, queryParams)
- local basePath = queryParams['filter[where][basePath]']
- basePath = basePath == nil and queryParams['basePath'] or basePath
- local name = queryParams['filter[where][name]']
- name = name == nil and queryParams['title'] or name
- -- missing or invalid query parameters
- if (basePath == nil and name == nil) then
- return nil
- end
- -- filter tenants
- local apiList = {}
- for k, v in pairs(apis) do
- if k%2 == 0 then
- local api = cjson.decode(v)
- if (basePath ~= nil and name == nil and api.basePath == basePath) or
- (name ~= nil and basePath == nil and api.name == name) or
- (basePath ~= nil and name ~= nil and api.basePath == basePath and api.name == name) then
- apiList[#apiList+1] = api
- end
- end
- end
- return apiList
-end
-
return _M
diff --git a/scripts/lua/management/lib/swagger.lua b/scripts/lua/management/lib/swagger.lua
index d70c08c..b02397e 100644
--- a/scripts/lua/management/lib/swagger.lua
+++ b/scripts/lua/management/lib/swagger.lua
@@ -18,14 +18,201 @@
--- @module swagger
-- Module for parsing swagger file
-local _M = {}
local utils = require "lib/utils"
+local _M = {}
+
+--- Parse request mapping
+local function parseRequestMapping(configObj)
+ local valueList = {}
+ if configObj ~= nil then
+ for _, obj in pairs(configObj.execute) do
+ for policy, v in pairs(obj) do
+ if policy == "set-variable" then
+ for _, actionObj in pairs(v.actions) do
+ local fromValue = actionObj.value
+ local toParsedArray = {string.match(actionObj.set, "([^.]+).([^.]+).([^.]+)") }
+ local toName = toParsedArray[3]
+ local toLocation = toParsedArray[2]
+ toLocation = toLocation == "headers" and "header" or toLocation
+ valueList[#valueList+1] = {
+ action = "insert",
+ from = {
+ value = fromValue
+ },
+ to = {
+ name = toName,
+ location = toLocation
+ }
+ }
+ end
+ end
+ end
+ end
+ end
+ if next(valueList) ~= nil then
+ return {
+ type = "reqMapping",
+ value = valueList
+ }
+ else
+ return nil
+ end
+end
+
+--- Parse backendUrl and backendMethod
+-- @param swagger swagger file to parse
+local function parseBackends(swagger)
+ local configObj = swagger["x-gateway-configuration"]
+ configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
+ if configObj ~= nil then
+ for _, obj in pairs(configObj.assembly.execute) do
+ for policy, v in pairs(obj) do
+ local res = {}
+ if policy == "operation-switch" then
+ local caseObj = v.case
+ for _, case in pairs(caseObj) do
+ for _, op in pairs(case.operations) do
+ res[op] = {}
+ for _, opPolicy in pairs(case.execute) do
+ if opPolicy.invoke ~= nil then
+ res[op].backendUrl = opPolicy.invoke["target-url"]
+ res[op].backendMethod = opPolicy.invoke.verb
+ elseif opPolicy["set-variable"] ~= nil then
+ local reqMappingPolicy = parseRequestMapping(case)
+ if reqMappingPolicy ~= nil then
+ res[op].policy = reqMappingPolicy
+ end
+ end
+ end
+ end
+ end
+ return res
+ end
+ if policy == "invoke" then
+ res["all"] = {
+ backendUrl = v["target-url"],
+ backendMethod = v.verb
+ }
+ return res
+ end
+ end
+ end
+ end
+end
+
+--- Parse rate limit
+local function parseRateLimit(rlObj)
+ if rlObj ~= nil and rlObj[1] ~= nil then
+ local unit
+ rlObj = rlObj[1]
+ if rlObj.unit == "second" then
+ unit = 1
+ elseif rlObj.unit == "minute" then
+ unit = 60
+ elseif rlObj.unit == "hour" then
+ unit = 3600
+ elseif rlObj.unit == "day" then
+ unit = 86400
+ else
+ unit = 60 -- default to minute
+ end
+ return {
+ type = "rateLimit",
+ value = {
+ interval = unit * rlObj.units,
+ rate = rlObj.rate,
+ scope = "api",
+ subscription = true
+ }
+ }
+ end
+ return nil
+end
+
+--- Parse policies in swagger
+-- @param swagger swagger file to parse
+local function parsePolicies(swagger)
+ local policies = {}
+ -- parse rate limit
+ local rlObj = swagger["x-gateway-rate-limit"]
+ rlObj = (rlObj == nil) and swagger["x-ibm-rate-limit"] or rlObj
+ local rateLimitPolicy = parseRateLimit(rlObj)
+ if rateLimitPolicy ~= nil then
+ policies[#policies+1] = rateLimitPolicy
+ end
+ -- parse set-variable
+ local configObj = swagger["x-gateway-configuration"]
+ configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
+ if configObj ~= nil then
+ local reqMappingPolicy = parseRequestMapping(configObj.assembly)
+ if reqMappingPolicy ~= nil then
+ policies[#policies+1] = reqMappingPolicy
+ end
+ end
+ return policies
+end
+
+--- Parse security in swagger
+-- @param swagger swagger file to parse
+local function parseSecurity(swagger)
+ local security = {}
+ if swagger["securityDefinitions"] ~= nil then
+ local secObject = swagger["securityDefinitions"]
+ if utils.tableLength(secObject) == 2 then
+ local secObj = {
+ type = 'clientSecret',
+ scope = 'api'
+ }
+ for key, sec in pairs(secObject) do
+ if key == 'client_id' then
+ secObj.idFieldName = sec.name
+ elseif key == 'client_secret' then
+ secObj.secretFieldName = sec.name
+ end
+ end
+ security[#security+1] = secObj
+ else
+ for key, sec in pairs(secObject) do
+ if sec.type == 'apiKey' then
+ security[#security+1] = {
+ type = sec.type,
+ scope = "api",
+ header = sec.name
+ }
+ elseif sec.type == 'oauth2' then
+ security[#security+1] = {
+ type = sec.type,
+ scope = "api",
+ provider = key
+ }
+ end
+ end
+ end
+ end
+ return security
+end
+
+local function parseCors(swagger)
+ local cors = { origin = nil, methods = nil }
+ local configObj = swagger["x-gateway-configuration"]
+ configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
+ if configObj.cors ~= nil then
+ if configObj.cors.enabled == true then
+ cors.origin = "true"
+ elseif configObj.cors.enabled == false then
+ cors.origin = "false"
+ end
+ return cors
+ end
+ return nil
+end
+
-- Convert passed-in swagger body to valid lua table
-- @param swagger swagger file to parse
function _M.parseSwagger(swagger)
local backends = parseBackends(swagger)
- local policies = parseSwaggerPolicies(swagger)
+ local policies = parsePolicies(swagger)
local security = parseSecurity(swagger)
local corsObj = parseCors(swagger)
local decoded = {
@@ -72,189 +259,4 @@
return decoded
end
---- Parse backendUrl and backendMethod
--- @param swagger swagger file to parse
-function parseBackends(swagger)
- local configObj = swagger["x-gateway-configuration"]
- configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
- if configObj ~= nil then
- for _, obj in pairs(configObj.assembly.execute) do
- for policy, v in pairs(obj) do
- local res = {}
- if policy == "operation-switch" then
- local caseObj = v.case
- for _, case in pairs(caseObj) do
- for _, op in pairs(case.operations) do
- res[op] = {}
- for _, opPolicy in pairs(case.execute) do
- if opPolicy.invoke ~= nil then
- res[op].backendUrl = opPolicy.invoke["target-url"]
- res[op].backendMethod = opPolicy.invoke.verb
- elseif opPolicy["set-variable"] ~= nil then
- local reqMappingPolicy = parseRequestMapping(case)
- if reqMappingPolicy ~= nil then
- res[op].policy = reqMappingPolicy
- end
- end
- end
- end
- end
- return res
- end
- if policy == "invoke" then
- res["all"] = {
- backendUrl = v["target-url"],
- backendMethod = v.verb
- }
- return res
- end
- end
- end
- end
-end
-
---- Parse policies in swagger
--- @param swagger swagger file to parse
-function parseSwaggerPolicies(swagger)
- local policies = {}
- -- parse rate limit
- local rlObj = swagger["x-gateway-rate-limit"]
- rlObj = (rlObj == nil) and swagger["x-ibm-rate-limit"] or rlObj
- local rateLimitPolicy = parseRateLimit(rlObj)
- if rateLimitPolicy ~= nil then
- policies[#policies+1] = rateLimitPolicy
- end
- -- parse set-variable
- local configObj = swagger["x-gateway-configuration"]
- configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
- if configObj ~= nil then
- local reqMappingPolicy = parseRequestMapping(configObj.assembly)
- if reqMappingPolicy ~= nil then
- policies[#policies+1] = reqMappingPolicy
- end
- end
- return policies
-end
-
---- Parse rate limit
-function parseRateLimit(rlObj)
- if rlObj ~= nil and rlObj[1] ~= nil then
- rlObj = rlObj[1]
- if rlObj.unit == "second" then
- unit = 1
- elseif rlObj.unit == "minute" then
- unit = 60
- elseif rlObj.unit == "hour" then
- unit = 3600
- elseif rlObj.unit == "day" then
- unit = 86400
- else
- unit = 60 -- default to minute
- end
- return {
- type = "rateLimit",
- value = {
- interval = unit * rlObj.units,
- rate = rlObj.rate,
- scope = "api",
- subscription = true
- }
- }
- end
- return nil
-end
-
---- Parse request mapping
-function parseRequestMapping(configObj)
- local valueList = {}
- if configObj ~= nil then
- for _, obj in pairs(configObj.execute) do
- for policy, v in pairs(obj) do
- if policy == "set-variable" then
- for _, actionObj in pairs(v.actions) do
- local fromValue = actionObj.value
- local toParsedArray = {string.match(actionObj.set, "([^.]+).([^.]+).([^.]+)") }
- local toName = toParsedArray[3]
- local toLocation = toParsedArray[2]
- toLocation = toLocation == "headers" and "header" or toLocation
- valueList[#valueList+1] = {
- action = "insert",
- from = {
- value = fromValue
- },
- to = {
- name = toName,
- location = toLocation
- }
- }
- end
- end
- end
- end
- end
- if next(valueList) ~= nil then
- return {
- type = "reqMapping",
- value = valueList
- }
- else
- return nil
- end
-end
-
---- Parse security in swagger
--- @param swagger swagger file to parse
-function parseSecurity(swagger)
- local security = {}
- if swagger["securityDefinitions"] ~= nil then
- local secObject = swagger["securityDefinitions"]
- if utils.tableLength(secObject) == 2 then
- secObj = {
- type = 'clientSecret',
- scope = 'api'
- }
- for key, sec in pairs(secObject) do
- if key == 'client_id' then
- secObj.idFieldName = sec.name
- elseif key == 'client_secret' then
- secObj.secretFieldName = sec.name
- end
- end
- security[#security+1] = secObj
- else
- for key, sec in pairs(secObject) do
- if sec.type == 'apiKey' then
- security[#security+1] = {
- type = sec.type,
- scope = "api",
- header = sec.name
- }
- elseif sec.type == 'oauth2' then
- security[#security+1] = {
- type = sec.type,
- scope = "api",
- provider = key
- }
- end
- end
- end
- end
- return security
-end
-
-function parseCors(swagger)
- local cors = { origin = nil, methods = nil }
- local configObj = swagger["x-gateway-configuration"]
- configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
- if configObj.cors ~= nil then
- if configObj.cors.enabled == true then
- cors.origin = "true"
- elseif configObj.cors.enabled == false then
- cors.origin = "false"
- end
- return cors
- end
- return nil
-end
-
return _M
diff --git a/scripts/lua/management/lib/tenants.lua b/scripts/lua/management/lib/tenants.lua
index a83c773..4fb7a8a 100644
--- a/scripts/lua/management/lib/tenants.lua
+++ b/scripts/lua/management/lib/tenants.lua
@@ -19,7 +19,6 @@
-- Management interface for tenants for the gateway
local cjson = require "cjson"
-local redis = require "lib/redis"
local utils = require "lib/utils"
local request = require "lib/request"
local apis = require "management/lib/apis"
@@ -38,6 +37,30 @@
return cjson.decode(tenantObj)
end
+--- Filter tenants based on query parameters
+-- @param tenants list of tenants
+-- @param queryParams query parameters to filter tenants
+local function filterTenants(tenants, queryParams)
+ local namespace = queryParams['filter[where][namespace]']
+ local instance = queryParams['filter[where][instance]']
+ -- missing or invalid query parameters
+ if (namespace == nil and instance == nil) or (instance ~= nil and namespace == nil) then
+ return nil
+ end
+ -- filter tenants
+ local tenantList = {}
+ for k, v in pairs(tenants) do
+ if k%2 == 0 then
+ local tenant = cjson.decode(v)
+ if (namespace ~= nil and instance == nil and tenant.namespace == namespace) or
+ (namespace ~= nil and instance ~= nil and tenant.namespace == namespace and tenant.instance == instance) then
+ tenantList[#tenantList+1] = tenant
+ end
+ end
+ end
+ return tenantList
+end
+
--- Get all tenants in redis
-- @param ds redis client
-- @param queryParams object containing optional query parameters
@@ -58,30 +81,6 @@
return tenantList
end
---- Filter tenants based on query parameters
--- @param tenants list of tenants
--- @param queryParams query parameters to filter tenants
-function filterTenants(tenants, queryParams)
- local namespace = queryParams['filter[where][namespace]']
- local instance = queryParams['filter[where][instance]']
- -- missing or invalid query parameters
- if (namespace == nil and instance == nil) or (instance ~= nil and namespace == nil) then
- return nil
- end
- -- filter tenants
- local tenantList = {}
- for k, v in pairs(tenants) do
- if k%2 == 0 then
- local tenant = cjson.decode(v)
- if (namespace ~= nil and instance == nil and tenant.namespace == namespace) or
- (namespace ~= nil and instance ~= nil and tenant.namespace == namespace and tenant.instance == instance) then
- tenantList[#tenantList+1] = tenant
- end
- end
- end
- return tenantList
-end
-
--- Get tenant by its id
-- @param ds redis client
-- @param id tenant id
@@ -93,38 +92,10 @@
return tenant
end
---- Get APIs associated with tenant
--- @param ds redis client
--- @param id tenant id
--- @param queryParams object containing optional query parameters
-function _M.getTenantAPIs(dataStore, id, queryParams)
- local apis = dataStore:getAllAPIs()
- local apiList
- if next(queryParams) ~= nil then
- apiList = filterTenantAPIs(id, apis, queryParams);
- end
- if apiList == nil then
- apiList = {}
- for k, v in pairs(apis) do
- if k%2 == 0 then
- local decoded = cjson.decode(v)
- if decoded.tenantId == id then
- apiList[#apiList+1] = decoded
- end
- end
- end
- end
- if (((queryParams['skip'] == nil or queryParams['skip'] == 'undefined') and (queryParams['limit'] == nil or queryParams['limit'] == 'undefined')) or table.getn(apiList) == 0) then
- return apiList
- else
- return applyPagingToAPIs(apiList, queryParams)
- end
-end
-
-- Apply paging on apis
-- @param apis the list of apis
-- @param queryparams object containing optional query parameters
-function applyPagingToAPIs(apiList, queryParams)
+local function applyPagingToAPIs(apiList, queryParams)
local skip = queryParams['skip'] == nil and 1 or queryParams['skip']
local limit = queryParams['limit'] == nil and table.getn(apiList) or queryParams['limit']
@@ -155,7 +126,7 @@
--- Filter apis based on query paramters
-- @param queryParams query parameters to filter apis
-function filterTenantAPIs(id, apis, queryParams)
+local function filterTenantAPIs(id, apis, queryParams)
local basePath = queryParams['filter[where][basePath]']
basePath = basePath == nil and queryParams['basePath'] or basePath
local name = queryParams['filter[where][name]']
@@ -180,6 +151,34 @@
return apiList
end
+--- Get APIs associated with tenant
+-- @param ds redis client
+-- @param id tenant id
+-- @param queryParams object containing optional query parameters
+function _M.getTenantAPIs(dataStore, id, queryParams)
+ local apis = dataStore:getAllAPIs()
+ local apiList
+ if next(queryParams) ~= nil then
+ apiList = filterTenantAPIs(id, apis, queryParams);
+ end
+ if apiList == nil then
+ apiList = {}
+ for k, v in pairs(apis) do
+ if k%2 == 0 then
+ local decoded = cjson.decode(v)
+ if decoded.tenantId == id then
+ apiList[#apiList+1] = decoded
+ end
+ end
+ end
+ end
+ if (((queryParams['skip'] == nil or queryParams['skip'] == 'undefined') and (queryParams['limit'] == nil or queryParams['limit'] == 'undefined')) or table.getn(apiList) == 0) then
+ return apiList
+ else
+ return applyPagingToAPIs(apiList, queryParams)
+ end
+end
+
--- Delete tenant from gateway
-- @param ds redis client
-- @param id id of tenant to delete
diff --git a/scripts/lua/management/lib/validation.lua b/scripts/lua/management/lib/validation.lua
index 4c8ed58..8eef460 100644
--- a/scripts/lua/management/lib/validation.lua
+++ b/scripts/lua/management/lib/validation.lua
@@ -23,76 +23,35 @@
local _M = {}
-function _M.validate(dataStore, decoded)
- local fields = {"name", "basePath", "tenantId", "resources"}
- for _, v in pairs(fields) do
- local res, err = isValid(dataStore, v, decoded[v])
- if res == false then
- return err
+--- Error checking for policies and security
+-- @param policies policies object
+-- @param security security object
+local function checkOptionalPolicies(policies, security)
+ if policies then
+ for _, v in pairs(policies) do
+ local validTypes = {"reqMapping", "rateLimit", "backendRouting"}
+ if (v.type == nil or v.value == nil) then
+ return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"value\"." }
+ elseif utils.tableContains(validTypes, v.type) == false then
+ return false, { statusCode = 400, message = "Invalid type in policy object. Valid: " .. cjson.encode(validTypes) }
+ end
end
end
- return nil
-end
-
---- Check JSON body fields for errors
--- @param ds edis client instance
--- @param field name of field
--- @param object field object
-function isValid(dataStore, field, object)
- -- Check that field exists in body
- if not object then
- return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) }
- end
- -- Additional check for basePath
- if field == "basePath" then
- local basePath = object
- if string.match(basePath, "'") then
- return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." }
- end
- end
- -- Additional check for tenantId
- if field == "tenantId" then
- local tenant = dataStore:getTenant(object)
- if tenant == nil then
- return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) }
- end
- end
- if field == "resources" then
- local res, err = checkResources(object)
- if res ~= nil and res == false then
- return res, err
- end
- end
- -- All error checks passed
- return true
-end
-
---- Error checking for resources
--- @param resources resources object
-function checkResources(resources)
- if next(resources) == nil then
- return false, { statusCode = 400, message = "Empty resources object." }
- end
- for path, resource in pairs(resources) do
- -- Check resource path for illegal characters
- if string.match(path, "'") then
- return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
- end
- -- Check that resource path begins with slash
- if path:sub(1,1) ~= '/' then
- return false, { statusCode = 400, message = "Resource path must begin with '/'." }
- end
- -- Check operations object
- local res, err = checkOperations(resource.operations)
- if res ~= nil and res == false then
- return res, err
+ if security then
+ for _, sec in ipairs(security) do
+ local validScopes = {"tenant", "api", "resource"}
+ if (sec.type == nil or sec.scope == nil) then
+ return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." }
+ elseif utils.tableContains(validScopes, sec.scope) == false then
+ return false, { statusCode = 400, message = "Invalid scope in security object. Valid: " .. cjson.encode(validScopes) }
+ end
end
end
end
--- Error checking for operations
-- @param operations operations object
-function checkOperations(operations)
+local function checkOperations(operations)
if not operations or next(operations) == nil then
return false, { statusCode = 400, message = "Missing or empty field 'operations' or in resource path object." }
end
@@ -122,30 +81,71 @@
end
end
---- Error checking for policies and security
--- @param policies policies object
--- @param security security object
-function checkOptionalPolicies(policies, security)
- if policies then
- for _, v in pairs(policies) do
- local validTypes = {"reqMapping", "rateLimit", "backendRouting"}
- if (v.type == nil or v.value == nil) then
- return false, { statusCode = 400, message = "Missing field in policy object. Need \"type\" and \"value\"." }
- elseif utils.tableContains(validTypes, v.type) == false then
- return false, { statusCode = 400, message = "Invalid type in policy object. Valid: " .. cjson.encode(validTypes) }
- end
+--- Error checking for resources
+-- @param resources resources object
+local function checkResources(resources)
+ if next(resources) == nil then
+ return false, { statusCode = 400, message = "Empty resources object." }
+ end
+ for path, resource in pairs(resources) do
+ -- Check resource path for illegal characters
+ if string.match(path, "'") then
+ return false, { statusCode = 400, message = "resource path contains illegal character \"'\"." }
+ end
+ -- Check that resource path begins with slash
+ if path:sub(1,1) ~= '/' then
+ return false, { statusCode = 400, message = "Resource path must begin with '/'." }
+ end
+ -- Check operations object
+ local res, err = checkOperations(resource.operations)
+ if res ~= nil and res == false then
+ return res, err
end
end
- if security then
- for _, sec in ipairs(security) do
- local validScopes = {"tenant", "api", "resource"}
- if (sec.type == nil or sec.scope == nil) then
- return false, { statusCode = 400, message = "Missing field in security object. Need \"type\" and \"scope\"." }
- elseif utils.tableContains(validScopes, sec.scope) == false then
- return false, { statusCode = 400, message = "Invalid scope in security object. Valid: " .. cjson.encode(validScopes) }
- end
+end
+
+--- Check JSON body fields for errors
+-- @param ds edis client instance
+-- @param field name of field
+-- @param object field object
+local function isValid(dataStore, field, object)
+ -- Check that field exists in body
+ if not object then
+ return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", field, "' in request body."}) }
+ end
+ -- Additional check for basePath
+ if field == "basePath" then
+ local basePath = object
+ if string.match(basePath, "'") then
+ return false, { statusCode = 400, message = "basePath contains illegal character \"'\"." }
end
end
+ -- Additional check for tenantId
+ if field == "tenantId" then
+ local tenant = dataStore:getTenant(object)
+ if tenant == nil then
+ return false, { statusCode = 404, message = utils.concatStrings({"Unknown tenant id ", object }) }
+ end
+ end
+ if field == "resources" then
+ local res, err = checkResources(object)
+ if res ~= nil and res == false then
+ return res, err
+ end
+ end
+ -- All error checks passed
+ return true
+end
+
+function _M.validate(dataStore, decoded)
+ local fields = {"name", "basePath", "tenantId", "resources"}
+ for _, v in pairs(fields) do
+ local res, err = isValid(dataStore, v, decoded[v])
+ if res == false then
+ return err
+ end
+ end
+ return nil
end
return _M
diff --git a/scripts/lua/management/routes/apis.lua b/scripts/lua/management/routes/apis.lua
index 73b0904..45086c1 100644
--- a/scripts/lua/management/routes/apis.lua
+++ b/scripts/lua/management/routes/apis.lua
@@ -33,22 +33,22 @@
local _M = {}
---- Request handler for routing API calls appropriately
-function _M.requestHandler(dataStore)
- local requestMethod = ngx.req.get_method()
- ngx.header.content_type = "application/json; charset=utf-8"
- if requestMethod == "GET" then
- getAPIs(dataStore)
- elseif requestMethod == 'POST' or requestMethod == 'PUT' then
- addAPI(dataStore)
- elseif requestMethod == "DELETE" then
- deleteAPI(dataStore)
- else
- request.err(400, "Invalid verb.")
+--- Check for api id from uri and use existing API if it already exists in redis
+-- @param red Redis client instance
+-- @param id API id to check
+local function checkForExistingAPI(dataStore, id)
+ local existing
+ if id ~= nil and id ~= '' then
+ existing = dataStore:getAPI(id)
+ if existing == nil then
+ dataStore:close()
+ request.err(404, utils.concatStrings({"Unknown API id ", id}))
+ end
end
+ return existing
end
-function getAPIs(dataStore)
+local function getAPIs(dataStore)
local queryParams = ngx.req.get_uri_args()
local id = ngx.var.api_id
local version = ngx.var.version
@@ -103,7 +103,7 @@
end
end
-function addAPI(dataStore)
+local function addAPI(dataStore)
local id = ngx.var.api_id
local existingAPI = checkForExistingAPI(dataStore, id)
ngx.req.read_body()
@@ -159,7 +159,7 @@
end
end
-function deleteAPI(dataStore)
+local function deleteAPI(dataStore)
local id = ngx.var.api_id
if id == nil or id == '' then
dataStore:close()
@@ -177,19 +177,19 @@
end
end
---- Check for api id from uri and use existing API if it already exists in redis
--- @param red Redis client instance
--- @param id API id to check
-function checkForExistingAPI(dataStore, id)
- local existing
- if id ~= nil and id ~= '' then
- existing = dataStore:getAPI(id)
- if existing == nil then
- dataStore:close()
- request.err(404, utils.concatStrings({"Unknown API id ", id}))
- end
+--- Request handler for routing API calls appropriately
+function _M.requestHandler(dataStore)
+ local requestMethod = ngx.req.get_method()
+ ngx.header.content_type = "application/json; charset=utf-8"
+ if requestMethod == "GET" then
+ getAPIs(dataStore)
+ elseif requestMethod == 'POST' or requestMethod == 'PUT' then
+ addAPI(dataStore)
+ elseif requestMethod == "DELETE" then
+ deleteAPI(dataStore)
+ else
+ request.err(400, "Invalid verb.")
end
- return existing
end
return _M;
diff --git a/scripts/lua/management/routes/subscriptions.lua b/scripts/lua/management/routes/subscriptions.lua
index 6993f56..6d9406a 100644
--- a/scripts/lua/management/routes/subscriptions.lua
+++ b/scripts/lua/management/routes/subscriptions.lua
@@ -30,119 +30,7 @@
local _M = {}
-function _M.requestHandler(dataStore)
- local version = ngx.var.version
- if version == "v2" then
- v2(dataStore)
- elseif version == "v1" then
- v1(dataStore)
- else
- request.err(404, "404 Not found")
- end
-end
-
-
--- v2 --
-
-function v2(dataStore)
- local requestMethod = ngx.req.get_method()
- if requestMethod == "POST" or requestMethod == "PUT" then
- v2AddSubscription(dataStore)
- elseif requestMethod == "GET" then
- v2GetSubscriptions(dataStore)
- elseif requestMethod == "DELETE" then
- v2DeleteSubscription(dataStore)
- else
- dataStore:close()
- request.err(400, "Invalid verb")
- end
-end
-
-function v2AddSubscription(dataStore)
- ngx.req.read_body()
- local args = ngx.req.get_body_data()
- if not args then
- dataStore:close()
- request.err(400, "Missing request body.")
- end
- local decoded = cjson.decode(args)
- local res, err = utils.tableContainsAll(decoded, {"client_id", "artifact_id"})
- if res == false then
- request.err(err.statusCode, err.message)
- end
- local artifactId = decoded.artifact_id
- local tenantId = ngx.var.tenant_id
- local clientId = decoded.client_id
- local clientSecret = decoded.client_secret
- subscriptions.addSubscription(dataStore, artifactId, tenantId, clientId, clientSecret, utils.hash)
- dataStore:close()
- local result = {
- message = utils.concatStrings({"Subscription '", clientId, "' created for API '", artifactId, "'"})
- }
- ngx.header.content_type = "application/json; charset=utf-8"
- request.success(200, cjson.encode(result))
-end
-
-function v2GetSubscriptions(dataStore)
- local tenantId = ngx.var.tenant_id
- local artifactId = ngx.req.get_uri_args()["artifact_id"]
- if artifactId == nil or artifactId == "" then
- request.err(400, "Missing artifact_id")
- end
- local subscriptionList = subscriptions.getSubscriptions(dataStore, artifactId, tenantId)
- redis.close(red)
- ngx.header.content_type = "application/json; charset=utf-8"
- request.success(200, cjson.encode(subscriptionList))
-end
-
-function v2DeleteSubscription(dataStore)
- local clientId = ngx.var.client_id
- local tenantId = ngx.var.tenant_id
- local artifactId = ngx.req.get_uri_args()["artifact_id"]
- if clientId == nil or clientId == "" then
- request.err(400, "Missing client_id")
- end
- if artifactId == nil or artifactId == "" then
- request.err(400, "Missing artifact_id")
- end
- local res = subscriptions.deleteSubscription(dataStore, artifactId, tenantId, clientId)
- if res == false then
- request.err(404, "Subscription doesn't exist")
- end
- redis.close(red)
- request.success(204)
-end
-
-
--- v1 --
-
-function v1(dataStore)
- local requestMethod = ngx.req.get_method()
- if requestMethod == "POST" or requestMethod == "PUT" then
- addSubscription(dataStore)
- elseif requestMethod == "DELETE" then
- deleteSubscription(dataStore)
- else
- dataStore:close()
- request.err(400, "Invalid verb")
- end
-end
-
-function addSubscription(dataStore)
- local redisKey = validateSubscriptionBody(dataStore)
- dataStore:createSubscription(redisKey)
- dataStore:close()
- request.success(200, "Subscription created.")
-end
-
-function deleteSubscription(dataStore)
- local redisKey = validateSubscriptionBody(dataStore)
- dataStore:deleteSubscription(redisKey)
- dataStore:close()
- request.success(200, "Subscription deleted.")
-end
-
-function validateSubscriptionBody(dataStore)
+local function validateSubscriptionBody(dataStore)
-- Read in the PUT JSON Body
ngx.req.read_body()
local args = ngx.req.get_body_data()
@@ -188,4 +76,114 @@
return redisKey
end
+local function addSubscription(dataStore)
+ local redisKey = validateSubscriptionBody(dataStore)
+ dataStore:createSubscription(redisKey)
+ dataStore:close()
+ request.success(200, "Subscription created.")
+end
+
+local function deleteSubscription(dataStore)
+ local redisKey = validateSubscriptionBody(dataStore)
+ dataStore:deleteSubscription(redisKey)
+ dataStore:close()
+ request.success(200, "Subscription deleted.")
+end
+
+-- v2 --
+
+local function v2AddSubscription(dataStore)
+ ngx.req.read_body()
+ local args = ngx.req.get_body_data()
+ if not args then
+ dataStore:close()
+ request.err(400, "Missing request body.")
+ end
+ local decoded = cjson.decode(args)
+ local res, err = utils.tableContainsAll(decoded, {"client_id", "artifact_id"})
+ if res == false then
+ request.err(err.statusCode, err.message)
+ end
+ local artifactId = decoded.artifact_id
+ local tenantId = ngx.var.tenant_id
+ local clientId = decoded.client_id
+ local clientSecret = decoded.client_secret
+ subscriptions.addSubscription(dataStore, artifactId, tenantId, clientId, clientSecret, utils.hash)
+ dataStore:close()
+ local result = {
+ message = utils.concatStrings({"Subscription '", clientId, "' created for API '", artifactId, "'"})
+ }
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, cjson.encode(result))
+end
+
+local function v2GetSubscriptions(dataStore)
+ local tenantId = ngx.var.tenant_id
+ local artifactId = ngx.req.get_uri_args()["artifact_id"]
+ if artifactId == nil or artifactId == "" then
+ request.err(400, "Missing artifact_id")
+ end
+ local subscriptionList = subscriptions.getSubscriptions(dataStore, artifactId, tenantId)
+ dataStore:close()
+ ngx.header.content_type = "application/json; charset=utf-8"
+ request.success(200, cjson.encode(subscriptionList))
+end
+
+local function v2DeleteSubscription(dataStore)
+ local clientId = ngx.var.client_id
+ local tenantId = ngx.var.tenant_id
+ local artifactId = ngx.req.get_uri_args()["artifact_id"]
+ if clientId == nil or clientId == "" then
+ request.err(400, "Missing client_id")
+ end
+ if artifactId == nil or artifactId == "" then
+ request.err(400, "Missing artifact_id")
+ end
+ local res = subscriptions.deleteSubscription(dataStore, artifactId, tenantId, clientId)
+ if res == false then
+ request.err(404, "Subscription doesn't exist")
+ end
+ dataStore:close()
+ request.success(204)
+end
+
+local function v2(dataStore)
+ local requestMethod = ngx.req.get_method()
+ if requestMethod == "POST" or requestMethod == "PUT" then
+ v2AddSubscription(dataStore)
+ elseif requestMethod == "GET" then
+ v2GetSubscriptions(dataStore)
+ elseif requestMethod == "DELETE" then
+ v2DeleteSubscription(dataStore)
+ else
+ dataStore:close()
+ request.err(400, "Invalid verb")
+ end
+end
+
+-- v1 --
+
+local function v1(dataStore)
+ local requestMethod = ngx.req.get_method()
+ if requestMethod == "POST" or requestMethod == "PUT" then
+ addSubscription(dataStore)
+ elseif requestMethod == "DELETE" then
+ deleteSubscription(dataStore)
+ else
+ dataStore:close()
+ request.err(400, "Invalid verb")
+ end
+end
+
+function _M.requestHandler(dataStore)
+ local version = ngx.var.version
+ if version == "v2" then
+ v2(dataStore)
+ elseif version == "v1" then
+ v1(dataStore)
+ else
+ request.err(404, "404 Not found")
+ end
+end
+
return _M
diff --git a/scripts/lua/management/routes/tenants.lua b/scripts/lua/management/routes/tenants.lua
index 9e26020..4b92bdd 100644
--- a/scripts/lua/management/routes/tenants.lua
+++ b/scripts/lua/management/routes/tenants.lua
@@ -29,22 +29,23 @@
local _M = {};
---- Request handler for routing tenant calls appropriately
-function _M.requestHandler(dataStore)
- local requestMethod = ngx.req.get_method()
- ngx.header.content_type = "application/json; charset=utf-8"
- if requestMethod == "GET" then
- getTenants(dataStore)
- elseif requestMethod == "PUT" or requestMethod == "POST" then
- addTenant(dataStore)
- elseif requestMethod == "DELETE" then
- deleteTenant(dataStore)
- else
- request.err(400, "Invalid verb.")
+--- Check for tenant id from uri and use existing tenant if it already exists in redis
+-- @param red Redis client instance
+local function checkForExistingTenant(dataStore)
+ local id = ngx.var.tenant_id
+ local existing
+ -- Get object from redis
+ if id ~= nil and id ~= '' then
+ existing = dataStore:getTenant(id)
+ if existing == nil then
+ dataStore:close()
+ request.err(404, utils.concatStrings({"Unknown Tenant id ", id}))
+ end
end
+ return existing
end
-function addTenant(dataStore)
+local function addTenant(dataStore)
-- Open connection to redis or use one from connection pool
-- Check for tenant id and use existingTenant if it already exists in redis
local existingTenant = checkForExistingTenant(dataStore)
@@ -76,25 +77,9 @@
request.success(200, tenantObj)
end
---- Check for tenant id from uri and use existing tenant if it already exists in redis
--- @param red Redis client instance
-function checkForExistingTenant(dataStore)
- local id = ngx.var.tenant_id
- local existing
- -- Get object from redis
- if id ~= nil and id ~= '' then
- existing = dataStore:getTenant(id)
- if existing == nil then
- dataStore:close()
- request.err(404, utils.concatStrings({"Unknown Tenant id ", id}))
- end
- end
- return existing
-end
-
--- Get one or all tenants from the gateway
-- GET /v1/tenants
-function getTenants(dataStore)
+local function getTenants(dataStore)
local queryParams = ngx.req.get_uri_args()
local id = ngx.var.tenant_id
if id == '' then
@@ -125,7 +110,7 @@
--- Delete tenant from gateway
-- DELETE /v1/tenants/<id>
-function deleteTenant(dataStore)
+local function deleteTenant(dataStore)
local id = ngx.var.tenant_id
if id == nil or id == '' then
request.err(400, "No id specified.")
@@ -139,4 +124,19 @@
request.success(200, cjson.encode({}))
end
+--- Request handler for routing tenant calls appropriately
+function _M.requestHandler(dataStore)
+ local requestMethod = ngx.req.get_method()
+ ngx.header.content_type = "application/json; charset=utf-8"
+ if requestMethod == "GET" then
+ getTenants(dataStore)
+ elseif requestMethod == "PUT" or requestMethod == "POST" then
+ addTenant(dataStore)
+ elseif requestMethod == "DELETE" then
+ deleteTenant(dataStore)
+ else
+ request.err(400, "Invalid verb.")
+ end
+end
+
return _M
diff --git a/scripts/lua/oauth/facebook.lua b/scripts/lua/oauth/facebook.lua
index a27b295..08b1f5b 100644
--- a/scripts/lua/oauth/facebook.lua
+++ b/scripts/lua/oauth/facebook.lua
@@ -20,25 +20,8 @@
local utils = require "lib/utils"
local _M = {}
-function _M.process(dataStore, token)
- local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_")
-
- local facebookAppToken = ngx.var[headerName]
- if facebookAppToken == nil then
- request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header')
- return nil
- end
-
- local result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken}))
- if result ~= ngx.null then
- return cjson.decode(result)
- end
-
- return exchangeOAuthToken(dataStore, token, facebookAppToken)
-end
-
-function exchangeOAuthToken(dataStore, token, facebookAppToken)
+local function exchangeOAuthToken(dataStore, token, facebookAppToken)
local http = require 'resty.http'
local request = require "lib/request"
local httpc = http.new()
@@ -73,4 +56,22 @@
return json_resp
end
+function _M.process(dataStore, token)
+
+ local headerName = utils.concatStrings({'http_', 'x-facebook-app-token'}):gsub("-", "_")
+
+ local facebookAppToken = ngx.var[headerName]
+ if facebookAppToken == nil then
+ request.err(401, 'Facebook requires you provide an app token to validate user tokens. Provide a X-Facebook-App-Token header')
+ return nil
+ end
+
+ local result = dataStore:getOAuthToken('facebook', utils.concatStrings({token, facebookAppToken}))
+ if result ~= ngx.null then
+ return cjson.decode(result)
+ end
+
+ return exchangeOAuthToken(dataStore, token, facebookAppToken)
+end
+
return _M
diff --git a/scripts/lua/oauth/google.lua b/scripts/lua/oauth/google.lua
index bd8e11b..efdbf28 100644
--- a/scripts/lua/oauth/google.lua
+++ b/scripts/lua/oauth/google.lua
@@ -29,7 +29,7 @@
local httpc = http.new()
if result ~= ngx.null then
- json_resp = cjson.decode(result)
+ local json_resp = cjson.decode(result)
ngx.header['X-OIDC-Sub'] = json_resp['sub']
ngx.header['X-OIDC-Email'] = json_resp['email']
ngx.header['X-OIDC-Scope'] = json_resp['scope']
diff --git a/scripts/lua/policies/backendRouting.lua b/scripts/lua/policies/backendRouting.lua
index 646de94..e426e53 100644
--- a/scripts/lua/policies/backendRouting.lua
+++ b/scripts/lua/policies/backendRouting.lua
@@ -26,6 +26,14 @@
local _M = {}
+local function setUpstream(u)
+ local upstream = utils.concatStrings({u.scheme, '://', u.host})
+ if u.port ~= nil and u.port ~= '' then
+ upstream = utils.concatStrings({upstream, ':', u.port})
+ end
+ ngx.var.upstream = upstream
+end
+
--- Set upstream based on the backendUrl
function _M.setRoute(backendUrl, gatewayPath)
_M.setRouteWithOverride(backendUrl, gatewayPath, backendOverride)
@@ -109,12 +117,4 @@
end
end
-function setUpstream(u)
- local upstream = utils.concatStrings({u.scheme, '://', u.host})
- if u.port ~= nil and u.port ~= '' then
- upstream = utils.concatStrings({upstream, ':', u.port})
- end
- ngx.var.upstream = upstream
-end
-
return _M
diff --git a/scripts/lua/policies/mapping.lua b/scripts/lua/policies/mapping.lua
index 63ba789..3ed6385 100644
--- a/scripts/lua/policies/mapping.lua
+++ b/scripts/lua/policies/mapping.lua
@@ -30,33 +30,58 @@
local headers
local path
---- Implementation for the mapping policy.
--- @param map The mapping object that contains details about request tranformations
-function processMap(map)
- getRequestParams()
- for k, v in pairs(map) do
- if v.action == "insert" then
- insertParam(v)
- elseif v.action == "remove" then
- removeParam(v)
- elseif v.action == "transform" then
- transformParam(v)
- elseif v.action == "default" then
- checkDefault(v)
- else
- logger.err(utils.concatStrings({'Map action not recognized. Skipping... ', v.action}))
- end
+local function insertHeader(k, v)
+ ngx.req.set_header(k, v)
+ headers[k] = v
+end
+
+local function insertQuery(k, v)
+ query[k] = v
+end
+
+local function insertBody(k, v)
+ body[k] = v
+end
+
+local function insertPath(k, v)
+ v = ngx.unescape_uri(v)
+ path = path:gsub(utils.concatStrings({"%{", k ,"%}"}), v)
+ ngx.req.set_uri(path)
+end
+
+local function removeHeader(k)
+ ngx.req.clear_header(k)
+end
+
+local function removeQuery(k)
+ query[k] = nil
+end
+
+local function removeBody(k)
+ body[k] = nil
+end
+
+local function decodeQuery(param)
+ local decoded = param:gsub('+', ' '):gsub('%%(%x%x)',
+ function(hex) return string.char(tonumber(hex, 16)) end)
+ return decoded
+end
+
+local function parseUrl(url)
+ local map = {}
+ for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do
+ map[ k ] = decodeQuery(v)
end
- finalize()
+ return map
end
--- Get request body, params, and headers from incoming requests
-function getRequestParams()
+local function getRequestParams()
ngx.req.read_body()
body = ngx.req.get_body_data()
if body ~= nil then
-- decode body if json
- decoded, err = cjson.decode(body)
+ local decoded, err = cjson.decode(body)
if err == nil then
body = decoded
end
@@ -74,7 +99,7 @@
--- Insert parameter value to header, body, or query params into request
-- @param m Parameter value to add to request
-function insertParam(m)
+local function insertParam(m)
local v
local k = m.to.name
if m.from.value ~= nil then
@@ -102,7 +127,7 @@
--- Remove parameter value to header, body, or query params from request
-- @param m Parameter value to remove from request
-function removeParam(m)
+local function removeParam(m)
if m.from.location == "header" then
removeHeader(m.from.name)
elseif m.from.location == "query" then
@@ -112,35 +137,12 @@
end
end
---- Move parameter value from one location to another in the request
--- @param m Parameter value to move within request
-function transformParam(m)
- if m.from.name == '*' then
- transformAllParams(m.from.location, m.to.location)
- else
- insertParam(m)
- removeParam(m)
- end
-end
-
---- Checks if the header has been set, and sets the header to a value if found to be null.
--- @param m Header name and value to be set, if header is null.
-function checkDefault(m)
- if m.to.location == "header" and headers[m.to.name] == nil then
- insertHeader(m.to.name, m.from.value)
- elseif m.to.location == "query" and query[m.to.name] == nil then
- insertQuery(m.to.name, m.from.value)
- elseif m.to.location == "body" and body[m.to.name] == nil then
- insertBody(m.to.name, m.from.value)
- end
-end
-
--- Function to handle wildcarding in the transform process.
-- If the value in the from object is '*', this function will pull all values from the incoming request
-- and move them to the location provided in the to object
-- @param s The source object from which we pull all parameters
-- @param d The destination object that we will move all found parameters to.
-function transformAllParams(s, d)
+local function transformAllParams(s, d)
if s == 'query' then
for k, v in pairs(query) do
local t = {}
@@ -192,7 +194,30 @@
end
end
-function finalize()
+--- Move parameter value from one location to another in the request
+-- @param m Parameter value to move within request
+local function transformParam(m)
+ if m.from.name == '*' then
+ transformAllParams(m.from.location, m.to.location)
+ else
+ insertParam(m)
+ removeParam(m)
+ end
+end
+
+--- Checks if the header has been set, and sets the header to a value if found to be null.
+-- @param m Header name and value to be set, if header is null.
+local function checkDefault(m)
+ if m.to.location == "header" and headers[m.to.name] == nil then
+ insertHeader(m.to.name, m.from.value)
+ elseif m.to.location == "query" and query[m.to.name] == nil then
+ insertQuery(m.to.name, m.from.value)
+ elseif m.to.location == "body" and body[m.to.name] == nil then
+ insertBody(m.to.name, m.from.value)
+ end
+end
+
+local function finalize()
if type(body) == 'table' and next(body) ~= nil then
local bodyJson = cjson.encode(body)
ngx.req.set_body_data(bodyJson)
@@ -200,49 +225,24 @@
ngx.req.set_uri_args(query)
end
-function insertHeader(k, v)
- ngx.req.set_header(k, v)
- headers[k] = v
-end
-
-function insertQuery(k, v)
- query[k] = v
-end
-
-function insertBody(k, v)
- body[k] = v
-end
-
-function insertPath(k, v)
- v = ngx.unescape_uri(v)
- path = path:gsub(utils.concatStrings({"%{", k ,"%}"}), v)
- ngx.req.set_uri(path)
-end
-
-function removeHeader(k)
- ngx.req.clear_header(k)
-end
-
-function removeQuery(k)
- query[k] = nil
-end
-
-function removeBody(k)
- body[k] = nil
-end
-
-function parseUrl(url)
- local map = {}
- for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do
- map[ k ] = decodeQuery(v)
+--- Implementation for the mapping policy.
+-- @param map The mapping object that contains details about request tranformations
+local function processMap(map)
+ getRequestParams()
+ for k, v in pairs(map) do
+ if v.action == "insert" then
+ insertParam(v)
+ elseif v.action == "remove" then
+ removeParam(v)
+ elseif v.action == "transform" then
+ transformParam(v)
+ elseif v.action == "default" then
+ checkDefault(v)
+ else
+ logger.err(utils.concatStrings({'Map action not recognized. Skipping... ', v.action}))
+ end
end
- return map
-end
-
-function decodeQuery(param)
- local decoded = param:gsub('+', ' '):gsub('%%(%x%x)',
- function(hex) return string.char(tonumber(hex, 16)) end)
- return decoded
+ finalize()
end
_M.processMap = processMap
diff --git a/scripts/lua/policies/rateLimit.lua b/scripts/lua/policies/rateLimit.lua
index 5f2ca98..468852f 100644
--- a/scripts/lua/policies/rateLimit.lua
+++ b/scripts/lua/policies/rateLimit.lua
@@ -26,7 +26,7 @@
-- @param dataStore the datastore object
-- @param obj rateLimit object containing interval, rate, scope, subscription fields
-- @param apiKey optional api key to use if subscription is set to true
-function limit(dataStore, obj, apiKey)
+local function limit(dataStore, obj, apiKey)
local rate = obj.interval / obj.rate
local tenantId = ngx.var.tenant
local gatewayPath = ngx.var.gatewayPath
diff --git a/scripts/lua/policies/security.lua b/scripts/lua/policies/security.lua
index 2bcdeb0..4afe76c 100644
--- a/scripts/lua/policies/security.lua
+++ b/scripts/lua/policies/security.lua
@@ -24,7 +24,7 @@
--- Allow or block a request by calling a loaded security policy
-- @param dataStore the datastore object
-- @param securityObj an object out of the security array in a given tenant / api / resource
-function process(dataStore, securityObj)
+local function process(dataStore, securityObj)
local ok, result = pcall(require, utils.concatStrings({'policies/security/', securityObj.type}))
if not ok then
ngx.log(ngx.ERR, 'An unexpected error ocurred while processing the security policy: ' .. securityObj.type)
diff --git a/scripts/lua/policies/security/apiKey.lua b/scripts/lua/policies/security/apiKey.lua
index 409d6d9..7abee5b 100644
--- a/scripts/lua/policies/security/apiKey.lua
+++ b/scripts/lua/policies/security/apiKey.lua
@@ -18,13 +18,9 @@
--- @module apiKey
-- Check a subscription with an API Key
-local dataStore = require "lib/dataStore"
local utils = require "lib/utils"
local request = require "lib/request"
-
-local REDIS_HOST = os.getenv("REDIS_HOST")
-local REDIS_PORT = os.getenv("REDIS_PORT")
-local REDIS_PASS = os.getenv("REDIS_PASS")
+local logger = require "lib/logger"
local _M = {}
@@ -36,7 +32,7 @@
-- @param scope scope of the subscription
-- @param apiKey the subscription api key
-- @param return boolean value indicating if the subscription exists in redis
-function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey)
+local function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey)
-- Open connection to redis or use one from connection pool
local k
if scope == 'tenant' then
@@ -54,27 +50,24 @@
end
end
-function process(dataStore, securityObj)
- return processWithHashFunction(dataStore, securityObj, sha256)
-end
-
--- Process the security object
-- @param dataStore the datastore object
-- @param securityObj security object from nginx conf file
-- @param hashFunction a function that will be called to hash the string
-- @return apiKey api key for the subscription
-function processWithHashFunction(dataStore, securityObj, hashFunction)
+local function processWithHashFunction(dataStore, securityObj, hashFunction)
local tenant = ngx.var.tenant
local gatewayPath = ngx.var.gatewayPath
local apiId = dataStore:resourceToApi(utils.concatStrings({'resources:', tenant, ':', gatewayPath}))
local scope = securityObj.scope
- local name = (securityObj.name == nil) and ((securityObj.header == nil) and 'x-api-key' or securityObj.header) or securityObj.name
local queryString = ngx.req.get_uri_args()
local location = (securityObj.location == nil) and 'header' or securityObj.location
-- backwards compatible with "header" argument for name value. "name" argument takes precedent if both provided
local name = (securityObj.name == nil and securityObj.header == nil) and 'x-api-key' or (securityObj.name or securityObj.header)
local apiKey = nil
+ ngx.log(ngx.DEBUG, "Processing API_KEY security policy")
+
if location == "header" then
apiKey = ngx.var[utils.concatStrings({'http_', name}):gsub("-", "_")]
end
@@ -97,17 +90,10 @@
return apiKey
end
---- Calculate the sha256 hash of a string
--- @param str the string you want to hash
--- @return a hashed version of the string
-function sha256(str)
- local resty_sha256 = require "resty.sha256"
- local resty_str = require "resty.string"
- local sha = resty_sha256:new()
- sha:update(str)
- local digest = sha:final()
- return resty_str.to_hex(digest)
+local function process(dataStore, securityObj)
+ return processWithHashFunction(dataStore, securityObj, utils.sha256)
end
+
_M.processWithHashFunction = processWithHashFunction
_M.process = process
diff --git a/scripts/lua/policies/security/clientSecret.lua b/scripts/lua/policies/security/clientSecret.lua
index 79f2b82..132c58a 100644
--- a/scripts/lua/policies/security/clientSecret.lua
+++ b/scripts/lua/policies/security/clientSecret.lua
@@ -19,76 +19,9 @@
-- Check a subscription with a client id and a hashed secret
local _M = {}
-local dataStore = require "lib/dataStore"
local utils = require "lib/utils"
local request = require "lib/request"
-
-local REDIS_HOST = os.getenv("REDIS_HOST")
-local REDIS_PORT = os.getenv("REDIS_PORT")
-local REDIS_PASS = os.getenv("REDIS_PASS")
-
---- Process function main entry point for the security block.
--- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
---@param dataStore the datastore object
---@param securityObj the security object loaded from nginx.conf
---@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret
-function process(dataStore, securityObj)
- local result = processWithHashFunction(dataStore, securityObj, utils.hash)
-end
-
---- In order to properly test this functionallity, I use this function to do all of the business logic with injected dependencies
--- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
--- @param dataStore the datastore object
--- @param securityObj the security configuration for the tenant/resource/api we are verifying
--- @param hashFunction the function used to perform the hash of the api secret
-function processWithHashFunction(dataStore, securityObj, hashFunction)
- -- pull the configuration from nginx
- local tenant = ngx.var.tenant
- local gatewayPath = ngx.var.gatewayPath
- local apiId = ngx.var.apiId
- local scope = securityObj.scope
- local queryString = ngx.req.get_uri_args()
- local location = (securityObj.location == nil) and 'header' or securityObj.location
- local clientId = nil
- local clientSecret = nil
-
- -- allow support for custom names in query or header
- local clientIdName = (securityObj.idFieldName == nil) and 'X-Client-ID' or securityObj.idFieldName
- if location == "header" then
- clientId = ngx.var[utils.concatStrings({'http_', clientIdName}):gsub("-", "_")]
- end
- if location == "query" then
- clientId = queryString[clientIdName]
- end
--- if they didn't supply whatever name this is configured to require, error out
- if clientId == nil or clientId == '' then
- request.err(401, clientIdName .. " required")
- return false
- end
-
--- allow support for custom names in query or header
- local clientSecretName = (securityObj.secretFieldName == nil) and 'X-Client-Secret' or securityObj.secretFieldName
- _G.clientSecretName = clientSecretName:lower()
- if location == "header" then
- clientSecret = ngx.var[utils.concatStrings({'http_', clientSecretName}):gsub("-","_")]
- end
- if location == "query" then
- clientSecret = queryString[clientSecretName]
- end
--- if they didn't supply whatever name this is configured to require, error out
- if clientSecret == nil or clientSecret == '' then
- request.err(401, clientSecretName .. " required")
- return false
- end
-
--- hash the secret
- local result = validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, hashFunction(clientSecret))
- if result == nil then
- request.err(401, "Secret mismatch or not subscribed to this api.")
- end
- ngx.var.apiKey = clientId
- return result
-end
+local logger = require "lib/logger"
--- Validate that the subscription exists in the dataStore
-- @param dataStore the datastore object
@@ -98,24 +31,90 @@
-- @param scope which values should we be using to find the location of the secret
-- @param clientId the subscribed client id
-- @param clientSecret the hashed client secret
-function validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, clientSecret)
--- Open connection to redis or use one from connection pool
+local function validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, clientSecret)
+ -- Open connection to redis or use one from connection pool
local k
- if scope == 'tenant' then
- k = utils.concatStrings({'subscriptions:tenant:', tenant})
- elseif scope == 'resource' then
- k = utils.concatStrings({'subscriptions:tenant:', tenant, ':resource:', gatewayPath})
- elseif scope == 'api' then
- k = utils.concatStrings({'subscriptions:tenant:', tenant, ':api:', apiId})
+ if scope == "tenant" then
+ k = utils.concatStrings({"subscriptions:tenant:", tenant})
+ elseif scope == "resource" then
+ k = utils.concatStrings({"subscriptions:tenant:", tenant, ":resource:", gatewayPath})
+ elseif scope == "api" then
+ k = utils.concatStrings({"subscriptions:tenant:", tenant, ":api:", apiId})
end
-- using the same key location in redis, just using :clientsecret: instead of :key:
- k = utils.concatStrings({k, ':clientsecret:', clientId, ':', clientSecret})
+ k = utils.concatStrings({k, ":clientsecret:", clientId, ":", clientSecret})
if dataStore:exists(k) == 1 then
return k
else
return nil
end
end
+
+--- In order to properly test this functionallity, I use this function to do all of the business logic with injected dependencies
+-- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
+-- @param dataStore the datastore object
+-- @param securityObj the security configuration for the tenant/resource/api we are verifying
+-- @param hashFunction the function used to perform the hash of the api secret
+local function processWithHashFunction(dataStore, securityObj, hashFunction)
+ -- pull the configuration from nginx
+ local tenant = ngx.var.tenant
+ local gatewayPath = ngx.var.gatewayPath
+ local apiId = ngx.var.apiId
+ local scope = securityObj.scope
+ local queryString = ngx.req.get_uri_args()
+ local location = (securityObj.location == nil) and "header" or securityObj.location
+ local clientId = nil
+ local clientSecret = nil
+
+ ngx.log(ngx.DEBUG, "Processing CLIENT_SECRET security policy")
+
+ -- allow support for custom names in query or header
+ local clientIdName = (securityObj.idFieldName == nil) and "X-Client-ID" or securityObj.idFieldName
+ if location == "header" then
+ clientId = ngx.var[utils.concatStrings({"http_", clientIdName}):gsub("-", "_")]
+ end
+ if location == "query" then
+ clientId = queryString[clientIdName]
+ end
+ -- if they didn't supply whatever name this is configured to require, error out
+ if clientId == nil or clientId == "" then
+ request.err(401, clientIdName .. " required")
+ return false
+ end
+
+ -- allow support for custom names in query or header
+ local clientSecretName = (securityObj.secretFieldName == nil) and "X-Client-Secret" or securityObj.secretFieldName
+ ngx.ctx.clientSecretName = clientSecretName:lower()
+ if location == "header" then
+ clientSecret = ngx.var[utils.concatStrings({"http_", clientSecretName}):gsub("-", "_")]
+ end
+ if location == "query" then
+ clientSecret = queryString[clientSecretName]
+ end
+ -- if they didn't supply whatever name this is configured to require, error out
+ if clientSecret == nil or clientSecret == "" then
+ request.err(401, clientSecretName .. " required")
+ return false
+ end
+
+ -- hash the secret
+ local result = validate(dataStore, tenant, gatewayPath, apiId, scope, clientId, hashFunction(clientSecret))
+ if result == nil then
+ request.err(401, "Secret mismatch or not subscribed to this api.")
+ end
+ ngx.var.apiKey = clientId
+ return result
+end
+
+--- Process function main entry point for the security block.
+-- Takes 2 headers and decides if the request should be allowed based on a hashed secret key
+--@param dataStore the datastore object
+--@param securityObj the security object loaded from nginx.conf
+--@return a string representation of what this looks like in redis :clientsecret:clientid:hashed secret
+local function process(dataStore, securityObj)
+ return processWithHashFunction(dataStore, securityObj, utils.hash)
+end
+
_M.processWithHashFunction = processWithHashFunction
_M.process = process
return _M
diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua
index 6b58bf9..c1492e5 100644
--- a/scripts/lua/policies/security/oauth2.lua
+++ b/scripts/lua/policies/security/oauth2.lua
@@ -20,20 +20,37 @@
local utils = require "lib/utils"
local request = require "lib/request"
-local dataStore = require "lib/dataStore"
-local cjson = require "cjson"
-
-local REDIS_HOST = os.getenv("REDIS_HOST")
-local REDIS_PORT = os.getenv("REDIS_PORT")
-local REDIS_PASS = os.getenv("REDIS_PASS")
local _M = {}
+--- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf
+-- @param dataStore the datastore object
+-- @param token the accessToken passed in the authorization header of the routing request
+-- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook
+-- @return the json object recieved from exchanging tokens with the provider
+local function exchange(dataStore, token, provider, securityObj)
+ -- exchange tokens with the provider
+ local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider}))
+ if not loaded then
+ request.err(500, 'Error loading OAuth provider authentication module')
+ print("error loading provider:", impl)
+ return nil
+ end
+
+ local result = impl.process(dataStore, token, securityObj)
+ if result == nil then
+ request.err('401', 'OAuth token didn\'t work or provider doesn\'t support OpenID connect')
+ end
+ -- cache the token
+ return result
+end
-- Process the security object
-- @param securityObj security object from nginx conf file
-- @return oauthId oauth identification
-function process(dataStore, securityObj)
+local function process(dataStore, securityObj)
+ ngx.log(ngx.DEBUG, "Processing OAUTH2 security policy")
+
local accessToken = ngx.var['http_Authorization']
if accessToken == nil then
request.err(401, "No Authorization header provided")
@@ -56,27 +73,5 @@
return token
end
---- Exchange tokens with an oauth provider. Loads a provider based on configuration in the nginx.conf
--- @param dataStore the datastore object
--- @param token the accessToken passed in the authorization header of the routing request
--- @param provider the name of the provider we will load from a file. Currently supported google/github/facebook
--- @return the json object recieved from exchanging tokens with the provider
-function exchange(dataStore, token, provider, securityObj)
- -- exchange tokens with the provider
- local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider}))
- if not loaded then
- request.err(500, 'Error loading OAuth provider authentication module')
- print("error loading provider:", impl)
- return nil
- end
-
- local result = impl.process(dataStore, token, securityObj)
- if result == nil then
- request.err('401', 'OAuth token didn\'t work or provider doesn\'t support OpenID connect')
- end
- -- cache the token
- return result
-end
-
_M.process = process
return _M
diff --git a/scripts/lua/routing.lua b/scripts/lua/routing.lua
index f4d1845..080fca6 100644
--- a/scripts/lua/routing.lua
+++ b/scripts/lua/routing.lua
@@ -44,6 +44,46 @@
local _M = {}
+local function setRequestLogs()
+ local requestHeaders = ngx.req.get_headers()
+ for k, v in pairs(requestHeaders) do
+ if k == 'authorization' or k == ngx.ctx.clientSecretName then
+ requestHeaders[k] = '[redacted]'
+ end
+ end
+ ngx.var.requestHeaders = cjson.encode(requestHeaders)
+ ngx.req.read_body()
+ ngx.var.requestBody = ngx.req.get_body_data()
+end
+
+--- Function to read the list of policies and send implementation to the correct backend
+-- @param red redis client instance
+-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
+-- @param apiKey optional subscription api key
+local function parsePolicies(dataStore, obj, apiKey)
+ for k, v in pairs (obj) do
+ if v.type == 'reqMapping' then
+ mapping.processMap(v.value)
+ elseif v.type == 'rateLimit' then
+ rateLimit.limit(dataStore, v.value, apiKey)
+ elseif v.type == 'backendRouting' then
+ backendRouting.setDynamicRoute(v.value)
+ end
+ end
+end
+
+--- Given a verb, transforms the backend request to use that method
+-- @param v Verb to set on the backend request
+local function setVerb(v)
+ local allowedVerbs = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'}
+ local verb = string.upper(v)
+ if utils.tableContains(allowedVerbs, verb) then
+ ngx.req.set_method(ngx[utils.concatStrings({"HTTP_", verb})])
+ else
+ ngx.req.set_method(ngx.HTTP_GET)
+ end
+end
+
--- Main function that handles parsing of invocation details and carries out implementation
function _M.processCall(dataStore)
-- Get request headers
@@ -154,7 +194,7 @@
end
local resourceKeys = dataStore:getAllResources(tenant)
- local result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
+ result = _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
if OPTIMIZE > 0 and result ~= nil then
dataStore:optimizeLookup(tenant, result, path)
@@ -177,6 +217,7 @@
return key
end
end
+ local cfUrl = ngx.req.get_headers()["x-cf-forwarded-url"]
if cfUrl ~= nil and cfUrl ~= "" then
return nil
end
@@ -242,46 +283,6 @@
return false
end
---- Function to read the list of policies and send implementation to the correct backend
--- @param red redis client instance
--- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
--- @param apiKey optional subscription api key
-function parsePolicies(dataStore, obj, apiKey)
- for k, v in pairs (obj) do
- if v.type == 'reqMapping' then
- mapping.processMap(v.value)
- elseif v.type == 'rateLimit' then
- rateLimit.limit(dataStore, v.value, apiKey)
- elseif v.type == 'backendRouting' then
- backendRouting.setDynamicRoute(v.value)
- end
- end
-end
-
---- Given a verb, transforms the backend request to use that method
--- @param v Verb to set on the backend request
-function setVerb(v)
- local allowedVerbs = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'}
- local verb = string.upper(v)
- if utils.tableContains(allowedVerbs, verb) then
- ngx.req.set_method(ngx[utils.concatStrings({"HTTP_", verb})])
- else
- ngx.req.set_method(ngx.HTTP_GET)
- end
-end
-
-function setRequestLogs()
- local requestHeaders = ngx.req.get_headers()
- for k, v in pairs(requestHeaders) do
- if k == 'authorization' or k == _G.clientSecretName then
- requestHeaders[k] = '[redacted]'
- end
- end
- ngx.var.requestHeaders = cjson.encode(requestHeaders)
- ngx.req.read_body()
- ngx.var.requestBody = ngx.req.get_body_data()
-end
-
function _M.setResponseLogs()
ngx.var.responseHeaders = cjson.encode(ngx.resp.get_headers())
local resp_body = ngx.arg[1]
diff --git a/tools/lua-releng b/tools/lua-releng
new file mode 100755
index 0000000..7560316
--- /dev/null
+++ b/tools/lua-releng
@@ -0,0 +1,104 @@
+#!/usr/bin/env perl
+
+use strict;
+use warnings;
+
+use Getopt::Std;
+
+my (@luas, @tests);
+
+my %opts;
+getopts('Lse', \%opts) or die "Usage: lua-releng [-L] [-s] [-e] [files]\n";
+
+my $silent = $opts{s};
+my $stop_on_error = $opts{e};
+my $no_long_line_check = $opts{L};
+
+my $check_lua_ver = "luac -v | awk '{print\$2}'| grep 5.1";
+my $output = `$check_lua_ver`;
+if ($output eq '') {
+ die "ERROR: lua-releng ONLY supports Lua 5.1!\n";
+}
+
+if ($#ARGV != -1) {
+ @luas = @ARGV;
+
+} else {
+ @luas = map glob, qw{ *.lua lib/*.lua lib/*/*.lua lib/*/*/*.lua lib/*/*/*/*.lua lib/*/*/*/*/*.lua };
+ if (-d 't') {
+ @tests = map glob, qw{ t/*.t t/*/*.t t/*/*/*.t };
+ }
+}
+
+for my $f (sort @luas) {
+ process_file($f);
+}
+
+for my $t (@tests) {
+ blank(qq{grep -H -n --color -E '\\--- ?(ONLY|LAST)' $t});
+}
+# p: prints a string to STDOUT appending \n
+# w: prints a string to STDERR appending \n
+# Both respect the $silent value
+sub p { print "$_[0]\n" if (!$silent) }
+sub w { warn "$_[0]\n" if (!$silent) }
+
+# blank: runs a command and looks at the output. If the output is not
+# blank it is printed (and the program dies if stop_on_error is 1)
+sub blank {
+ my ($command) = @_;
+ if ($stop_on_error) {
+ my $output = `$command`;
+ if ($output ne '') {
+ die $output;
+ }
+ } else {
+ system($command);
+ }
+}
+
+my $version;
+sub process_file {
+ my $file = shift;
+ # Check the sanity of each .lua file
+ open my $in, $file or
+ die "ERROR: Can't open $file for reading: $!\n";
+ my $found_ver;
+ while (<$in>) {
+ my ($ver, $skipping);
+ if (/(?x) (?:_VERSION|version) \s* = .*? ([\d\.]*\d+) (.*? SKIP)?/) {
+ my $orig_ver = $ver = $1;
+ $found_ver = 1;
+ $skipping = $2;
+ $ver =~ s{^(\d+)\.(\d{3})(\d{3})$}{join '.', int($1), int($2), int($3)}e;
+ w("$file: $orig_ver ($ver)");
+ last;
+
+ } elsif (/(?x) (?:_VERSION|version) \s* = \s* ([a-zA-Z_]\S*)/) {
+ w("$file: $1");
+ $found_ver = 1;
+ last;
+ }
+
+ if ($ver and $version and !$skipping) {
+ if ($version ne $ver) {
+ die "$file: $ver != $version\n";
+ }
+ } elsif ($ver and !$version) {
+ $version = $ver;
+ }
+ }
+ if (!$found_ver) {
+ w("WARNING: No \"_VERSION\" or \"version\" field found in `$file`.");
+ }
+ close $in;
+
+ p("Checking use of Lua global variables in file $file...");
+ p("\top no.\tline\tinstruction\targs\t; code");
+ blank("luac -p -l $file | grep -E '[GS]ETGLOBAL' | grep -vE '\\<(require|type|tostring|error|ngx|ndk|jit|setmetatable|getmetatable|string|table|io|os|print|tonumber|math|pcall|xpcall|unpack|pairs|ipairs|assert|module|package|coroutine|[gs]etfenv|next|rawget|rawset|rawlen|select)\\>'");
+ unless ($no_long_line_check) {
+ p("Checking line length exceeding 80...");
+ blank("grep -H -n -E --color '.{81}' $file");
+ }
+}
+
\ No newline at end of file