You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by mh...@apache.org on 2020/08/06 21:35:51 UTC
[openwhisk-apigateway] 01/06: fix(core): upstream openresty fixes;
lua global scope pollution
This is an automated email from the ASF dual-hosted git repository.
mhamann pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/openwhisk-apigateway.git
commit 9733fb9bdbf156cbf6d23ddba55f2ee3cac50f06
Author: Matt Hamann <mh...@us.ibm.com>
AuthorDate: Mon Aug 3 11:28:14 2020 -0400
fix(core): upstream openresty fixes; lua global scope pollution
---
.dockerignore | 1 +
Dockerfile | 4 +-
scripts/lua/lib/dataStore.lua | 4 +-
scripts/lua/lib/redis.lua | 330 ++++++++++++------------
scripts/lua/lib/request.lua | 4 +-
scripts/lua/lib/utils.lua | 2 +-
scripts/lua/management/lib/apis.lua | 54 ++--
scripts/lua/management/lib/swagger.lua | 212 +++++++--------
scripts/lua/management/lib/tenants.lua | 103 ++++----
scripts/lua/management/lib/validation.lua | 154 +++++------
scripts/lua/management/routes/apis.lua | 54 ++--
scripts/lua/management/routes/subscriptions.lua | 164 ++++++------
scripts/lua/management/routes/tenants.lua | 62 ++---
scripts/lua/oauth/facebook.lua | 37 +--
scripts/lua/oauth/google.lua | 2 +-
scripts/lua/policies/backendRouting.lua | 16 +-
scripts/lua/policies/mapping.lua | 176 ++++++-------
scripts/lua/policies/rateLimit.lua | 2 +-
scripts/lua/policies/security.lua | 2 +-
scripts/lua/policies/security/apiKey.lua | 30 +--
scripts/lua/policies/security/clientSecret.lua | 99 ++++---
scripts/lua/policies/security/oauth2.lua | 53 ++--
scripts/lua/routing.lua | 83 +++---
tools/lua-releng | 104 ++++++++
24 files changed, 919 insertions(+), 833 deletions(-)
diff --git a/.dockerignore b/.dockerignore
index 1b5d582..b6643df 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -5,3 +5,4 @@ CONTRIBUTING.md
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 @@ RUN apk update && \
&& 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 @@ end
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 @@ end
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 @@ function _M.close(red)
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 @@ function _M.optimizeLookup(red, tenant, resourceKey, pathStr)
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 @@ function _M.lockSnapshot(red, snapshotId)
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 @@ local _Request = {}
--- 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 @@ end
--- 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 @@ end
-- @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 @@ GATEWAY_URL = (GATEWAY_URL ~= nil and GATEWAY_URL ~= '') and GATEWAY_URL or util
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 @@ function _M.deleteAPI(dataStore, id)
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,63 +18,51 @@
--- @module swagger
-- Module for parsing swagger file
-local _M = {}
local utils = require "lib/utils"
--- 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 security = parseSecurity(swagger)
- local corsObj = parseCors(swagger)
- local decoded = {
- name = swagger.info.title,
- basePath = swagger.basePath,
- resources = {}
- }
- for path, verbObj in pairs(swagger.paths) do
- decoded.resources[path] = { operations = {} }
- decoded.resources[path].cors = corsObj
- for verb, value in pairs(verbObj) do
- decoded.resources[path].operations[verb] = {}
- local verbObj = decoded.resources[path].operations[verb]
- verbObj.policies = utils.deepCloneTable(policies) or {}
- verbObj.security = security
- if backends ~= nil then
- local backend = (backends["all"] ~= nil) and backends["all"] or backends[value.operationId]
- verbObj.backendUrl = backend.backendUrl
- verbObj.backendMethod = (backend.backendMethod == 'keep') and verb or backend.backendMethod
- if backend.policy ~= nil then
- local globalReqMappingPolicy = nil;
- for _, policy in pairs(verbObj.policies) do
- if policy.type == 'reqMapping' then
- globalReqMappingPolicy = policy;
- end
- end
- if globalReqMappingPolicy ~= nil then
- for _, v in pairs(backend.policy.value) do
- globalReqMappingPolicy.value[#globalReqMappingPolicy.value+1] = v
- end
- else
- verbObj.policies[#verbObj.policies+1] = {
- type = 'reqMapping',
- value = backend.policy.value
+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
- else
- verbObj.backendUrl = ''
- verbObj.backendMethod = verb
end
end
end
- return decoded
+ 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
-function parseBackends(swagger)
+local function parseBackends(swagger)
local configObj = swagger["x-gateway-configuration"]
configObj = (configObj == nil) and swagger["x-ibm-configuration"] or configObj
if configObj ~= nil then
@@ -113,32 +101,10 @@ function parseBackends(swagger)
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)
+local function parseRateLimit(rlObj)
if rlObj ~= nil and rlObj[1] ~= nil then
+ local unit
rlObj = rlObj[1]
if rlObj.unit == "second" then
unit = 1
@@ -164,52 +130,37 @@ function parseRateLimit(rlObj)
return nil
end
---- Parse request mapping
-function parseRequestMapping(configObj)
- local valueList = {}
+--- 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
- 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
+ local reqMappingPolicy = parseRequestMapping(configObj.assembly)
+ if reqMappingPolicy ~= nil then
+ policies[#policies+1] = reqMappingPolicy
end
end
- if next(valueList) ~= nil then
- return {
- type = "reqMapping",
- value = valueList
- }
- else
- return nil
- end
+ return policies
end
--- Parse security in swagger
-- @param swagger swagger file to parse
-function parseSecurity(swagger)
+local function parseSecurity(swagger)
local security = {}
if swagger["securityDefinitions"] ~= nil then
local secObject = swagger["securityDefinitions"]
if utils.tableLength(secObject) == 2 then
- secObj = {
+ local secObj = {
type = 'clientSecret',
scope = 'api'
}
@@ -242,7 +193,7 @@ function parseSecurity(swagger)
return security
end
-function parseCors(swagger)
+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
@@ -257,4 +208,55 @@ function parseCors(swagger)
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 = parsePolicies(swagger)
+ local security = parseSecurity(swagger)
+ local corsObj = parseCors(swagger)
+ local decoded = {
+ name = swagger.info.title,
+ basePath = swagger.basePath,
+ resources = {}
+ }
+ for path, verbObj in pairs(swagger.paths) do
+ decoded.resources[path] = { operations = {} }
+ decoded.resources[path].cors = corsObj
+ for verb, value in pairs(verbObj) do
+ decoded.resources[path].operations[verb] = {}
+ local verbObj = decoded.resources[path].operations[verb]
+ verbObj.policies = utils.deepCloneTable(policies) or {}
+ verbObj.security = security
+ if backends ~= nil then
+ local backend = (backends["all"] ~= nil) and backends["all"] or backends[value.operationId]
+ verbObj.backendUrl = backend.backendUrl
+ verbObj.backendMethod = (backend.backendMethod == 'keep') and verb or backend.backendMethod
+ if backend.policy ~= nil then
+ local globalReqMappingPolicy = nil;
+ for _, policy in pairs(verbObj.policies) do
+ if policy.type == 'reqMapping' then
+ globalReqMappingPolicy = policy;
+ end
+ end
+ if globalReqMappingPolicy ~= nil then
+ for _, v in pairs(backend.policy.value) do
+ globalReqMappingPolicy.value[#globalReqMappingPolicy.value+1] = v
+ end
+ else
+ verbObj.policies[#verbObj.policies+1] = {
+ type = 'reqMapping',
+ value = backend.policy.value
+ }
+ end
+ end
+ else
+ verbObj.backendUrl = ''
+ verbObj.backendMethod = verb
+ end
+ end
+ end
+ return decoded
+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,30 +37,10 @@ function _M.addTenant(dataStore, decoded, existingTenant)
return cjson.decode(tenantObj)
end
---- Get all tenants in redis
--- @param ds redis client
--- @param queryParams object containing optional query parameters
-function _M.getAllTenants(dataStore, queryParams)
- local tenants = dataStore:getAllTenants()
- local tenantList
- if next(queryParams) ~= nil then
- tenantList = filterTenants(tenants, queryParams);
- end
- if tenantList == nil then
- tenantList = {}
- for k, v in pairs(tenants) do
- if k%2 == 0 then
- tenantList[#tenantList+1] = cjson.decode(v)
- end
- end
- end
- 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 function filterTenants(tenants, queryParams)
local namespace = queryParams['filter[where][namespace]']
local instance = queryParams['filter[where][instance]']
-- missing or invalid query parameters
@@ -82,49 +61,41 @@ function filterTenants(tenants, queryParams)
return tenantList
end
---- Get tenant by its id
--- @param ds redis client
--- @param id tenant id
-function _M.getTenant(dataStore, id)
- local tenant = dataStore:getTenant(id)
- if tenant == nil then
- request.err(404, utils.concatStrings({"Unknown tenant id ", id }))
- end
- return tenant
-end
-
---- Get APIs associated with tenant
+--- Get all tenants in redis
-- @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
+function _M.getAllTenants(dataStore, queryParams)
+ local tenants = dataStore:getAllTenants()
+ local tenantList
if next(queryParams) ~= nil then
- apiList = filterTenantAPIs(id, apis, queryParams);
+ tenantList = filterTenants(tenants, queryParams);
end
- if apiList == nil then
- apiList = {}
- for k, v in pairs(apis) do
+ if tenantList == nil then
+ tenantList = {}
+ for k, v in pairs(tenants) do
if k%2 == 0 then
- local decoded = cjson.decode(v)
- if decoded.tenantId == id then
- apiList[#apiList+1] = decoded
- end
+ tenantList[#tenantList+1] = cjson.decode(v)
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)
+ return tenantList
+end
+
+--- Get tenant by its id
+-- @param ds redis client
+-- @param id tenant id
+function _M.getTenant(dataStore, id)
+ local tenant = dataStore:getTenant(id)
+ if tenant == nil then
+ request.err(404, utils.concatStrings({"Unknown tenant id ", id }))
end
+ return tenant
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 @@ end
--- 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 @@ function filterTenantAPIs(id, apis, queryParams)
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,53 +23,67 @@ local utils = require "lib/utils"
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
+ 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
- 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."}) }
+--- Error checking for operations
+-- @param operations operations object
+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
- -- 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 \"'\"." }
+ local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
+ for verb, verbObj in pairs(operations) do
+ if allowedVerbs[verb:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
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 }) }
+ -- Check required fields
+ local requiredFields = {"backendMethod", "backendUrl"}
+ for k, v in pairs(requiredFields) do
+ if verbObj[v] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
+ end
+ if v == "backendMethod" then
+ local backendMethod = verbObj[v]
+ if allowedVerbs[backendMethod:upper()] == nil then
+ return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
+ end
+ end
end
- end
- if field == "resources" then
- local res, err = checkResources(object)
+ -- Check optional fields
+ local res, err = checkOptionalPolicies(verbObj.policies, verbObj.security)
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)
+local function checkResources(resources)
if next(resources) == nil then
return false, { statusCode = 400, message = "Empty resources object." }
end
@@ -90,62 +104,48 @@ function checkResources(resources)
end
end
---- Error checking for operations
--- @param operations operations object
-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." }
+--- 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
- local allowedVerbs = {GET=true, POST=true, PUT=true, DELETE=true, PATCH=true, HEAD=true, OPTIONS=true}
- for verb, verbObj in pairs(operations) do
- if allowedVerbs[verb:upper()] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"Resource verb '", verb, "' not supported."}) }
+ -- 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
- -- Check required fields
- local requiredFields = {"backendMethod", "backendUrl"}
- for k, v in pairs(requiredFields) do
- if verbObj[v] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"Missing field '", v, "' for '", verb, "' operation."}) }
- end
- if v == "backendMethod" then
- local backendMethod = verbObj[v]
- if allowedVerbs[backendMethod:upper()] == nil then
- return false, { statusCode = 400, message = utils.concatStrings({"backendMethod '", backendMethod, "' not supported."}) }
- end
- 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
- -- Check optional fields
- local res, err = checkOptionalPolicies(verbObj.policies, verbObj.security)
+ 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 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
- 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
+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 REDIS_PASS = os.getenv("REDIS_PASS")
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 @@ function getAPIs(dataStore)
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 @@ function addAPI(dataStore)
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 @@ function deleteAPI(dataStore)
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,35 +30,69 @@ local REDIS_PASS = os.getenv("REDIS_PASS")
local _M = {}
-function _M.requestHandler(dataStore)
- local version = ngx.var.version
- if version == "v2" then
- v2(dataStore)
- elseif version == "v1" then
- v1(dataStore)
+local function validateSubscriptionBody(dataStore)
+ -- Read in the PUT JSON Body
+ ngx.req.read_body()
+ local args = ngx.req.get_body_data()
+ if not args then
+ dataStore:close()
+ request.err(400, "Missing request body.")
+ end
+ -- Convert json into Lua table
+ local decoded = cjson.decode(args)
+ -- Check required fields
+ local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"})
+ if res == false then
+ dataStore:close()
+ request.err(err.statusCode, err.message)
+ end
+ -- Check if we're using tenant or resource or api
+ local resource = decoded.resource
+ local apiId = decoded.apiId
+ local redisKey
+ dataStore:setSnapshotId(decoded.tenantId)
+ local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
+ if decoded.scope == "tenant" then
+ redisKey = prefix
+ elseif decoded.scope == "resource" then
+ if resource ~= nil then
+ redisKey = utils.concatStrings({prefix, ":resource:", resource})
+ else
+ dataStore:close()
+ request.err(400, "\"resource\" missing from request body.")
+ end
+ elseif decoded.scope == "api" then
+ if apiId ~= nil then
+ redisKey = utils.concatStrings({prefix, ":api:", apiId})
+ else
+ dataStore:close()
+ request.err(400, "\"apiId\" missing from request body.")
+ end
else
- request.err(404, "404 Not found")
+ dataStore:close()
+ request.err(400, "Invalid scope")
end
+ redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
+ return redisKey
end
+local function addSubscription(dataStore)
+ local redisKey = validateSubscriptionBody(dataStore)
+ dataStore:createSubscription(redisKey)
+ dataStore:close()
+ request.success(200, "Subscription created.")
+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
+local function deleteSubscription(dataStore)
+ local redisKey = validateSubscriptionBody(dataStore)
+ dataStore:deleteSubscription(redisKey)
+ dataStore:close()
+ request.success(200, "Subscription deleted.")
end
-function v2AddSubscription(dataStore)
+-- v2 --
+
+local function v2AddSubscription(dataStore)
ngx.req.read_body()
local args = ngx.req.get_body_data()
if not args then
@@ -83,19 +117,19 @@ function v2AddSubscription(dataStore)
request.success(200, cjson.encode(result))
end
-function v2GetSubscriptions(dataStore)
+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)
- redis.close(red)
+ dataStore:close()
ngx.header.content_type = "application/json; charset=utf-8"
request.success(200, cjson.encode(subscriptionList))
end
-function v2DeleteSubscription(dataStore)
+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"]
@@ -109,14 +143,27 @@ function v2DeleteSubscription(dataStore)
if res == false then
request.err(404, "Subscription doesn't exist")
end
- redis.close(red)
+ 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 --
-function v1(dataStore)
+local function v1(dataStore)
local requestMethod = ngx.req.get_method()
if requestMethod == "POST" or requestMethod == "PUT" then
addSubscription(dataStore)
@@ -128,64 +175,15 @@ function v1(dataStore)
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)
- -- Read in the PUT JSON Body
- ngx.req.read_body()
- local args = ngx.req.get_body_data()
- if not args then
- dataStore:close()
- request.err(400, "Missing request body.")
- end
- -- Convert json into Lua table
- local decoded = cjson.decode(args)
- -- Check required fields
- local res, err = utils.tableContainsAll(decoded, {"key", "scope", "tenantId"})
- if res == false then
- dataStore:close()
- request.err(err.statusCode, err.message)
- end
- -- Check if we're using tenant or resource or api
- local resource = decoded.resource
- local apiId = decoded.apiId
- local redisKey
- dataStore:setSnapshotId(decoded.tenantId)
- local prefix = utils.concatStrings({"subscriptions:tenant:", decoded.tenantId})
- if decoded.scope == "tenant" then
- redisKey = prefix
- elseif decoded.scope == "resource" then
- if resource ~= nil then
- redisKey = utils.concatStrings({prefix, ":resource:", resource})
- else
- dataStore:close()
- request.err(400, "\"resource\" missing from request body.")
- end
- elseif decoded.scope == "api" then
- if apiId ~= nil then
- redisKey = utils.concatStrings({prefix, ":api:", apiId})
- else
- dataStore:close()
- request.err(400, "\"apiId\" missing from request body.")
- end
+function _M.requestHandler(dataStore)
+ local version = ngx.var.version
+ if version == "v2" then
+ v2(dataStore)
+ elseif version == "v1" then
+ v1(dataStore)
else
- dataStore:close()
- request.err(400, "Invalid scope")
+ request.err(404, "404 Not found")
end
- redisKey = utils.concatStrings({redisKey, ":key:", decoded.key})
- return redisKey
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 REDIS_PASS = os.getenv("REDIS_PASS")
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 @@ function addTenant(dataStore)
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 @@ end
--- 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 @@ function deleteTenant(dataStore)
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 cjson = require 'cjson'
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 @@ function exchangeOAuthToken(dataStore, token, facebookAppToken)
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 @@ function _M.process (dataStore, token)
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 backendOverride = os.getenv("BACKEND_HOST")
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 @@ function _M.getUriPath(backendPath)
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 query
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 @@ end
--- 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 @@ end
--- 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 @@ function removeParam(m)
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,57 +194,55 @@ function transformAllParams(s, d)
end
end
-function finalize()
- if type(body) == 'table' and next(body) ~= nil then
- local bodyJson = cjson.encode(body)
- ngx.req.set_body_data(bodyJson)
+--- 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
- 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
+--- 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
-function parseUrl(url)
- local map = {}
- for k,v in url:gmatch('([^&=?]+)=([^&=?]+)') do
- map[ k ] = decodeQuery(v)
+local function finalize()
+ if type(body) == 'table' and next(body) ~= nil then
+ local bodyJson = cjson.encode(body)
+ ngx.req.set_body_data(bodyJson)
end
- return map
+ ngx.req.set_uri_args(query)
end
-function decodeQuery(param)
- local decoded = param:gsub('+', ' '):gsub('%%(%x%x)',
- function(hex) return string.char(tonumber(hex, 16)) end)
- return decoded
+--- 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
+ 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 @@ local _M = {}
-- @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 @@ local utils = require "lib/utils"
--- 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 @@ local _M = {}
-- @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 @@ function validate(dataStore, tenant, gatewayPath, apiId, scope, apiKey)
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 @@ function processWithHashFunction(dataStore, securityObj, hashFunction)
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,21 +19,35 @@
-- 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 logger = require "lib/logger"
-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)
+--- Validate that the subscription exists in the dataStore
+-- @param dataStore the datastore object
+-- @param tenant the tenantId we are checking for
+-- @param gatewayPath the possible resource we are checking for
+-- @param apiId if we are checking for an api
+-- @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
+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})
+ end
+ -- using the same key location in redis, just using :clientsecret: instead of :key:
+ 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
@@ -41,47 +55,49 @@ end
-- @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)
+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 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
+ local clientIdName = (securityObj.idFieldName == nil) and "X-Client-ID" or securityObj.idFieldName
if location == "header" then
- clientId = ngx.var[utils.concatStrings({'http_', clientIdName}):gsub("-", "_")]
+ 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
+ -- 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()
+ -- 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("-","_")]
+ 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
+ -- 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
+ -- 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.")
@@ -90,32 +106,15 @@ function processWithHashFunction(dataStore, securityObj, hashFunction)
return result
end
---- Validate that the subscription exists in the dataStore
--- @param dataStore the datastore object
--- @param tenant the tenantId we are checking for
--- @param gatewayPath the possible resource we are checking for
--- @param apiId if we are checking for an api
--- @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 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})
- end
- -- using the same key location in redis, just using :clientsecret: instead of :key:
- k = utils.concatStrings({k, ':clientsecret:', clientId, ':', clientSecret})
- if dataStore:exists(k) == 1 then
- return k
- else
- return nil
- 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 @@ function process(dataStore, securityObj)
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 SNAPSHOTTING = os.getenv('SNAPSHOTTING')
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 @@ function _M.findResource(dataStore, tenant, path)
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 @@ function _M.slowLookup(resourceKeys, tenant, path, redisKey, cfRedisKey)
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 @@ function _M.pathParamMatch(key, resourceKey)
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