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(hmac) multiple HMAC algorithms support and enforce_headers option #2644

Merged
merged 2 commits into from
Jun 23, 2017
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
78 changes: 75 additions & 3 deletions kong/plugins/hmac-auth/access.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ local utils = require "kong.tools.utils"
local responses = require "kong.tools.responses"
local constants = require "kong.constants"
local singletons = require "kong.singletons"
local crypto = require "crypto"
local resty_sha256 = require "resty.sha256"

local math_abs = math.abs
Expand All @@ -10,14 +11,16 @@ 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 ngx_hmac_sha1 = ngx.hmac_sha1

local split = utils.split
local fmt = string.format
local ipairs = ipairs

local AUTHORIZATION = "authorization"
local PROXY_AUTHORIZATION = "proxy-authorization"
Expand All @@ -27,8 +30,70 @@ local DIGEST = "digest"
local SIGNATURE_NOT_VALID = "HMAC signature cannot be verified"
local SIGNATURE_NOT_SAME = "HMAC signature does not match"

local new_tab
do
local ok
ok, new_tab = pcall(require, "table.new")
if not ok then
new_tab = function() return {} end
end
end

local _M = {}

local hmac = {
["hmac-sha1"] = function(secret, data)
return ngx_hmac_sha1(secret, data)
end,
["hmac-sha256"] = function(secret, data)
return crypto.hmac.digest("sha256", data, secret, true)
end,
["hmac-sha384"] = function(secret, data)
return crypto.hmac.digest("sha384", data, secret, true)
end,
["hmac-sha512"] = function(secret, data)
return crypto.hmac.digest("sha512", data, secret, true)
end
}

local function list_as_set(list)
local set = new_tab(0, #list)
for _, v in ipairs(list) do
set[v] = true
end

return set
end

local function validate_params(params, conf)
-- check username and signature are present
if not params.username and params.signature then
return nil, "username or signature missing"
end

-- check enforced headers are present
if conf.enforce_headers and #conf.enforce_headers >= 1 then
local enforced_header_set = list_as_set(conf.enforce_headers)
for _, header in ipairs(params.hmac_headers) do
enforced_header_set[header] = nil
end
for _, header in ipairs(conf.enforce_headers) do
if enforced_header_set[header] then
return nil, "enforced header not used for signature creation"
end
end
end

-- check supported alorithm used
for _, algo in ipairs(conf.algorithms) do
if algo == params.algorithm then
return true
end
end

return nil, fmt("algorithm %s not supported", params.algorithm)
end

local function retrieve_hmac_fields(request, headers, header_name, conf)
local hmac_params = {}
local authorization_header = headers[header_name]
Expand Down Expand Up @@ -84,7 +149,7 @@ local function create_hash(request, hmac_params, headers)
signing_string = signing_string .. "\n"
end
end
return ngx_sha1(hmac_params.secret, signing_string)
return hmac[hmac_params.algorithm](hmac_params.secret, signing_string)
end

local function validate_signature(request, hmac_params, headers)
Expand Down Expand Up @@ -196,26 +261,33 @@ local function do_authentication(conf)

-- retrieve hmac parameter from Proxy-Authorization header
local hmac_params = retrieve_hmac_fields(ngx.req, headers, PROXY_AUTHORIZATION, conf)

-- Try with the authorization header
if not hmac_params.username then
hmac_params = retrieve_hmac_fields(ngx.req, headers, AUTHORIZATION, conf)
end
if not (hmac_params.username and hmac_params.signature) then

local ok, err = validate_params(hmac_params, conf)
if not ok then
ngx_log(ngx.DEBUG, err)
return false, {status = 403, message = SIGNATURE_NOT_VALID}
end

-- validate signature
local credential = load_credential(hmac_params.username)
if not credential then
ngx_log(ngx.DEBUG, "failed to retrieve credential for ", hmac_params.username)
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 false, { status = 403, message = SIGNATURE_NOT_SAME }
end

-- If request body validation is enabled, then verify digest.
if conf.validate_request_body and not validate_body(headers[DIGEST]) then
ngx_log(ngx.DEBUG, "digest validation failed")
return false, { status = 403, message = SIGNATURE_NOT_SAME }
end

Expand Down
21 changes: 21 additions & 0 deletions kong/plugins/hmac-auth/migrations/cassandra.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,26 @@ return {
down = [[
DROP TABLE hmacauth_credentials;
]]
},
{
name = "2017-06-21-132400_init_hmacauth",
up = function(_, _, dao)
local rows, err = dao.plugins:find_all { name = "hmac-auth" }
if err then
return err
end

for _, row in ipairs(rows) do
row.config.validate_request_body = false
row.config.enforce_headers = {}
row.config.algorithms = { "hmac-sha1" }
local _, err = dao.plugins:update(row, row)
if err then
return err
end
end
end,
down = function()
end
}
}
21 changes: 21 additions & 0 deletions kong/plugins/hmac-auth/migrations/postgres.lua
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,26 @@ return {
down = [[
DROP TABLE hmacauth_credentials;
]]
},
{
name = "2017-06-21-132400_init_hmacauth",
up = function(_, _, dao)
local rows, err = dao.plugins:find_all { name = "hmac-auth" }
if err then
return err
end

for _, row in ipairs(rows) do
row.config.validate_request_body = false
row.config.enforce_headers = {}
row.config.algorithms = {"hmac-sha1"}
Copy link
Contributor

Choose a reason for hiding this comment

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

not quite sure i agree with this default assignment, as the schema default is to support all 4 algorithms.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is intentional, older version only has support for hmac-sha1, so after migration I defaulted it to {"hmac-sha1"}. If user want to support all 4, he would need to patch the plugin.

local _, err = dao.plugins:update(row, row)
if err then
return err
end
end
end,
down = function()
end
}
}
13 changes: 11 additions & 2 deletions kong/plugins/hmac-auth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ local function check_user(anonymous)
if anonymous == "" or utils.is_valid_uuid(anonymous) then
return true
end

