This is an automated email from the ASF dual-hosted git repository.
shreemaan-abhishek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 7c9d6056b fix(secret): log an error when a secret reference cannot be
resolved (#13737)
7c9d6056b is described below
commit 7c9d6056b0c0670d27e91684ac083702de5d7e18
Author: Shreemaan Abhishek <[email protected]>
AuthorDate: Mon Jul 27 12:13:57 2026 +0800
fix(secret): log an error when a secret reference cannot be resolved
(#13737)
---
apisix/secret.lua | 16 +++--
docs/en/latest/terminology/secret.md | 2 +
docs/zh/latest/terminology/secret.md | 2 +
t/secret/central-secret-refs.t | 132 +++++++++++++++++++++++++++++++++++
t/secret/secret_lru.t | 2 +
5 files changed, 147 insertions(+), 7 deletions(-)
diff --git a/apisix/secret.lua b/apisix/secret.lua
index f25daa647..143f2e37b 100644
--- a/apisix/secret.lua
+++ b/apisix/secret.lua
@@ -225,12 +225,7 @@ local function fetch(uri, use_cache)
end
if not use_cache then
- local val, err = fetch_by_uri(uri)
- if err then
- core.log.error("failed to fetch secret value: ", err)
- return nil
- end
- return val
+ return fetch_by_uri(uri)
end
-- pass the secrets conf_version so the cache re-resolves when /secrets
changes
@@ -242,7 +237,14 @@ local function retrieve_refs(refs, use_cache)
for k, v in pairs(refs) do
local typ = type(v)
if typ == "string" then
- refs[k] = fetch(v, use_cache) or v
+ local val, err = fetch(v, use_cache)
+ if val == nil and _M.is_secret_ref(v) then
+ -- an unresolved reference is kept as-is, so it would otherwise
+ -- be used verbatim as a password, key or token
+ core.log.error("failed to resolve secret reference: ", v,
+ ", field: ", k, ", err: ", err or "no value")
+ end
+ refs[k] = val or v
elseif typ == "table" then
retrieve_refs(v, use_cache)
end
diff --git a/docs/en/latest/terminology/secret.md
b/docs/en/latest/terminology/secret.md
index ec713a661..304856fb0 100644
--- a/docs/en/latest/terminology/secret.md
+++ b/docs/en/latest/terminology/secret.md
@@ -62,6 +62,8 @@ When a configuration field uses a secret reference like
`$secret://...` or `$env
If a key-value pair `key: "$ENV://ABC"` is configured in APISIX and the value
of `$ENV://ABC` is unassigned in the environment variable, `$ENV://ABC` will be
interpreted as a string literal, instead of `nil`.
+This applies to every secret reference: when a reference cannot be resolved
(the secret manager is not configured, the lookup fails, or the environment
variable is unset), the literal reference string is used as the field value and
an error such as `failed to resolve secret reference:
$secret://vault/1/foo/bar, field: password` is written to the error log. Check
the error log if a plugin behaves as if its credential were wrong.
+
:::
## Use environment variables to manage secrets
diff --git a/docs/zh/latest/terminology/secret.md
b/docs/zh/latest/terminology/secret.md
index 9384a54b1..bf06b3885 100644
--- a/docs/zh/latest/terminology/secret.md
+++ b/docs/zh/latest/terminology/secret.md
@@ -62,6 +62,8 @@ APISIX 目前支持通过以下方式存储密钥:
如果某个配置项为:`key: "$ENV://ABC"`,当 APISIX Secret 中没有检索到 $ENV://ABC 对应的真实值,那么 key
的值将是 "$ENV://ABC" 而不是 `nil`。
+该行为适用于所有密钥引用:当引用无法解析时(密钥管理器未配置、查询失败或环境变量未设置),字段将保留引用字符串本身,同时错误日志中会记录类似 `failed
to resolve secret reference: $secret://vault/1/foo/bar, field: password`
的错误。如果插件表现得像凭证不正确,请先检查错误日志。
+
:::
## 使用环境变量管理密钥
diff --git a/t/secret/central-secret-refs.t b/t/secret/central-secret-refs.t
index 47f99675c..0a0460c58 100644
--- a/t/secret/central-secret-refs.t
+++ b/t/secret/central-secret-refs.t
@@ -374,3 +374,135 @@ openid-connect has required field client_secret that must
be a string.
}
--- response_body
success
+
+
+
+=== TEST 13: unresolvable $secret:// ref keeps the literal and logs an error
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+
+ local conf = {
+ client_secret = "$secret://vault/not-exist/foo/bar"
+ }
+ local resolved = secret.fetch_secrets(conf, true)
+ ngx.say("resolved: ", resolved.client_secret)
+ }
+ }
+--- response_body
+resolved: $secret://vault/not-exist/foo/bar
+--- error_log
+failed to resolve secret reference: $secret://vault/not-exist/foo/bar, field:
client_secret
+
+
+
+=== TEST 14: unset $env:// ref keeps the literal and logs an error
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+
+ local conf = {
+ nested = {
+ password = "$env://TEST_ENV_NOT_SET"
+ }
+ }
+ local resolved = secret.fetch_secrets(conf, true)
+ ngx.say("resolved: ", resolved.nested.password)
+ }
+ }
+--- response_body
+resolved: $env://TEST_ENV_NOT_SET
+--- error_log
+failed to resolve secret reference: $env://TEST_ENV_NOT_SET, field: password
+
+
+
+=== TEST 15: resolvable refs do not log a resolution error
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+
+ local conf = {
+ host = "$env://TEST_HOST",
+ plain = "not-a-ref"
+ }
+ local resolved = secret.fetch_secrets(conf, true)
+ ngx.say("resolved: ", resolved.host)
+ }
+ }
+--- response_body
+resolved: test.example.com
+--- no_error_log
+failed to resolve secret reference
+
+
+
+=== TEST 16: unresolvable ref on the uncached path keeps the literal and logs
+--- config
+ location /t {
+ content_by_lua_block {
+ local secret = require("apisix.secret")
+
+ local conf = {
+ token = "$secret://vault/not-exist/foo/bar"
+ }
+ -- use_cache = false exercises the direct fetch_by_uri path
+ local resolved = secret.fetch_secrets(conf, false)
+ ngx.say("resolved: ", resolved.token)
+ }
+ }
+--- response_body
+resolved: $secret://vault/not-exist/foo/bar
+--- error_log
+failed to resolve secret reference: $secret://vault/not-exist/foo/bar, field:
token, err: no secret conf
+
+
+
+=== TEST 17: route with a plugin referencing an unresolvable secret
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "proxy-rewrite": {
+ "headers": {
+ "set": {
+ "X-Secret-Token":
"$env://SECRET_REF_UNRESOLVABLE"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+ ngx.say("success")
+ }
+ }
+--- response_body
+success
+
+
+
+=== TEST 18: request still succeeds, the literal is forwarded and the failure
logged
+--- request
+GET /hello
+--- error_code: 200
+--- error_log
+failed to resolve secret reference: $env://SECRET_REF_UNRESOLVABLE, field:
X-Secret-Token
diff --git a/t/secret/secret_lru.t b/t/secret/secret_lru.t
index 670bf03b5..52a80dfc4 100644
--- a/t/secret/secret_lru.t
+++ b/t/secret/secret_lru.t
@@ -169,3 +169,5 @@ GET /t
--- response_body
value
$secret://vault/lru/lru-key/jack/key
+--- error_log
+failed to resolve secret reference: $secret://vault/lru/lru-key/jack/key,
field: key