-
Notifications
You must be signed in to change notification settings - Fork 5.1k
feat(vault): Added an option to use {vault://} in specific fields of plugins #14775
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 20 commits
98a6475
e2064f6
5ef5005
7df0b8a
7bd8bb3
a852681
c05fb9b
6d52709
59e6de3
89a263c
f3185d8
9f0e84c
2c83103
8be60d5
33e9a90
eebdbf1
a2a110f
6bdaab0
36828d1
cd8a648
f6ae29d
b8e188f
8aa9286
c190c95
ad160aa
b671a5e
17c9ac2
f89c5bb
0877600
cb41634
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| message: Added an option to use {vault://} in specific fields of plugins | ||
| type: feature | ||
| scope: Plugin |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 }, }, | ||||||
|
||||||
| { secret = { type = "string", auto = true, referenceable = true }, }, | |
| { secret = { type = "string", auto = true }, }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see this as a valid point. The fields from consumer should be referenceable using the vault semantics. If its not referenceable (empty), then the user hasn't follow the guidelines. This is exactly the same as if user would add EMPTY secret directly into consumer part. Please advise @raoxiaoyan
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -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 | ||||||
|
||||||
| { 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 | |
| { client_secret = { type = "string", required = false, auto = true, encrypted = true }, }, -- encrypted = true is a Kong Enterprise Exclusive feature. It does nothing in Kong CE |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont see this as an error, almost same as with jwt. Please advise @raoxiaoyan .
|
lordgreg marked this conversation as resolved.
|
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,201 @@ | ||||||||||||||||||||||||||||||||||
| local helpers = require("spec.helpers") | ||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| local conf_loader = require("kong.conf_loader") | ||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| 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") | ||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| _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) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| 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=", | ||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||
| assert.is_nil(err) | ||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||
| end) | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
lordgreg marked this conversation as resolved.
Outdated
|
||||||||||||||||||||||||||||||||||
| 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) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
| 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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Marking the HMAC credential
secretfield asreferenceablemeans this value will be resolved viakong.vault.geton the data plane. If the vault reference cannot be resolved (for example, a missing or mis-typed environment variable),resolve_referenceinkong.db.schema.initreplaces it with an empty string, sohmac-authwill happily verify signatures using a known empty key, allowing an attacker who guesses the username to forge valid HMAC signatures. This field should fail closed on vault resolution errors (e.g., reject the credential or authentication) instead of silently falling back to an empty secret.