Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(plugins) anonymous authentication in auth plugins #1666

Merged
merged 3 commits into from
Nov 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use trailing comma for table constructors, to keep the diff clean

same for the other plugins

},
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"}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not just return success, statuscode, message without creating the intermediate table? More efficient and less GC.

same for the other plugins

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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't we cache those constants locally? the above are 15 table lookups

same for the other plugins

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