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

Reply via email to