From d5bedea79e221f4136dfd9302eb4cae6fefc4305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Quiller=C3=A9?= Date: Wed, 2 Mar 2022 15:38:50 +0100 Subject: [PATCH 1/8] feat(hmac-auth) add support for RSA signatures The hmac-auth plugin allow authentication with HMAC signatures based on the draft-cavage-http-signatures draft. This commit aims to add support for RSA signatures as described in the draft, providing a stronger layer of security via asymmetric encryption. This implementation has been made with backward compatibility in mind and only one new field has been added to the DAOs to store the RSA public key. Depending on the algorithm used during the request, the plugin will use either the HMAC secret or the RSA public key to verify the signature. --- kong/plugins/hmac-auth/access.lua | 69 +++++++++++---- kong/plugins/hmac-auth/daos.lua | 1 + .../migrations/004_add_public_key.lua | 18 ++++ kong/plugins/hmac-auth/migrations/init.lua | 1 + kong/plugins/hmac-auth/schema.lua | 2 + .../19-hmac-auth/01-schema_spec.lua | 2 +- .../19-hmac-auth/03-access_spec.lua | 88 +++++++++++++++++++ 7 files changed, 162 insertions(+), 19 deletions(-) create mode 100644 kong/plugins/hmac-auth/migrations/004_add_public_key.lua diff --git a/kong/plugins/hmac-auth/access.lua b/kong/plugins/hmac-auth/access.lua index 6a2b37437689..f4fe7a9f1a15 100644 --- a/kong/plugins/hmac-auth/access.lua +++ b/kong/plugins/hmac-auth/access.lua @@ -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" @@ -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, } @@ -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") @@ -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 @@ -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 @@ -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 @@ -324,6 +356,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 } diff --git a/kong/plugins/hmac-auth/daos.lua b/kong/plugins/hmac-auth/daos.lua index 34f788b307d1..14ea04c51b79 100644 --- a/kong/plugins/hmac-auth/daos.lua +++ b/kong/plugins/hmac-auth/daos.lua @@ -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" }, }, { tags = typedefs.tags }, }, }, diff --git a/kong/plugins/hmac-auth/migrations/004_add_public_key.lua b/kong/plugins/hmac-auth/migrations/004_add_public_key.lua new file mode 100644 index 000000000000..7d94e4c1eabe --- /dev/null +++ b/kong/plugins/hmac-auth/migrations/004_add_public_key.lua @@ -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 = { + up = [[ + ALTER TABLE hmacauth_credentials ADD public_key text; + ]], + } +} diff --git a/kong/plugins/hmac-auth/migrations/init.lua b/kong/plugins/hmac-auth/migrations/init.lua index fd7e8ffce781..fe313d350114 100644 --- a/kong/plugins/hmac-auth/migrations/init.lua +++ b/kong/plugins/hmac-auth/migrations/init.lua @@ -2,4 +2,5 @@ return { "000_base_hmac_auth", "002_130_to_140", "003_200_to_210", + "004_add_public_key", } diff --git a/kong/plugins/hmac-auth/schema.lua b/kong/plugins/hmac-auth/schema.lua index a95b53bd62f9..ea0e19aee6ae 100644 --- a/kong/plugins/hmac-auth/schema.lua +++ b/kong/plugins/hmac-auth/schema.lua @@ -6,6 +6,8 @@ local ALGORITHMS = { "hmac-sha256", "hmac-sha384", "hmac-sha512", + "rsa-sha256", + "rsa-sha512", } diff --git a/spec/03-plugins/19-hmac-auth/01-schema_spec.lua b/spec/03-plugins/19-hmac-auth/01-schema_spec.lua index 85c6be445949..953819594e72 100644 --- a/spec/03-plugins/19-hmac-auth/01-schema_spec.lua +++ b/spec/03-plugins/19-hmac-auth/01-schema_spec.lua @@ -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) diff --git a/spec/03-plugins/19-hmac-auth/03-access_spec.lua b/spec/03-plugins/19-hmac-auth/03-access_spec.lua index dd34001d0b4c..684ebdba9513 100644 --- a/spec/03-plugins/19-hmac-auth/03-access_spec.lua +++ b/spec/03-plugins/19-hmac-auth/03-access_spec.lua @@ -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" @@ -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, { @@ -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" } @@ -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", @@ -1690,6 +1718,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) From b5db13995d91a6ee252c8ec70792d4679d451c48 Mon Sep 17 00:00:00 2001 From: Vinicius Mignot Date: Tue, 27 Jun 2023 15:36:18 -0300 Subject: [PATCH 2/8] chore(plugins/hmac-auth): renamed migrations file --- kong-3.5.0-0.rockspec | 1 + .../{004_add_public_key.lua => 004_330_to_340.lua} | 5 ----- kong/plugins/hmac-auth/migrations/init.lua | 2 +- 3 files changed, 2 insertions(+), 6 deletions(-) rename kong/plugins/hmac-auth/migrations/{004_add_public_key.lua => 004_330_to_340.lua} (71%) diff --git a/kong-3.5.0-0.rockspec b/kong-3.5.0-0.rockspec index 8c59cf2906b6..5936dc9827d0 100644 --- a/kong-3.5.0-0.rockspec +++ b/kong-3.5.0-0.rockspec @@ -404,6 +404,7 @@ build = { ["kong.plugins.hmac-auth.migrations.000_base_hmac_auth"] = "kong/plugins/hmac-auth/migrations/000_base_hmac_auth.lua", ["kong.plugins.hmac-auth.migrations.002_130_to_140"] = "kong/plugins/hmac-auth/migrations/002_130_to_140.lua", ["kong.plugins.hmac-auth.migrations.003_200_to_210"] = "kong/plugins/hmac-auth/migrations/003_200_to_210.lua", + ["kong.plugins.hmac-auth.migrations.004_330_to_340"] = "kong/plugins/hmac-auth/migrations/004_330_to_340.lua", ["kong.plugins.hmac-auth.handler"] = "kong/plugins/hmac-auth/handler.lua", ["kong.plugins.hmac-auth.access"] = "kong/plugins/hmac-auth/access.lua", ["kong.plugins.hmac-auth.schema"] = "kong/plugins/hmac-auth/schema.lua", diff --git a/kong/plugins/hmac-auth/migrations/004_add_public_key.lua b/kong/plugins/hmac-auth/migrations/004_330_to_340.lua similarity index 71% rename from kong/plugins/hmac-auth/migrations/004_add_public_key.lua rename to kong/plugins/hmac-auth/migrations/004_330_to_340.lua index 7d94e4c1eabe..b2b21d97dfb9 100644 --- a/kong/plugins/hmac-auth/migrations/004_add_public_key.lua +++ b/kong/plugins/hmac-auth/migrations/004_330_to_340.lua @@ -10,9 +10,4 @@ return { ]], }, - cassandra = { - up = [[ - ALTER TABLE hmacauth_credentials ADD public_key text; - ]], - } } diff --git a/kong/plugins/hmac-auth/migrations/init.lua b/kong/plugins/hmac-auth/migrations/init.lua index fe313d350114..19ed819e57d8 100644 --- a/kong/plugins/hmac-auth/migrations/init.lua +++ b/kong/plugins/hmac-auth/migrations/init.lua @@ -2,5 +2,5 @@ return { "000_base_hmac_auth", "002_130_to_140", "003_200_to_210", - "004_add_public_key", + "004_330_to_340", } From 49ce30a26f0365e4a5242f811e6a656be0401d1b Mon Sep 17 00:00:00 2001 From: Vinicius Mignot Date: Tue, 27 Jun 2023 15:38:36 -0300 Subject: [PATCH 3/8] tests(unit): check public_key field cluster compatibility --- spec/01-unit/19-hybrid/03-compat_spec.lua | 28 +++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/01-unit/19-hybrid/03-compat_spec.lua b/spec/01-unit/19-hybrid/03-compat_spec.lua index 11cc6e672783..4f95ac327e92 100644 --- a/spec/01-unit/19-hybrid/03-compat_spec.lua +++ b/spec/01-unit/19-hybrid/03-compat_spec.lua @@ -409,6 +409,7 @@ describe("kong.clustering.compat", function() "plugins", "consumers", "upstreams", + "hmacauth_credentials", }) _G.kong.db = db @@ -427,10 +428,18 @@ describe("kong.clustering.compat", function() cert = ssl_fixtures.cert_ca, } + local consumer_def = { + _tags = ngx.null, + created_at = 1687800000, + id = "f6c12564-47c8-48b4-b171-0a0d9dbf7cb2", + username = "gruceo", + custom_id = "201710", + } assert(declarative.load_into_db({ ca_certificates = { [ca_certificate_def.id] = ca_certificate_def }, certificates = { [certificate_def.id] = certificate_def }, + consumers = { [consumer_def.id] = consumer_def }, upstreams = { upstreams1 = { id = "01a2b3c4-d5e6-f7a8-b9c0-d1e2f3a4b5c6", @@ -557,10 +566,19 @@ describe("kong.clustering.compat", function() enabled = true, }, }, + hmacauth_credentials = { + hmacauth_credential1 = { + id = "1aeb7ca9-3317-49c2-a607-6a8759c27318", + username = "gruceo", + consumer = { id = consumer_def.id }, + public_key = "notakeytbh", + }, + }, }, { _transform = true })) config = { config_table = declarative.export_config() } end) + it(function() local has_update, result = compat.update_compatible_payload(config, "3.0.0", "test_") assert.truthy(has_update) @@ -630,5 +648,15 @@ describe("kong.clustering.compat", function() assert.is_nil(assert(services[3]).ca_certificates) end) + it("hmacauth_credentials.public_key", function() + local has_update, result = compat.update_compatible_payload(config, "3.3.0", "test_") + assert.truthy(has_update) + result = cjson_decode(inflate_gzip(result)).config_table + + local credentials = assert(assert(assert(result).hmacauth_credentials)) + assert.equals(assert(credentials[1]).username, "gruceo") + assert.is_nil(assert(credentials[1]).public_key) + end) + end) end) From d309fffc9062273bd2f8a5ab3432086da0f9a4a9 Mon Sep 17 00:00:00 2001 From: Vinicius Mignot Date: Tue, 27 Jun 2023 15:39:31 -0300 Subject: [PATCH 4/8] feat(clustering): public_key field compatibility --- kong/clustering/compat/checkers.lua | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/kong/clustering/compat/checkers.lua b/kong/clustering/compat/checkers.lua index 7fbfca3cec22..c9c744288dec 100644 --- a/kong/clustering/compat/checkers.lua +++ b/kong/clustering/compat/checkers.lua @@ -264,6 +264,32 @@ local compatible_checkers = { return has_update end }, + + { 3004000000, --[[ 3.4.0.0 ]] + function(config_table, dp_version, log_suffix) + local config_hmacauth_credentials = config_table["hmacauth_credentials"] + if not config_hmacauth_credentials then + return nil + end + + local has_update + for _, t in ipairs(config_hmacauth_credentials) do + if t["public_key"] ~= nil then + t["public_key"] = nil + has_update = true + end + end + + if has_update then + log_warn_message("contains configuration 'hmacauth_credentials.public_key'", + "be removed", + dp_version, + log_suffix) + end + + return has_update + end + }, } From 79f8e15a5210cc454d783f1a14ae99ddf83efbe9 Mon Sep 17 00:00:00 2001 From: Vinicius Mignot Date: Tue, 27 Jun 2023 18:16:19 -0300 Subject: [PATCH 5/8] tests(migration): check the new public_key column --- .../plugins/hmac-auth/migrations/004_330_to_340_spec.lua | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 spec/05-migration/plugins/hmac-auth/migrations/004_330_to_340_spec.lua diff --git a/spec/05-migration/plugins/hmac-auth/migrations/004_330_to_340_spec.lua b/spec/05-migration/plugins/hmac-auth/migrations/004_330_to_340_spec.lua new file mode 100644 index 000000000000..1fe29bf32709 --- /dev/null +++ b/spec/05-migration/plugins/hmac-auth/migrations/004_330_to_340_spec.lua @@ -0,0 +1,7 @@ +local uh = require "spec/upgrade_helpers" + +describe("database migration", function () + uh.all_phases("has added the public_key column", function () + assert.table_has_column("hmacauth_credentials", "public_key", "text") + end) +end) From ea5c7bd2ac3b9852de60f5da7900ff2a87767a1a Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Sat, 23 Sep 2023 21:58:38 +0800 Subject: [PATCH 6/8] add unreleased changelog entry --- CHANGELOG/unreleased/kong/11133.yaml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CHANGELOG/unreleased/kong/11133.yaml diff --git a/CHANGELOG/unreleased/kong/11133.yaml b/CHANGELOG/unreleased/kong/11133.yaml new file mode 100644 index 000000000000..835cd2947d76 --- /dev/null +++ b/CHANGELOG/unreleased/kong/11133.yaml @@ -0,0 +1,5 @@ +message: "hmac-auth: Added support for RSA signatures" +type: feature +scope: Plugin +prs: + - 11133 From 54edb06e135949b8bf22ba1768cb360b963282ea Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Sun, 24 Sep 2023 10:34:04 +0800 Subject: [PATCH 7/8] chore(plugins/hmac-auth): add public_key into removed_fields --- kong/clustering/compat/removed_fields.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 81022d0e26a6..05a8be4e70f2 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -101,5 +101,11 @@ return { cors = { "private_network", } + }, + + [3005000000] = { + hmac_auth = { + "public_key", + } } } From f52df68f04771c7ed3983a959cc1fd6a1d180888 Mon Sep 17 00:00:00 2001 From: Xiaochen Wang Date: Mon, 25 Sep 2023 15:03:53 +0800 Subject: [PATCH 8/8] chore(plugins/hmac-auth): fix public_key overflow in removed_fields --- kong/clustering/compat/removed_fields.lua | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/kong/clustering/compat/removed_fields.lua b/kong/clustering/compat/removed_fields.lua index 05a8be4e70f2..b0cb3078056c 100644 --- a/kong/clustering/compat/removed_fields.lua +++ b/kong/clustering/compat/removed_fields.lua @@ -100,12 +100,9 @@ return { [3005000000] = { cors = { "private_network", - } - }, - - [3005000000] = { + }, hmac_auth = { "public_key", } - } + }, }