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/03 15:28:31 UTC
[openwhisk-apigateway] branch scope-fixes created (now e83218c)
This is an automated email from the ASF dual-hosted git repository.
mhamann pushed a change to branch scope-fixes
in repository https://gitbox.apache.org/repos/asf/openwhisk-apigateway.git.
at e83218c fix(core): upstream openresty fixes; lua global scope pollution
This branch includes the following new commits:
new e83218c fix(core): upstream openresty fixes; lua global scope pollution
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
[openwhisk-apigateway] 01/01: fix(core): upstream openresty fixes;
lua global scope pollution
Posted by mh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
mhamann pushed a commit to branch scope-fixes
in repository https://gitbox.apache.org/repos/asf/openwhisk-apigateway.git
commit e83218ce6d63837bdf1cd5655610e830535814bc
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