Skip to content

Commit

Permalink
feat(plugins) anonymous authentication in auth plugins (#1666)
Browse files Browse the repository at this point in the history
  • Loading branch information
subnetmarco authored Nov 19, 2016
1 parent bbb62cd commit f231623
Show file tree
Hide file tree
Showing 21 changed files with 453 additions and 86 deletions.
3 changes: 2 additions & 1 deletion kong/constants.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ return {
RATELIMIT_REMAINING = "X-RateLimit-Remaining",
CONSUMER_GROUPS = "X-Consumer-Groups",
FORWARDED_HOST = "X-Forwarded-Host",
FORWARDED_PREFIX = "X-Forwarded-Prefix"
FORWARDED_PREFIX = "X-Forwarded-Prefix",
ANONYMOUS = "X-Anonymous-Consumer"
},
RATELIMIT = {
PERIODS = {
Expand Down
34 changes: 26 additions & 8 deletions kong/plugins/basic-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ local singletons = require "kong.singletons"
local constants = require "kong.constants"
local responses = require "kong.tools.responses"

local ngx_set_header = ngx.req.set_header
local ngx_get_headers = ngx.req.get_headers

local realm = 'Basic realm="'.._KONG._NAME..'"'

local _M = {}
Expand Down Expand Up @@ -81,11 +84,12 @@ local function load_credential_from_db(username)
return credential
end

function _M.execute(conf)
local function do_authentication(conf)
-- If both headers are missing, return 401
if not (ngx.req.get_headers()["authorization"] or ngx.req.get_headers()["proxy-authorization"]) then
local headers = ngx_get_headers()
if not (headers["authorization"] or headers["proxy-authorization"]) then
ngx.header["WWW-Authenticate"] = realm
return responses.send_HTTP_UNAUTHORIZED()
return false, {status = 401}
end

local credential
Expand All @@ -101,7 +105,7 @@ function _M.execute(conf)
end

if not credential or not validate_credentials(credential, given_password) then
return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
return false, {status = 403, message = "Invalid authentication credentials"}
end

-- Retrieve consumer
Expand All @@ -113,12 +117,26 @@ function _M.execute(conf)
return result
end)

ngx.req.set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx.req.set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx.req.set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx.req.set_header(constants.HEADERS.CREDENTIAL_USERNAME, credential.username)
ngx_set_header(constants.HEADERS.ANONYMOUS, nil) -- In case of auth plugins concatenation
ngx_set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx_set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx_set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx_set_header(constants.HEADERS.CREDENTIAL_USERNAME, credential.username)
ngx.ctx.authenticated_credential = credential
ngx.ctx.authenticated_consumer = consumer

return true
end

function _M.execute(conf)
local ok, err = do_authentication(conf)
if not ok then
if conf.anonymous then
ngx_set_header(constants.HEADERS.ANONYMOUS, true)
else
return responses.send(err.status, err.message, err.headers)
end
end
end

return _M
1 change: 1 addition & 0 deletions kong/plugins/basic-auth/schema.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
return {
no_consumer = true,
fields = {
anonymous = {type = "boolean", default = false},
hide_credentials = {type = "boolean", default = false}
}
}
32 changes: 23 additions & 9 deletions kong/plugins/hmac-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ local ngx_decode_base64 = ngx.decode_base64
local ngx_parse_time = ngx.parse_http_time
local ngx_sha1 = ngx.hmac_sha1
local ngx_set_header = ngx.req.set_header
local ngx_set_headers = ngx.req.get_headers
local ngx_get_headers = ngx.req.get_headers
local ngx_log = ngx.log

local split = utils.split
Expand Down Expand Up @@ -138,16 +138,16 @@ local function validate_clock_skew(headers, date_header_name, allowed_clock_skew
return true
end

function _M.execute(conf)
local headers = ngx_set_headers();
local function do_authentication(conf)
local headers = ngx_get_headers()
-- If both headers are missing, return 401
if not (headers[AUTHORIZATION] or headers[PROXY_AUTHORIZATION]) then
return responses.send_HTTP_UNAUTHORIZED()
return false, {status = 401}
end

-- validate clock skew
if not (validate_clock_skew(headers, X_DATE, conf.clock_skew) or validate_clock_skew(headers, DATE, conf.clock_skew)) then
responses.send_HTTP_FORBIDDEN("HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication")
return false, {status = 403, message = "HMAC signature cannot be verified, a valid date or x-date header is required for HMAC Authentication"}
end

-- retrieve hmac parameter from Proxy-Authorization header
Expand All @@ -157,17 +157,17 @@ function _M.execute(conf)
hmac_params = retrieve_hmac_fields(ngx.req, headers, AUTHORIZATION, conf)
end
if not (hmac_params.username and hmac_params.signature) then
responses.send_HTTP_FORBIDDEN(SIGNATURE_NOT_VALID)
return false, {status = 403, message = SIGNATURE_NOT_VALID}
end

-- validate signature
local credential = load_credential(hmac_params.username)
if not credential then
responses.send_HTTP_FORBIDDEN(SIGNATURE_NOT_VALID)
return false, {status = 403, message = SIGNATURE_NOT_VALID}
end
hmac_params.secret = credential.secret
if not validate_signature(ngx.req, hmac_params, headers) then
return responses.send_HTTP_FORBIDDEN("HMAC signature does not match")
return false, {status = 403, message = "HMAC signature does not match"}
end

-- Retrieve consumer
Expand All @@ -179,12 +179,26 @@ function _M.execute(conf)
return result
end)

ngx_set_header(constants.HEADERS.ANONYMOUS, nil) -- In case of auth plugins concatenation
ngx_set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx_set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx_set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx.req.set_header(constants.HEADERS.CREDENTIAL_USERNAME, credential.username)
ngx_set_header(constants.HEADERS.CREDENTIAL_USERNAME, credential.username)
ngx.ctx.authenticated_credential = credential
ngx.ctx.authenticated_consumer = consumer

return true
end

function _M.execute(conf)
local ok, err = do_authentication(conf)
if not ok then
if conf.anonymous then
ngx.req.set_header(constants.HEADERS.ANONYMOUS, true)
else
return responses.send(err.status, err.message, err.headers)
end
end
end

return _M
3 changes: 2 additions & 1 deletion kong/plugins/hmac-auth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ return {
no_consumer = true,
fields = {
hide_credentials = { type = "boolean", default = false },
clock_skew = { type = "number", default = 300, func = check_clock_skew_positive }
clock_skew = { type = "number", default = 300, func = check_clock_skew_positive },
anonymous = {type = "boolean", default = false}
}
}
48 changes: 32 additions & 16 deletions kong/plugins/jwt/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ local jwt_decoder = require "kong.plugins.jwt.jwt_parser"
local string_format = string.format
local ngx_re_gmatch = ngx.re.gmatch

local ngx_set_header = ngx.req.set_header

local JwtHandler = BasePlugin:extend()

Expand Down Expand Up @@ -49,8 +50,7 @@ function JwtHandler:new()
JwtHandler.super.new(self, "jwt")
end

function JwtHandler:access(conf)
JwtHandler.super.access(self)
local function do_authentication(conf)
local token, err = retrieve_token(ngx.req, conf)
if err then
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err)
Expand All @@ -59,25 +59,25 @@ function JwtHandler:access(conf)
local ttype = type(token)
if ttype ~= "string" then
if ttype == "nil" then
return responses.send_HTTP_UNAUTHORIZED()
return false, {status = 401}
elseif ttype == "table" then
return responses.send_HTTP_UNAUTHORIZED("Multiple tokens provided")
return false, {status = 401, message = "Multiple tokens provided"}
else
return responses.send_HTTP_UNAUTHORIZED("Unrecognizable token")
return false, {status = 401, message = "Unrecognizable token"}
end
end

-- Decode token to find out who the consumer is
local jwt, err = jwt_decoder:new(token)
if err then
return responses.send_HTTP_UNAUTHORIZED("Bad token; "..tostring(err))
return false, {status = 401, message = "Bad token; "..tostring(err)}
end

local claims = jwt.claims

local jwt_secret_key = claims[conf.key_claim_name]
if not jwt_secret_key then
return responses.send_HTTP_UNAUTHORIZED("No mandatory '"..conf.key_claim_name.."' in claims")
return false, {status = 401, message = "No mandatory '"..conf.key_claim_name.."' in claims"}
end

-- Retrieve the secret
Expand All @@ -91,14 +91,14 @@ function JwtHandler:access(conf)
end)

