blob: 132c58a21e0a721f89650e19467373f82ec8bf5c [file] [log] [blame]
--
-- 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"
local logger = require "lib/logger"
--- 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
-- 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