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-auth) add support for RSA signatures #8530

Closed
wants to merge 1 commit into from
Closed
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
69 changes: 51 additions & 18 deletions kong/plugins/hmac-auth/access.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
local constants = require "kong.constants"
local sha256 = require "resty.sha256"
local openssl_hmac = require "resty.openssl.hmac"
local openssl_pkey = require "resty.openssl.pkey"
local utils = require "kong.tools.utils"


Expand Down Expand Up @@ -31,18 +32,45 @@ local SIGNATURE_NOT_VALID = "HMAC signature cannot be verified"
local SIGNATURE_NOT_SAME = "HMAC signature does not match"


local function verify_rsa(public_key, signature, signing_string, md_alg)
local pub, err = openssl_pkey.new(public_key)
if not pub then
kong.log.err("failed to create public key : ", err)
return false
end

local verified, err = pub:verify(signature, signing_string, md_alg)
if not err then
return verified
else
kong.log.err("failed to verify signature : ", err)
return false
end
end


local rsa = {
["rsa-sha256"] = function(public_key, signature, signing_string)
return verify_rsa(public_key, signature, signing_string, "sha256")
end,
["rsa-sha512"] = function(public_key, signature, signing_string)
return verify_rsa(public_key, signature, signing_string, "sha512")
end,
}


local hmac = {
["hmac-sha1"] = function(secret, data)
return hmac_sha1(secret, data)
["hmac-sha1"] = function(secret, signature, signing_string)
return signature == hmac_sha1(secret, signing_string)
end,
["hmac-sha256"] = function(secret, data)
return openssl_hmac.new(secret, "sha256"):final(data)
["hmac-sha256"] = function(secret, signature, signing_string)
return signature == openssl_hmac.new(secret, "sha256"):final(signing_string)
end,
["hmac-sha384"] = function(secret, data)
return openssl_hmac.new(secret, "sha384"):final(data)
["hmac-sha384"] = function(secret, signature, signing_string)
return signature == openssl_hmac.new(secret, "sha384"):final(signing_string)
end,
["hmac-sha512"] = function(secret, data)
return openssl_hmac.new(secret, "sha512"):final(data)
["hmac-sha512"] = function(secret, signature, signing_string)
return signature == openssl_hmac.new(secret, "sha512"):final(signing_string)
end,
}

Expand Down Expand Up @@ -97,7 +125,7 @@ local function retrieve_hmac_fields(authorization_header)
-- parse the header to retrieve hamc parameters
if authorization_header then
local iterator, iter_err = re_gmatch(authorization_header,
"\\s*[Hh]mac\\s*username=\"(.+)\"," ..
"(\\s*[Hh]mac)?\\s*username=\"(.+)\"," ..
"\\s*algorithm=\"(.+)\",\\s*header" ..
"s=\"(.+)\",\\s*signature=\"(.+)\"",
"jo")
Expand All @@ -113,10 +141,10 @@ local function retrieve_hmac_fields(authorization_header)
end

if m and #m >= 4 then
hmac_params.username = m[1]
hmac_params.algorithm = m[2]
hmac_params.hmac_headers = utils.split(m[3], " ")
hmac_params.signature = m[4]
hmac_params.username = m[2]
hmac_params.algorithm = m[3]
hmac_params.hmac_headers = utils.split(m[4], " ")
hmac_params.signature = m[5]
end
end

Expand All @@ -126,7 +154,7 @@ end

-- plugin assumes the request parameters being used for creating
-- signature by client are not changed by core or any other plugin
local function create_hash(request_uri, hmac_params)
local function generate_signing_string(request_uri, hmac_params)
local signing_string = ""
local hmac_headers = hmac_params.hmac_headers

Expand Down Expand Up @@ -161,14 +189,18 @@ local function create_hash(request_uri, hmac_params)
end
end

return hmac[hmac_params.algorithm](hmac_params.secret, signing_string)
return signing_string
end


local function validate_signature(hmac_params)
local signature_1 = create_hash(kong_request.get_path_with_query(), hmac_params)
local signature_2 = decode_base64(hmac_params.signature)
return signature_1 == signature_2
local signature = decode_base64(hmac_params.signature)
local signing_string = generate_signing_string(kong_request.get_path_with_query(), hmac_params)
if hmac_params.algorithm:sub(1, 4) == "rsa-" then
return rsa[hmac_params.algorithm](hmac_params.public_key, signature, signing_string)
else
return hmac[hmac_params.algorithm](hmac_params.secret, signature, signing_string)
end
end


