scripts/lua/policies/security/clientSecret.lua (65 lines of code) (raw):

-- -- Licensed to the Apache Software Foundation (ASF) under one or more -- contributor license agreements. See the NOTICE file distributed with -- this work for additional information regarding copyright ownership. -- The ASF licenses this file to You under the Apache License, Version 2.0 -- (the "License"); you may not use this file except in compliance with -- the License. You may obtain a copy of the License at -- -- http://www.apache.org/licenses/LICENSE-2.0 -- -- Unless required by applicable law or agreed to in writing, software -- distributed under the License is distributed on an "AS IS" BASIS, -- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -- See the License for the specific language governing permissions and -- limitations under the License. -- --- @module clientSecret -- Check a subscription with a client id and a hashed secret local _M = {} local utils = require "lib/utils" local request = require "lib/request" --- 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 functionality, I use this function to do all of the business logic with injected dependencies -- 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 configuration for the tenant/resource/api we are verifying -- @param hashFunction the function used to perform the hash of the api secret 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 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 if location == "header" then 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 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 ngx.ctx.clientSecretName = clientSecretName:lower() if location == "header" then 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 request.err(401, clientSecretName .. " required") return false end -- 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.") end ngx.var.apiKey = clientId return result 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