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==