if not jwt_secret then
return responses.send_HTTP_FORBIDDEN("No credentials found for given '"..conf.key_claim_name.."'")
return false, {status = 403, message = "No credentials found for given '"..conf.key_claim_name.."'"}
end

local algorithm = jwt_secret.algorithm or "HS256"

-- Verify "alg"
if jwt.header.alg ~= algorithm then
return responses.send_HTTP_FORBIDDEN("Invalid algorithm")
return false, {status = 403, message = "Invalid algorithm"}
end

local jwt_secret_value = algorithm == "HS256" and jwt_secret.secret or jwt_secret.rsa_public_key
Expand All @@ -107,18 +107,18 @@ function JwtHandler:access(conf)
end

if not jwt_secret_value then
return responses.send_HTTP_FORBIDDEN("Invalid key/secret")
return false, {status = 403, message = "Invalid key/secret"}
end

-- Now verify the JWT signature
if not jwt:verify_signature(jwt_secret_value) then
return responses.send_HTTP_FORBIDDEN("Invalid signature")
return false, {status = 403, message = "Invalid signature"}
end

-- Verify the JWT registered claims
local ok_claims, errors = jwt:verify_registered_claims(conf.claims_to_verify)
if not ok_claims then
return responses.send_HTTP_FORBIDDEN(errors)
return false, {status = 403, message = errors}
end

