This is an automated email from the ASF dual-hosted git repository.
nic-6443 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 5750d4fdd fix(secret): aws secret manager fails when the secret name
contains a slash (#13519)
5750d4fdd is described below
commit 5750d4fdd274599afe0c40631598ac0620950d14
Author: Nic <[email protected]>
AuthorDate: Fri Jun 12 11:04:42 2026 +0800
fix(secret): aws secret manager fails when the secret name contains a slash
(#13519)
---
apisix/secret/aws.lua | 63 +++++++++++++++++--------
ci/init-common-test-service.sh | 2 +
docs/en/latest/terminology/secret.md | 2 +
docs/zh/latest/terminology/secret.md | 2 +
t/secret/aws.t | 91 +++++++++++++++++++++++++++++++++++-
5 files changed, 139 insertions(+), 21 deletions(-)
diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua
index af2e045ca..a538c2c7e 100644
--- a/apisix/secret/aws.lua
+++ b/apisix/secret/aws.lua
@@ -25,7 +25,9 @@ local aws_instance
local sub = core.string.sub
local find = core.string.find
+local rfind_char = core.string.rfind_char
local env = core.env
+local type = type
local unpack = unpack
local schema = {
@@ -93,47 +95,68 @@ local function make_request_to_aws(conf, key)
end
if res.status ~= 200 then
+ local err_type = type(res.body) == "table" and res.body.__type
+ local not_found = type(err_type) == "string"
+ and find(err_type, "ResourceNotFoundException") ~=
nil
+
local data = core.json.encode(res.body)
if data then
- return nil, "invalid status code " .. res.status .. ", " .. data
+ return nil, "invalid status code " .. res.status .. ", " .. data,
not_found
end
- return nil, "invalid status code " .. res.status
+ return nil, "invalid status code " .. res.status, not_found
end
return res.body.SecretString
end
--- key is the aws secretId
+-- key is the aws secretId, optionally followed by a JSON field name.
+-- As AWS secret names may contain slashes, the boundary between the secret
+-- name and the field name is ambiguous. Try the longest possible secret name
+-- first, then on ResourceNotFoundException move path segments from the right
into the
+-- field position, e.g. for "a/b/c": ("a/b/c"), ("a/b", "c"), ("a", "b/c").
function _M.get(conf, key)
core.log.info("fetching data from aws for key: ", key)
- local idx = find(key, '/')
-
- local main_key = idx and sub(key, 1, idx - 1) or key
- if main_key == "" then
+ if key == "" or find(key, '/') == 1 then
return nil, "can't find main key, key: " .. key
end
- local sub_key = idx and sub(key, idx + 1) or nil
+ local main_key = key
+ local sub_key
+ local last_err
+ while true do
+ core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or
"")
- core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "")
+ local res, err, not_found = make_request_to_aws(conf, main_key)
+ if res then
+ if not sub_key then
+ return res
+ end
- local res, err = make_request_to_aws(conf, main_key)
- if not res then
- return nil, "failed to retrtive data from aws secret manager: " .. err
- end
+ local data, err = core.json.decode(res)
+ if not data then
+ return nil, "failed to decode result, res: " .. res .. ", err:
" .. err
+ end
- if not sub_key then
- return res
- end
+ return data[sub_key]
+ end
+
+ last_err = err
+ if not not_found then
+ break
+ end
+
+ local idx = rfind_char(main_key, '/')
+ if not idx then
+ break
+ end
- local data, err = core.json.decode(res)
- if not data then
- return nil, "failed to decode result, res: " .. res .. ", err: " .. err
+ main_key = sub(key, 1, idx - 1)
+ sub_key = sub(key, idx + 1)
end
- return data[sub_key]
+ return nil, "failed to retrieve data from aws secret manager: " .. last_err
end
diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh
index 2674c1ec6..dc34f7550 100755
--- a/ci/init-common-test-service.sh
+++ b/ci/init-common-test-service.sh
@@ -48,6 +48,8 @@ done
# prepare localstack
docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
apisix-key --description 'APISIX Secret' --secret-string '{\"jack\":\"value\"}'"
docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
apisix-mysql --description 'APISIX Secret' --secret-string 'secret'"
+docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
'john/secret' --description 'APISIX Secret' --secret-string
'{\"john-key-auth\":\"value\"}'"
+docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name
'apisix/full/path' --description 'APISIX Secret' --secret-string 'full-value'"
# prepare filesystem mcp server
sleep 3s
diff --git a/docs/en/latest/terminology/secret.md
b/docs/en/latest/terminology/secret.md
index 27474052c..ee5ba1260 100644
--- a/docs/en/latest/terminology/secret.md
+++ b/docs/en/latest/terminology/secret.md
@@ -224,6 +224,8 @@ $secret://$manager/$id/$secret_name/$key
- secret_name: the secret name in the secrets management service
- key: get the value of a property when the value of the secret is a JSON
string
+Note that the secret name in AWS Secrets Manager may itself contain slashes
(e.g. `john/secret`), which makes the boundary between `secret_name` and `key`
ambiguous. APISIX resolves the reference by trying the longest possible secret
name first: it treats the whole remaining path as the secret name, and on
`ResourceNotFoundException` it moves path segments from the right into the
`key` position until the lookup succeeds. For example,
`$secret://aws/1/john/secret/john-key-auth` tries the [...]
+
### Required Parameters
| Name | Required | Default Value | Description |
diff --git a/docs/zh/latest/terminology/secret.md
b/docs/zh/latest/terminology/secret.md
index fabe66f49..fc1cedf50 100644
--- a/docs/zh/latest/terminology/secret.md
+++ b/docs/zh/latest/terminology/secret.md
@@ -227,6 +227,8 @@ $secret://$manager/$id/$secret_name/$key
- secret_name: 密钥管理服务中的密钥名称
- key:当密钥的值是 JSON 字符串时,获取某个属性的值
+注意:AWS Secrets Manager 中的密钥名称本身可以包含斜杠(例如 `john/secret`),因此 `secret_name` 与
`key` 之间的边界存在歧义。APISIX 会优先尝试最长的密钥名称:先将剩余路径整体作为密钥名称查询,如果返回
`ResourceNotFoundException`,则从右侧逐段将路径移入 `key` 位置,直到查询成功。例如
`$secret://aws/1/john/secret/john-key-auth` 会先尝试名为 `john/secret/john-key-auth`
的密钥,再尝试名为 `john/secret` 的密钥并取其中的 `john-key-auth` 字段,最后尝试名为 `john` 的密钥并取其中的
`secret/john-key-auth` 字段。当存在多种可能的解释时,最长匹配的密钥名称优先。
+
### 相关参数
| 名称 | 必选项 | 默认值 | 描述 |
diff --git a/t/secret/aws.t b/t/secret/aws.t
index 8342748ff..ce6c81dae 100644
--- a/t/secret/aws.t
+++ b/t/secret/aws.t
@@ -128,7 +128,7 @@ can't find main key, key: /apisix
--- request
GET /t
--- response_body
-failed to retrtive data from aws secret manager:
SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080':
connection refused
+failed to retrieve data from aws secret manager:
SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080':
connection refused
--- timeout: 6
@@ -319,3 +319,92 @@ GET /t
secret value: value
no secret conf, secret_uri: $secret://aws/mysecret/apisix-key/jack/key
all done
+
+
+
+=== TEST 9: get json value from aws (secret name contains a slash)
+--- config
+ location /t {
+ content_by_lua_block {
+ local aws = require("apisix.secret.aws")
+ local conf = {
+ endpoint_url = "http://127.0.0.1:4566",
+ region = "us-east-1",
+ access_key_id = "access",
+ secret_access_key = "secret",
+ session_token = "token",
+ }
+ local data, err = aws.get(conf, "john/secret/john-key-auth")
+ if err then
+ return ngx.say(err)
+ end
+ ngx.say(data)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 10: get string value from aws (full key as the secret name, no field)
+--- config
+ location /t {
+ content_by_lua_block {
+ local aws = require("apisix.secret.aws")
+ local conf = {
+ endpoint_url = "http://127.0.0.1:4566",
+ region = "us-east-1",
+ access_key_id = "access",
+ secret_access_key = "secret",
+ session_token = "token",
+ }
+ local data, err = aws.get(conf, "apisix/full/path")
+ if err then
+ return ngx.say(err)
+ end
+ ngx.say(data)
+ }
+ }
+--- request
+GET /t
+--- response_body
+full-value
+
+
+
+=== TEST 11: fetch secret value by uri (secret name contains a slash)
+--- request
+GET /t
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/secrets/aws/mysecret',
+ ngx.HTTP_PUT,
+ [[{
+ "endpoint_url": "http://127.0.0.1:4566",
+ "region": "us-east-1",
+ "access_key_id": "access",
+ "secret_access_key": "secret",
+ "session_token": "token"
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ return ngx.say(body)
+ end
+
+ ngx.sleep(1)
+
+ local secret = require("apisix.secret")
+ local value, err =
secret.fetch_by_uri("$secret://aws/mysecret/john/secret/john-key-auth")
+ if err then
+ return ngx.say(err)
+ end
+ ngx.say("secret value: ", value)
+ }
+ }
+--- response_body
+secret value: value