-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
clock skew check
- Loading branch information
Shashi Ranjan
committed
Sep 18, 2015
1 parent
b1e752d
commit e1018b7
Showing
13 changed files
with
578 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
local cache = require "kong.tools.database_cache" | ||
local stringy = require "stringy" | ||
local responses = require "kong.tools.responses" | ||
local constants = require "kong.constants" | ||
|
||
local math_abs = math.abs | ||
local ngx_time = ngx.time | ||
local ngx_gmatch = ngx.re.gmatch | ||
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 AUTHORIZATION = "authorization" | ||
local PROXY_AUTHORIZATION = "proxy-authorization" | ||
local DATE = "date" | ||
local SIGNATURE_NOT_VALID = "HMAC signature cannot be verified" | ||
|
||
local _M = {} | ||
|
||
local function retrieve_hmac_fields(request, header_name, conf) | ||
local username, signature, algorithm | ||
local authorization_header = request.get_headers()[header_name] | ||
if authorization_header then | ||
-- Authentication: hmac username:base64(hmac-sha1(Date)) | ||
local iterator, iter_err = ngx_gmatch(authorization_header, "\\s*[Hh]mac\\s*(.+)") | ||
if not iterator then | ||
ngx.log(ngx.ERR, iter_err) | ||
return | ||
end | ||
|
||
local m, err = iterator() | ||
if err then | ||
ngx.log(ngx.ERR, err) | ||
return | ||
end | ||
|
||
if m and table.getn(m) > 0 then | ||
local hmac_fields = stringy.split(m[1], ":") | ||
if hmac_fields and #hmac_fields > 2 then | ||
username = hmac_fields[1] | ||
signature = ngx_decode_base64(hmac_fields[2]) | ||
algorithm = hmac_fields[3] | ||
end | ||
end | ||
end | ||
|
||
if conf.hide_credentials then | ||
request.clear_header(header_name) | ||
end | ||
|
||
return username, signature, algorithm | ||
end | ||
|
||
local function validate_signature(request, secret, signature, algorithm, defaultClockSkew) | ||
-- validate clock skew | ||
local date = request.get_headers()[DATE] | ||
local requestTime = ngx_parse_time(date) | ||
if requestTime == nil then | ||
responses.send_HTTP_UNAUTHORIZED(SIGNATURE_NOT_VALID) | ||
end | ||
|
||
local skew = math_abs(ngx_time() - requestTime) | ||
if skew > defaultClockSkew then | ||
responses.send_HTTP_UNAUTHORIZED("HMAC signature expired") | ||
end | ||
|
||
-- validate signature | ||
local digest = ngx_sha1(secret.secret, date) | ||
if digest then | ||
return digest == signature | ||
end | ||
end | ||
|
||
local function hmacauth_credential_key(username) | ||
return "hmacauth_credentials/"..username | ||
end | ||
|
||
local function load_secret(username) | ||
local secret | ||
if username then | ||
secret = cache.get_or_set(hmacauth_credential_key(username), function() | ||
local keys, err = dao.hmacauth_credentials:find_by_keys { username = username } | ||
local result | ||
if err then | ||
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) | ||
elseif #keys > 0 then | ||
result = keys[1] | ||
end | ||
return result | ||
end) | ||
end | ||
return secret | ||
end | ||
|
||
function _M.execute(conf) | ||
-- If both headers are missing, return 401 | ||
if not (ngx.req.get_headers()[AUTHORIZATION] or ngx.req.get_headers()[PROXY_AUTHORIZATION]) then | ||
ngx.ctx.stop_phases = true | ||
return responses.send_HTTP_UNAUTHORIZED() | ||
end | ||
|
||
local username, signature, algorithm = retrieve_hmac_fields(ngx.req, PROXY_AUTHORIZATION, conf) | ||
-- Try with the authorization header | ||
if not username then | ||
username, signature, algorithm = retrieve_hmac_fields(ngx.req, AUTHORIZATION, conf) | ||
end | ||
|
||
if not (username and signature) then | ||
responses.send_HTTP_FORBIDDEN(SIGNATURE_NOT_VALID) | ||
end | ||
|
||
local secret = load_secret(username) | ||
if not validate_signature(ngx.req, secret, signature, algorithm, conf.clock_skew) then | ||
ngx.ctx.stop_phases = true -- interrupt other phases of this request | ||
return responses.send_HTTP_FORBIDDEN("HMAC signature does not match") | ||
end | ||
|
||
-- Retrieve consumer | ||
local consumer = cache.get_or_set(cache.consumer_key(secret.consumer_id), function() | ||
local result, err = dao.consumers:find_by_primary_key({ id = secret.consumer_id }) | ||
if err then | ||
return responses.send_HTTP_INTERNAL_SERVER_ERROR(err) | ||
end | ||
return result | ||
end) | ||
|
||
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_entity = secret | ||
end | ||
|
||
return _M |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
local crud = require "kong.api.crud_helpers" | ||
|
||
return{ | ||
["/consumers/:username_or_id/hmac-auth/"] = { | ||
before = function(self, dao_factory, helpers) | ||
crud.find_consumer_by_username_or_id(self, dao_factory, helpers) | ||
self.params.consumer_id = self.consumer.id | ||
end, | ||
|
||
GET = function(self, dao_factory, helpers) | ||
crud.paginated_set(self, dao_factory.hmacauth_credentials) | ||
end, | ||
|
||
PUT = function(self, dao_factory) | ||
crud.put(self.params, dao_factory.hmacauth_credentials) | ||
end, | ||
|
||
POST = function(self, dao_factory) | ||
crud.post(self.params, dao_factory.hmacauth_credentials) | ||
end | ||
}, | ||
|
||
["/consumers/:username_or_id/hmac-auth/:id"] = { | ||
before = function(self, dao_factory, helpers) | ||
crud.find_consumer_by_username_or_id(self, dao_factory, helpers) | ||
self.params.consumer_id = self.consumer.id | ||
|
||
local data, err = dao_factory.hmacauth_credentials:find_by_keys({ id = self.params.id }) | ||
if err then | ||
return helpers.yield_error(err) | ||
end | ||
|
||
self.credential = data[1] | ||
if not self.credential then | ||
return helpers.responses.send_HTTP_NOT_FOUND() | ||
end | ||
end, | ||
|
||
GET = function(self, dao_factory, helpers) | ||
return helpers.responses.send_HTTP_OK(self.credential) | ||
end, | ||
|
||
PATCH = function(self, dao_factory) | ||
crud.patch(self.params, self.credential, dao_factory.hmacauth_credentials) | ||
end, | ||
|
||
DELETE = function(self, dao_factory) | ||
crud.delete(self.credential, dao_factory.hmacauth_credentials) | ||
end | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
local BaseDao = require "kong.dao.cassandra.base_dao" | ||
|
||
local SCHEMA = { | ||
primary_key = {"id"}, | ||
fields = { | ||
id = { type = "id", dao_insert_value = true }, | ||
created_at = { type = "timestamp", dao_insert_value = true }, | ||
consumer_id = { type = "id", required = true, queryable = true, foreign = "consumers:id" }, | ||
username = { type = "string", required = true, unique = true, queryable = true }, | ||
secret = { type = "string" } | ||
} | ||
} | ||
|
||
local HMACAuthCredentials = BaseDao:extend() | ||
|
||
function HMACAuthCredentials:new(properties) | ||
self._table = "hmacauth_credentials" | ||
self._schema = SCHEMA | ||
HMACAuthCredentials.super.new(self, properties) | ||
end | ||
|
||
return { hmacauth_credentials = HMACAuthCredentials } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
-- Copyright (C) Mashape, Inc. | ||
|
||
local BasePlugin = require "kong.plugins.base_plugin" | ||
local access = require "kong.plugins.hmac-auth.access" | ||
|
||
local HMACAuthHandler = BasePlugin:extend() | ||
|
||
function HMACAuthHandler:new() | ||
HMACAuthHandler.super.new(self, "hmac-auth") | ||
end | ||
|
||
function HMACAuthHandler:access(conf) | ||
HMACAuthHandler.super.access(self) | ||
access.execute(conf) | ||
end | ||
|
||
HMACAuthHandler.PRIORITY = 999 | ||
|
||
return HMACAuthHandler |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
local Migrations = { | ||
{ | ||
name = "2015-09-16-132400_init_hmacauth", | ||
up = function(options) | ||
return [[ | ||
CREATE TABLE IF NOT EXISTS hmacauth_credentials( | ||
id uuid, | ||
consumer_id uuid, | ||
username text, | ||
secret text, | ||
created_at timestamp, | ||
PRIMARY KEY (id) | ||
); | ||
CREATE INDEX IF NOT EXISTS ON hmacauth_credentials(username); | ||
CREATE INDEX IF NOT EXISTS hmacauth_consumer_id ON hmacauth_credentials(consumer_id); | ||
]] | ||
end, | ||
down = function(options) | ||
return [[ | ||
DROP TABLE hmacauth_credentials; | ||
]] | ||
end | ||
} | ||
} | ||
|
||
return Migrations |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
local function check_clock_skew_positive(v) | ||
if v and v < 0 then | ||
return false, "Clock Skew should be positive" | ||
end | ||
return true | ||
end | ||
|
||
return { | ||
no_consumer = true, | ||
fields = { | ||
hide_credentials = { type = "boolean", default = false }, | ||
clock_skew = { type = "number", default = 300, func = check_clock_skew_positive } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @shashiranjan84 thanks for this HMAC plugin, it looks promising. Is there any documentation on how to configure this?
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ejoncas if you really want to try now,
Add an api
curl -i -X POST --url http://localhost:8001/apis/ --data "name=mockbin" --data "upstream_url=http://mockbin.org" --data "inbound_dns=mockbin.com"
Add hmac-auth plugin
curl -i -X POST --url http://localhost:8001/apis/mockbin/plugins/ --data "name=hmac-auth"
Add a customer
curl -i -X POST --url http://localhost:8001/consumers/ --data "username=bob"
Add hmac credentials
curl -i -X POST --url http://localhost:8001/consumers/bob/hmac-auth --data "username=bob" --data "secret=secret"
Make request
set the authorization header in following format
"hamc username:base64(hmac-sha1-raw(date header value in GMT, secret)):hmac-sha1"
curl -i -X GET --url http://localhost:8000/request --header "Host: mockbin.com" --header "date: Mon, 21 Sep 2015 05:13:00 GMT" --header "Proxy-Authorization: hmac bob:Oq1Aj0GnNT0BD3sbH9oFX390t5E=:hmac-sha1"
To update the clock skew
curl-i -X PATCH --url http://localhost:8001/apis/mockbin/plugins/<plugin uuid> --data "config.clock_skew=10000
Note: Checkout the master code(0.5.0 snapshot)
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shashiranjan84 Thanks a lot! That's exactly what I needed
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ejoncas I just finished adding support for other headers and request line. I have not merged it but in case you want to try https://github.com/shashiranjan84/kong(not yet merged to release branch)
Now its based on https://tools.ietf.org/html/draft-cavage-http-signatures-00, so there is slight change in format.
here is the link to the test to get some idea https://github.com/shashiranjan84/kong/blob/master/spec/plugins/hmac-auth/access_spec.lua#L176
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @shashiranjan84 thanks for the update.... I'm trying to write a new plugin because our HMAC Auth doesn't follow the standard so the string to sign is a little bit different but your plugin helped me a lot to understand what I need to do. Thansk a lot!
e1018b7
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I glad that it helped you :)