Expand Down Expand Up @@ -326,6 +358,7 @@ local function do_authentication(conf)
end

hmac_params.secret = credential.secret
hmac_params.public_key = credential.public_key

if not validate_signature(hmac_params) then
return false, { status = 401, message = SIGNATURE_NOT_SAME }
Expand Down
1 change: 1 addition & 0 deletions kong/plugins/hmac-auth/daos.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ return {
{ consumer = { type = "foreign", reference = "consumers", required = true, on_delete = "cascade", }, },
{ username = { type = "string", required = true, unique = true }, },
{ secret = { type = "string", auto = true }, },
{ public_key = { type = "string" }, },
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a breaking change for version compatibility with older data planes; updates will need to be made to the compatibility layer to handle these changes ahead of time of before a release.

Copy link
Author

Choose a reason for hiding this comment

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

I'm not sure to understand what needs to be updated. You linked to a file named removed_fields.lua but no field has been removed, only added. I've checked in the folder if there was a file named added_fields.lua but could not find any. I've also checked other PR doing similar changes on the configuration and could not find any modification related to the compatibility layer.

Could you provide more informations on what needs to be done exactly ?

Copy link
Contributor

Choose a reason for hiding this comment

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

@mideuger This is something that will need to be handled closer to release time when this feature is targeting a particular version of Kong Gateway; I just wanted to mark this PR as something that is going to need further testing and updates when it comes to release time.

I've also checked other PR doing similar changes on the configuration and could not find any modification related to the compatibility layer.

Since you are adding a new field to the hmac-auth plugin, older data planes will not know how to process this field when it is transmitted in the payload in hybrid mode. To allow for newer control planes to interoperate with older data planes the field public_key needs to be removed before the payload is sent down.

Copy link
Contributor

@chobits chobits Sep 17, 2023

Choose a reason for hiding this comment

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

hi @mideuger

The comment of @mikefero means that you need to add your new field "public_key" into this file removed_fields.lua, here is an example of how to add a new field to plugin conf and make it compatitble with old version: #11523

{ tags = typedefs.tags },
},
},
Expand Down
18 changes: 18 additions & 0 deletions kong/plugins/hmac-auth/migrations/004_add_public_key.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
return {
postgres = {
up = [[
DO $$
BEGIN
ALTER TABLE IF EXISTS ONLY hmacauth_credentials ADD public_key TEXT;
EXCEPTION WHEN DUPLICATE_COLUMN THEN
-- Do nothing, accept existing state
END$$;

]],
},
cassandra = {
Copy link
Contributor

@chobits chobits Sep 17, 2023

Choose a reason for hiding this comment

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

hi, cassandra is not supported at present, it might be removed from this commit. Otherwise it will report error:

with kong 3.4+ or kong latest master branch


(kong-dev) xc kong-ee $ kong start
Error: [PostgreSQL error] [PostgreSQL error] migration 'kong.plugins.hmac-auth.migrations.004_add_public_key
' of 'hmac-auth' subsystem is invalid: schema violation (cassandra: unknown field)

  Run with --v (verbose) or --vv (debug) for more details
(kong-dev) xc kong-ee $ kong migrations bootstrap
Error: [PostgreSQL error] [PostgreSQL error] migration 'kong.plugins.hmac-auth.migrations.004_add_public_key
' of 'hmac-auth' subsystem is invalid: schema violation (cassandra: unknown field)

  Run with --v (verbose) or --vv (debug) for more details

up = [[
ALTER TABLE hmacauth_credentials ADD public_key text;
]],
}
}
1 change: 1 addition & 0 deletions kong/plugins/hmac-auth/migrations/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ return {
"000_base_hmac_auth",
"002_130_to_140",
"003_200_to_210",
"004_add_public_key",
}
2 changes: 2 additions & 0 deletions kong/plugins/hmac-auth/schema.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ local ALGORITHMS = {
"hmac-sha256",
"hmac-sha384",
"hmac-sha512",
"rsa-sha256",
"rsa-sha512",
}


