-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Implements request body validation inside HmacAuth #2419
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,23 +3,28 @@ local cache = require "kong.tools.database_cache" | |
local responses = require "kong.tools.responses" | ||
local constants = require "kong.constants" | ||
local singletons = require "kong.singletons" | ||
local resty_sha256 = require "resty.sha256" | ||
|
||
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_encode_base64 = ngx.encode_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_get_headers = ngx.req.get_headers | ||
local ngx_log = ngx.log | ||
local req_read_body = ngx.req.read_body | ||
local req_get_body_data = ngx.req.get_body_data | ||
|
||
local split = utils.split | ||
|
||
local AUTHORIZATION = "authorization" | ||
local PROXY_AUTHORIZATION = "proxy-authorization" | ||
local DATE = "date" | ||
local X_DATE = "x-date" | ||
local DIGEST = "digest" | ||
local SIGNATURE_NOT_VALID = "HMAC signature cannot be verified" | ||
|
||
local _M = {} | ||
|
@@ -83,10 +88,10 @@ local function create_hash(request, hmac_params, headers) | |
end | ||
|
||
local function validate_signature(request, hmac_params, headers) | ||
local digest = create_hash(request, hmac_params, headers) | ||
local sig = ngx_decode_base64(hmac_params.signature) | ||
local signature_1 = create_hash(request, hmac_params, headers) | ||
local signature_2 = ngx_decode_base64(hmac_params.signature) | ||
|
||
return digest == sig | ||
return signature_1 == signature_2 | ||
end | ||
|
||
local function load_credential_into_memory(username) | ||
|
@@ -129,6 +134,22 @@ local function validate_clock_skew(headers, date_header_name, allowed_clock_skew | |
return true | ||
end | ||
|
||
local function validate_request_body(headers, body) | ||
local sha_recieved = headers[DIGEST] | ||
|
||
if body == nil then | ||
return true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. style, needs a blank line after return. not a blocker, we can adjust this as part of merge |
||
elseif sha_recieved == nil then | ||
return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaibhavatul47 here your logic forcing client to send
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I ll update the code and tests There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaibhavatul47 here is what I am thinking to change it to
I will also work on adding support for other hashing algorithm, at least SHA1 and SHA-256 for both Digest and HTTP signature validation. May be a new PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shashiranjan84 if validate_request_body is enabled then forcing client to send digest header is important. If this is not mandated then someone who can change request body would also delete digest header and then this corrupted request would be validated by plugin as it didn't contain any digest header. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @vaibhavatul47 Not really, If signature is created using digest, it would not pass validation as it is tampered. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
end | ||
|
||
local sha256 = resty_sha256:new() | ||
sha256:update(body) | ||
local sha_created = "SHA-256=" .. ngx_encode_base64(sha256:final()) | ||
|
||
return sha_created == sha_recieved | ||
end | ||
|
||
local function load_consumer_into_memory(consumer_id, anonymous) | ||
local result, err = singletons.dao.consumers:find { id = consumer_id } | ||
if not result then | ||
|
@@ -177,6 +198,16 @@ local function do_authentication(conf) | |
return false, {status = 403, message = SIGNATURE_NOT_VALID} | ||
end | ||
|
||
-- If request body validation is enabled, then verify hash of request body. | ||
if conf.validate_request_body then | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also we should validate headers first and then body. As if header is tampered there is no point validating body. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @shashiranjan84 I'll update code according to this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please don't, I already updated the code. Planning to merge it today There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok |
||
-- retrieve request body | ||
req_read_body() | ||
local body = req_get_body_data() | ||
if not validate_request_body(headers, body) then | ||
return false, {status = 403, message = "HMAC signature cannot be verified, a valid Sha-256 digest header is required for HMAC Authentication"} | ||
end | ||
end | ||
|
||
-- validate signature | ||
local credential = load_credential(hmac_params.username) | ||
if not credential then | ||
|
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.
The ietf draft indicates that a digest header may look something like:
digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n
It doesn't appear that the generate hash will match such a format, with the form of the hash prepended to the base64-encoded hash itself. Do we care about this case?
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 wrote my logic assuming Kong will always check the SHA-256 hash of request body.
As per ieft draft, the user can use any hash algorithm to generate the hash. If we allow this, then for each request, Kong would need to parse
Digest
header and then figure out which hashing algorithm to use. Then there could be cases where a user uses an algorithm not supported by Kong libraries.So for simplicity, I assumed only SHA-256.
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 can do one thing to abide by ieft draft.
At Kong, we can generate hash string prepended by the hash algorithm as recommended by draft, i.e.:
digest: SHA-256=X48E9qOokqqrvdts8nOJRJN3OWDUoyWxBf7kbu9DBPE=\n
Then we would need to mention it clearly that user needs to send digest header in this format and only
sha-256
algorithm is supported by Kong.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.
@vaibhavatul47 please also rebase to
next
.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.
@vaibhavatul47 i think this is a good approach, as it could allow us to support additional algorithms in the future.
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've fixed this.