App (#237)
* jwk validation
* appid
* move introspection into c
* move c code into a module
diff --git a/Dockerfile b/Dockerfile
index a30f2f2..6ac7126 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -148,11 +148,12 @@
RUN opm get openresty/lua-resty-string=${LUA_RESTY_STRING_VERSION}
ENV LUA_RESTY_LRUCACHE_VERSION 0.04
RUN opm get openresty/lua-resty-lrucache=${LUA_RESTY_LRUCACHE_VERSION}
-ENV LUA_RESTY_JWT_VERSION 0.1.10
-RUN opm get SkyLothar/lua-resty-jwt=${LUA_RESTY_JWT_VERSION}
-ENV NETURL_LUA_VERSION 0.9-1
+ENV LUA_RESTY_CJOSE_VERSION 0.3
+RUN opm get taylorking/lua-resty-cjose=${LUA_RESTY_CJOSE_VERSION}
RUN opm get taylorking/lua-resty-rate-limit
+
+ENV NETURL_LUA_VERSION 0.9-1
RUN echo " ... installing neturl.lua ... " \
&& mkdir -p /tmp/api-gateway \
&& curl -k -L https://github.com/golgote/neturl/archive/${NETURL_LUA_VERSION}.tar.gz -o /tmp/api-gateway/neturl.lua-${NETURL_LUA_VERSION}.tar.gz \
@@ -162,6 +163,18 @@
&& cp lib/net/url.lua ${LUA_LIB_DIR} \
&& rm -rf /tmp/api-gateway
+ENV CJOSE_VERSION 0.5.1
+RUN echo " ... installing cjose ... " \
+ && apk update && apk add automake autoconf git gcc make jansson jansson-dev \
+ && mkdir -p /tmp/api-gateway \
+ && curl -L -k https://github.com/cisco/cjose/archive/${CJOSE_VERSION}.tar.gz -o /tmp/api-gateway/cjose-${CJOSE_VERSION}.tar.gz \
+ && tar -xf /tmp/api-gateway/cjose-${CJOSE_VERSION}.tar.gz -C /tmp/api-gateway/ \
+ && cd /tmp/api-gateway/cjose-${CJOSE_VERSION} \
+ && sh configure \
+ && make && make install
+RUN mkdir -p /tmp/api-gateway
+
+
RUN \
curl -L -k -s -o /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 \
&& apk update \
@@ -176,7 +189,6 @@
COPY init.sh /etc/init-container.sh
ONBUILD COPY init.sh /etc/init-container.sh
-
# add the default configuration for the Gateway
COPY . /etc/api-gateway
RUN adduser -S nginx-api-gateway \
@@ -185,6 +197,7 @@
EXPOSE 80 8080 8423 9000
+ENV LD_LIBRARY_PATH /usr/local/lib
ENTRYPOINT ["/usr/local/bin/dumb-init", "--"]
CMD ["/etc/init-container.sh"]
diff --git a/Makefile b/Makefile
index 209eea1..ee90289 100644
--- a/Makefile
+++ b/Makefile
@@ -44,6 +44,8 @@
-e TOKEN_GOOGLE_URL=https://www.googleapis.com/oauth2/v3/tokeninfo \
-e TOKEN_FACEBOOK_URL=https://graph.facebook.com/debug_token \
-e TOKEN_GITHUB_URL=https://api.github.com/user \
+ -e APPID_PKURL=https://appid-oauth.ng.bluemix.net/oauth/v3/ \
+ -e LD_LIBRARY_PATH=/usr/local/lib \
openwhisk/apigateway:latest
.PHONY: docker-debug
diff --git a/api-gateway.conf b/api-gateway.conf
index 3aea095..ecf7b22 100644
--- a/api-gateway.conf
+++ b/api-gateway.conf
@@ -35,6 +35,8 @@
env PORT;
env OPTIMIZE;
+env APPID_PKURL;
+
env REDIS_RETRY_COUNT;
env SNAPSHOTTING;
env CACHING_ENABLED;
diff --git a/scripts/lua/oauth/appid.lua b/scripts/lua/oauth/appid.lua
new file mode 100644
index 0000000..b400df4
--- /dev/null
+++ b/scripts/lua/oauth/appid.lua
@@ -0,0 +1,51 @@
+local request = require 'lib/request'
+local cjson = require 'cjson'
+local utils = require 'lib/utils'
+local APPID_PKURL = os.getenv("APPID_PKURL")
+local _M = {}
+local http = require 'resty.http'
+local cjose = require 'resty.cjose'
+
+function _M.process(dataStore, token, securityObj)
+ local result = dataStore:getOAuthToken('appId', token)
+ local httpc = http.new()
+ local json_resp
+ if result ~= ngx.null then
+ json_resp = cjson.decode(result)
+ ngx.header['X-OIDC-Email'] = json_resp['email']
+ ngx.header['X-OIDC-Sub'] = json_resp['sub']
+ return json_resp
+ end
+ local keyUrl = utils.concatStrings({APPID_PKURL, securityObj.tenantId, '/publickeys'})
+ local request_options = {
+ headers = {
+ ["Accept"] = "application/json"
+ },
+ ssl_verify = false
+ }
+ local res, err = httpc:request_uri(keyUrl, request_options)
+ if err then
+ request.err(500, 'error getting app id key: ' .. err)
+ end
+
+ local key
+ local keys = cjson.decode(res.body).keys
+ for _, v in ipairs(keys) do
+ key = v
+ end
+ local result = cjose.validateJWS(token, cjson.encode(key))
+ if not result then
+ request.err(401, 'AppId key signature verification failed.')
+ return nil
+ end
+ jwt_obj = cjson.decode(cjose.getJWSInfo(token))
+ ngx.header['X-OIDC-Email'] = jwt_obj['email']
+ ngx.header['X-OIDC-Sub'] = jwt_obj['sub']
+ dataStore:saveOAuthToken('appId', token, cjson.encode(jwt_obj), jwt_obj['exp'])
+ return jwt_obj
+end
+
+
+return _M
+
+
diff --git a/scripts/lua/policies/security/oauth2.lua b/scripts/lua/policies/security/oauth2.lua
index 778e7b4..be3ef2a 100644
--- a/scripts/lua/policies/security/oauth2.lua
+++ b/scripts/lua/policies/security/oauth2.lua
@@ -48,7 +48,7 @@
end
accessToken = string.gsub(accessToken, '^Bearer%s', '')
- local token = exchange(dataStore, accessToken, securityObj.provider)
+ local token = exchange(dataStore, accessToken, securityObj.provider, securityObj)
if token == nil then
request.err(401, 'Token didn\'t work or provider doesn\'t support OpenID connect. ')
return nil
@@ -67,7 +67,7 @@
-- @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)
+function exchange(dataStore, token, provider, securityObj)
-- exchange tokens with the provider
local loaded, impl = pcall(require, utils.concatStrings({'oauth/', provider}))
if not loaded then
@@ -76,7 +76,7 @@
return nil
end
- local result = impl.process(dataStore, token)
+ 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