Expand Down
2 changes: 1 addition & 1 deletion spec/03-plugins/19-hmac-auth/01-schema_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ describe("Plugin: hmac-auth (schema)", function()
it("errors with wrong algorithm", function()
local ok, err = v({ algorithms = { "sha1024" } }, schema_def)
assert.is_falsy(ok)
assert.equal("expected one of: hmac-sha1, hmac-sha256, hmac-sha384, hmac-sha512",
assert.equal("expected one of: hmac-sha1, hmac-sha256, hmac-sha384, hmac-sha512, rsa-sha256, rsa-sha512",
err.config.algorithms[1])
end)
end)
88 changes: 88 additions & 0 deletions spec/03-plugins/19-hmac-auth/03-access_spec.lua
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
local cjson = require "cjson"
local openssl_hmac = require "resty.openssl.hmac"
local openssl_pkey = require "resty.openssl.pkey"
local helpers = require "spec.helpers"
local utils = require "kong.tools.utils"
local resty_sha256 = require "resty.sha256"
Expand All @@ -20,6 +21,7 @@ for _, strategy in helpers.each_strategy() do
local proxy_client
local consumer
local credential
local rsa_key_pair

lazy_setup(function()
local bp = helpers.get_db_utils(strategy, {
Expand Down Expand Up @@ -70,6 +72,19 @@ for _, strategy in helpers.each_strategy() do
consumer = { id = consumer.id },
}

rsa_key_pair = openssl_pkey.new()

local consumer2 = bp.consumers:insert {
username = "alice",
custom_id = "123456"
}

bp.hmacauth_credentials:insert {
username = "alice",
public_key = rsa_key_pair:to_PEM("public"),
consumer = { id = consumer2.id },
}

local anonymous_user = bp.consumers:insert {
username = "no-body"
}
Expand Down Expand Up @@ -155,6 +170,19 @@ for _, strategy in helpers.each_strategy() do
}
}

local route8 = bp.routes:insert {
hosts = { "hmacauth8.com" },
}

bp.plugins:insert {
name = "hmac-auth",
route = { id = route8.id },
config = {
enforce_headers = {"date"},
algorithms = {"rsa-sha256", "rsa-sha512"},
}
}

assert(helpers.start_kong {
database = strategy,
real_ip_header = "X-Forwarded-For",
Expand Down Expand Up @@ -1694,6 +1722,66 @@ for _, strategy in helpers.each_strategy() do
assert.res_status(200, res)
end)

it("should not pass with GET with rsa-sha256 when signing with wrong key", function()
local date = os.date("!%a, %d %b %Y %H:%M:%S GMT")
local signature = openssl_pkey.new():sign("date: " .. date .. "\nGET /request HTTP/1.1", "sha256")
local encodedSignature = ngx.encode_base64(signature)
local hmacAuth = [[username="alice", algorithm="rsa-sha256", ]]
.. [[headers="date request-line", signature="]]
.. encodedSignature .. [["]]
local res = assert(proxy_client:send {
method = "GET",
path = "/request",
body = {},
headers = {
["HOST"] = "hmacauth8.com",
date = date,
["proxy-authorization"] = hmacAuth,
},
})
assert.res_status(401, res)
end)

it("should pass with GET with rsa-sha256", function()
local date = os.date("!%a, %d %b %Y %H:%M:%S GMT")
local signature = rsa_key_pair:sign("date: " .. date .. "\nGET /request HTTP/1.1", "sha256")
local encodedSignature = ngx.encode_base64(signature)
local hmacAuth = [[username="alice", algorithm="rsa-sha256", ]]
.. [[headers="date request-line", signature="]]
.. encodedSignature .. [["]]
local res = assert(proxy_client:send {
method = "GET",
path = "/request",
body = {},
headers = {
["HOST"] = "hmacauth8.com",
date = date,
["proxy-authorization"] = hmacAuth,
},
})
assert.res_status(200, res)
end)

it("should pass with GET with rsa-sha512", function()
local date = os.date("!%a, %d %b %Y %H:%M:%S GMT")
local signature = rsa_key_pair:sign("date: " .. date .. "\nGET /request HTTP/1.1", "sha512")
local encodedSignature = ngx.encode_base64(signature)
local hmacAuth = [[username="alice", algorithm="rsa-sha512", ]]
.. [[headers="date request-line", signature="]]
.. encodedSignature .. [["]]
local res = assert(proxy_client:send {
method = "GET",
path = "/request",
body = {},
headers = {
["HOST"] = "hmacauth8.com",
date = date,
["proxy-authorization"] = hmacAuth,
},
})
assert.res_status(200, res)
end)

end)
end)

Expand Down