-- Retrieve the consumer
Expand All @@ -132,14 +132,30 @@ function JwtHandler:access(conf)

-- However this should not happen
if not consumer then
return responses.send_HTTP_FORBIDDEN(string_format("Could not find consumer for '%s=%s'", conf.key_claim_name, jwt_secret_key))
return false, {status = 403, message = string_format("Could not find consumer for '%s=%s'", conf.key_claim_name, jwt_secret_key)}
end

ngx.req.set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx.req.set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx.req.set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx_set_header(constants.HEADERS.ANONYMOUS, nil) -- In case of auth plugins concatenation
ngx_set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx_set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx_set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx.ctx.authenticated_credential = jwt_secret
ngx.ctx.authenticated_consumer = consumer

return true
end

function JwtHandler:access(conf)
JwtHandler.super.access(self)

local ok, err = do_authentication(conf)
if not ok then
if conf.anonymous then
ngx_set_header(constants.HEADERS.ANONYMOUS, true)
else
return responses.send(err.status, err.message, err.headers)
end
end
end

return JwtHandler
3 changes: 2 additions & 1 deletion kong/plugins/jwt/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ return {
uri_param_names = {type = "array", default = {"jwt"}},
key_claim_name = {type = "string", default = "iss"},
secret_is_base64 = {type = "boolean", default = false},
claims_to_verify = {type = "array", enum = {"exp", "nbf"}}
claims_to_verify = {type = "array", enum = {"exp", "nbf"}},
anonymous = {type = "boolean", default = false}
}
}
42 changes: 28 additions & 14 deletions kong/plugins/key-auth/handler.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ local constants = require "kong.constants"
local singletons = require "kong.singletons"
local BasePlugin = require "kong.plugins.base_plugin"

local set_header = ngx.req.set_header
local get_headers = ngx.req.get_headers
local ngx_set_header = ngx.req.set_header
local ngx_get_headers = ngx.req.get_headers
local set_uri_args = ngx.req.set_uri_args
local get_uri_args = ngx.req.get_uri_args
local clear_header = ngx.req.clear_header
Expand All @@ -21,16 +21,14 @@ function KeyAuthHandler:new()
KeyAuthHandler.super.new(self, "key-auth")
end

function KeyAuthHandler:access(conf)
KeyAuthHandler.super.access(self)

local function do_authentication(conf)
if type(conf.key_names) ~= "table" then
ngx.log(ngx.ERR, "[key-auth] no conf.key_names set, aborting plugin execution")
return
return false, {status = 500, message= "Invalid plugin configuration"}
end

local key
local headers = get_headers()
local headers = ngx_get_headers()
local uri_args = get_uri_args()

-- search in headers & querystring
Expand All @@ -52,15 +50,15 @@ function KeyAuthHandler:access(conf)
break
elseif type(v) == "table" then
-- duplicate API key, HTTP 401
return responses.send_HTTP_UNAUTHORIZED("Duplicate API key found")
return false, {status = 401, message = "Duplicate API key found"}
end
end

-- this request is missing an API key, HTTP 401
if not key then
ngx.header["WWW-Authenticate"] = _realm
return responses.send_HTTP_UNAUTHORIZED("No API key found in headers"
.." or querystring")
return false, {status = 401, message = "No API key found in headers"
.." or querystring"}
end

-- retrieve our consumer linked to this API key
Expand All @@ -77,7 +75,7 @@ function KeyAuthHandler:access(conf)

-- no credential in DB, for this key, it is invalid, HTTP 403
if not credential then
return responses.send_HTTP_FORBIDDEN("Invalid authentication credentials")
return false, {status = 403, message = "Invalid authentication credentials"}
end

-----------------------------------------
Expand All @@ -95,11 +93,27 @@ function KeyAuthHandler:access(conf)
return row
end)

set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx_set_header(constants.HEADERS.ANONYMOUS, nil) -- In case of auth plugins concatenation
ngx_set_header(constants.HEADERS.CONSUMER_ID, consumer.id)
ngx_set_header(constants.HEADERS.CONSUMER_CUSTOM_ID, consumer.custom_id)
ngx_set_header(constants.HEADERS.CONSUMER_USERNAME, consumer.username)
ngx.ctx.authenticated_credential = credential
ngx.ctx.authenticated_consumer = consumer

return true
end

function KeyAuthHandler:access(conf)
KeyAuthHandler.super.access(self)

local ok, err = do_authentication(conf)
if not ok then
if conf.anonymous then
ngx_set_header(constants.HEADERS.ANONYMOUS, true)
else
return responses.send(err.status, err.message, err.headers)
end
end
end

return KeyAuthHandler
Loading

0 comments on commit f231623

Please sign in to comment.