This is an automated email from the ASF dual-hosted git repository.
baoyuan 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 9a2380c68 feat: enhance encrypt_fields to support nested structures
(#13192)
9a2380c68 is described below
commit 9a2380c6826e87e8b7080f2f6a8e6b0b1c39c8fe
Author: Nic <[email protected]>
AuthorDate: Fri Apr 10 10:38:47 2026 +0800
feat: enhance encrypt_fields to support nested structures (#13192)
---
apisix/plugin.lua | 154 ++++++-----
apisix/plugins/ai-proxy/schema.lua | 10 +-
apisix/plugins/ai-rag.lua | 6 +-
t/node/data_encrypt3.t | 537 +++++++++++++++++++++++++++++++++++++
4 files changed, 641 insertions(+), 66 deletions(-)
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index 8b4177ae0..723ededd0 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -21,7 +21,7 @@ local enable_debug = require("apisix.debug").enable_debug
local wasm = require("apisix.wasm")
local expr = require("resty.expr.v1")
local apisix_ssl = require("apisix.ssl")
-local re_split = require("ngx.re").split
+
local ngx = ngx
local ngx_ok = ngx.OK
local ngx_print = ngx.print
@@ -988,6 +988,88 @@ local function get_plugin_schema_for_gde(name, schema_type)
end
+-- Process a single encrypt_field path on the given config table.
+-- Supports:
+-- - Arbitrary depth dotted paths (e.g., "a.b.c.d")
+-- - Array traversal at intermediate nodes (iterate each element)
+-- - Leaf type dispatch: string, array of strings, map of strings
+local function process_encrypt_field(conf, key_path, operation, plugin_name,
op_name)
+ local dot_pos = core.string.find(key_path, ".")
+
+ if not dot_pos then
+ -- leaf segment
+ local val = conf[key_path]
+ if val == nil then
+ return
+ end
+
+ if type(val) == "string" then
+ local result, err = operation(val, "data_encrypt")
+ if not result then
+ core.log.warn("failed to ", op_name, " the conf of plugin [",
+ plugin_name, "] key [", key_path, "], err: ",
err)
+ else
+ conf[key_path] = result
+ end
+
+ elseif type(val) == "table" then
+ if core.table.isarray(val) then
+ -- array of strings
+ for i, item in ipairs(val) do
+ if type(item) == "string" then
+ local result, err = operation(item, "data_encrypt")
+ if not result then
+ core.log.warn("failed to ", op_name, " the conf of
plugin [",
+ plugin_name, "] key [", key_path,
+ "] index [", i, "], err: ", err)
+ else
+ val[i] = result
+ end
+ end
+ end
+ else
+ -- map of strings
+ for k, v in pairs(val) do
+ if type(v) == "string" then
+ local result, err = operation(v, "data_encrypt")
+ if not result then
+ core.log.warn("failed to ", op_name, " the conf of
plugin [",
+ plugin_name, "] key [", key_path,
+ ".", k, "], err: ", err)
+ else
+ val[k] = result
+ end
+ end
+ end
+ end
+ end
+
+ else
+ -- intermediate segment: split on first dot and recurse
+ local segment = key_path:sub(1, dot_pos - 1)
+ local rest = key_path:sub(dot_pos + 1)
+ local val = conf[segment]
+
+ if val == nil or type(val) ~= "table" then
+ return
+ end
+
+ if core.table.isarray(val) then
+ -- array: iterate each element and recurse
+ for _, item in ipairs(val) do
+ if type(item) == "table" then
+ process_encrypt_field(item, rest, operation, plugin_name,
op_name)
+ end
+ end
+ else
+ -- map: recurse into it
+ process_encrypt_field(val, rest, operation, plugin_name, op_name)
+ end
+ end
+end
+_M.process_encrypt_field = process_encrypt_field
+
+
local function decrypt_conf(name, conf, schema_type)
if not enable_gde() then
return
@@ -1000,34 +1082,7 @@ local function decrypt_conf(name, conf, schema_type)
if schema.encrypt_fields and not core.table.isempty(schema.encrypt_fields)
then
for _, key in ipairs(schema.encrypt_fields) do
- if conf[key] then
- local decrypted, err = apisix_ssl.aes_decrypt_pkey(conf[key],
"data_encrypt")
- if not decrypted then
- core.log.warn("failed to decrypt the conf of plugin [",
name,
- "] key [", key, "], err: ", err)
- else
- conf[key] = decrypted
- end
- elseif core.string.find(key, ".") then
- -- decrypt fields has indents
- local res, err = re_split(key, "\\.", "jo")
- if not res then
- core.log.warn("failed to split key [", key, "], err: ",
err)
- return
- end
-
- -- we only support two levels
- if conf[res[1]] and conf[res[1]][res[2]] then
- local decrypted, err = apisix_ssl.aes_decrypt_pkey(
- conf[res[1]][res[2]],
"data_encrypt")
- if not decrypted then
- core.log.warn("failed to decrypt the conf of plugin
[", name,
- "] key [", key, "], err: ", err)
- else
- conf[res[1]][res[2]] = decrypted
- end
- end
- end
+ process_encrypt_field(conf, key, apisix_ssl.aes_decrypt_pkey,
name, "decrypt")
end
end
end
@@ -1046,34 +1101,7 @@ local function encrypt_conf(name, conf, schema_type)
if schema.encrypt_fields and not core.table.isempty(schema.encrypt_fields)
then
for _, key in ipairs(schema.encrypt_fields) do
- if conf[key] then
- local encrypted, err = apisix_ssl.aes_encrypt_pkey(conf[key],
"data_encrypt")
- if not encrypted then
- core.log.warn("failed to encrypt the conf of plugin [",
name,
- "] key [", key, "], err: ", err)
- else
- conf[key] = encrypted
- end
- elseif core.string.find(key, ".") then
- -- encrypt fields has indents
- local res, err = re_split(key, "\\.", "jo")
- if not res then
- core.log.warn("failed to split key [", key, "], err: ",
err)
- return
- end
-
- -- we only support two levels
- if conf[res[1]] and conf[res[1]][res[2]] then
- local encrypted, err = apisix_ssl.aes_encrypt_pkey(
- conf[res[1]][res[2]],
"data_encrypt")
- if not encrypted then
- core.log.warn("failed to encrypt the conf of plugin
[", name,
- "] key [", key, "], err: ", err)
- else
- conf[res[1]][res[2]] = encrypted
- end
- end
- end
+ process_encrypt_field(conf, key, apisix_ssl.aes_encrypt_pkey,
name, "encrypt")
end
end
end
@@ -1142,16 +1170,16 @@ _M.stream_check_schema = stream_check_schema
function _M.plugin_checker(item, schema_type)
if item.plugins then
- local skip_disabled_plugins = not (core.config.type == "yaml" or
core.config.type == "json")
- local ok, err = check_schema(item.plugins, schema_type,
skip_disabled_plugins)
-
- if ok and enable_gde() then
- -- decrypt conf
+ if enable_gde() then
+ -- decrypt conf before validation so that content-level checks
+ -- (e.g. ai-proxy service_account_json JSON parsing) see plaintext
for name, conf in pairs(item.plugins) do
decrypt_conf(name, conf, schema_type)
end
end
- return ok, err
+
+ local skip_disabled_plugins = not (core.config.type == "yaml" or
core.config.type == "json")
+ return check_schema(item.plugins, schema_type, skip_disabled_plugins)
end
return true
diff --git a/apisix/plugins/ai-proxy/schema.lua
b/apisix/plugins/ai-proxy/schema.lua
index 4fef8ee01..397f210f3 100644
--- a/apisix/plugins/ai-proxy/schema.lua
+++ b/apisix/plugins/ai-proxy/schema.lua
@@ -202,7 +202,8 @@ _M.ai_proxy_schema = {
},
},
},
- required = {"provider", "auth"}
+ required = {"provider", "auth"},
+ encrypt_fields = {"auth.header", "auth.query",
"auth.gcp.service_account_json"},
}
_M.ai_proxy_multi_schema = {
@@ -267,7 +268,12 @@ _M.ai_proxy_multi_schema = {
keepalive_pool = {type = "integer", minimum = 1, default = 30},
ssl_verify = {type = "boolean", default = true },
},
- required = {"instances"}
+ required = {"instances"},
+ encrypt_fields = {
+ "instances.auth.header",
+ "instances.auth.query",
+ "instances.auth.gcp.service_account_json",
+ },
}
return _M
diff --git a/apisix/plugins/ai-rag.lua b/apisix/plugins/ai-rag.lua
index d897fb528..fa40f5605 100644
--- a/apisix/plugins/ai-rag.lua
+++ b/apisix/plugins/ai-rag.lua
@@ -53,7 +53,11 @@ local schema = {
maxProperties = 1
},
},
- required = { "embeddings_provider", "vector_search_provider" }
+ required = { "embeddings_provider", "vector_search_provider" },
+ encrypt_fields = {
+ "embeddings_provider.azure_openai.api_key",
+ "vector_search_provider.azure_ai_search.api_key",
+ },
}
local request_schema = {
diff --git a/t/node/data_encrypt3.t b/t/node/data_encrypt3.t
new file mode 100644
index 000000000..407c276a6
--- /dev/null
+++ b/t/node/data_encrypt3.t
@@ -0,0 +1,537 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_root_location();
+no_shuffle();
+log_level("info");
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ if (!defined $block->request) {
+ $block->set_value("request", "GET /t");
+ }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: ai-proxy: encrypt auth.header (map of strings) and
auth.gcp.service_account_json (3-level nested string)
+--- 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,
+ [[{
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer sk-test-key"
+ },
+ "query": {
+ "api-key": "my-query-secret"
+ },
+ "gcp": {
+ "service_account_json":
"{\"type\":\"service_account\"}"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ -- admin API should return decrypted values
+ local code, message, res = t('/apisix/admin/routes/1',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+
+ local ai_proxy = res.value.plugins["ai-proxy"]
+ ngx.say("header.Authorization: ",
ai_proxy.auth.header.Authorization)
+ ngx.say("query.api-key: ", ai_proxy.auth.query["api-key"])
+ ngx.say("gcp.service_account_json: ",
ai_proxy.auth.gcp.service_account_json)
+
+ -- etcd should have encrypted values
+ local etcd = require("apisix.core.etcd")
+ local res = assert(etcd.get('/routes/1'))
+ local ai_proxy_etcd = res.body.node.value.plugins["ai-proxy"]
+ ngx.say("etcd header encrypted: ",
+ ai_proxy_etcd.auth.header.Authorization ~= "Bearer
sk-test-key")
+ ngx.say("etcd query encrypted: ",
+ ai_proxy_etcd.auth.query["api-key"] ~= "my-query-secret")
+ ngx.say("etcd gcp encrypted: ",
+ ai_proxy_etcd.auth.gcp.service_account_json ~=
"{\"type\":\"service_account\"}")
+ }
+ }
+--- response_body
+header.Authorization: Bearer sk-test-key
+query.api-key: my-query-secret
+gcp.service_account_json: {"type":"service_account"}
+etcd header encrypted: true
+etcd query encrypted: true
+etcd gcp encrypted: true
+
+
+
+=== TEST 2: ai-proxy-multi: encrypt instances[].auth.header (array with nested
map of strings)
+--- 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,
+ [[{
+ "plugins": {
+ "ai-proxy-multi": {
+ "instances": [
+ {
+ "name": "openai-1",
+ "provider": "openai",
+ "weight": 1,
+ "auth": {
+ "header": {
+ "Authorization": "Bearer
sk-instance1-key"
+ }
+ }
+ },
+ {
+ "name": "openai-2",
+ "provider": "openai",
+ "weight": 1,
+ "auth": {
+ "header": {
+ "Authorization": "Bearer
sk-instance2-key"
+ },
+ "gcp": {
+ "service_account_json":
"{\"type\":\"service_account\",\"project_id\":\"test\"}"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ -- admin API should return decrypted values
+ local code, message, res = t('/apisix/admin/routes/1',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+
+ local multi = res.value.plugins["ai-proxy-multi"]
+ ngx.say("instance1 header: ",
multi.instances[1].auth.header.Authorization)
+ ngx.say("instance2 header: ",
multi.instances[2].auth.header.Authorization)
+ ngx.say("instance2 gcp: ",
multi.instances[2].auth.gcp.service_account_json)
+
+ -- etcd should have encrypted values
+ local etcd = require("apisix.core.etcd")
+ local res = assert(etcd.get('/routes/1'))
+ local multi_etcd = res.body.node.value.plugins["ai-proxy-multi"]
+ ngx.say("etcd instance1 header encrypted: ",
+ multi_etcd.instances[1].auth.header.Authorization ~=
"Bearer sk-instance1-key")
+ ngx.say("etcd instance2 header encrypted: ",
+ multi_etcd.instances[2].auth.header.Authorization ~=
"Bearer sk-instance2-key")
+ ngx.say("etcd instance2 gcp encrypted: ",
+ multi_etcd.instances[2].auth.gcp.service_account_json ~=
+ "{\"type\":\"service_account\",\"project_id\":\"test\"}")
+ }
+ }
+--- response_body
+instance1 header: Bearer sk-instance1-key
+instance2 header: Bearer sk-instance2-key
+instance2 gcp: {"type":"service_account","project_id":"test"}
+etcd instance1 header encrypted: true
+etcd instance2 header encrypted: true
+etcd instance2 gcp encrypted: true
+
+
+
+=== TEST 3: ai-rag: encrypt deeply nested api_key fields (3-level dotted 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,
+ [[{
+ "plugins": {
+ "ai-rag": {
+ "embeddings_provider": {
+ "azure_openai": {
+ "endpoint":
"https://test.openai.azure.com/embeddings",
+ "api_key": "embeddings-secret-key"
+ }
+ },
+ "vector_search_provider": {
+ "azure_ai_search": {
+ "endpoint":
"https://test.search.windows.net/indexes/idx/docs/search",
+ "api_key": "search-secret-key"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ -- admin API should return decrypted values
+ local code, message, res = t('/apisix/admin/routes/1',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+
+ local ai_rag = res.value.plugins["ai-rag"]
+ ngx.say("embeddings api_key: ",
+ ai_rag.embeddings_provider.azure_openai.api_key)
+ ngx.say("search api_key: ",
+ ai_rag.vector_search_provider.azure_ai_search.api_key)
+
+ -- etcd should have encrypted values
+ local etcd = require("apisix.core.etcd")
+ local res = assert(etcd.get('/routes/1'))
+ local ai_rag_etcd = res.body.node.value.plugins["ai-rag"]
+ ngx.say("etcd embeddings encrypted: ",
+ ai_rag_etcd.embeddings_provider.azure_openai.api_key ~=
"embeddings-secret-key")
+ ngx.say("etcd search encrypted: ",
+ ai_rag_etcd.vector_search_provider.azure_ai_search.api_key
~= "search-secret-key")
+ }
+ }
+--- response_body
+embeddings api_key: embeddings-secret-key
+search api_key: search-secret-key
+etcd embeddings encrypted: true
+etcd search encrypted: true
+
+
+
+=== TEST 4: process_encrypt_field handles nil and missing fields gracefully
+--- 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
+ -- ai-proxy with no auth.gcp set: should not error
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "plugins": {
+ "ai-proxy": {
+ "provider": "openai",
+ "auth": {
+ "header": {
+ "Authorization": "Bearer sk-only-header"
+ }
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ local code, message, res = t('/apisix/admin/routes/1',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+
+ local ai_proxy = res.value.plugins["ai-proxy"]
+ ngx.say("header.Authorization: ",
ai_proxy.auth.header.Authorization)
+ ngx.say("query is nil: ", ai_proxy.auth.query == nil)
+ ngx.say("gcp is nil: ", ai_proxy.auth.gcp == nil)
+ }
+ }
+--- response_body
+header.Authorization: Bearer sk-only-header
+query is nil: true
+gcp is nil: true
+
+
+
+=== TEST 5: regression: flat key encryption still works (basic-auth password)
+--- 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/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "test_encrypt3",
+ "plugins": {
+ "basic-auth": {
+ "username": "foo",
+ "password": "bar"
+ }
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ -- admin API returns decrypted
+ local code, message, res =
t('/apisix/admin/consumers/test_encrypt3',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+ ngx.say(res.value.plugins["basic-auth"].password)
+
+ -- etcd stores encrypted
+ local etcd = require("apisix.core.etcd")
+ local res = assert(etcd.get('/consumers/test_encrypt3'))
+ ngx.say(res.body.node.value.plugins["basic-auth"].password ~=
"bar")
+ }
+ }
+--- response_body
+bar
+true
+
+
+
+=== TEST 6: regression: 2-level dotted path encryption still works
(kafka-proxy sasl.password)
+--- 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,
+ [[{
+ "plugins": {
+ "kafka-proxy": {
+ "sasl": {
+ "username": "admin",
+ "password": "admin-secret"
+ }
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ },
+ "uri": "/hello"
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+
+ ngx.sleep(0.1)
+
+ local code, message, res = t('/apisix/admin/routes/1',
+ ngx.HTTP_GET
+ )
+ res = json.decode(res)
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(message)
+ return
+ end
+ ngx.say(res.value.plugins["kafka-proxy"].sasl.password)
+
+ local etcd = require("apisix.core.etcd")
+ local res = assert(etcd.get('/routes/1'))
+ ngx.say(res.body.node.value.plugins["kafka-proxy"].sasl.password
~= "admin-secret")
+ }
+ }
+--- response_body
+admin-secret
+true
+
+
+
+=== TEST 7: encrypt_fields with array of strings leaf via process_encrypt_field
+--- yaml_config
+apisix:
+ data_encryption:
+ enable_encrypt_fields: true
+ keyring:
+ - edd1c9f0985e76a2
+--- config
+ location /t {
+ content_by_lua_block {
+ local plugin = require("apisix.plugin")
+ local ssl = require("apisix.ssl")
+
+ -- Simulate array-of-strings encryption (e.g., secret_fallbacks)
+ local conf = {
+ secrets = {"secret-one", "secret-two", "secret-three"}
+ }
+
+ -- Encrypt
+ plugin.process_encrypt_field(conf, "secrets",
ssl.aes_encrypt_pkey, "test", "encrypt")
+
+ -- Verify all elements are encrypted (not plaintext)
+ for i, v in ipairs(conf.secrets) do
+ ngx.say("encrypted[" .. i .. "] differs: ", v ~= "secret-" ..
+ (i == 1 and "one" or i == 2 and "two" or "three"))
+ end
+
+ -- Decrypt
+ plugin.process_encrypt_field(conf, "secrets",
ssl.aes_decrypt_pkey, "test", "decrypt")
+
+ -- Verify all elements are restored
+ ngx.say("decrypted[1]: ", conf.secrets[1])
+ ngx.say("decrypted[2]: ", conf.secrets[2])
+ ngx.say("decrypted[3]: ", conf.secrets[3])
+ }
+ }
+--- response_body
+encrypted[1] differs: true
+encrypted[2] differs: true
+encrypted[3] differs: true
+decrypted[1]: secret-one
+decrypted[2]: secret-two
+decrypted[3]: secret-three