return false, "the anonymous user must be empty or a valid uuid"
end

Expand All @@ -15,12 +15,21 @@ local function check_clock_skew_positive(v)
return true
end

local algorithms = {
"hmac-sha1",
"hmac-sha256",
"hmac-sha384",
"hmac-sha512",
}

return {
no_consumer = true,
fields = {
hide_credentials = { type = "boolean", default = false },
clock_skew = { type = "number", default = 300, func = check_clock_skew_positive },
anonymous = {type = "string", default = "", func = check_user},
anonymous = { type = "string", default = "", func = check_user },
validate_request_body = { type = "boolean", default = false },
enforce_headers = { type = "array", default = {} },
algorithms = { type = "array", default = algorithms, enum = algorithms }
}
}
11 changes: 8 additions & 3 deletions spec/03-plugins/20-hmac-auth/01-schema_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,21 @@ describe("Plugin: hmac-auth (schema)", function()
it("accepts empty config", function()
local ok, err = validate_entity({}, hmac_auth_schema)
assert.is_nil(err)
assert.True(ok)
assert.is_true(ok)
end)
it("accepts correct clock skew", function()
local ok, err = validate_entity({clock_skew = 10}, hmac_auth_schema)
assert.is_nil(err)
assert.True(ok)
assert.is_true(ok)
end)
it("errors with negative clock skew", function()
local ok, err = validate_entity({clock_skew = -10}, hmac_auth_schema)
assert.equal("Clock Skew should be positive", err.clock_skew)
assert.False(ok)
assert.is_false(ok)
end)
it("errors with wrong algorithm", function()
local ok, err = validate_entity({algorithms = {"sha1024"}}, hmac_auth_schema)
assert.equal('"sha1024" is not allowed. Allowed values are: "hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512"', err.algorithms)
assert.is_false(ok)
end)
end)
Loading