Merge pull request #88 from taylorking/clientsecret
Support for client secret validation
diff --git a/Dockerfile b/Dockerfile
index 81fb111..18c7c26 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -152,6 +152,23 @@
&& $INSTALL lib/resty/*.lua ${LUA_LIB_DIR}/resty/ \
&& rm -rf /tmp/api-gateway
+
+
+ENV LUA_RESTY_STRING_VERSION 0.09
+RUN echo " ... installing lua-resty-string..." \
+ && apk update \
+ && apk add make \
+ && mkdir -p /tmp/api-gateway \
+ && curl -k -L https://github.com/openresty/lua-resty-string/archive/v${LUA_RESTY_STRING_VERSION}.tar.gz -o /tmp/api-gateway/lua-resty-string-${LUA_RESTY_STRING_VERSION}.tar.gz \
+ && tar -xf /tmp/api-gateway/lua-resty-string-${LUA_RESTY_STRING_VERSION}.tar.gz -C /tmp/api-gateway/ \
+ && cd /tmp/api-gateway/lua-resty-string-${LUA_RESTY_STRING_VERSION} \
+ && make install \
+ LUA_LIB_DIR=${_prefix}/api-gateway/lualib \
+ INSTALL=${_prefix}/api-gateway/bin/resty-install \
+ && rm -rf /tmp/api-gateway
+
+
+
ENV NETURL_LUA_VERSION 0.9-1
RUN echo " ... installing neturl.lua ... " \
&& mkdir -p /tmp/api-gateway \
diff --git a/api-gateway-config/scripts/lua/policies/security/clientSecret.lua b/api-gateway-config/scripts/lua/policies/security/clientSecret.lua
new file mode 100644
index 0000000..6151e71
--- /dev/null
+++ b/api-gateway-config/scripts/lua/policies/security/clientSecret.lua
@@ -0,0 +1,95 @@
+-- Copyright (c) 2016 IBM. All rights reserved.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a
+-- copy of this software and associated documentation files (the "Software"),
+-- to deal in the Software without restriction, including without limitation
+-- the rights to use, copy, modify, merge, publish, distribute, sublicense,
+-- and/or sell copies of the Software, and to permit persons to whom the
+-- Software is furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+-- DEALINGS IN THE SOFTWARE.
+
+--- @module mapping
+-- Process mapping object, turning implementation details into request transformations
+-- @author Cody Walker (cmwalker), Alex Song (songs)
+
+local _M = {}
+
+local redis = require "lib/redis"
+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 resty_sha256 = require "resty.sha256"
+local resty_str = require "resty.string"
+
+function process(securityObj)
+ local tenant = ngx.var.tenant
+ local gatewayPath = ngx.var.gatewayPath
+ local apiId = ngx.var.apiId
+ local scope = securityObj.scope
+
+ local location = (securityObj.keyLocation == nil) and 'http_' or securityObj.keyLocation
+ if location == 'header' then
+ location = 'http_'
+ end
+
+ local clientIdName = (securityObj.idFieldName == nil) and 'X-Client-ID' or securityObj.idFieldName
+
+ local clientId = ngx.var[utils.concatStrings({location, clientIdName}):gsub("-", "_")]
+
+ if not clientId then
+ request.err(401, clientIdName .. " required")
+ end
+
+ local clientSecretName = (securityObj.secretFieldName == nil) and 'X-Client-Secret' or securityObj.secretFieldName
+
+ local clientSecret = ngx.var[utils.concatStrings({location, clientSecretName}):gsub("-","_")]
+ if not clientSecret then
+ request.err(401, clientSecretName .. " required")
+ end
+
+ local sha256 = resty_sha256:new()
+ sha256:update(clientSecret)
+ local digest = sha256:final()
+ local result = validate(tenant, gatewayPath, apiId, scope, clientId, resty_str.to_hex(digest))
+ if not result then
+ request.err(401, "Secret mismatch or not subscribed to this api.")
+ end
+
+end
+
+function validate(tenant, gatewayPath, apiId, scope, clientId, clientSecret)
+ -- Open connection to redis or use one from connection pool
+ local red = redis.init(REDIS_HOST, REDIS_PORT, REDIS_PASS, 1000)
+ 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
+
+
+
+ k = utils.concatStrings({k, ':clientsecret:', clientId, ':', clientSecret})
+ local exists = red:exists(k)
+ redis.close(red)
+ return exists == 1
+end
+
+_M.process = process
+return _M
diff --git a/doc/policies.md b/doc/policies.md
index eaf6d30..b9494d2 100644
--- a/doc/policies.md
+++ b/doc/policies.md
@@ -109,10 +109,14 @@
##Security
-Supported types: `apiKey`.
+Supported types: `apiKey, clientSecret`.
_scope:_ `api`, `tenant`, `resource`.
_header:_ _(optional)_ custom name of auth header (default is x-api-key)
+_keyLocation:_ _(optional)_ custom location for client secret keys. header, query_string (default is header)
+_idFieldName:_ _(optional)_ key for locating client id. default (X-Client-ID)
+_secretFieldName:_ _(optional)_ key for locating client secret. default (X-Client-Secret)
+
```
"security":[{
"type":"apiKey",