From 98a64753e79a72a8fe352eadb0b079b03c91e796 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:44:58 +0200 Subject: [PATCH 01/24] feat(basic-auth): Add option to use {vault://} in username and password fields --- kong/plugins/basic-auth/daos.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kong/plugins/basic-auth/daos.lua b/kong/plugins/basic-auth/daos.lua index 7963628a4c7..ff76bab85c1 100644 --- a/kong/plugins/basic-auth/daos.lua +++ b/kong/plugins/basic-auth/daos.lua @@ -14,8 +14,8 @@ return { { id = typedefs.uuid }, { created_at = typedefs.auto_timestamp_s }, { consumer = { type = "foreign", reference = "consumers", required = true, on_delete = "cascade" }, }, - { username = { type = "string", required = true, unique = true }, }, - { password = { type = "string", required = true, encrypted = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature, it does nothing in Kong CE + { username = { type = "string", required = true, unique = true, referenceable = true }, }, + { password = { type = "string", required = true, encrypted = true, referenceable = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature, it does nothing in Kong CE { tags = typedefs.tags }, }, transformations = { From e2064f6cbe9e7e7b90157b4faf5e8c7b3742e190 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:45:20 +0200 Subject: [PATCH 02/24] feat(hmac-auth): Add option to use {vault://} in username and secret fields --- kong/plugins/hmac-auth/daos.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kong/plugins/hmac-auth/daos.lua b/kong/plugins/hmac-auth/daos.lua index 34f788b307d..3b7eaec0d59 100644 --- a/kong/plugins/hmac-auth/daos.lua +++ b/kong/plugins/hmac-auth/daos.lua @@ -15,8 +15,8 @@ return { { id = typedefs.uuid }, { created_at = typedefs.auto_timestamp_s }, { consumer = { type = "foreign", reference = "consumers", required = true, on_delete = "cascade", }, }, - { username = { type = "string", required = true, unique = true }, }, - { secret = { type = "string", auto = true }, }, + { username = { type = "string", required = true, unique = true, referenceable = true }, }, + { secret = { type = "string", auto = true, referenceable = true }, }, { tags = typedefs.tags }, }, }, From 5ef50052fcc4d3ea4bdc4dc146475879b78e2156 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:45:40 +0200 Subject: [PATCH 03/24] feat(jwt): Add option to use {vault://} in secret field --- kong/plugins/jwt/daos.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong/plugins/jwt/daos.lua b/kong/plugins/jwt/daos.lua index 32c46d2da27..bb07df4b405 100644 --- a/kong/plugins/jwt/daos.lua +++ b/kong/plugins/jwt/daos.lua @@ -24,7 +24,7 @@ return { { created_at = typedefs.auto_timestamp_s }, { consumer = { type = "foreign", reference = "consumers", required = true, on_delete = "cascade", }, }, { key = { type = "string", required = false, unique = true, auto = true }, }, - { secret = { type = "string", auto = true }, }, + { secret = { type = "string", auto = true, referenceable = true }, }, { rsa_public_key = { type = "string" }, }, { algorithm = { type = "string", From 7df0b8afbbd1ff801db5d1caa7d2766845361a19 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:46:07 +0200 Subject: [PATCH 04/24] feat(oauth2): Add option to use {vault://} in client_id, client_secret and hash_secret fields --- kong/plugins/oauth2/daos.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kong/plugins/oauth2/daos.lua b/kong/plugins/oauth2/daos.lua index 354fdb17aa1..5f865008997 100644 --- a/kong/plugins/oauth2/daos.lua +++ b/kong/plugins/oauth2/daos.lua @@ -31,9 +31,9 @@ local oauth2_credentials = { { created_at = typedefs.auto_timestamp_s }, { consumer = { type = "foreign", reference = "consumers", required = true, on_delete = "cascade", }, }, { name = { type = "string", required = true }, }, - { client_id = { type = "string", required = false, unique = true, auto = true }, }, - { client_secret = { type = "string", required = false, auto = true, encrypted = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature. It does nothing in Kong CE - { hash_secret = { type = "boolean", required = true, default = false }, }, + { client_id = { type = "string", required = false, unique = true, auto = true, referenceable = true }, }, + { client_secret = { type = "string", required = false, auto = true, encrypted = true, referenceable = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature. It does nothing in Kong CE + { hash_secret = { type = "boolean", required = true, default = false, referenceable = true }, }, { redirect_uris = { type = "array", required = false, From 7bd8bb31e0716bfb771930ee031d7e49c05f40ea Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:46:39 +0200 Subject: [PATCH 05/24] feat(request-transformer): Add option to use {vault://} in fields --- kong/plugins/request-transformer/schema.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kong/plugins/request-transformer/schema.lua b/kong/plugins/request-transformer/schema.lua index b8828b1cd58..a1a8370c35b 100644 --- a/kong/plugins/request-transformer/schema.lua +++ b/kong/plugins/request-transformer/schema.lua @@ -50,6 +50,7 @@ local strings_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string" }, } @@ -58,6 +59,7 @@ local headers_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", custom_validator = validate_headers }, } @@ -76,6 +78,7 @@ local colon_strings_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", custom_validator = check_for_value } } @@ -102,6 +105,7 @@ local colon_headers_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", match = "^[^:]+:.*$", custom_validator = validate_colon_headers }, } From a852681c25c860785a4623dc199197fc5a25cacf Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:46:51 +0200 Subject: [PATCH 06/24] feat(response-transformer): Add option to use {vault://} in fields --- kong/plugins/response-transformer/schema.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/kong/plugins/response-transformer/schema.lua b/kong/plugins/response-transformer/schema.lua index 28bd6fc967b..e34a225eeeb 100644 --- a/kong/plugins/response-transformer/schema.lua +++ b/kong/plugins/response-transformer/schema.lua @@ -25,6 +25,7 @@ local string_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string" }, } @@ -33,6 +34,7 @@ local colon_string_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", match = "^[^:]+:.*$" }, } @@ -53,6 +55,7 @@ local colon_string_record = { { json_types = { description = "List of JSON type names. Specify the types of the JSON values returned when appending\nJSON properties. Each string element can be one of: boolean, number, or string.", type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", one_of = { "boolean", "number", "string" } @@ -66,6 +69,7 @@ local colon_headers_array = { type = "array", default = {}, required = true, + referenceable = true, elements = { type = "string", match = "^[^:]+:.*$", custom_validator = validate_colon_headers }, } From c05fb9becf6f22e4eaa8823850482888ad3c4724 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Mon, 13 Oct 2025 12:53:06 +0200 Subject: [PATCH 07/24] docs(changelog): Add changelog for vault template support --- .../kong/feat-add-vault-template-support-in-different-plugins | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins diff --git a/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins b/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins new file mode 100644 index 00000000000..ef4686acd1e --- /dev/null +++ b/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins @@ -0,0 +1,3 @@ +message: Added an option to use {vault://} in specific fields of plugins" +type: feature +scope: Plugin From 6d527097f5c9ad5a55991f692a8af921024a79a8 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:38:45 +0200 Subject: [PATCH 08/24] feat(vault): Add tests for vault integration in basic-auth plugin --- .../10-basic-auth/06-vault_spec.lua | 198 ++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 spec/03-plugins/10-basic-auth/06-vault_spec.lua diff --git a/spec/03-plugins/10-basic-auth/06-vault_spec.lua b/spec/03-plugins/10-basic-auth/06-vault_spec.lua new file mode 100644 index 00000000000..d8c8e072be5 --- /dev/null +++ b/spec/03-plugins/10-basic-auth/06-vault_spec.lua @@ -0,0 +1,198 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("basic-auth: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("vault reference resolution", function() + it("should handle all variations of variable name", function () + local env_name = "MY_VAR_NAME" + local env_value = "complex_value_789" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + assert.equal(env_value, get("{vault://env/MY_VAR_NAME}")) + assert.equal(env_value, get("{vault://env/MY-VAR-NAME}")) + assert.equal(env_value, get("{vault://env/my_var_name}")) + assert.equal(env_value, get("{vault://env/my-var-name}")) + assert.equal(env_value, get("{vault://env/My_Var_Name}")) + assert.equal(env_value, get("{vault://env/My-Var-Name}")) + end) + + it("should handle vault reference with different environment variable name", function() + local env_name = "BASIC_AUTH_SECRET" + local env_value = "another_secret_456" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/basic_auth_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with JSON secret", function() + local env_name = "TEST_JSON_SECRETS" + local env_value = '{"username": "json_user", "password": "db_secret_789"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/test_json_secrets/password}") + assert.is_nil(err) + assert.equal("db_secret_789", res) + end) + + it("should fail gracefully when environment variable does not exist", function() + helpers.unsetenv("NON_EXISTENT_VAR") + + local res, err = get("{vault://env/non_existent_var}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) + + it("should handle vault reference with prefix", function() + local env_name = "TEST_PASSWORD" + local env_value = "prefixed_secret" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/password?prefix=test_}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should work with empty environment variable value", function() + local env_name = "EMPTY_PASSWORD" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, "") + + local res, err = get("{vault://env/empty_password}") + assert.is_nil(err) + assert.equal("", res) + end) + end) + + describe("username field vault references", function() + it("should handle both username and password as vault references", function() + finally(function() + helpers.unsetenv("AUTH_USERNAME") + helpers.unsetenv("AUTH_PASSWORD") + end) + + helpers.setenv("AUTH_USERNAME", "vault_user_both") + helpers.setenv("AUTH_PASSWORD", "vault_pass_both") + + local username_res, username_err = get("{vault://env/auth_username}") + local password_res, password_err = get("{vault://env/auth_password}") + + assert.is_nil(username_err) + assert.is_nil(password_err) + assert.equal("vault_user_both", username_res) + assert.equal("vault_pass_both", password_res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + -- Test various malformed vault references + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + "{vault://env/valid_name?invalid_query=", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + -- Should either return nil with error, or return the original string unchanged + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should preserve non-vault values unchanged", function() + local regular_value = "regular_password" + + local res, err = get(regular_value) + if res then + assert.equal(regular_value, res) + end + end) + + it("should work with special characters in environment variable names", function() + local env_name = "SPECIAL_CHARS_(1337@)" + local env_value = "special_value" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + assert.equal(env_value, get("{vault://env/SPECIAL_CHARS_(1337@)}")) + assert.equal(env_value, get("{vault://env/SPECIAL-CHARS_(1337@)}")) + assert.equal(env_value, get("{vault://env/special-chars_(1337@)}")) + assert.equal(env_value, get("{vault://env/special_chars_(1337@)}")) + end) + end) + + describe("integration with basic-auth plugin", function() + it("should demonstrate vault usage in basic-auth context", function() + local password_env = "BASIC_AUTH_PASSWORD" + local username_env = "BASIC_AUTH_USERNAME" + + finally(function() + helpers.unsetenv(password_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(password_env, "secure_password_123") + helpers.setenv(username_env, "secure_username") + + -- Simulate how basic-auth would resolve vault references + local resolved_password, pass_err = get("{vault://env/basic_auth_password}") + local resolved_username, user_err = get("{vault://env/basic_auth_username}") + + assert.is_nil(pass_err) + assert.is_nil(user_err) + assert.equal("secure_password_123", resolved_password) + assert.equal("secure_username", resolved_username) + end) + end) +end) \ No newline at end of file From 59e6de34e2f0df05824ac7533cfbc0d5e7697da9 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:41:26 +0200 Subject: [PATCH 09/24] feat(vault): Add tests for vault integration in response-transformer plugin --- .../15-response-transformer/06-vault_spec.lua | 374 ++++++++++++++++++ 1 file changed, 374 insertions(+) create mode 100644 spec/03-plugins/15-response-transformer/06-vault_spec.lua diff --git a/spec/03-plugins/15-response-transformer/06-vault_spec.lua b/spec/03-plugins/15-response-transformer/06-vault_spec.lua new file mode 100644 index 00000000000..4e489cb11cf --- /dev/null +++ b/spec/03-plugins/15-response-transformer/06-vault_spec.lua @@ -0,0 +1,374 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("response-transformer: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("response-transformer configuration vault reference resolution", function() + it("should dereference vault value for header transformation", function() + local env_name = "RESPONSE_HEADER_VALUE" + local env_value = "X-Custom-Response-Header-Value" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/response_header_value}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should dereference vault value for JSON response transformation", function() + local env_name = "RESPONSE_JSON_VALUE" + local env_value = "transformed_json_field" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/response_json_value}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with different transformation types", function() + local header_env = "ADD_RESPONSE_HEADER" + local json_env = "ADD_RESPONSE_JSON" + + local header_value = "X-API-Version:2.0" + local json_value = "api_response_status:success" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(json_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(json_env, json_value) + + local header_res, header_err = get("{vault://env/add_response_header}") + local json_res, json_err = get("{vault://env/add_response_json}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal(header_value, header_res) + assert.equal(json_value, json_res) + end) + + it("should handle vault reference with JSON configuration", function() + local env_name = "RESPONSE_TRANSFORM_CONFIG" + local env_value = '{"header": "X-Response-ID:response_id_123", "json": "metadata:response_metadata"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local header_res, header_err = get("{vault://env/response_transform_config/header}") + local json_res, json_err = get("{vault://env/response_transform_config/json}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal("X-Response-ID:response_id_123", header_res) + assert.equal("metadata:response_metadata", json_res) + end) + + it("should fail gracefully when response transformation environment variables do not exist", function() + helpers.unsetenv("NON_EXISTENT_RESPONSE_HEADER") + helpers.unsetenv("NON_EXISTENT_RESPONSE_JSON") + + local header_res, header_err = get("{vault://env/non_existent_response_header}") + local json_res, json_err = get("{vault://env/non_existent_response_json}") + + assert.matches("could not get value from external vault", header_err) + assert.matches("could not get value from external vault", json_err) + assert.is_nil(header_res) + assert.is_nil(json_res) + end) + + it("should handle vault reference with prefix for response transformations", function() + local header_env = "RESP_HEADER_CORS" + local json_env = "RESP_JSON_STATUS" + + local header_value = "Access-Control-Allow-Origin:*" + local json_value = "response_code:200" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(json_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(json_env, json_value) + + local header_res, header_err = get("{vault://env/header_cors?prefix=resp_}") + local json_res, json_err = get("{vault://env/json_status?prefix=resp_}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal(header_value, header_res) + assert.equal(json_value, json_res) + end) + + it("should work with empty response transformation environment variable values", function() + local header_env = "EMPTY_RESPONSE_HEADER" + local json_env = "EMPTY_RESPONSE_JSON" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(json_env) + end) + + helpers.setenv(header_env, "") + helpers.setenv(json_env, "") + + local header_res, header_err = get("{vault://env/empty_response_header}") + local json_res, json_err = get("{vault://env/empty_response_json}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal("", header_res) + assert.equal("", json_res) + end) + + it("should handle security headers from environment variables", function() + local security_env = "SECURITY_HEADERS" + local security_value = "X-Frame-Options:DENY" + + finally(function() + helpers.unsetenv(security_env) + end) + + helpers.setenv(security_env, security_value) + + local res, err = get("{vault://env/security_headers}") + assert.is_nil(err) + assert.equal(security_value, res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_Response_Header") + helpers.unsetenv("Test_Response_JSON") + end) + + helpers.setenv("Test_Response_Header", "case_sensitive_header") + helpers.setenv("Test_Response_JSON", "case_sensitive_json") + + local header_res, header_err = get("{vault://env/test_response_header}") + local json_res, json_err = get("{vault://env/test_response_json}") + + assert.matches("could not get value from external vault", header_err) + assert.matches("could not get value from external vault", json_err) + assert.is_nil(header_res) + assert.is_nil(json_res) + end) + + it("should work with special characters in response transformation environment variable names", function() + local header_env = "RESPONSE_HEADER_123" + local json_env = "RESPONSE_JSON_456" + local header_value = "X-Special-Response:special_response_value" + local json_value = "special_field:special_json_value" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(json_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(json_env, json_value) + + local header_res, header_err = get("{vault://env/response_header_123}") + local json_res, json_err = get("{vault://env/response_json_456}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal(header_value, header_res) + assert.equal(json_value, json_res) + end) + + it("should handle colon-separated key:value transformation formats", function() + local env_name = "COLON_RESPONSE_TRANSFORM" + local env_value = "Cache-Control:no-cache, no-store, must-revalidate" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/colon_response_transform}") + assert.is_nil(err) + assert.equal(env_value, res) + + -- Verify the format matches response-transformer expectations + local key, value = env_value:match("^([^:]+):(.+)$") + assert.equal("Cache-Control", key) + assert.equal("no-cache, no-store, must-revalidate", value) + end) + end) + + describe("integration with response-transformer plugin", function() + it("should demonstrate vault usage in Response-Transformer context", function() + local add_header_env = "ADD_CORS_HEADER" + local add_json_env = "ADD_METADATA" + + finally(function() + helpers.unsetenv(add_header_env) + helpers.unsetenv(add_json_env) + end) + + helpers.setenv(add_header_env, "Access-Control-Allow-Origin:https://example.com") + helpers.setenv(add_json_env, "server_info:kong_gateway_v3") + + local resolved_header, header_err = get("{vault://env/add_cors_header}") + local resolved_json, json_err = get("{vault://env/add_metadata}") + + assert.is_nil(header_err) + assert.is_nil(json_err) + assert.equal("Access-Control-Allow-Origin:https://example.com", resolved_header) + assert.equal("server_info:kong_gateway_v3", resolved_json) + + -- In actual usage, these resolved values would be used in: + -- config.add.headers = ["{vault://env/add_cors_header}"] + -- config.add.json = ["{vault://env/add_metadata}"] + end) + + it("should handle multiple response transformation operations", function() + finally(function() + helpers.unsetenv("REPLACE_RESPONSE_HEADER") + helpers.unsetenv("APPEND_RESPONSE_HEADER") + helpers.unsetenv("REMOVE_RESPONSE_HEADER") + helpers.unsetenv("RENAME_RESPONSE_HEADER") + end) + + -- Different transformation operations for responses + helpers.setenv("REPLACE_RESPONSE_HEADER", "Server:Kong-Gateway") + helpers.setenv("APPEND_RESPONSE_HEADER", "X-Rate-Limit-Remaining:1000") + helpers.setenv("REMOVE_RESPONSE_HEADER", "X-Internal-Header") + helpers.setenv("RENAME_RESPONSE_HEADER", "X-Old-Response:X-New-Response") + + local replace_res, replace_err = get("{vault://env/replace_response_header}") + local append_res, append_err = get("{vault://env/append_response_header}") + local remove_res, remove_err = get("{vault://env/remove_response_header}") + local rename_res, rename_err = get("{vault://env/rename_response_header}") + + assert.is_nil(replace_err) + assert.is_nil(append_err) + assert.is_nil(remove_err) + assert.is_nil(rename_err) + + assert.equal("Server:Kong-Gateway", replace_res) + assert.equal("X-Rate-Limit-Remaining:1000", append_res) + assert.equal("X-Internal-Header", remove_res) + assert.equal("X-Old-Response:X-New-Response", rename_res) + + -- These would be used in configuration like: + -- config.replace.headers = ["{vault://env/replace_response_header}"] + -- config.append.headers = ["{vault://env/append_response_header}"] + -- config.remove.headers = ["{vault://env/remove_response_header}"] + -- config.rename.headers = ["{vault://env/rename_response_header}"] + end) + + it("should handle JSON response transformations", function() + finally(function() + helpers.unsetenv("ADD_JSON_FIELD") + helpers.unsetenv("REPLACE_JSON_FIELD") + helpers.unsetenv("REMOVE_JSON_FIELD") + helpers.unsetenv("RENAME_JSON_FIELD") + end) + + -- JSON transformation operations + helpers.setenv("ADD_JSON_FIELD", "timestamp:$(now)") + helpers.setenv("REPLACE_JSON_FIELD", "status:processed") + helpers.setenv("REMOVE_JSON_FIELD", "internal_id") + helpers.setenv("RENAME_JSON_FIELD", "old_field:new_field") + + local add_res, add_err = get("{vault://env/add_json_field}") + local replace_res, replace_err = get("{vault://env/replace_json_field}") + local remove_res, remove_err = get("{vault://env/remove_json_field}") + local rename_res, rename_err = get("{vault://env/rename_json_field}") + + assert.is_nil(add_err) + assert.is_nil(replace_err) + assert.is_nil(remove_err) + assert.is_nil(rename_err) + + assert.equal("timestamp:$(now)", add_res) + assert.equal("status:processed", replace_res) + assert.equal("internal_id", remove_res) + assert.equal("old_field:new_field", rename_res) + + -- These would be used in configuration like: + -- config.add.json = ["{vault://env/add_json_field}"] + -- config.replace.json = ["{vault://env/replace_json_field}"] + -- config.remove.json = ["{vault://env/remove_json_field}"] + -- config.rename.json = ["{vault://env/rename_json_field}"] + end) + + it("should handle security and compliance headers", function() + finally(function() + helpers.unsetenv("SECURITY_HEADERS_CSP") + helpers.unsetenv("SECURITY_HEADERS_HSTS") + helpers.unsetenv("SECURITY_HEADERS_CT") + end) + + -- Common security headers + helpers.setenv("SECURITY_HEADERS_CSP", "Content-Security-Policy:default-src 'self'") + helpers.setenv("SECURITY_HEADERS_HSTS", "Strict-Transport-Security:max-age=31536000") + helpers.setenv("SECURITY_HEADERS_CT", "Content-Type:application/json; charset=utf-8") + + local csp_res, csp_err = get("{vault://env/security_headers_csp}") + local hsts_res, hsts_err = get("{vault://env/security_headers_hsts}") + local ct_res, ct_err = get("{vault://env/security_headers_ct}") + + assert.is_nil(csp_err) + assert.is_nil(hsts_err) + assert.is_nil(ct_err) + + assert.equal("Content-Security-Policy:default-src 'self'", csp_res) + assert.equal("Strict-Transport-Security:max-age=31536000", hsts_res) + assert.equal("Content-Type:application/json; charset=utf-8", ct_res) + + -- These demonstrate vault usage for security compliance + end) + end) +end) \ No newline at end of file From 89a263c3db9fbba32f3ae8c6633d1097c7f1582b Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:42:01 +0200 Subject: [PATCH 10/24] feat(vault): Add tests for vault integration in JWT plugin --- spec/03-plugins/16-jwt/06-vault_spec.lua | 215 +++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 spec/03-plugins/16-jwt/06-vault_spec.lua diff --git a/spec/03-plugins/16-jwt/06-vault_spec.lua b/spec/03-plugins/16-jwt/06-vault_spec.lua new file mode 100644 index 00000000000..796af19ee76 --- /dev/null +++ b/spec/03-plugins/16-jwt/06-vault_spec.lua @@ -0,0 +1,215 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("jwt: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("jwt credentials vault reference resolution", function() + it("should dereference vault value for secret field", function() + local env_name = "JWT_SECRET" + local env_value = "jwt_secret_key_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with different environment variable name", function() + local env_name = "JWT_PRIVATE_KEY" + local env_value = "private_key_456" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_private_key}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with JSON secret containing JWT keys", function() + local env_name = "JWT_KEYS" + local env_value = '{"secret": "jwt_secret_789", "algorithm": "HS256"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_keys/secret}") + assert.is_nil(err) + assert.equal("jwt_secret_789", res) + end) + + it("should fail gracefully when JWT secret environment variable does not exist", function() + helpers.unsetenv("NON_EXISTENT_JWT_SECRET") + + local res, err = get("{vault://env/non_existent_jwt_secret}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) + + it("should handle vault reference with prefix for JWT secrets", function() + local env_name = "JWT_SECRET" + local env_value = "prefixed_jwt_secret" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/secret?prefix=jwt_}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should work with empty JWT secret environment variable value", function() + local env_name = "EMPTY_JWT_SECRET" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, "") + + local res, err = get("{vault://env/empty_jwt_secret}") + assert.is_nil(err) + assert.equal("", res) + end) + + it("should handle RSA public key from environment variable", function() + local env_name = "JWT_RSA_PUBLIC_KEY" + local env_value = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_rsa_public_key}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_JWT_Secret") + end) + + helpers.setenv("Test_JWT_Secret", "case_sensitive_jwt_value") + + local res, err = get("{vault://env/test_jwt_secret}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) + + it("should work with special characters in JWT environment variable names", function() + local env_name = "JWT_SECRET_123" + local env_value = "special_jwt_value" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_secret_123}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + end) + + describe("integration with jwt plugin", function() + it("should demonstrate vault usage in JWT context", function() + local secret_env = "JWT_INTEGRATION_SECRET" + local key_env = "JWT_INTEGRATION_KEY" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(key_env) + end) + + helpers.setenv(secret_env, "secure_jwt_secret_123") + helpers.setenv(key_env, "jwt_consumer_key") + + local resolved_secret, secret_err = get("{vault://env/jwt_integration_secret}") + local resolved_key, key_err = get("{vault://env/jwt_integration_key}") + + assert.is_nil(secret_err) + assert.is_nil(key_err) + assert.equal("secure_jwt_secret_123", resolved_secret) + assert.equal("jwt_consumer_key", resolved_key) + + -- In actual usage, these resolved values would be used to: + -- 1. Create JWT credentials with resolved secret + -- 2. Validate JWT tokens using the resolved secret + -- 3. The secret can be used for HS256/HS384/HS512 algorithms + -- 4. For RSA algorithms, the secret would contain the private/public key + end) + + it("should handle both symmetric and asymmetric key scenarios", function() + finally(function() + helpers.unsetenv("JWT_HMAC_SECRET") + helpers.unsetenv("JWT_RSA_PRIVATE_KEY") + end) + + -- Symmetric key scenario (HMAC) + helpers.setenv("JWT_HMAC_SECRET", "hmac_shared_secret") + + -- Asymmetric key scenario (RSA) + helpers.setenv("JWT_RSA_PRIVATE_KEY", "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----") + + local hmac_res, hmac_err = get("{vault://env/jwt_hmac_secret}") + local rsa_res, rsa_err = get("{vault://env/jwt_rsa_private_key}") + + assert.is_nil(hmac_err) + assert.is_nil(rsa_err) + assert.equal("hmac_shared_secret", hmac_res) + assert.equal("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", rsa_res) + end) + end) +end) \ No newline at end of file From f3185d8e5c0a47698914b99b4832d4bce1aa1ddc Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:42:32 +0200 Subject: [PATCH 11/24] feat(vault): Add tests for vault integration in hmac-auth plugin --- .../03-plugins/19-hmac-auth/06-vault_spec.lua | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 spec/03-plugins/19-hmac-auth/06-vault_spec.lua diff --git a/spec/03-plugins/19-hmac-auth/06-vault_spec.lua b/spec/03-plugins/19-hmac-auth/06-vault_spec.lua new file mode 100644 index 00000000000..9647a361600 --- /dev/null +++ b/spec/03-plugins/19-hmac-auth/06-vault_spec.lua @@ -0,0 +1,272 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("hmac-auth: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("hmac-auth credentials vault reference resolution", function() + it("should dereference vault value for secret field", function() + local env_name = "HMAC_SECRET" + local env_value = "hmac_secret_key_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/hmac_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should dereference vault value for username field", function() + local env_name = "HMAC_USERNAME" + local env_value = "hmac_user_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/hmac_username}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with different environment variable names", function() + local secret_env = "HMAC_PRIVATE_SECRET" + local username_env = "HMAC_USER_ID" + local secret_value = "private_hmac_456" + local username_value = "hmac_consumer" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(secret_env, secret_value) + helpers.setenv(username_env, username_value) + + local secret_res, secret_err = get("{vault://env/hmac_private_secret}") + local username_res, username_err = get("{vault://env/hmac_user_id}") + + assert.is_nil(secret_err) + assert.is_nil(username_err) + assert.equal(secret_value, secret_res) + assert.equal(username_value, username_res) + end) + + it("should handle vault reference with JSON secrets containing HMAC credentials", function() + local env_name = "HMAC_CREDENTIALS" + local env_value = '{"username": "hmac_user", "secret": "hmac_secret_789"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local username_res, username_err = get("{vault://env/hmac_credentials/username}") + local secret_res, secret_err = get("{vault://env/hmac_credentials/secret}") + + assert.is_nil(username_err) + assert.is_nil(secret_err) + assert.equal("hmac_user", username_res) + assert.equal("hmac_secret_789", secret_res) + end) + + it("should fail gracefully when HMAC environment variables do not exist", function() + helpers.unsetenv("NON_EXISTENT_HMAC_SECRET") + helpers.unsetenv("NON_EXISTENT_HMAC_USERNAME") + + local secret_res, secret_err = get("{vault://env/non_existent_hmac_secret}") + local username_res, username_err = get("{vault://env/non_existent_hmac_username}") + + assert.matches("could not get value from external vault", secret_err) + assert.matches("could not get value from external vault", username_err) + assert.is_nil(secret_res) + assert.is_nil(username_res) + end) + + it("should handle vault reference with prefix for HMAC credentials", function() + local secret_env = "HMAC_SECRET" + local username_env = "HMAC_USERNAME" + local secret_value = "prefixed_hmac_secret" + local username_value = "prefixed_hmac_user" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(secret_env, secret_value) + helpers.setenv(username_env, username_value) + + local secret_res, secret_err = get("{vault://env/secret?prefix=hmac_}") + local username_res, username_err = get("{vault://env/username?prefix=hmac_}") + + assert.is_nil(secret_err) + assert.is_nil(username_err) + assert.equal(secret_value, secret_res) + assert.equal(username_value, username_res) + end) + + it("should work with empty HMAC environment variable values", function() + local secret_env = "EMPTY_HMAC_SECRET" + local username_env = "EMPTY_HMAC_USERNAME" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(secret_env, "") + helpers.setenv(username_env, "") + + local secret_res, secret_err = get("{vault://env/empty_hmac_secret}") + local username_res, username_err = get("{vault://env/empty_hmac_username}") + + assert.is_nil(secret_err) + assert.is_nil(username_err) + assert.equal("", secret_res) + assert.equal("", username_res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_HMAC_Secret") + helpers.unsetenv("Test_HMAC_User") + end) + + helpers.setenv("Test_HMAC_Secret", "case_sensitive_hmac_secret") + helpers.setenv("Test_HMAC_User", "case_sensitive_hmac_user") + + local secret_res, secret_err = get("{vault://env/test_hmac_secret}") + local user_res, user_err = get("{vault://env/test_hmac_user}") + + assert.matches("could not get value from external vault", secret_err) + assert.matches("could not get value from external vault", user_err) + assert.is_nil(secret_res) + assert.is_nil(user_res) + end) + + it("should work with special characters in HMAC environment variable names", function() + local secret_env = "HMAC_SECRET_123" + local username_env = "HMAC_USER_456" + local secret_value = "special_hmac_secret" + local username_value = "special_hmac_user" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(secret_env, secret_value) + helpers.setenv(username_env, username_value) + + local secret_res, secret_err = get("{vault://env/hmac_secret_123}") + local username_res, username_err = get("{vault://env/hmac_user_456}") + + assert.is_nil(secret_err) + assert.is_nil(username_err) + assert.equal(secret_value, secret_res) + assert.equal(username_value, username_res) + end) + end) + + describe("integration with hmac-auth plugin", function() + it("should demonstrate vault usage in HMAC-Auth context", function() + local secret_env = "HMAC_INTEGRATION_SECRET" + local username_env = "HMAC_INTEGRATION_USERNAME" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(username_env) + end) + + helpers.setenv(secret_env, "secure_hmac_secret_123") + helpers.setenv(username_env, "secure_hmac_username") + + local resolved_secret, secret_err = get("{vault://env/hmac_integration_secret}") + local resolved_username, username_err = get("{vault://env/hmac_integration_username}") + + assert.is_nil(secret_err) + assert.is_nil(username_err) + assert.equal("secure_hmac_secret_123", resolved_secret) + assert.equal("secure_hmac_username", resolved_username) + + -- In actual usage, these resolved values would be used to: + -- 1. Create HMAC-Auth credentials with resolved username and secret + -- 2. Validate HMAC signatures using the resolved secret + -- 3. The secret is used to generate HMAC signatures for request validation + -- 4. The username identifies the consumer making the request + end) + + it("should handle multiple HMAC credential scenarios", function() + finally(function() + helpers.unsetenv("HMAC_ADMIN_SECRET") + helpers.unsetenv("HMAC_ADMIN_USER") + helpers.unsetenv("HMAC_API_SECRET") + helpers.unsetenv("HMAC_API_USER") + end) + + -- Admin credentials + helpers.setenv("HMAC_ADMIN_SECRET", "admin_hmac_secret") + helpers.setenv("HMAC_ADMIN_USER", "admin_user") + + -- API credentials + helpers.setenv("HMAC_API_SECRET", "api_hmac_secret") + helpers.setenv("HMAC_API_USER", "api_user") + + local admin_secret_res, admin_secret_err = get("{vault://env/hmac_admin_secret}") + local admin_user_res, admin_user_err = get("{vault://env/hmac_admin_user}") + local api_secret_res, api_secret_err = get("{vault://env/hmac_api_secret}") + local api_user_res, api_user_err = get("{vault://env/hmac_api_user}") + + assert.is_nil(admin_secret_err) + assert.is_nil(admin_user_err) + assert.is_nil(api_secret_err) + assert.is_nil(api_user_err) + + assert.equal("admin_hmac_secret", admin_secret_res) + assert.equal("admin_user", admin_user_res) + assert.equal("api_hmac_secret", api_secret_res) + assert.equal("api_user", api_user_res) + end) + end) +end) \ No newline at end of file From 9f0e84c151699e336cd92d89e106e9fc4b92d62f Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:42:47 +0200 Subject: [PATCH 12/24] feat(vault): Add tests for vault integration in request-transformer plugin --- .../36-request-transformer/06-vault_spec.lua | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 spec/03-plugins/36-request-transformer/06-vault_spec.lua diff --git a/spec/03-plugins/36-request-transformer/06-vault_spec.lua b/spec/03-plugins/36-request-transformer/06-vault_spec.lua new file mode 100644 index 00000000000..5a18b06e828 --- /dev/null +++ b/spec/03-plugins/36-request-transformer/06-vault_spec.lua @@ -0,0 +1,364 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("request-transformer: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("request-transformer configuration vault reference resolution", function() + it("should dereference vault value for header transformation", function() + local env_name = "REQUEST_HEADER_VALUE" + local env_value = "X-Custom-Header-Value" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/request_header_value}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should dereference vault value for body transformation", function() + local env_name = "REQUEST_BODY_VALUE" + local env_value = "transformed_body_content" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/request_body_value}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should dereference vault value for querystring transformation", function() + local env_name = "REQUEST_QUERY_VALUE" + local env_value = "query_param_value_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/request_query_value}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with different transformation types", function() + local header_env = "ADD_HEADER_VALUE" + local body_env = "ADD_BODY_PARAM" + local query_env = "ADD_QUERY_PARAM" + + local header_value = "Authorization: Bearer secret_token" + local body_value = "api_key:secret_api_key_123" + local query_value = "token:query_token_456" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(body_env) + helpers.unsetenv(query_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(body_env, body_value) + helpers.setenv(query_env, query_value) + + local header_res, header_err = get("{vault://env/add_header_value}") + local body_res, body_err = get("{vault://env/add_body_param}") + local query_res, query_err = get("{vault://env/add_query_param}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.is_nil(query_err) + assert.equal(header_value, header_res) + assert.equal(body_value, body_res) + assert.equal(query_value, query_res) + end) + + it("should handle vault reference with JSON configuration", function() + local env_name = "REQUEST_TRANSFORM_CONFIG" + local env_value = '{"header": "X-API-Key:secret_key", "body": "auth_token:bearer_token_789"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local header_res, header_err = get("{vault://env/request_transform_config/header}") + local body_res, body_err = get("{vault://env/request_transform_config/body}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.equal("X-API-Key:secret_key", header_res) + assert.equal("auth_token:bearer_token_789", body_res) + end) + + it("should fail gracefully when transformation environment variables do not exist", function() + helpers.unsetenv("NON_EXISTENT_HEADER") + helpers.unsetenv("NON_EXISTENT_BODY") + helpers.unsetenv("NON_EXISTENT_QUERY") + + local header_res, header_err = get("{vault://env/non_existent_header}") + local body_res, body_err = get("{vault://env/non_existent_body}") + local query_res, query_err = get("{vault://env/non_existent_query}") + + assert.matches("could not get value from external vault", header_err) + assert.matches("could not get value from external vault", body_err) + assert.matches("could not get value from external vault", query_err) + assert.is_nil(header_res) + assert.is_nil(body_res) + assert.is_nil(query_res) + end) + + it("should handle vault reference with prefix for request transformations", function() + local header_env = "REQ_HEADER_AUTH" + local body_env = "REQ_BODY_TOKEN" + local query_env = "REQ_QUERY_KEY" + + local header_value = "X-Auth-Token:prefixed_header_token" + local body_value = "token:prefixed_body_token" + local query_value = "key:prefixed_query_key" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(body_env) + helpers.unsetenv(query_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(body_env, body_value) + helpers.setenv(query_env, query_value) + + local header_res, header_err = get("{vault://env/header_auth?prefix=req_}") + local body_res, body_err = get("{vault://env/body_token?prefix=req_}") + local query_res, query_err = get("{vault://env/query_key?prefix=req_}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.is_nil(query_err) + assert.equal(header_value, header_res) + assert.equal(body_value, body_res) + assert.equal(query_value, query_res) + end) + + it("should work with empty transformation environment variable values", function() + local header_env = "EMPTY_HEADER_TRANSFORM" + local body_env = "EMPTY_BODY_TRANSFORM" + local query_env = "EMPTY_QUERY_TRANSFORM" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(body_env) + helpers.unsetenv(query_env) + end) + + helpers.setenv(header_env, "") + helpers.setenv(body_env, "") + helpers.setenv(query_env, "") + + local header_res, header_err = get("{vault://env/empty_header_transform}") + local body_res, body_err = get("{vault://env/empty_body_transform}") + local query_res, query_err = get("{vault://env/empty_query_transform}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.is_nil(query_err) + assert.equal("", header_res) + assert.equal("", body_res) + assert.equal("", query_res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_Transform_Header") + helpers.unsetenv("Test_Transform_Body") + end) + + helpers.setenv("Test_Transform_Header", "case_sensitive_header") + helpers.setenv("Test_Transform_Body", "case_sensitive_body") + + local header_res, header_err = get("{vault://env/test_transform_header}") + local body_res, body_err = get("{vault://env/test_transform_body}") + + assert.matches("could not get value from external vault", header_err) + assert.matches("could not get value from external vault", body_err) + assert.is_nil(header_res) + assert.is_nil(body_res) + end) + + it("should work with special characters in transformation environment variable names", function() + local header_env = "TRANSFORM_HEADER_123" + local body_env = "TRANSFORM_BODY_456" + local header_value = "X-Special-Header:special_value" + local body_value = "special_param:special_body_value" + + finally(function() + helpers.unsetenv(header_env) + helpers.unsetenv(body_env) + end) + + helpers.setenv(header_env, header_value) + helpers.setenv(body_env, body_value) + + local header_res, header_err = get("{vault://env/transform_header_123}") + local body_res, body_err = get("{vault://env/transform_body_456}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.equal(header_value, header_res) + assert.equal(body_value, body_res) + end) + + it("should handle colon-separated key:value transformation formats", function() + local env_name = "COLON_TRANSFORM" + local env_value = "X-Custom-Auth:Bearer secret_token_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/colon_transform}") + assert.is_nil(err) + assert.equal(env_value, res) + + -- Verify the format matches request-transformer expectations + local key, value = env_value:match("^([^:]+):(.+)$") + assert.equal("X-Custom-Auth", key) + assert.equal("Bearer secret_token_123", value) + end) + end) + + describe("integration with request-transformer plugin", function() + it("should demonstrate vault usage in Request-Transformer context", function() + local add_header_env = "ADD_AUTH_HEADER" + local add_body_env = "ADD_API_KEY" + local add_query_env = "ADD_VERSION_PARAM" + + finally(function() + helpers.unsetenv(add_header_env) + helpers.unsetenv(add_body_env) + helpers.unsetenv(add_query_env) + end) + + helpers.setenv(add_header_env, "Authorization:Bearer vault_token_123") + helpers.setenv(add_body_env, "api_key:vault_api_key_456") + helpers.setenv(add_query_env, "version:v2") + + local resolved_header, header_err = get("{vault://env/add_auth_header}") + local resolved_body, body_err = get("{vault://env/add_api_key}") + local resolved_query, query_err = get("{vault://env/add_version_param}") + + assert.is_nil(header_err) + assert.is_nil(body_err) + assert.is_nil(query_err) + assert.equal("Authorization:Bearer vault_token_123", resolved_header) + assert.equal("api_key:vault_api_key_456", resolved_body) + assert.equal("version:v2", resolved_query) + + -- In actual usage, these resolved values would be used in: + -- config.add.headers = ["{vault://env/add_auth_header}"] + -- config.add.body = ["{vault://env/add_api_key}"] + -- config.add.querystring = ["{vault://env/add_version_param}"] + end) + + it("should handle multiple transformation operations", function() + finally(function() + helpers.unsetenv("REPLACE_HEADER") + helpers.unsetenv("APPEND_HEADER") + helpers.unsetenv("REMOVE_HEADER") + helpers.unsetenv("RENAME_HEADER") + end) + + -- Different transformation operations + helpers.setenv("REPLACE_HEADER", "X-Original-Header:X-Replaced-Header") + helpers.setenv("APPEND_HEADER", "X-Append-Token:appended_token_789") + helpers.setenv("REMOVE_HEADER", "X-Remove-This") + helpers.setenv("RENAME_HEADER", "X-Old-Name:X-New-Name") + + local replace_res, replace_err = get("{vault://env/replace_header}") + local append_res, append_err = get("{vault://env/append_header}") + local remove_res, remove_err = get("{vault://env/remove_header}") + local rename_res, rename_err = get("{vault://env/rename_header}") + + assert.is_nil(replace_err) + assert.is_nil(append_err) + assert.is_nil(remove_err) + assert.is_nil(rename_err) + + assert.equal("X-Original-Header:X-Replaced-Header", replace_res) + assert.equal("X-Append-Token:appended_token_789", append_res) + assert.equal("X-Remove-This", remove_res) + assert.equal("X-Old-Name:X-New-Name", rename_res) + + -- These would be used in configuration like: + -- config.replace.headers = ["{vault://env/replace_header}"] + -- config.append.headers = ["{vault://env/append_header}"] + -- config.remove.headers = ["{vault://env/remove_header}"] + -- config.rename.headers = ["{vault://env/rename_header}"] + end) + + it("should handle template variables in transformations", function() + finally(function() + helpers.unsetenv("TEMPLATE_HEADER") + helpers.unsetenv("TEMPLATE_BODY") + end) + + -- Template variables that can be used in request-transformer + helpers.setenv("TEMPLATE_HEADER", "X-Consumer-ID:$(headers.x_consumer_id)") + helpers.setenv("TEMPLATE_BODY", "upstream_uri:$(upstream_uri)") + + local template_header_res, template_header_err = get("{vault://env/template_header}") + local template_body_res, template_body_err = get("{vault://env/template_body}") + + assert.is_nil(template_header_err) + assert.is_nil(template_body_err) + assert.equal("X-Consumer-ID:$(headers.x_consumer_id)", template_header_res) + assert.equal("upstream_uri:$(upstream_uri)", template_body_res) + + -- These demonstrate how vault can store template expressions + -- that will be evaluated during request transformation + end) + end) +end) \ No newline at end of file From 2c83103dbcdf74e54fbcbe72b70fda4e14448290 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 13:43:13 +0200 Subject: [PATCH 13/24] feat(vault): Add tests for vault integration in oauth2 plugin --- spec/03-plugins/25-oauth2/06-vault_spec.lua | 305 ++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 spec/03-plugins/25-oauth2/06-vault_spec.lua diff --git a/spec/03-plugins/25-oauth2/06-vault_spec.lua b/spec/03-plugins/25-oauth2/06-vault_spec.lua new file mode 100644 index 00000000000..4570cba3d64 --- /dev/null +++ b/spec/03-plugins/25-oauth2/06-vault_spec.lua @@ -0,0 +1,305 @@ +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" + +describe("oauth2: (vault integration)", function() + local get + + before_each(function() + local conf = assert(conf_loader(nil, { + vaults = "bundled", + })) + + local kong_global = require "kong.global" + _G.kong = kong_global.new() + kong_global.init_pdk(kong, conf) + + get = _G.kong.vault.get + end) + + describe("oauth2 credentials vault reference resolution", function() + it("should dereference vault value for client_secret field", function() + local env_name = "OAUTH2_CLIENT_SECRET" + local env_value = "oauth2_client_secret_123" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/oauth2_client_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should dereference vault value for client_id field", function() + local env_name = "OAUTH2_CLIENT_ID" + local env_value = "oauth2_client_id_456" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/oauth2_client_id}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + + it("should handle vault reference with different environment variable names", function() + local client_id_env = "OAUTH2_APP_ID" + local client_secret_env = "OAUTH2_APP_SECRET" + local client_id_value = "app_id_789" + local client_secret_value = "app_secret_012" + + finally(function() + helpers.unsetenv(client_id_env) + helpers.unsetenv(client_secret_env) + end) + + helpers.setenv(client_id_env, client_id_value) + helpers.setenv(client_secret_env, client_secret_value) + + local client_id_res, client_id_err = get("{vault://env/oauth2_app_id}") + local client_secret_res, client_secret_err = get("{vault://env/oauth2_app_secret}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal(client_id_value, client_id_res) + assert.equal(client_secret_value, client_secret_res) + end) + + it("should handle vault reference with JSON secrets containing OAuth2 credentials", function() + local env_name = "OAUTH2_CREDENTIALS" + local env_value = '{"client_id": "oauth2_client_123", "client_secret": "oauth2_secret_456"}' + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local client_id_res, client_id_err = get("{vault://env/oauth2_credentials/client_id}") + local client_secret_res, client_secret_err = get("{vault://env/oauth2_credentials/client_secret}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal("oauth2_client_123", client_id_res) + assert.equal("oauth2_secret_456", client_secret_res) + end) + + it("should fail gracefully when OAuth2 environment variables do not exist", function() + helpers.unsetenv("NON_EXISTENT_CLIENT_ID") + helpers.unsetenv("NON_EXISTENT_CLIENT_SECRET") + + local client_id_res, client_id_err = get("{vault://env/non_existent_client_id}") + local client_secret_res, client_secret_err = get("{vault://env/non_existent_client_secret}") + + assert.matches("could not get value from external vault", client_id_err) + assert.matches("could not get value from external vault", client_secret_err) + assert.is_nil(client_id_res) + assert.is_nil(client_secret_res) + end) + + it("should handle vault reference with prefix for OAuth2 credentials", function() + local client_id_env = "OAUTH2_CLIENT_ID" + local client_secret_env = "OAUTH2_CLIENT_SECRET" + local client_id_value = "prefixed_oauth2_id" + local client_secret_value = "prefixed_oauth2_secret" + + finally(function() + helpers.unsetenv(client_id_env) + helpers.unsetenv(client_secret_env) + end) + + helpers.setenv(client_id_env, client_id_value) + helpers.setenv(client_secret_env, client_secret_value) + + local client_id_res, client_id_err = get("{vault://env/client_id?prefix=oauth2_}") + local client_secret_res, client_secret_err = get("{vault://env/client_secret?prefix=oauth2_}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal(client_id_value, client_id_res) + assert.equal(client_secret_value, client_secret_res) + end) + + it("should work with empty OAuth2 environment variable values", function() + local client_id_env = "EMPTY_OAUTH2_CLIENT_ID" + local client_secret_env = "EMPTY_OAUTH2_CLIENT_SECRET" + + finally(function() + helpers.unsetenv(client_id_env) + helpers.unsetenv(client_secret_env) + end) + + helpers.setenv(client_id_env, "") + helpers.setenv(client_secret_env, "") + + local client_id_res, client_id_err = get("{vault://env/empty_oauth2_client_id}") + local client_secret_res, client_secret_err = get("{vault://env/empty_oauth2_client_secret}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal("", client_id_res) + assert.equal("", client_secret_res) + end) + + it("should handle hash_secret boolean field from environment", function() + local env_name = "OAUTH2_HASH_SECRET" + local env_value = "true" + + finally(function() + helpers.unsetenv(env_name) + end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/oauth2_hash_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) + end) + + describe("edge cases and validation", function() + it("should handle malformed vault references gracefully", function() + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + if res then + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) + else + assert.is_string(err, "Should have error message for malformed reference: " .. ref) + end + end + end) + + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_OAuth2_Client_Id") + helpers.unsetenv("Test_OAuth2_Client_Secret") + end) + + helpers.setenv("Test_OAuth2_Client_Id", "case_sensitive_client_id") + helpers.setenv("Test_OAuth2_Client_Secret", "case_sensitive_client_secret") + + local client_id_res, client_id_err = get("{vault://env/test_oauth2_client_id}") + local client_secret_res, client_secret_err = get("{vault://env/test_oauth2_client_secret}") + + assert.matches("could not get value from external vault", client_id_err) + assert.matches("could not get value from external vault", client_secret_err) + assert.is_nil(client_id_res) + assert.is_nil(client_secret_res) + end) + + it("should work with special characters in OAuth2 environment variable names", function() + local client_id_env = "OAUTH2_CLIENT_ID_123" + local client_secret_env = "OAUTH2_CLIENT_SECRET_456" + local client_id_value = "special_oauth2_client_id" + local client_secret_value = "special_oauth2_client_secret" + + finally(function() + helpers.unsetenv(client_id_env) + helpers.unsetenv(client_secret_env) + end) + + helpers.setenv(client_id_env, client_id_value) + helpers.setenv(client_secret_env, client_secret_value) + + local client_id_res, client_id_err = get("{vault://env/oauth2_client_id_123}") + local client_secret_res, client_secret_err = get("{vault://env/oauth2_client_secret_456}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal(client_id_value, client_id_res) + assert.equal(client_secret_value, client_secret_res) + end) + end) + + describe("integration with oauth2 plugin", function() + it("should demonstrate vault usage in OAuth2 context", function() + local client_id_env = "OAUTH2_INTEGRATION_CLIENT_ID" + local client_secret_env = "OAUTH2_INTEGRATION_CLIENT_SECRET" + + finally(function() + helpers.unsetenv(client_id_env) + helpers.unsetenv(client_secret_env) + end) + + helpers.setenv(client_id_env, "secure_oauth2_client_id") + helpers.setenv(client_secret_env, "secure_oauth2_client_secret") + + local resolved_client_id, client_id_err = get("{vault://env/oauth2_integration_client_id}") + local resolved_client_secret, client_secret_err = get("{vault://env/oauth2_integration_client_secret}") + + assert.is_nil(client_id_err) + assert.is_nil(client_secret_err) + assert.equal("secure_oauth2_client_id", resolved_client_id) + assert.equal("secure_oauth2_client_secret", resolved_client_secret) + + -- In actual usage, these resolved values would be used to: + -- 1. Create OAuth2 application credentials with resolved client_id and client_secret + -- 2. Validate OAuth2 authorization requests using the resolved credentials + -- 3. The client_secret is used to authenticate the application during token exchange + -- 4. The client_id identifies the OAuth2 application making the request + end) + + it("should handle multiple OAuth2 application scenarios", function() + finally(function() + helpers.unsetenv("OAUTH2_WEB_CLIENT_ID") + helpers.unsetenv("OAUTH2_WEB_CLIENT_SECRET") + helpers.unsetenv("OAUTH2_MOBILE_CLIENT_ID") + helpers.unsetenv("OAUTH2_MOBILE_CLIENT_SECRET") + end) + + -- Web application credentials + helpers.setenv("OAUTH2_WEB_CLIENT_ID", "web_app_client_id") + helpers.setenv("OAUTH2_WEB_CLIENT_SECRET", "web_app_client_secret") + + -- Mobile application credentials + helpers.setenv("OAUTH2_MOBILE_CLIENT_ID", "mobile_app_client_id") + helpers.setenv("OAUTH2_MOBILE_CLIENT_SECRET", "mobile_app_client_secret") + + local web_id_res, web_id_err = get("{vault://env/oauth2_web_client_id}") + local web_secret_res, web_secret_err = get("{vault://env/oauth2_web_client_secret}") + local mobile_id_res, mobile_id_err = get("{vault://env/oauth2_mobile_client_id}") + local mobile_secret_res, mobile_secret_err = get("{vault://env/oauth2_mobile_client_secret}") + + assert.is_nil(web_id_err) + assert.is_nil(web_secret_err) + assert.is_nil(mobile_id_err) + assert.is_nil(mobile_secret_err) + + assert.equal("web_app_client_id", web_id_res) + assert.equal("web_app_client_secret", web_secret_res) + assert.equal("mobile_app_client_id", mobile_id_res) + assert.equal("mobile_app_client_secret", mobile_secret_res) + end) + + it("should handle OAuth2 configuration flags", function() + finally(function() + helpers.unsetenv("OAUTH2_INTEGRATION_HASH_SECRET") + end) + + helpers.setenv("OAUTH2_INTEGRATION_HASH_SECRET", "false") + + local hash_secret_res, hash_secret_err = get("{vault://env/oauth2_integration_hash_secret}") + + assert.is_nil(hash_secret_err) + assert.equal("false", hash_secret_res) + + -- In actual usage: + -- - hash_secret=true: client_secret will be hashed before storage + -- - hash_secret=false: client_secret will be stored as plaintext + -- The vault reference resolves the boolean value as string + end) + end) +end) \ No newline at end of file From 8be60d575ce15111c2a852049f489526a8df2d65 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 15:20:28 +0200 Subject: [PATCH 14/24] fix(changelog): Fix filename for changelog --- ...s => feat-add-vault-template-support-in-different-plugins.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename changelog/unreleased/kong/{feat-add-vault-template-support-in-different-plugins => feat-add-vault-template-support-in-different-plugins.yml} (100%) diff --git a/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins b/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml similarity index 100% rename from changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins rename to changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml From 33e9a9090a69342e05082ad02934df0818e70acb Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 16 Oct 2025 15:32:54 +0200 Subject: [PATCH 15/24] fix(changelog): Fix typo in changelog file --- .../feat-add-vault-template-support-in-different-plugins.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml b/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml index ef4686acd1e..7094502644f 100644 --- a/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml +++ b/changelog/unreleased/kong/feat-add-vault-template-support-in-different-plugins.yml @@ -1,3 +1,3 @@ -message: Added an option to use {vault://} in specific fields of plugins" +message: Added an option to use {vault://} in specific fields of plugins type: feature scope: Plugin From 6bdaab0d613db70229a7d3b72cbf6fd781a6d26d Mon Sep 17 00:00:00 2001 From: lordgreg Date: Tue, 4 Nov 2025 07:44:50 +0100 Subject: [PATCH 16/24] fix(test): Update linting error --- .../10-basic-auth/06-vault_spec.lua | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/03-plugins/10-basic-auth/06-vault_spec.lua b/spec/03-plugins/10-basic-auth/06-vault_spec.lua index d8c8e072be5..240157648ab 100644 --- a/spec/03-plugins/10-basic-auth/06-vault_spec.lua +++ b/spec/03-plugins/10-basic-auth/06-vault_spec.lua @@ -1,5 +1,5 @@ -local helpers = require "spec.helpers" -local conf_loader = require "kong.conf_loader" +local helpers = require("spec.helpers") +local conf_loader = require("kong.conf_loader") describe("basic-auth: (vault integration)", function() local get @@ -9,7 +9,7 @@ describe("basic-auth: (vault integration)", function() vaults = "bundled", })) - local kong_global = require "kong.global" + local kong_global = require("kong.global") _G.kong = kong_global.new() kong_global.init_pdk(kong, conf) @@ -17,7 +17,7 @@ describe("basic-auth: (vault integration)", function() end) describe("vault reference resolution", function() - it("should handle all variations of variable name", function () + it("should handle all variations of variable name", function() local env_name = "MY_VAR_NAME" local env_value = "complex_value_789" @@ -67,7 +67,7 @@ describe("basic-auth: (vault integration)", function() it("should fail gracefully when environment variable does not exist", function() helpers.unsetenv("NON_EXISTENT_VAR") - + local res, err = get("{vault://env/non_existent_var}") assert.matches("could not get value from external vault", err) assert.is_nil(res) @@ -115,7 +115,7 @@ describe("basic-auth: (vault integration)", function() local username_res, username_err = get("{vault://env/auth_username}") local password_res, password_err = get("{vault://env/auth_password}") - + assert.is_nil(username_err) assert.is_nil(password_err) assert.equal("vault_user_both", username_res) @@ -148,10 +148,12 @@ describe("basic-auth: (vault integration)", function() it("should preserve non-vault values unchanged", function() local regular_value = "regular_password" - + local res, err = get(regular_value) if res then assert.equal(regular_value, res) + else + assert.is_nil(err) end end) @@ -173,10 +175,10 @@ describe("basic-auth: (vault integration)", function() end) describe("integration with basic-auth plugin", function() - it("should demonstrate vault usage in basic-auth context", function() + it("should demonstrate vault usage in basic-auth context", function() local password_env = "BASIC_AUTH_PASSWORD" local username_env = "BASIC_AUTH_USERNAME" - + finally(function() helpers.unsetenv(password_env) helpers.unsetenv(username_env) @@ -195,4 +197,5 @@ describe("basic-auth: (vault integration)", function() assert.equal("secure_username", resolved_username) end) end) -end) \ No newline at end of file +end) + From f6ae29d153c33706326b4f020447896c9d81564c Mon Sep 17 00:00:00 2001 From: lordgreg Date: Wed, 14 Jan 2026 09:55:26 +0100 Subject: [PATCH 17/24] Revert "feat(response-transformer): Add option to use {vault://} in fields" This reverts commit a852681c25c860785a4623dc199197fc5a25cacf. --- kong/plugins/response-transformer/schema.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/kong/plugins/response-transformer/schema.lua b/kong/plugins/response-transformer/schema.lua index e34a225eeeb..28bd6fc967b 100644 --- a/kong/plugins/response-transformer/schema.lua +++ b/kong/plugins/response-transformer/schema.lua @@ -25,7 +25,6 @@ local string_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string" }, } @@ -34,7 +33,6 @@ local colon_string_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", match = "^[^:]+:.*$" }, } @@ -55,7 +53,6 @@ local colon_string_record = { { json_types = { description = "List of JSON type names. Specify the types of the JSON values returned when appending\nJSON properties. Each string element can be one of: boolean, number, or string.", type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", one_of = { "boolean", "number", "string" } @@ -69,7 +66,6 @@ local colon_headers_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", match = "^[^:]+:.*$", custom_validator = validate_colon_headers }, } From b8e188f74d4f74914a741e6a85533453c147836a Mon Sep 17 00:00:00 2001 From: lordgreg Date: Wed, 14 Jan 2026 09:55:33 +0100 Subject: [PATCH 18/24] Revert "feat(request-transformer): Add option to use {vault://} in fields" This reverts commit 7bd8bb31e0716bfb771930ee031d7e49c05f40ea. --- kong/plugins/request-transformer/schema.lua | 4 ---- 1 file changed, 4 deletions(-) diff --git a/kong/plugins/request-transformer/schema.lua b/kong/plugins/request-transformer/schema.lua index a1a8370c35b..b8828b1cd58 100644 --- a/kong/plugins/request-transformer/schema.lua +++ b/kong/plugins/request-transformer/schema.lua @@ -50,7 +50,6 @@ local strings_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string" }, } @@ -59,7 +58,6 @@ local headers_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", custom_validator = validate_headers }, } @@ -78,7 +76,6 @@ local colon_strings_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", custom_validator = check_for_value } } @@ -105,7 +102,6 @@ local colon_headers_array = { type = "array", default = {}, required = true, - referenceable = true, elements = { type = "string", match = "^[^:]+:.*$", custom_validator = validate_colon_headers }, } From 8aa9286e729027a7131fd8fdde8f99196b49a82b Mon Sep 17 00:00:00 2001 From: lordgreg Date: Wed, 14 Jan 2026 10:55:12 +0100 Subject: [PATCH 19/24] feat(vault): Remove referenceable from hash_secret in oauth2 dao --- kong/plugins/oauth2/daos.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kong/plugins/oauth2/daos.lua b/kong/plugins/oauth2/daos.lua index 5f865008997..a65b4fa2ec6 100644 --- a/kong/plugins/oauth2/daos.lua +++ b/kong/plugins/oauth2/daos.lua @@ -33,7 +33,7 @@ local oauth2_credentials = { { name = { type = "string", required = true }, }, { client_id = { type = "string", required = false, unique = true, auto = true, referenceable = true }, }, { client_secret = { type = "string", required = false, auto = true, encrypted = true, referenceable = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature. It does nothing in Kong CE - { hash_secret = { type = "boolean", required = true, default = false, referenceable = true }, }, + { hash_secret = { type = "boolean", required = true, default = false }, }, { redirect_uris = { type = "array", required = false, From c190c959cb9b0a20c3e346ce804918a2990207af Mon Sep 17 00:00:00 2001 From: lordgreg Date: Wed, 14 Jan 2026 13:38:14 +0100 Subject: [PATCH 20/24] fix(test): Correct filename for vault spec --- .../{03-schem-vault_spec.lua => 03-schema-vault_spec.lua} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename spec/03-plugins/03-http-log/{03-schem-vault_spec.lua => 03-schema-vault_spec.lua} (100%) diff --git a/spec/03-plugins/03-http-log/03-schem-vault_spec.lua b/spec/03-plugins/03-http-log/03-schema-vault_spec.lua similarity index 100% rename from spec/03-plugins/03-http-log/03-schem-vault_spec.lua rename to spec/03-plugins/03-http-log/03-schema-vault_spec.lua From ad160aa1004971fa09570a7e7e958d232b48ef3e Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 15 Jan 2026 09:43:29 +0100 Subject: [PATCH 21/24] fix(test): remove vault specs --- .../15-response-transformer/06-vault_spec.lua | 374 ------------------ .../36-request-transformer/06-vault_spec.lua | 364 ----------------- 2 files changed, 738 deletions(-) delete mode 100644 spec/03-plugins/15-response-transformer/06-vault_spec.lua delete mode 100644 spec/03-plugins/36-request-transformer/06-vault_spec.lua diff --git a/spec/03-plugins/15-response-transformer/06-vault_spec.lua b/spec/03-plugins/15-response-transformer/06-vault_spec.lua deleted file mode 100644 index 4e489cb11cf..00000000000 --- a/spec/03-plugins/15-response-transformer/06-vault_spec.lua +++ /dev/null @@ -1,374 +0,0 @@ -local helpers = require "spec.helpers" -local conf_loader = require "kong.conf_loader" - -describe("response-transformer: (vault integration)", function() - local get - - before_each(function() - local conf = assert(conf_loader(nil, { - vaults = "bundled", - })) - - local kong_global = require "kong.global" - _G.kong = kong_global.new() - kong_global.init_pdk(kong, conf) - - get = _G.kong.vault.get - end) - - describe("response-transformer configuration vault reference resolution", function() - it("should dereference vault value for header transformation", function() - local env_name = "RESPONSE_HEADER_VALUE" - local env_value = "X-Custom-Response-Header-Value" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/response_header_value}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should dereference vault value for JSON response transformation", function() - local env_name = "RESPONSE_JSON_VALUE" - local env_value = "transformed_json_field" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/response_json_value}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with different transformation types", function() - local header_env = "ADD_RESPONSE_HEADER" - local json_env = "ADD_RESPONSE_JSON" - - local header_value = "X-API-Version:2.0" - local json_value = "api_response_status:success" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(json_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(json_env, json_value) - - local header_res, header_err = get("{vault://env/add_response_header}") - local json_res, json_err = get("{vault://env/add_response_json}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal(header_value, header_res) - assert.equal(json_value, json_res) - end) - - it("should handle vault reference with JSON configuration", function() - local env_name = "RESPONSE_TRANSFORM_CONFIG" - local env_value = '{"header": "X-Response-ID:response_id_123", "json": "metadata:response_metadata"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local header_res, header_err = get("{vault://env/response_transform_config/header}") - local json_res, json_err = get("{vault://env/response_transform_config/json}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal("X-Response-ID:response_id_123", header_res) - assert.equal("metadata:response_metadata", json_res) - end) - - it("should fail gracefully when response transformation environment variables do not exist", function() - helpers.unsetenv("NON_EXISTENT_RESPONSE_HEADER") - helpers.unsetenv("NON_EXISTENT_RESPONSE_JSON") - - local header_res, header_err = get("{vault://env/non_existent_response_header}") - local json_res, json_err = get("{vault://env/non_existent_response_json}") - - assert.matches("could not get value from external vault", header_err) - assert.matches("could not get value from external vault", json_err) - assert.is_nil(header_res) - assert.is_nil(json_res) - end) - - it("should handle vault reference with prefix for response transformations", function() - local header_env = "RESP_HEADER_CORS" - local json_env = "RESP_JSON_STATUS" - - local header_value = "Access-Control-Allow-Origin:*" - local json_value = "response_code:200" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(json_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(json_env, json_value) - - local header_res, header_err = get("{vault://env/header_cors?prefix=resp_}") - local json_res, json_err = get("{vault://env/json_status?prefix=resp_}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal(header_value, header_res) - assert.equal(json_value, json_res) - end) - - it("should work with empty response transformation environment variable values", function() - local header_env = "EMPTY_RESPONSE_HEADER" - local json_env = "EMPTY_RESPONSE_JSON" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(json_env) - end) - - helpers.setenv(header_env, "") - helpers.setenv(json_env, "") - - local header_res, header_err = get("{vault://env/empty_response_header}") - local json_res, json_err = get("{vault://env/empty_response_json}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal("", header_res) - assert.equal("", json_res) - end) - - it("should handle security headers from environment variables", function() - local security_env = "SECURITY_HEADERS" - local security_value = "X-Frame-Options:DENY" - - finally(function() - helpers.unsetenv(security_env) - end) - - helpers.setenv(security_env, security_value) - - local res, err = get("{vault://env/security_headers}") - assert.is_nil(err) - assert.equal(security_value, res) - end) - end) - - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_Response_Header") - helpers.unsetenv("Test_Response_JSON") - end) - - helpers.setenv("Test_Response_Header", "case_sensitive_header") - helpers.setenv("Test_Response_JSON", "case_sensitive_json") - - local header_res, header_err = get("{vault://env/test_response_header}") - local json_res, json_err = get("{vault://env/test_response_json}") - - assert.matches("could not get value from external vault", header_err) - assert.matches("could not get value from external vault", json_err) - assert.is_nil(header_res) - assert.is_nil(json_res) - end) - - it("should work with special characters in response transformation environment variable names", function() - local header_env = "RESPONSE_HEADER_123" - local json_env = "RESPONSE_JSON_456" - local header_value = "X-Special-Response:special_response_value" - local json_value = "special_field:special_json_value" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(json_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(json_env, json_value) - - local header_res, header_err = get("{vault://env/response_header_123}") - local json_res, json_err = get("{vault://env/response_json_456}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal(header_value, header_res) - assert.equal(json_value, json_res) - end) - - it("should handle colon-separated key:value transformation formats", function() - local env_name = "COLON_RESPONSE_TRANSFORM" - local env_value = "Cache-Control:no-cache, no-store, must-revalidate" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/colon_response_transform}") - assert.is_nil(err) - assert.equal(env_value, res) - - -- Verify the format matches response-transformer expectations - local key, value = env_value:match("^([^:]+):(.+)$") - assert.equal("Cache-Control", key) - assert.equal("no-cache, no-store, must-revalidate", value) - end) - end) - - describe("integration with response-transformer plugin", function() - it("should demonstrate vault usage in Response-Transformer context", function() - local add_header_env = "ADD_CORS_HEADER" - local add_json_env = "ADD_METADATA" - - finally(function() - helpers.unsetenv(add_header_env) - helpers.unsetenv(add_json_env) - end) - - helpers.setenv(add_header_env, "Access-Control-Allow-Origin:https://example.com") - helpers.setenv(add_json_env, "server_info:kong_gateway_v3") - - local resolved_header, header_err = get("{vault://env/add_cors_header}") - local resolved_json, json_err = get("{vault://env/add_metadata}") - - assert.is_nil(header_err) - assert.is_nil(json_err) - assert.equal("Access-Control-Allow-Origin:https://example.com", resolved_header) - assert.equal("server_info:kong_gateway_v3", resolved_json) - - -- In actual usage, these resolved values would be used in: - -- config.add.headers = ["{vault://env/add_cors_header}"] - -- config.add.json = ["{vault://env/add_metadata}"] - end) - - it("should handle multiple response transformation operations", function() - finally(function() - helpers.unsetenv("REPLACE_RESPONSE_HEADER") - helpers.unsetenv("APPEND_RESPONSE_HEADER") - helpers.unsetenv("REMOVE_RESPONSE_HEADER") - helpers.unsetenv("RENAME_RESPONSE_HEADER") - end) - - -- Different transformation operations for responses - helpers.setenv("REPLACE_RESPONSE_HEADER", "Server:Kong-Gateway") - helpers.setenv("APPEND_RESPONSE_HEADER", "X-Rate-Limit-Remaining:1000") - helpers.setenv("REMOVE_RESPONSE_HEADER", "X-Internal-Header") - helpers.setenv("RENAME_RESPONSE_HEADER", "X-Old-Response:X-New-Response") - - local replace_res, replace_err = get("{vault://env/replace_response_header}") - local append_res, append_err = get("{vault://env/append_response_header}") - local remove_res, remove_err = get("{vault://env/remove_response_header}") - local rename_res, rename_err = get("{vault://env/rename_response_header}") - - assert.is_nil(replace_err) - assert.is_nil(append_err) - assert.is_nil(remove_err) - assert.is_nil(rename_err) - - assert.equal("Server:Kong-Gateway", replace_res) - assert.equal("X-Rate-Limit-Remaining:1000", append_res) - assert.equal("X-Internal-Header", remove_res) - assert.equal("X-Old-Response:X-New-Response", rename_res) - - -- These would be used in configuration like: - -- config.replace.headers = ["{vault://env/replace_response_header}"] - -- config.append.headers = ["{vault://env/append_response_header}"] - -- config.remove.headers = ["{vault://env/remove_response_header}"] - -- config.rename.headers = ["{vault://env/rename_response_header}"] - end) - - it("should handle JSON response transformations", function() - finally(function() - helpers.unsetenv("ADD_JSON_FIELD") - helpers.unsetenv("REPLACE_JSON_FIELD") - helpers.unsetenv("REMOVE_JSON_FIELD") - helpers.unsetenv("RENAME_JSON_FIELD") - end) - - -- JSON transformation operations - helpers.setenv("ADD_JSON_FIELD", "timestamp:$(now)") - helpers.setenv("REPLACE_JSON_FIELD", "status:processed") - helpers.setenv("REMOVE_JSON_FIELD", "internal_id") - helpers.setenv("RENAME_JSON_FIELD", "old_field:new_field") - - local add_res, add_err = get("{vault://env/add_json_field}") - local replace_res, replace_err = get("{vault://env/replace_json_field}") - local remove_res, remove_err = get("{vault://env/remove_json_field}") - local rename_res, rename_err = get("{vault://env/rename_json_field}") - - assert.is_nil(add_err) - assert.is_nil(replace_err) - assert.is_nil(remove_err) - assert.is_nil(rename_err) - - assert.equal("timestamp:$(now)", add_res) - assert.equal("status:processed", replace_res) - assert.equal("internal_id", remove_res) - assert.equal("old_field:new_field", rename_res) - - -- These would be used in configuration like: - -- config.add.json = ["{vault://env/add_json_field}"] - -- config.replace.json = ["{vault://env/replace_json_field}"] - -- config.remove.json = ["{vault://env/remove_json_field}"] - -- config.rename.json = ["{vault://env/rename_json_field}"] - end) - - it("should handle security and compliance headers", function() - finally(function() - helpers.unsetenv("SECURITY_HEADERS_CSP") - helpers.unsetenv("SECURITY_HEADERS_HSTS") - helpers.unsetenv("SECURITY_HEADERS_CT") - end) - - -- Common security headers - helpers.setenv("SECURITY_HEADERS_CSP", "Content-Security-Policy:default-src 'self'") - helpers.setenv("SECURITY_HEADERS_HSTS", "Strict-Transport-Security:max-age=31536000") - helpers.setenv("SECURITY_HEADERS_CT", "Content-Type:application/json; charset=utf-8") - - local csp_res, csp_err = get("{vault://env/security_headers_csp}") - local hsts_res, hsts_err = get("{vault://env/security_headers_hsts}") - local ct_res, ct_err = get("{vault://env/security_headers_ct}") - - assert.is_nil(csp_err) - assert.is_nil(hsts_err) - assert.is_nil(ct_err) - - assert.equal("Content-Security-Policy:default-src 'self'", csp_res) - assert.equal("Strict-Transport-Security:max-age=31536000", hsts_res) - assert.equal("Content-Type:application/json; charset=utf-8", ct_res) - - -- These demonstrate vault usage for security compliance - end) - end) -end) \ No newline at end of file diff --git a/spec/03-plugins/36-request-transformer/06-vault_spec.lua b/spec/03-plugins/36-request-transformer/06-vault_spec.lua deleted file mode 100644 index 5a18b06e828..00000000000 --- a/spec/03-plugins/36-request-transformer/06-vault_spec.lua +++ /dev/null @@ -1,364 +0,0 @@ -local helpers = require "spec.helpers" -local conf_loader = require "kong.conf_loader" - -describe("request-transformer: (vault integration)", function() - local get - - before_each(function() - local conf = assert(conf_loader(nil, { - vaults = "bundled", - })) - - local kong_global = require "kong.global" - _G.kong = kong_global.new() - kong_global.init_pdk(kong, conf) - - get = _G.kong.vault.get - end) - - describe("request-transformer configuration vault reference resolution", function() - it("should dereference vault value for header transformation", function() - local env_name = "REQUEST_HEADER_VALUE" - local env_value = "X-Custom-Header-Value" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/request_header_value}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should dereference vault value for body transformation", function() - local env_name = "REQUEST_BODY_VALUE" - local env_value = "transformed_body_content" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/request_body_value}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should dereference vault value for querystring transformation", function() - local env_name = "REQUEST_QUERY_VALUE" - local env_value = "query_param_value_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/request_query_value}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with different transformation types", function() - local header_env = "ADD_HEADER_VALUE" - local body_env = "ADD_BODY_PARAM" - local query_env = "ADD_QUERY_PARAM" - - local header_value = "Authorization: Bearer secret_token" - local body_value = "api_key:secret_api_key_123" - local query_value = "token:query_token_456" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(body_env) - helpers.unsetenv(query_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(body_env, body_value) - helpers.setenv(query_env, query_value) - - local header_res, header_err = get("{vault://env/add_header_value}") - local body_res, body_err = get("{vault://env/add_body_param}") - local query_res, query_err = get("{vault://env/add_query_param}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.is_nil(query_err) - assert.equal(header_value, header_res) - assert.equal(body_value, body_res) - assert.equal(query_value, query_res) - end) - - it("should handle vault reference with JSON configuration", function() - local env_name = "REQUEST_TRANSFORM_CONFIG" - local env_value = '{"header": "X-API-Key:secret_key", "body": "auth_token:bearer_token_789"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local header_res, header_err = get("{vault://env/request_transform_config/header}") - local body_res, body_err = get("{vault://env/request_transform_config/body}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.equal("X-API-Key:secret_key", header_res) - assert.equal("auth_token:bearer_token_789", body_res) - end) - - it("should fail gracefully when transformation environment variables do not exist", function() - helpers.unsetenv("NON_EXISTENT_HEADER") - helpers.unsetenv("NON_EXISTENT_BODY") - helpers.unsetenv("NON_EXISTENT_QUERY") - - local header_res, header_err = get("{vault://env/non_existent_header}") - local body_res, body_err = get("{vault://env/non_existent_body}") - local query_res, query_err = get("{vault://env/non_existent_query}") - - assert.matches("could not get value from external vault", header_err) - assert.matches("could not get value from external vault", body_err) - assert.matches("could not get value from external vault", query_err) - assert.is_nil(header_res) - assert.is_nil(body_res) - assert.is_nil(query_res) - end) - - it("should handle vault reference with prefix for request transformations", function() - local header_env = "REQ_HEADER_AUTH" - local body_env = "REQ_BODY_TOKEN" - local query_env = "REQ_QUERY_KEY" - - local header_value = "X-Auth-Token:prefixed_header_token" - local body_value = "token:prefixed_body_token" - local query_value = "key:prefixed_query_key" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(body_env) - helpers.unsetenv(query_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(body_env, body_value) - helpers.setenv(query_env, query_value) - - local header_res, header_err = get("{vault://env/header_auth?prefix=req_}") - local body_res, body_err = get("{vault://env/body_token?prefix=req_}") - local query_res, query_err = get("{vault://env/query_key?prefix=req_}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.is_nil(query_err) - assert.equal(header_value, header_res) - assert.equal(body_value, body_res) - assert.equal(query_value, query_res) - end) - - it("should work with empty transformation environment variable values", function() - local header_env = "EMPTY_HEADER_TRANSFORM" - local body_env = "EMPTY_BODY_TRANSFORM" - local query_env = "EMPTY_QUERY_TRANSFORM" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(body_env) - helpers.unsetenv(query_env) - end) - - helpers.setenv(header_env, "") - helpers.setenv(body_env, "") - helpers.setenv(query_env, "") - - local header_res, header_err = get("{vault://env/empty_header_transform}") - local body_res, body_err = get("{vault://env/empty_body_transform}") - local query_res, query_err = get("{vault://env/empty_query_transform}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.is_nil(query_err) - assert.equal("", header_res) - assert.equal("", body_res) - assert.equal("", query_res) - end) - end) - - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_Transform_Header") - helpers.unsetenv("Test_Transform_Body") - end) - - helpers.setenv("Test_Transform_Header", "case_sensitive_header") - helpers.setenv("Test_Transform_Body", "case_sensitive_body") - - local header_res, header_err = get("{vault://env/test_transform_header}") - local body_res, body_err = get("{vault://env/test_transform_body}") - - assert.matches("could not get value from external vault", header_err) - assert.matches("could not get value from external vault", body_err) - assert.is_nil(header_res) - assert.is_nil(body_res) - end) - - it("should work with special characters in transformation environment variable names", function() - local header_env = "TRANSFORM_HEADER_123" - local body_env = "TRANSFORM_BODY_456" - local header_value = "X-Special-Header:special_value" - local body_value = "special_param:special_body_value" - - finally(function() - helpers.unsetenv(header_env) - helpers.unsetenv(body_env) - end) - - helpers.setenv(header_env, header_value) - helpers.setenv(body_env, body_value) - - local header_res, header_err = get("{vault://env/transform_header_123}") - local body_res, body_err = get("{vault://env/transform_body_456}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.equal(header_value, header_res) - assert.equal(body_value, body_res) - end) - - it("should handle colon-separated key:value transformation formats", function() - local env_name = "COLON_TRANSFORM" - local env_value = "X-Custom-Auth:Bearer secret_token_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/colon_transform}") - assert.is_nil(err) - assert.equal(env_value, res) - - -- Verify the format matches request-transformer expectations - local key, value = env_value:match("^([^:]+):(.+)$") - assert.equal("X-Custom-Auth", key) - assert.equal("Bearer secret_token_123", value) - end) - end) - - describe("integration with request-transformer plugin", function() - it("should demonstrate vault usage in Request-Transformer context", function() - local add_header_env = "ADD_AUTH_HEADER" - local add_body_env = "ADD_API_KEY" - local add_query_env = "ADD_VERSION_PARAM" - - finally(function() - helpers.unsetenv(add_header_env) - helpers.unsetenv(add_body_env) - helpers.unsetenv(add_query_env) - end) - - helpers.setenv(add_header_env, "Authorization:Bearer vault_token_123") - helpers.setenv(add_body_env, "api_key:vault_api_key_456") - helpers.setenv(add_query_env, "version:v2") - - local resolved_header, header_err = get("{vault://env/add_auth_header}") - local resolved_body, body_err = get("{vault://env/add_api_key}") - local resolved_query, query_err = get("{vault://env/add_version_param}") - - assert.is_nil(header_err) - assert.is_nil(body_err) - assert.is_nil(query_err) - assert.equal("Authorization:Bearer vault_token_123", resolved_header) - assert.equal("api_key:vault_api_key_456", resolved_body) - assert.equal("version:v2", resolved_query) - - -- In actual usage, these resolved values would be used in: - -- config.add.headers = ["{vault://env/add_auth_header}"] - -- config.add.body = ["{vault://env/add_api_key}"] - -- config.add.querystring = ["{vault://env/add_version_param}"] - end) - - it("should handle multiple transformation operations", function() - finally(function() - helpers.unsetenv("REPLACE_HEADER") - helpers.unsetenv("APPEND_HEADER") - helpers.unsetenv("REMOVE_HEADER") - helpers.unsetenv("RENAME_HEADER") - end) - - -- Different transformation operations - helpers.setenv("REPLACE_HEADER", "X-Original-Header:X-Replaced-Header") - helpers.setenv("APPEND_HEADER", "X-Append-Token:appended_token_789") - helpers.setenv("REMOVE_HEADER", "X-Remove-This") - helpers.setenv("RENAME_HEADER", "X-Old-Name:X-New-Name") - - local replace_res, replace_err = get("{vault://env/replace_header}") - local append_res, append_err = get("{vault://env/append_header}") - local remove_res, remove_err = get("{vault://env/remove_header}") - local rename_res, rename_err = get("{vault://env/rename_header}") - - assert.is_nil(replace_err) - assert.is_nil(append_err) - assert.is_nil(remove_err) - assert.is_nil(rename_err) - - assert.equal("X-Original-Header:X-Replaced-Header", replace_res) - assert.equal("X-Append-Token:appended_token_789", append_res) - assert.equal("X-Remove-This", remove_res) - assert.equal("X-Old-Name:X-New-Name", rename_res) - - -- These would be used in configuration like: - -- config.replace.headers = ["{vault://env/replace_header}"] - -- config.append.headers = ["{vault://env/append_header}"] - -- config.remove.headers = ["{vault://env/remove_header}"] - -- config.rename.headers = ["{vault://env/rename_header}"] - end) - - it("should handle template variables in transformations", function() - finally(function() - helpers.unsetenv("TEMPLATE_HEADER") - helpers.unsetenv("TEMPLATE_BODY") - end) - - -- Template variables that can be used in request-transformer - helpers.setenv("TEMPLATE_HEADER", "X-Consumer-ID:$(headers.x_consumer_id)") - helpers.setenv("TEMPLATE_BODY", "upstream_uri:$(upstream_uri)") - - local template_header_res, template_header_err = get("{vault://env/template_header}") - local template_body_res, template_body_err = get("{vault://env/template_body}") - - assert.is_nil(template_header_err) - assert.is_nil(template_body_err) - assert.equal("X-Consumer-ID:$(headers.x_consumer_id)", template_header_res) - assert.equal("upstream_uri:$(upstream_uri)", template_body_res) - - -- These demonstrate how vault can store template expressions - -- that will be evaluated during request transformation - end) - end) -end) \ No newline at end of file From b671a5e037985de7275c04d83173855bd66d3f27 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 15 Jan 2026 09:49:35 +0100 Subject: [PATCH 22/24] tests(jwt): update vault tests to match test semantics --- spec/03-plugins/16-jwt/06-vault_spec.lua | 289 +++++++++++------------ 1 file changed, 134 insertions(+), 155 deletions(-) diff --git a/spec/03-plugins/16-jwt/06-vault_spec.lua b/spec/03-plugins/16-jwt/06-vault_spec.lua index 796af19ee76..aecb879b9c1 100644 --- a/spec/03-plugins/16-jwt/06-vault_spec.lua +++ b/spec/03-plugins/16-jwt/06-vault_spec.lua @@ -1,215 +1,194 @@ local helpers = require "spec.helpers" local conf_loader = require "kong.conf_loader" -describe("jwt: (vault integration)", function() - local get +local PLUGIN_NAME = "jwt" - before_each(function() +describe(PLUGIN_NAME .. ": (vault integration)", function() + local plugins_schema = assert(Entity.new(plugins_schema_def)) + + lazy_setup(function() local conf = assert(conf_loader(nil, { vaults = "bundled", + plugins = "bundled", })) local kong_global = require "kong.global" _G.kong = kong_global.new() kong_global.init_pdk(kong, conf) - get = _G.kong.vault.get + local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema") + assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - describe("jwt credentials vault reference resolution", function() - it("should dereference vault value for secret field", function() - local env_name = "JWT_SECRET" - local env_value = "jwt_secret_key_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) + it("should dereference vault value for secret field", function() + local env_name = "JWT_SECRET" + local env_value = "jwt_secret_key_123" - local res, err = get("{vault://env/jwt_secret}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle vault reference with different environment variable name", function() - local env_name = "JWT_PRIVATE_KEY" - local env_value = "private_key_456" + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/jwt_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) - helpers.setenv(env_name, env_value) + it("should handle vault reference with different environment variable name", function() + local env_name = "JWT_PRIVATE_KEY" + local env_value = "private_key_456" - local res, err = get("{vault://env/jwt_private_key}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle vault reference with JSON secret containing JWT keys", function() - local env_name = "JWT_KEYS" - local env_value = '{"secret": "jwt_secret_789", "algorithm": "HS256"}' + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/jwt_private_key}") + assert.is_nil(err) + assert.equal(env_value, res) + end) - helpers.setenv(env_name, env_value) + it("should handle vault reference with JSON secret containing JWT keys", function() + local env_name = "JWT_KEYS" + local env_value = '{"secret": "jwt_secret_789", "algorithm": "HS256"}' - local res, err = get("{vault://env/jwt_keys/secret}") - assert.is_nil(err) - assert.equal("jwt_secret_789", res) + finally(function() + helpers.unsetenv(env_name) end) - it("should fail gracefully when JWT secret environment variable does not exist", function() - helpers.unsetenv("NON_EXISTENT_JWT_SECRET") - - local res, err = get("{vault://env/non_existent_jwt_secret}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) - end) + helpers.setenv(env_name, env_value) - it("should handle vault reference with prefix for JWT secrets", function() - local env_name = "JWT_SECRET" - local env_value = "prefixed_jwt_secret" + local res, err = get("{vault://env/jwt_keys/secret}") + assert.is_nil(err) + assert.equal("jwt_secret_789", res) + end) + + it("should fail gracefully when JWT secret environment variable does not exist", function() + helpers.unsetenv("NON_EXISTENT_JWT_SECRET") - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/non_existent_jwt_secret}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) - helpers.setenv(env_name, env_value) + it("should handle vault reference with prefix for JWT secrets", function() + local env_name = "JWT_SECRET" + local env_value = "prefixed_jwt_secret" - local res, err = get("{vault://env/secret?prefix=jwt_}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) - it("should work with empty JWT secret environment variable value", function() - local env_name = "EMPTY_JWT_SECRET" + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/secret?prefix=jwt_}") + assert.is_nil(err) + assert.equal(env_value, res) + end) - helpers.setenv(env_name, "") + it("should work with empty JWT secret environment variable value", function() + local env_name = "EMPTY_JWT_SECRET" - local res, err = get("{vault://env/empty_jwt_secret}") - assert.is_nil(err) - assert.equal("", res) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle RSA public key from environment variable", function() - local env_name = "JWT_RSA_PUBLIC_KEY" - local env_value = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----" + helpers.setenv(env_name, "") - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/empty_jwt_secret}") + assert.is_nil(err) + assert.equal("", res) + end) - helpers.setenv(env_name, env_value) + it("should handle RSA public key from environment variable", function() + local env_name = "JWT_RSA_PUBLIC_KEY" + local env_value = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----" - local res, err = get("{vault://env/jwt_rsa_public_key}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_rsa_public_key}") + assert.is_nil(err) + assert.equal(env_value, res) end) - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end + it("should demonstrate vault usage in JWT context", function() + local secret_env = "JWT_INTEGRATION_SECRET" + local key_env = "JWT_INTEGRATION_KEY" + + finally(function() + helpers.unsetenv(secret_env) + helpers.unsetenv(key_env) end) - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_JWT_Secret") - end) + helpers.setenv(secret_env, "secure_jwt_secret_123") + helpers.setenv(key_env, "jwt_consumer_key") + + local resolved_secret, secret_err = get("{vault://env/jwt_integration_secret}") + local resolved_key, key_err = get("{vault://env/jwt_integration_key}") - helpers.setenv("Test_JWT_Secret", "case_sensitive_jwt_value") + assert.is_nil(secret_err) + assert.is_nil(key_err) + assert.equal("secure_jwt_secret_123", resolved_secret) + assert.equal("jwt_consumer_key", resolved_key) - local res, err = get("{vault://env/test_jwt_secret}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) + -- In actual usage, these resolved values would be used to: + -- 1. Create JWT credentials with resolved secret + -- 2. Validate JWT tokens using the resolved secret + -- 3. The secret can be used for HS256/HS384/HS512 algorithms + -- 4. For RSA algorithms, the secret would contain the private/public key + end) + + it("should handle both symmetric and asymmetric key scenarios", function() + finally(function() + helpers.unsetenv("JWT_HMAC_SECRET") + helpers.unsetenv("JWT_RSA_PRIVATE_KEY") end) - it("should work with special characters in JWT environment variable names", function() - local env_name = "JWT_SECRET_123" - local env_value = "special_jwt_value" + -- Symmetric key scenario (HMAC) + helpers.setenv("JWT_HMAC_SECRET", "hmac_shared_secret") - finally(function() - helpers.unsetenv(env_name) - end) + -- Asymmetric key scenario (RSA) + helpers.setenv("JWT_RSA_PRIVATE_KEY", "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----") - helpers.setenv(env_name, env_value) + local hmac_res, hmac_err = get("{vault://env/jwt_hmac_secret}") + local rsa_res, rsa_err = get("{vault://env/jwt_rsa_private_key}") - local res, err = get("{vault://env/jwt_secret_123}") - assert.is_nil(err) - assert.equal(env_value, res) - end) + assert.is_nil(hmac_err) + assert.is_nil(rsa_err) + assert.equal("hmac_shared_secret", hmac_res) + assert.equal("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", rsa_res) end) - describe("integration with jwt plugin", function() - it("should demonstrate vault usage in JWT context", function() - local secret_env = "JWT_INTEGRATION_SECRET" - local key_env = "JWT_INTEGRATION_KEY" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(key_env) - end) - - helpers.setenv(secret_env, "secure_jwt_secret_123") - helpers.setenv(key_env, "jwt_consumer_key") - - local resolved_secret, secret_err = get("{vault://env/jwt_integration_secret}") - local resolved_key, key_err = get("{vault://env/jwt_integration_key}") - - assert.is_nil(secret_err) - assert.is_nil(key_err) - assert.equal("secure_jwt_secret_123", resolved_secret) - assert.equal("jwt_consumer_key", resolved_key) - - -- In actual usage, these resolved values would be used to: - -- 1. Create JWT credentials with resolved secret - -- 2. Validate JWT tokens using the resolved secret - -- 3. The secret can be used for HS256/HS384/HS512 algorithms - -- 4. For RSA algorithms, the secret would contain the private/public key + it("should handle case sensitivity correctly", function() + finally(function() + helpers.unsetenv("Test_JWT_Secret") end) - it("should handle both symmetric and asymmetric key scenarios", function() - finally(function() - helpers.unsetenv("JWT_HMAC_SECRET") - helpers.unsetenv("JWT_RSA_PRIVATE_KEY") - end) - - -- Symmetric key scenario (HMAC) - helpers.setenv("JWT_HMAC_SECRET", "hmac_shared_secret") - - -- Asymmetric key scenario (RSA) - helpers.setenv("JWT_RSA_PRIVATE_KEY", "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----") - - local hmac_res, hmac_err = get("{vault://env/jwt_hmac_secret}") - local rsa_res, rsa_err = get("{vault://env/jwt_rsa_private_key}") - - assert.is_nil(hmac_err) - assert.is_nil(rsa_err) - assert.equal("hmac_shared_secret", hmac_res) - assert.equal("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", rsa_res) + helpers.setenv("Test_JWT_Secret", "case_sensitive_jwt_value") + + local res, err = get("{vault://env/test_jwt_secret}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) + + it("should work with special characters in JWT environment variable names", function() + local env_name = "JWT_SECRET_123" + local env_value = "special_jwt_value" + + finally(function() + helpers.unsetenv(env_name) end) + + helpers.setenv(env_name, env_value) + + local res, err = get("{vault://env/jwt_secret_123}") + assert.is_nil(err) + assert.equal(env_value, res) end) end) \ No newline at end of file From 17c9ac2dc00024b5601a9dee7341d11a016c0ca4 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Thu, 15 Jan 2026 10:24:38 +0100 Subject: [PATCH 23/24] tests(basic-auth): update vault tests to match test semantics --- .../10-basic-auth/06-vault_spec.lua | 249 +++++++----------- 1 file changed, 100 insertions(+), 149 deletions(-) diff --git a/spec/03-plugins/10-basic-auth/06-vault_spec.lua b/spec/03-plugins/10-basic-auth/06-vault_spec.lua index 240157648ab..9d140e34024 100644 --- a/spec/03-plugins/10-basic-auth/06-vault_spec.lua +++ b/spec/03-plugins/10-basic-auth/06-vault_spec.lua @@ -1,201 +1,152 @@ -local helpers = require("spec.helpers") -local conf_loader = require("kong.conf_loader") +local helpers = require "spec.helpers" +local conf_loader = require "kong.conf_loader" -describe("basic-auth: (vault integration)", function() - local get +local PLUGIN_NAME = "basic-auth" - before_each(function() +describe(PLUGIN_NAME .. ": (schema-vault)", function() + local plugins_schema = assert(Entity.new(plugins_schema_def)) + + lazy_setup(function() local conf = assert(conf_loader(nil, { vaults = "bundled", + plugins = "bundled", })) - local kong_global = require("kong.global") + local kong_global = require "kong.global" _G.kong = kong_global.new() kong_global.init_pdk(kong, conf) - get = _G.kong.vault.get + local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema") + assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - describe("vault reference resolution", function() - it("should handle all variations of variable name", function() - local env_name = "MY_VAR_NAME" - local env_value = "complex_value_789" - - finally(function() - helpers.unsetenv(env_name) - end) - helpers.setenv(env_name, env_value) + it("should handle vault reference with different environment variable name", function() + local env_name = "BASIC_AUTH_SECRET" + local env_value = "another_secret_456" - assert.equal(env_value, get("{vault://env/MY_VAR_NAME}")) - assert.equal(env_value, get("{vault://env/MY-VAR-NAME}")) - assert.equal(env_value, get("{vault://env/my_var_name}")) - assert.equal(env_value, get("{vault://env/my-var-name}")) - assert.equal(env_value, get("{vault://env/My_Var_Name}")) - assert.equal(env_value, get("{vault://env/My-Var-Name}")) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle vault reference with different environment variable name", function() - local env_name = "BASIC_AUTH_SECRET" - local env_value = "another_secret_456" + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/basic_auth_secret}") + assert.is_nil(err) + assert.equal(env_value, res) + end) - helpers.setenv(env_name, env_value) + it("should handle vault reference with JSON secret", function() + local env_name = "TEST_JSON_SECRETS" + local env_value = '{"username": "json_user", "password": "db_secret_789"}' - local res, err = get("{vault://env/basic_auth_secret}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle vault reference with JSON secret", function() - local env_name = "TEST_JSON_SECRETS" - local env_value = '{"username": "json_user", "password": "db_secret_789"}' + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/test_json_secrets/password}") + assert.is_nil(err) + assert.equal("db_secret_789", res) + end) - helpers.setenv(env_name, env_value) + it("should fail gracefully when environment variable does not exist", function() + helpers.unsetenv("NON_EXISTENT_VAR") - local res, err = get("{vault://env/test_json_secrets/password}") - assert.is_nil(err) - assert.equal("db_secret_789", res) - end) + local res, err = get("{vault://env/non_existent_var}") + assert.matches("could not get value from external vault", err) + assert.is_nil(res) + end) - it("should fail gracefully when environment variable does not exist", function() - helpers.unsetenv("NON_EXISTENT_VAR") + it("should handle vault reference with prefix", function() + local env_name = "TEST_PASSWORD" + local env_value = "prefixed_secret" - local res, err = get("{vault://env/non_existent_var}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) + finally(function() + helpers.unsetenv(env_name) end) - it("should handle vault reference with prefix", function() - local env_name = "TEST_PASSWORD" - local env_value = "prefixed_secret" + helpers.setenv(env_name, env_value) - finally(function() - helpers.unsetenv(env_name) - end) + local res, err = get("{vault://env/password?prefix=test_}") + assert.is_nil(err) + assert.equal(env_value, res) + end) - helpers.setenv(env_name, env_value) + it("should work with empty environment variable value", function() + local env_name = "EMPTY_PASSWORD" - local res, err = get("{vault://env/password?prefix=test_}") - assert.is_nil(err) - assert.equal(env_value, res) + finally(function() + helpers.unsetenv(env_name) end) - it("should work with empty environment variable value", function() - local env_name = "EMPTY_PASSWORD" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, "") + helpers.setenv(env_name, "") - local res, err = get("{vault://env/empty_password}") - assert.is_nil(err) - assert.equal("", res) - end) + local res, err = get("{vault://env/empty_password}") + assert.is_nil(err) + assert.equal("", res) end) - describe("username field vault references", function() - it("should handle both username and password as vault references", function() - finally(function() - helpers.unsetenv("AUTH_USERNAME") - helpers.unsetenv("AUTH_PASSWORD") - end) + it("should handle both username and password as vault references", function() + finally(function() + helpers.unsetenv("AUTH_USERNAME") + helpers.unsetenv("AUTH_PASSWORD") + end) - helpers.setenv("AUTH_USERNAME", "vault_user_both") - helpers.setenv("AUTH_PASSWORD", "vault_pass_both") + helpers.setenv("AUTH_USERNAME", "vault_user_both") + helpers.setenv("AUTH_PASSWORD", "vault_pass_both") - local username_res, username_err = get("{vault://env/auth_username}") - local password_res, password_err = get("{vault://env/auth_password}") + local username_res, username_err = get("{vault://env/auth_username}") + local password_res, password_err = get("{vault://env/auth_password}") - assert.is_nil(username_err) - assert.is_nil(password_err) - assert.equal("vault_user_both", username_res) - assert.equal("vault_pass_both", password_res) - end) + assert.is_nil(username_err) + assert.is_nil(password_err) + assert.equal("vault_user_both", username_res) + assert.equal("vault_pass_both", password_res) end) - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - -- Test various malformed vault references - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - "{vault://env/valid_name?invalid_query=", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - -- Should either return nil with error, or return the original string unchanged - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should preserve non-vault values unchanged", function() - local regular_value = "regular_password" - - local res, err = get(regular_value) + it("should handle malformed vault references gracefully", function() + -- Test various malformed vault references + local malformed_refs = { + "{vault://invalid/format", + "vault://env/missing_braces}", + "{vault://env/}", + "{vault://env}", + "{vault://}", + } + + for _, ref in ipairs(malformed_refs) do + local res, err = get(ref) + -- Should either return nil with error, or return the original string unchanged if res then - assert.equal(regular_value, res) + assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) else - assert.is_nil(err) + assert.is_string(err, "Should have error message for malformed reference: " .. ref) end - end) - - it("should work with special characters in environment variable names", function() - local env_name = "SPECIAL_CHARS_(1337@)" - local env_value = "special_value" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - assert.equal(env_value, get("{vault://env/SPECIAL_CHARS_(1337@)}")) - assert.equal(env_value, get("{vault://env/SPECIAL-CHARS_(1337@)}")) - assert.equal(env_value, get("{vault://env/special-chars_(1337@)}")) - assert.equal(env_value, get("{vault://env/special_chars_(1337@)}")) - end) + end end) - describe("integration with basic-auth plugin", function() - it("should demonstrate vault usage in basic-auth context", function() - local password_env = "BASIC_AUTH_PASSWORD" - local username_env = "BASIC_AUTH_USERNAME" + it("should demonstrate vault usage in basic-auth context", function() + local password_env = "BASIC_AUTH_PASSWORD" + local username_env = "BASIC_AUTH_USERNAME" - finally(function() - helpers.unsetenv(password_env) - helpers.unsetenv(username_env) - end) + finally(function() + helpers.unsetenv(password_env) + helpers.unsetenv(username_env) + end) - helpers.setenv(password_env, "secure_password_123") - helpers.setenv(username_env, "secure_username") + helpers.setenv(password_env, "secure_password_123") + helpers.setenv(username_env, "secure_username") - -- Simulate how basic-auth would resolve vault references - local resolved_password, pass_err = get("{vault://env/basic_auth_password}") - local resolved_username, user_err = get("{vault://env/basic_auth_username}") + -- Simulate how basic-auth would resolve vault references + local resolved_password, pass_err = get("{vault://env/basic_auth_password}") + local resolved_username, user_err = get("{vault://env/basic_auth_username}") - assert.is_nil(pass_err) - assert.is_nil(user_err) - assert.equal("secure_password_123", resolved_password) - assert.equal("secure_username", resolved_username) - end) + assert.is_nil(pass_err) + assert.is_nil(user_err) + assert.equal("secure_password_123", resolved_password) + assert.equal("secure_username", resolved_username) end) end) From 0877600fa92fba5a3cb6e021330361fb7f5db662 Mon Sep 17 00:00:00 2001 From: lordgreg Date: Tue, 3 Feb 2026 07:41:01 +0100 Subject: [PATCH 24/24] fix(test): Standardize vault-referenced tests --- .../10-basic-auth/06-vault_spec.lua | 133 +------- spec/03-plugins/16-jwt/06-vault_spec.lua | 179 +--------- .../03-plugins/19-hmac-auth/06-vault_spec.lua | 277 ++-------------- spec/03-plugins/25-oauth2/06-vault_spec.lua | 310 ++---------------- 4 files changed, 78 insertions(+), 821 deletions(-) diff --git a/spec/03-plugins/10-basic-auth/06-vault_spec.lua b/spec/03-plugins/10-basic-auth/06-vault_spec.lua index 9d140e34024..54c1a8e2987 100644 --- a/spec/03-plugins/10-basic-auth/06-vault_spec.lua +++ b/spec/03-plugins/10-basic-auth/06-vault_spec.lua @@ -1,8 +1,11 @@ local helpers = require "spec.helpers" +local Entity = require "kong.db.schema.entity" +local plugins_schema_def = require "kong.db.schema.entities.plugins" local conf_loader = require "kong.conf_loader" local PLUGIN_NAME = "basic-auth" + describe(PLUGIN_NAME .. ": (schema-vault)", function() local plugins_schema = assert(Entity.new(plugins_schema_def)) @@ -20,10 +23,9 @@ describe(PLUGIN_NAME .. ": (schema-vault)", function() assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - - it("should handle vault reference with different environment variable name", function() - local env_name = "BASIC_AUTH_SECRET" - local env_value = "another_secret_456" + it("should dereference vault value", function() + local env_name = "BASIC_AUTH_HIDE_CREDENTIALS" + local env_value = "true" finally(function() helpers.unsetenv(env_name) @@ -31,122 +33,13 @@ describe(PLUGIN_NAME .. ": (schema-vault)", function() helpers.setenv(env_name, env_value) - local res, err = get("{vault://env/basic_auth_secret}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with JSON secret", function() - local env_name = "TEST_JSON_SECRETS" - local env_value = '{"username": "json_user", "password": "db_secret_789"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/test_json_secrets/password}") - assert.is_nil(err) - assert.equal("db_secret_789", res) - end) - - it("should fail gracefully when environment variable does not exist", function() - helpers.unsetenv("NON_EXISTENT_VAR") + local entity = plugins_schema:process_auto_fields({ + name = PLUGIN_NAME, + config = { + hide_credentials = "{vault://env/basic-auth-hide-credentials}" + }, + }, "select") - local res, err = get("{vault://env/non_existent_var}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) - end) - - it("should handle vault reference with prefix", function() - local env_name = "TEST_PASSWORD" - local env_value = "prefixed_secret" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/password?prefix=test_}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should work with empty environment variable value", function() - local env_name = "EMPTY_PASSWORD" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, "") - - local res, err = get("{vault://env/empty_password}") - assert.is_nil(err) - assert.equal("", res) - end) - - it("should handle both username and password as vault references", function() - finally(function() - helpers.unsetenv("AUTH_USERNAME") - helpers.unsetenv("AUTH_PASSWORD") - end) - - helpers.setenv("AUTH_USERNAME", "vault_user_both") - helpers.setenv("AUTH_PASSWORD", "vault_pass_both") - - local username_res, username_err = get("{vault://env/auth_username}") - local password_res, password_err = get("{vault://env/auth_password}") - - assert.is_nil(username_err) - assert.is_nil(password_err) - assert.equal("vault_user_both", username_res) - assert.equal("vault_pass_both", password_res) - end) - - it("should handle malformed vault references gracefully", function() - -- Test various malformed vault references - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - -- Should either return nil with error, or return the original string unchanged - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should demonstrate vault usage in basic-auth context", function() - local password_env = "BASIC_AUTH_PASSWORD" - local username_env = "BASIC_AUTH_USERNAME" - - finally(function() - helpers.unsetenv(password_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(password_env, "secure_password_123") - helpers.setenv(username_env, "secure_username") - - -- Simulate how basic-auth would resolve vault references - local resolved_password, pass_err = get("{vault://env/basic_auth_password}") - local resolved_username, user_err = get("{vault://env/basic_auth_username}") - - assert.is_nil(pass_err) - assert.is_nil(user_err) - assert.equal("secure_password_123", resolved_password) - assert.equal("secure_username", resolved_username) + assert.equal(env_value, entity.config.hide_credentials) end) end) - diff --git a/spec/03-plugins/16-jwt/06-vault_spec.lua b/spec/03-plugins/16-jwt/06-vault_spec.lua index aecb879b9c1..582a32997fb 100644 --- a/spec/03-plugins/16-jwt/06-vault_spec.lua +++ b/spec/03-plugins/16-jwt/06-vault_spec.lua @@ -1,9 +1,12 @@ local helpers = require "spec.helpers" +local Entity = require "kong.db.schema.entity" +local plugins_schema_def = require "kong.db.schema.entities.plugins" local conf_loader = require "kong.conf_loader" local PLUGIN_NAME = "jwt" -describe(PLUGIN_NAME .. ": (vault integration)", function() + +describe(PLUGIN_NAME .. ": (schema-vault)", function() local plugins_schema = assert(Entity.new(plugins_schema_def)) lazy_setup(function() @@ -20,62 +23,9 @@ describe(PLUGIN_NAME .. ": (vault integration)", function() assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - it("should dereference vault value for secret field", function() - local env_name = "JWT_SECRET" - local env_value = "jwt_secret_key_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/jwt_secret}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with different environment variable name", function() - local env_name = "JWT_PRIVATE_KEY" - local env_value = "private_key_456" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/jwt_private_key}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with JSON secret containing JWT keys", function() - local env_name = "JWT_KEYS" - local env_value = '{"secret": "jwt_secret_789", "algorithm": "HS256"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/jwt_keys/secret}") - assert.is_nil(err) - assert.equal("jwt_secret_789", res) - end) - - it("should fail gracefully when JWT secret environment variable does not exist", function() - helpers.unsetenv("NON_EXISTENT_JWT_SECRET") - - local res, err = get("{vault://env/non_existent_jwt_secret}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) - end) - - it("should handle vault reference with prefix for JWT secrets", function() - local env_name = "JWT_SECRET" - local env_value = "prefixed_jwt_secret" + it("should dereference vault value", function() + local env_name = "JWT_SECRET_IS_BASE64" + local env_value = "true" finally(function() helpers.unsetenv(env_name) @@ -83,112 +33,13 @@ describe(PLUGIN_NAME .. ": (vault integration)", function() helpers.setenv(env_name, env_value) - local res, err = get("{vault://env/secret?prefix=jwt_}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should work with empty JWT secret environment variable value", function() - local env_name = "EMPTY_JWT_SECRET" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, "") - - local res, err = get("{vault://env/empty_jwt_secret}") - assert.is_nil(err) - assert.equal("", res) - end) - - it("should handle RSA public key from environment variable", function() - local env_name = "JWT_RSA_PUBLIC_KEY" - local env_value = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/jwt_rsa_public_key}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should demonstrate vault usage in JWT context", function() - local secret_env = "JWT_INTEGRATION_SECRET" - local key_env = "JWT_INTEGRATION_KEY" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(key_env) - end) - - helpers.setenv(secret_env, "secure_jwt_secret_123") - helpers.setenv(key_env, "jwt_consumer_key") - - local resolved_secret, secret_err = get("{vault://env/jwt_integration_secret}") - local resolved_key, key_err = get("{vault://env/jwt_integration_key}") - - assert.is_nil(secret_err) - assert.is_nil(key_err) - assert.equal("secure_jwt_secret_123", resolved_secret) - assert.equal("jwt_consumer_key", resolved_key) - - -- In actual usage, these resolved values would be used to: - -- 1. Create JWT credentials with resolved secret - -- 2. Validate JWT tokens using the resolved secret - -- 3. The secret can be used for HS256/HS384/HS512 algorithms - -- 4. For RSA algorithms, the secret would contain the private/public key - end) - - it("should handle both symmetric and asymmetric key scenarios", function() - finally(function() - helpers.unsetenv("JWT_HMAC_SECRET") - helpers.unsetenv("JWT_RSA_PRIVATE_KEY") - end) - - -- Symmetric key scenario (HMAC) - helpers.setenv("JWT_HMAC_SECRET", "hmac_shared_secret") - - -- Asymmetric key scenario (RSA) - helpers.setenv("JWT_RSA_PRIVATE_KEY", "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----") - - local hmac_res, hmac_err = get("{vault://env/jwt_hmac_secret}") - local rsa_res, rsa_err = get("{vault://env/jwt_rsa_private_key}") - - assert.is_nil(hmac_err) - assert.is_nil(rsa_err) - assert.equal("hmac_shared_secret", hmac_res) - assert.equal("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", rsa_res) - end) - - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_JWT_Secret") - end) - - helpers.setenv("Test_JWT_Secret", "case_sensitive_jwt_value") - - local res, err = get("{vault://env/test_jwt_secret}") - assert.matches("could not get value from external vault", err) - assert.is_nil(res) - end) - - it("should work with special characters in JWT environment variable names", function() - local env_name = "JWT_SECRET_123" - local env_value = "special_jwt_value" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) + local entity = plugins_schema:process_auto_fields({ + name = PLUGIN_NAME, + config = { + secret_is_base64 = "{vault://env/jwt-secret-is-base64}" + }, + }, "select") - local res, err = get("{vault://env/jwt_secret_123}") - assert.is_nil(err) - assert.equal(env_value, res) + assert.equal(env_value, entity.config.secret_is_base64) end) -end) \ No newline at end of file +end) diff --git a/spec/03-plugins/19-hmac-auth/06-vault_spec.lua b/spec/03-plugins/19-hmac-auth/06-vault_spec.lua index 9647a361600..1be1e299998 100644 --- a/spec/03-plugins/19-hmac-auth/06-vault_spec.lua +++ b/spec/03-plugins/19-hmac-auth/06-vault_spec.lua @@ -1,272 +1,45 @@ local helpers = require "spec.helpers" +local Entity = require "kong.db.schema.entity" +local plugins_schema_def = require "kong.db.schema.entities.plugins" local conf_loader = require "kong.conf_loader" -describe("hmac-auth: (vault integration)", function() - local get +local PLUGIN_NAME = "hmac-auth" - before_each(function() + +describe(PLUGIN_NAME .. ": (schema-vault)", function() + local plugins_schema = assert(Entity.new(plugins_schema_def)) + + lazy_setup(function() local conf = assert(conf_loader(nil, { vaults = "bundled", + plugins = "bundled", })) local kong_global = require "kong.global" _G.kong = kong_global.new() kong_global.init_pdk(kong, conf) - get = _G.kong.vault.get - end) - - describe("hmac-auth credentials vault reference resolution", function() - it("should dereference vault value for secret field", function() - local env_name = "HMAC_SECRET" - local env_value = "hmac_secret_key_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/hmac_secret}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should dereference vault value for username field", function() - local env_name = "HMAC_USERNAME" - local env_value = "hmac_user_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/hmac_username}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with different environment variable names", function() - local secret_env = "HMAC_PRIVATE_SECRET" - local username_env = "HMAC_USER_ID" - local secret_value = "private_hmac_456" - local username_value = "hmac_consumer" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(secret_env, secret_value) - helpers.setenv(username_env, username_value) - - local secret_res, secret_err = get("{vault://env/hmac_private_secret}") - local username_res, username_err = get("{vault://env/hmac_user_id}") - - assert.is_nil(secret_err) - assert.is_nil(username_err) - assert.equal(secret_value, secret_res) - assert.equal(username_value, username_res) - end) - - it("should handle vault reference with JSON secrets containing HMAC credentials", function() - local env_name = "HMAC_CREDENTIALS" - local env_value = '{"username": "hmac_user", "secret": "hmac_secret_789"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local username_res, username_err = get("{vault://env/hmac_credentials/username}") - local secret_res, secret_err = get("{vault://env/hmac_credentials/secret}") - - assert.is_nil(username_err) - assert.is_nil(secret_err) - assert.equal("hmac_user", username_res) - assert.equal("hmac_secret_789", secret_res) - end) - - it("should fail gracefully when HMAC environment variables do not exist", function() - helpers.unsetenv("NON_EXISTENT_HMAC_SECRET") - helpers.unsetenv("NON_EXISTENT_HMAC_USERNAME") - - local secret_res, secret_err = get("{vault://env/non_existent_hmac_secret}") - local username_res, username_err = get("{vault://env/non_existent_hmac_username}") - - assert.matches("could not get value from external vault", secret_err) - assert.matches("could not get value from external vault", username_err) - assert.is_nil(secret_res) - assert.is_nil(username_res) - end) - - it("should handle vault reference with prefix for HMAC credentials", function() - local secret_env = "HMAC_SECRET" - local username_env = "HMAC_USERNAME" - local secret_value = "prefixed_hmac_secret" - local username_value = "prefixed_hmac_user" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(secret_env, secret_value) - helpers.setenv(username_env, username_value) - - local secret_res, secret_err = get("{vault://env/secret?prefix=hmac_}") - local username_res, username_err = get("{vault://env/username?prefix=hmac_}") - - assert.is_nil(secret_err) - assert.is_nil(username_err) - assert.equal(secret_value, secret_res) - assert.equal(username_value, username_res) - end) - - it("should work with empty HMAC environment variable values", function() - local secret_env = "EMPTY_HMAC_SECRET" - local username_env = "EMPTY_HMAC_USERNAME" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(secret_env, "") - helpers.setenv(username_env, "") - - local secret_res, secret_err = get("{vault://env/empty_hmac_secret}") - local username_res, username_err = get("{vault://env/empty_hmac_username}") - - assert.is_nil(secret_err) - assert.is_nil(username_err) - assert.equal("", secret_res) - assert.equal("", username_res) - end) - end) - - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_HMAC_Secret") - helpers.unsetenv("Test_HMAC_User") - end) - - helpers.setenv("Test_HMAC_Secret", "case_sensitive_hmac_secret") - helpers.setenv("Test_HMAC_User", "case_sensitive_hmac_user") - - local secret_res, secret_err = get("{vault://env/test_hmac_secret}") - local user_res, user_err = get("{vault://env/test_hmac_user}") - - assert.matches("could not get value from external vault", secret_err) - assert.matches("could not get value from external vault", user_err) - assert.is_nil(secret_res) - assert.is_nil(user_res) - end) - - it("should work with special characters in HMAC environment variable names", function() - local secret_env = "HMAC_SECRET_123" - local username_env = "HMAC_USER_456" - local secret_value = "special_hmac_secret" - local username_value = "special_hmac_user" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(secret_env, secret_value) - helpers.setenv(username_env, username_value) - - local secret_res, secret_err = get("{vault://env/hmac_secret_123}") - local username_res, username_err = get("{vault://env/hmac_user_456}") - - assert.is_nil(secret_err) - assert.is_nil(username_err) - assert.equal(secret_value, secret_res) - assert.equal(username_value, username_res) - end) + local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema") + assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - describe("integration with hmac-auth plugin", function() - it("should demonstrate vault usage in HMAC-Auth context", function() - local secret_env = "HMAC_INTEGRATION_SECRET" - local username_env = "HMAC_INTEGRATION_USERNAME" - - finally(function() - helpers.unsetenv(secret_env) - helpers.unsetenv(username_env) - end) - - helpers.setenv(secret_env, "secure_hmac_secret_123") - helpers.setenv(username_env, "secure_hmac_username") + it("should dereference vault value", function() + local env_name = "HMAC_AUTH_HIDE_CREDENTIALS" + local env_value = "true" - local resolved_secret, secret_err = get("{vault://env/hmac_integration_secret}") - local resolved_username, username_err = get("{vault://env/hmac_integration_username}") - - assert.is_nil(secret_err) - assert.is_nil(username_err) - assert.equal("secure_hmac_secret_123", resolved_secret) - assert.equal("secure_hmac_username", resolved_username) - - -- In actual usage, these resolved values would be used to: - -- 1. Create HMAC-Auth credentials with resolved username and secret - -- 2. Validate HMAC signatures using the resolved secret - -- 3. The secret is used to generate HMAC signatures for request validation - -- 4. The username identifies the consumer making the request + finally(function() + helpers.unsetenv(env_name) end) - it("should handle multiple HMAC credential scenarios", function() - finally(function() - helpers.unsetenv("HMAC_ADMIN_SECRET") - helpers.unsetenv("HMAC_ADMIN_USER") - helpers.unsetenv("HMAC_API_SECRET") - helpers.unsetenv("HMAC_API_USER") - end) + helpers.setenv(env_name, env_value) - -- Admin credentials - helpers.setenv("HMAC_ADMIN_SECRET", "admin_hmac_secret") - helpers.setenv("HMAC_ADMIN_USER", "admin_user") - - -- API credentials - helpers.setenv("HMAC_API_SECRET", "api_hmac_secret") - helpers.setenv("HMAC_API_USER", "api_user") + local entity = plugins_schema:process_auto_fields({ + name = PLUGIN_NAME, + config = { + hide_credentials = "{vault://env/hmac-auth-hide-credentials}" + }, + }, "select") - local admin_secret_res, admin_secret_err = get("{vault://env/hmac_admin_secret}") - local admin_user_res, admin_user_err = get("{vault://env/hmac_admin_user}") - local api_secret_res, api_secret_err = get("{vault://env/hmac_api_secret}") - local api_user_res, api_user_err = get("{vault://env/hmac_api_user}") - - assert.is_nil(admin_secret_err) - assert.is_nil(admin_user_err) - assert.is_nil(api_secret_err) - assert.is_nil(api_user_err) - - assert.equal("admin_hmac_secret", admin_secret_res) - assert.equal("admin_user", admin_user_res) - assert.equal("api_hmac_secret", api_secret_res) - assert.equal("api_user", api_user_res) - end) + assert.equal(env_value, entity.config.hide_credentials) end) -end) \ No newline at end of file +end) diff --git a/spec/03-plugins/25-oauth2/06-vault_spec.lua b/spec/03-plugins/25-oauth2/06-vault_spec.lua index 4570cba3d64..191a3568033 100644 --- a/spec/03-plugins/25-oauth2/06-vault_spec.lua +++ b/spec/03-plugins/25-oauth2/06-vault_spec.lua @@ -1,305 +1,45 @@ local helpers = require "spec.helpers" +local Entity = require "kong.db.schema.entity" +local plugins_schema_def = require "kong.db.schema.entities.plugins" local conf_loader = require "kong.conf_loader" -describe("oauth2: (vault integration)", function() - local get +local PLUGIN_NAME = "oauth2" - before_each(function() + +describe(PLUGIN_NAME .. ": (schema-vault)", function() + local plugins_schema = assert(Entity.new(plugins_schema_def)) + + lazy_setup(function() local conf = assert(conf_loader(nil, { vaults = "bundled", + plugins = "bundled", })) local kong_global = require "kong.global" _G.kong = kong_global.new() kong_global.init_pdk(kong, conf) - get = _G.kong.vault.get - end) - - describe("oauth2 credentials vault reference resolution", function() - it("should dereference vault value for client_secret field", function() - local env_name = "OAUTH2_CLIENT_SECRET" - local env_value = "oauth2_client_secret_123" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/oauth2_client_secret}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should dereference vault value for client_id field", function() - local env_name = "OAUTH2_CLIENT_ID" - local env_value = "oauth2_client_id_456" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/oauth2_client_id}") - assert.is_nil(err) - assert.equal(env_value, res) - end) - - it("should handle vault reference with different environment variable names", function() - local client_id_env = "OAUTH2_APP_ID" - local client_secret_env = "OAUTH2_APP_SECRET" - local client_id_value = "app_id_789" - local client_secret_value = "app_secret_012" - - finally(function() - helpers.unsetenv(client_id_env) - helpers.unsetenv(client_secret_env) - end) - - helpers.setenv(client_id_env, client_id_value) - helpers.setenv(client_secret_env, client_secret_value) - - local client_id_res, client_id_err = get("{vault://env/oauth2_app_id}") - local client_secret_res, client_secret_err = get("{vault://env/oauth2_app_secret}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal(client_id_value, client_id_res) - assert.equal(client_secret_value, client_secret_res) - end) - - it("should handle vault reference with JSON secrets containing OAuth2 credentials", function() - local env_name = "OAUTH2_CREDENTIALS" - local env_value = '{"client_id": "oauth2_client_123", "client_secret": "oauth2_secret_456"}' - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local client_id_res, client_id_err = get("{vault://env/oauth2_credentials/client_id}") - local client_secret_res, client_secret_err = get("{vault://env/oauth2_credentials/client_secret}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal("oauth2_client_123", client_id_res) - assert.equal("oauth2_secret_456", client_secret_res) - end) - - it("should fail gracefully when OAuth2 environment variables do not exist", function() - helpers.unsetenv("NON_EXISTENT_CLIENT_ID") - helpers.unsetenv("NON_EXISTENT_CLIENT_SECRET") - - local client_id_res, client_id_err = get("{vault://env/non_existent_client_id}") - local client_secret_res, client_secret_err = get("{vault://env/non_existent_client_secret}") - - assert.matches("could not get value from external vault", client_id_err) - assert.matches("could not get value from external vault", client_secret_err) - assert.is_nil(client_id_res) - assert.is_nil(client_secret_res) - end) - - it("should handle vault reference with prefix for OAuth2 credentials", function() - local client_id_env = "OAUTH2_CLIENT_ID" - local client_secret_env = "OAUTH2_CLIENT_SECRET" - local client_id_value = "prefixed_oauth2_id" - local client_secret_value = "prefixed_oauth2_secret" - - finally(function() - helpers.unsetenv(client_id_env) - helpers.unsetenv(client_secret_env) - end) - - helpers.setenv(client_id_env, client_id_value) - helpers.setenv(client_secret_env, client_secret_value) - - local client_id_res, client_id_err = get("{vault://env/client_id?prefix=oauth2_}") - local client_secret_res, client_secret_err = get("{vault://env/client_secret?prefix=oauth2_}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal(client_id_value, client_id_res) - assert.equal(client_secret_value, client_secret_res) - end) - - it("should work with empty OAuth2 environment variable values", function() - local client_id_env = "EMPTY_OAUTH2_CLIENT_ID" - local client_secret_env = "EMPTY_OAUTH2_CLIENT_SECRET" - - finally(function() - helpers.unsetenv(client_id_env) - helpers.unsetenv(client_secret_env) - end) - - helpers.setenv(client_id_env, "") - helpers.setenv(client_secret_env, "") - - local client_id_res, client_id_err = get("{vault://env/empty_oauth2_client_id}") - local client_secret_res, client_secret_err = get("{vault://env/empty_oauth2_client_secret}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal("", client_id_res) - assert.equal("", client_secret_res) - end) - - it("should handle hash_secret boolean field from environment", function() - local env_name = "OAUTH2_HASH_SECRET" - local env_value = "true" - - finally(function() - helpers.unsetenv(env_name) - end) - - helpers.setenv(env_name, env_value) - - local res, err = get("{vault://env/oauth2_hash_secret}") - assert.is_nil(err) - assert.equal(env_value, res) - end) + local plugin_schema = require("kong.plugins."..PLUGIN_NAME..".schema") + assert(plugins_schema:new_subschema(PLUGIN_NAME, plugin_schema)) end) - describe("edge cases and validation", function() - it("should handle malformed vault references gracefully", function() - local malformed_refs = { - "{vault://invalid/format", - "vault://env/missing_braces}", - "{vault://env/}", - "{vault://env}", - "{vault://}", - } - - for _, ref in ipairs(malformed_refs) do - local res, err = get(ref) - if res then - assert.equal(ref, res, "Malformed reference should be returned unchanged: " .. ref) - else - assert.is_string(err, "Should have error message for malformed reference: " .. ref) - end - end - end) - - it("should handle case sensitivity correctly", function() - finally(function() - helpers.unsetenv("Test_OAuth2_Client_Id") - helpers.unsetenv("Test_OAuth2_Client_Secret") - end) + it("should dereference vault value", function() + local env_name = "OAUTH2_HIDE_CREDENTIALS" + local env_value = "true" - helpers.setenv("Test_OAuth2_Client_Id", "case_sensitive_client_id") - helpers.setenv("Test_OAuth2_Client_Secret", "case_sensitive_client_secret") - - local client_id_res, client_id_err = get("{vault://env/test_oauth2_client_id}") - local client_secret_res, client_secret_err = get("{vault://env/test_oauth2_client_secret}") - - assert.matches("could not get value from external vault", client_id_err) - assert.matches("could not get value from external vault", client_secret_err) - assert.is_nil(client_id_res) - assert.is_nil(client_secret_res) + finally(function() + helpers.unsetenv(env_name) end) - it("should work with special characters in OAuth2 environment variable names", function() - local client_id_env = "OAUTH2_CLIENT_ID_123" - local client_secret_env = "OAUTH2_CLIENT_SECRET_456" - local client_id_value = "special_oauth2_client_id" - local client_secret_value = "special_oauth2_client_secret" - - finally(function() - helpers.unsetenv(client_id_env) - helpers.unsetenv(client_secret_env) - end) + helpers.setenv(env_name, env_value) - helpers.setenv(client_id_env, client_id_value) - helpers.setenv(client_secret_env, client_secret_value) + local entity = plugins_schema:process_auto_fields({ + name = PLUGIN_NAME, + config = { + hide_credentials = "{vault://env/oauth2-hide-credentials}" + }, + }, "select") - local client_id_res, client_id_err = get("{vault://env/oauth2_client_id_123}") - local client_secret_res, client_secret_err = get("{vault://env/oauth2_client_secret_456}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal(client_id_value, client_id_res) - assert.equal(client_secret_value, client_secret_res) - end) - end) - - describe("integration with oauth2 plugin", function() - it("should demonstrate vault usage in OAuth2 context", function() - local client_id_env = "OAUTH2_INTEGRATION_CLIENT_ID" - local client_secret_env = "OAUTH2_INTEGRATION_CLIENT_SECRET" - - finally(function() - helpers.unsetenv(client_id_env) - helpers.unsetenv(client_secret_env) - end) - - helpers.setenv(client_id_env, "secure_oauth2_client_id") - helpers.setenv(client_secret_env, "secure_oauth2_client_secret") - - local resolved_client_id, client_id_err = get("{vault://env/oauth2_integration_client_id}") - local resolved_client_secret, client_secret_err = get("{vault://env/oauth2_integration_client_secret}") - - assert.is_nil(client_id_err) - assert.is_nil(client_secret_err) - assert.equal("secure_oauth2_client_id", resolved_client_id) - assert.equal("secure_oauth2_client_secret", resolved_client_secret) - - -- In actual usage, these resolved values would be used to: - -- 1. Create OAuth2 application credentials with resolved client_id and client_secret - -- 2. Validate OAuth2 authorization requests using the resolved credentials - -- 3. The client_secret is used to authenticate the application during token exchange - -- 4. The client_id identifies the OAuth2 application making the request - end) - - it("should handle multiple OAuth2 application scenarios", function() - finally(function() - helpers.unsetenv("OAUTH2_WEB_CLIENT_ID") - helpers.unsetenv("OAUTH2_WEB_CLIENT_SECRET") - helpers.unsetenv("OAUTH2_MOBILE_CLIENT_ID") - helpers.unsetenv("OAUTH2_MOBILE_CLIENT_SECRET") - end) - - -- Web application credentials - helpers.setenv("OAUTH2_WEB_CLIENT_ID", "web_app_client_id") - helpers.setenv("OAUTH2_WEB_CLIENT_SECRET", "web_app_client_secret") - - -- Mobile application credentials - helpers.setenv("OAUTH2_MOBILE_CLIENT_ID", "mobile_app_client_id") - helpers.setenv("OAUTH2_MOBILE_CLIENT_SECRET", "mobile_app_client_secret") - - local web_id_res, web_id_err = get("{vault://env/oauth2_web_client_id}") - local web_secret_res, web_secret_err = get("{vault://env/oauth2_web_client_secret}") - local mobile_id_res, mobile_id_err = get("{vault://env/oauth2_mobile_client_id}") - local mobile_secret_res, mobile_secret_err = get("{vault://env/oauth2_mobile_client_secret}") - - assert.is_nil(web_id_err) - assert.is_nil(web_secret_err) - assert.is_nil(mobile_id_err) - assert.is_nil(mobile_secret_err) - - assert.equal("web_app_client_id", web_id_res) - assert.equal("web_app_client_secret", web_secret_res) - assert.equal("mobile_app_client_id", mobile_id_res) - assert.equal("mobile_app_client_secret", mobile_secret_res) - end) - - it("should handle OAuth2 configuration flags", function() - finally(function() - helpers.unsetenv("OAUTH2_INTEGRATION_HASH_SECRET") - end) - - helpers.setenv("OAUTH2_INTEGRATION_HASH_SECRET", "false") - - local hash_secret_res, hash_secret_err = get("{vault://env/oauth2_integration_hash_secret}") - - assert.is_nil(hash_secret_err) - assert.equal("false", hash_secret_res) - - -- In actual usage: - -- - hash_secret=true: client_secret will be hashed before storage - -- - hash_secret=false: client_secret will be stored as plaintext - -- The vault reference resolves the boolean value as string - end) + assert.equal(env_value, entity.config.hide_credentials) end) -end) \ No newline at end of file +end)