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 8df43e22e fix(admin): encrypt_fields get double-encrypted on every 
PATCH (#13525)
8df43e22e is described below

commit 8df43e22e0520fb3140b30596e64903b02a187d6
Author: Nic <[email protected]>
AuthorDate: Fri Jun 12 10:59:44 2026 +0800

    fix(admin): encrypt_fields get double-encrypted on every PATCH (#13525)
---
 apisix/admin/resource.lua |  14 ++++-
 t/admin/routes4.t         | 150 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 162 insertions(+), 2 deletions(-)

diff --git a/apisix/admin/resource.lua b/apisix/admin/resource.lua
index 87c5692f6..ceef868b4 100644
--- a/apisix/admin/resource.lua
+++ b/apisix/admin/resource.lua
@@ -17,6 +17,7 @@
 local core = require("apisix.core")
 local utils = require("apisix.admin.utils")
 local apisix_ssl = require("apisix.ssl")
+local apisix_plugin = require("apisix.plugin")
 local apisix_consumer = require("apisix.consumer")
 local tbl_deepcopy = require("apisix.core.table").deepcopy
 local setmetatable = setmetatable
@@ -421,6 +422,13 @@ function _M:patch(id, conf, sub_path, args)
     local node_value = res_old.body.node.value
     local modified_index = res_old.body.node.modifiedIndex
 
+    -- the encrypt_fields of the stored plugin conf are ciphertext, decrypt
+    -- them before merging, otherwise the check_conf below will encrypt them
+    -- again, resulting in fields that are encrypted multiple times
+    if apisix_plugin.enable_gde() then
+        utils.decrypt_params(apisix_plugin.decrypt_conf, res_old.body)
+    end
+
     if sub_path and sub_path ~= "" then
         if self.name == "ssls" then
             if sub_path == "key" then
@@ -453,13 +461,15 @@ function _M:patch(id, conf, sub_path, args)
         utils.inject_timestamp(node_value, nil, conf)
     end
 
-    core.log.info("new conf: ", core.json.delay_encode(node_value, true))
-
     local ok, err = self:check_conf(id, node_value, true, typ, true)
     if not ok then
         return 400, err
     end
 
+    -- log after check_conf so the encrypt_fields are encrypted again and
+    -- the plaintext values decrypted above don't leak into the log
+    core.log.info("new conf: ", core.json.delay_encode(node_value, true))
+
     local ttl = nil
     if args then
         ttl = args.ttl
diff --git a/t/admin/routes4.t b/t/admin/routes4.t
index 9405ddf42..7a636b178 100644
--- a/t/admin/routes4.t
+++ b/t/admin/routes4.t
@@ -795,3 +795,153 @@ passed
 --- error_code: 400
 --- response_body eval
 qr/\{"error_msg":"the property is forbidden:.*"\}/
+
+
+
+=== TEST 23: PATCH must not re-encrypt the encrypted plugin fields (full body)
+--- yaml_config
+apisix:
+    data_encryption:
+        enable_encrypt_fields: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/hello",
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        }
+                    },
+                    "plugins": {
+                        "csrf": {
+                            "key": "userkey",
+                            "expires": 1000000000
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            -- patch an unrelated field twice
+            for i = 1, 2 do
+                local code, body = t('/apisix/admin/routes/1',
+                    ngx.HTTP_PATCH,
+                    '{"desc": "patch ' .. i .. '"}'
+                )
+                if code >= 300 then
+                    ngx.status = code
+                    ngx.say(body)
+                    return
+                end
+            end
+
+            -- get plugin conf from admin api, the key is decrypted
+            local code, message, res = t('/apisix/admin/routes/1',
+                ngx.HTTP_GET
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+            res = json.decode(res)
+            ngx.say(res.value.plugins["csrf"].key)
+
+            -- get plugin conf from etcd, the key is encrypted exactly once
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/routes/1'))
+            ngx.say(res.body.node.value.plugins["csrf"].key)
+        }
+    }
+--- response_body
+userkey
+mt39FazQccyMqt4ctoRV7w==
+
+
+
+=== TEST 24: PATCH must not re-encrypt the encrypted plugin fields (sub path)
+--- yaml_config
+apisix:
+    data_encryption:
+        enable_encrypt_fields: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/hello",
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        }
+                    },
+                    "plugins": {
+                        "csrf": {
+                            "key": "userkey",
+                            "expires": 1000000000
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            -- patch an unrelated field twice via sub path
+            for i = 1, 2 do
+                local code, body = t('/apisix/admin/routes/1/desc',
+                    ngx.HTTP_PATCH,
+                    '"patch ' .. i .. '"'
+                )
+                if code >= 300 then
+                    ngx.status = code
+                    ngx.say(body)
+                    return
+                end
+            end
+
+            -- get plugin conf from admin api, the key is decrypted
+            local code, message, res = t('/apisix/admin/routes/1',
+                ngx.HTTP_GET
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+            res = json.decode(res)
+            ngx.say(res.value.plugins["csrf"].key)
+
+            -- get plugin conf from etcd, the key is encrypted exactly once
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/routes/1'))
+            ngx.say(res.body.node.value.plugins["csrf"].key)
+
+            t('/apisix/admin/routes/1', ngx.HTTP_DELETE)
+        }
+    }
+--- response_body
+userkey
+mt39FazQccyMqt4ctoRV7w==

Reply via email to