Merge pull request #40 from apimesh/rate-limit-group
Implement API rate limit policy
diff --git a/api-gateway-config/scripts/lua/lib/filemgmt.lua b/api-gateway-config/scripts/lua/lib/filemgmt.lua
index 2980a46..f805093 100644
--- a/api-gateway-config/scripts/lua/lib/filemgmt.lua
+++ b/api-gateway-config/scripts/lua/lib/filemgmt.lua
@@ -35,17 +35,21 @@
-- @param resourceObj
-- @return fileLocation location of created/updated conf file
function _M.createResourceConf(baseConfDir, namespace, gatewayPath, resourceObj)
- resourceObj = utils.serializeTable(cjson.decode(resourceObj))
+ local decoded = cjson.decode(resourceObj)
+ resourceObj = utils.serializeTable(decoded)
local prefix = utils.concatStrings({"\tinclude /etc/api-gateway/conf.d/commons/common-headers.conf;\n",
"\tset $upstream https://172.17.0.1;\n",
"\tset $namespace ", namespace, ";\n",
- "\tset $gatewayPath ", gatewayPath, ";\n\n"})
- -- Set resource headers and mapping by calling routing.processCall()
+ "\tset $gatewayPath ", gatewayPath, ";\n"})
+ if decoded.apiId ~= nil then
+ prefix = utils.concatStrings({prefix, "\tset $apiId ", decoded.apiId, ";\n"})
+ end
+ -- Set route headers and mapping by calling routing.processCall()
local outgoingResource = utils.concatStrings({"\taccess_by_lua_block {\n",
- "\t\tlocal routing = require \"routing\"\n",
- "\t\trouting.processCall(", resourceObj, ")\n",
- "\t}\n\n",
- "\tproxy_pass $upstream;\n"})
+ "\t\tlocal routing = require \"routing\"\n",
+ "\t\trouting.processCall(", resourceObj, ")\n",
+ "\t}\n\n",
+ "\tproxy_pass $upstream;\n"})
-- Add to endpoint conf file
os.execute(utils.concatStrings({"mkdir -p ", baseConfDir, namespace}))
diff --git a/api-gateway-config/scripts/lua/policies/rateLimit.lua b/api-gateway-config/scripts/lua/policies/rateLimit.lua
index 23d4836..a4b6e7b 100644
--- a/api-gateway-config/scripts/lua/policies/rateLimit.lua
+++ b/api-gateway-config/scripts/lua/policies/rateLimit.lua
@@ -31,31 +31,39 @@
local _M = {}
-function limit(obj)
+function limit(obj, subHeader)
local i = 60 / obj.interval
local r = i * obj.rate
r = utils.concatStrings({tostring(r), 'r/m'})
local k
- if obj.field == 'namespace' then
- k = ngx.var.namespace
- elseif obj.field == 'apikey' then
- k = utils.concatStrings({ngx.var.namespace, '_', ngx.var['http_x_api_key']})
- elseif obj.field == 'resource' then
- k = utils.concatStrings({ngx.var.namespace, '_', ngx.var.gatewayPath})
- end
+ -- Check scope
+ if obj.scope == 'tenant' then
+ k = utils.concatStrings({"tenant:", ngx.var.namespace})
+ elseif obj.scope == 'api' then
+ k = utils.concatStrings({"tenant:", ngx.var.namespace, ":api:", ngx.var.apiId})
+ elseif obj.scope == 'resource' then
+ k = utils.concatStrings({"tenant:", ngx.var.namespace, ":resource:", ngx.var.gatewayPath})
+ end
+ -- Check subscription
+ if obj.subscription ~= nil and obj.subscription == true and subHeader ~= nil then
+ apiKey = ngx.var[subHeader]
+ k = utils.concatStrings({k, ':subscription:', apiKey})
+ end
local config = {
- key = k,
- zone = 'rateLimiting',
- rate = r,
- interval = obj.interval,
- log_level = ngx.NOTICE,
- rds = { host = REDIS_HOST, port = REDIS_PORT, pass = REDIS_PASS}
- }
- local ok = request.limit (config)
+ key = k,
+ zone = 'rateLimiting',
+ rate = r,
+ interval = obj.interval,
+ log_level = ngx.NOTICE,
+ rds = { host = REDIS_HOST, port = REDIS_PORT, pass = REDIS_PASS}
+ }
+ local ok = request.limit(config)
if not ok then
+ ngx.status = 429
logger.err('Rate limit exceeded. Sending 429')
- ngx.exit(429)
+ ngx.say('Rate limit exceeded.')
+ ngx.exit(ngx.status)
end
end
diff --git a/api-gateway-config/scripts/lua/policies/security.lua b/api-gateway-config/scripts/lua/policies/security.lua
index 693f267..dce81fd 100644
--- a/api-gateway-config/scripts/lua/policies/security.lua
+++ b/api-gateway-config/scripts/lua/policies/security.lua
@@ -51,12 +51,16 @@
local apiKey = ngx.var[h]
if not apiKey then
logger.err('No api-key passed. Sending 401')
- ngx.exit(401)
+ ngx.status = 401
+ ngx.say('API key is required.')
+ ngx.exit(ngx.status)
end
local ok = validateAPIKey(namespace, gatewayPath, apiKey)
if not ok then
logger.err('api-key does not match. Sending 401')
- ngx.exit(401)
+ ngx.status = 401
+ ngx.say('Invalid API Key.')
+ ngx.exit(ngx.status)
end
end
diff --git a/api-gateway-config/scripts/lua/routing.lua b/api-gateway-config/scripts/lua/routing.lua
index 4bce185..0ef2bd5 100644
--- a/api-gateway-config/scripts/lua/routing.lua
+++ b/api-gateway-config/scripts/lua/routing.lua
@@ -51,6 +51,7 @@
k = string.upper(k)
if k == verb then
-- Check if auth is required
+ local subHeader
if (v.security and string.lower(v.security.type) == 'apikey') then
local h = v.security.header
if h == nil then
@@ -58,7 +59,8 @@
else
h = utils.concatStrings({'http_', h})
end
- security.processAPIKey(h:gsub("-", "_"))
+ subHeader = h:gsub("-", "_")
+ security.processAPIKey(subHeader)
end
local u = url.parse(v.backendUrl)
ngx.req.set_uri(u.path)
@@ -67,7 +69,7 @@
setVerb(v.backendMethod)
end
if v.policies ~= nil then
- parsePolicies(v.policies)
+ parsePolicies(v.policies, subHeader)
end
found = true
break
@@ -80,13 +82,14 @@
end
--- Function to read the list of policies and send implementation to the correct backend
--- @param obj List of policies containing a type and value field. This function reads the type field and resources it appropriately.
-function parsePolicies(obj)
+-- @param obj List of policies containing a type and value field. This function reads the type field and routes it appropriately.
+-- @param subHeader optional subscription header
+function parsePolicies(obj, subHeader)
for k, v in pairs (obj) do
if v.type == 'reqMapping' then
mapping.processMap(v.value)
elseif v.type == 'rateLimit' then
- rateLimit.limit(v.value)
+ rateLimit.limit(v.value, subHeader)
end
end
end