Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions apisix/secret.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/en/latest/terminology/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions docs/zh/latest/terminology/secret.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 的错误。如果插件表现得像凭证不正确,请先检查错误日志。

:::

## 使用环境变量管理密钥
Expand Down
132 changes: 132 additions & 0 deletions t/secret/central-secret-refs.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions t/secret/secret_lru.t
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading