This is an automated email from the ASF dual-hosted git repository.

spacewander 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 3d5128de4 feat: support global data encryption of secret information 
(#8403)
3d5128de4 is described below

commit 3d5128de4dde90dc53443cccecc04723962f3831
Author: tzssangglass <[email protected]>
AuthorDate: Wed Nov 30 14:26:43 2022 +0800

    feat: support global data encryption of secret information (#8403)
    
    Fixes https://github.com/apache/apisix/issues/8407
---
 apisix/admin/init.lua                       |   9 +
 apisix/admin/plugins.lua                    |  10 +-
 apisix/admin/utils.lua                      |  26 ++
 apisix/plugin.lua                           |  76 ++-
 apisix/plugins/basic-auth.lua               |   3 +-
 apisix/plugins/clickhouse-logger.lua        |   2 +-
 apisix/plugins/key-auth.lua                 |   2 +-
 apisix/ssl.lua                              | 116 +++--
 conf/config-default.yaml                    |   7 +
 docs/en/latest/plugin-develop.md            |  34 +-
 docs/en/latest/plugins/basic-auth.md        |   2 +
 docs/en/latest/plugins/clickhouse-logger.md |   2 +
 docs/en/latest/plugins/key-auth.md          |   2 +
 docs/zh/latest/plugin-develop.md            |  34 +-
 docs/zh/latest/plugins/basic-auth.md        |   2 +
 docs/zh/latest/plugins/clickhouse-logger.md |   2 +
 docs/zh/latest/plugins/key-auth.md          |   2 +
 t/admin/plugins.t                           |   2 +-
 t/lib/server.lua                            |  13 +
 t/node/data_encrypt.t                       | 571 +++++++++++++++++++++++
 t/node/data_encrypt2.t                      | 699 ++++++++++++++++++++++++++++
 t/plugin/clickhouse-logger.t                |  21 +-
 t/plugin/error-log-logger-clickhouse.t      |  34 +-
 23 files changed, 1586 insertions(+), 85 deletions(-)

diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua
index 02c8cd9d6..4ae937a28 100644
--- a/apisix/admin/init.lua
+++ b/apisix/admin/init.lua
@@ -19,6 +19,7 @@ local core = require("apisix.core")
 local route = require("apisix.utils.router")
 local plugin = require("apisix.plugin")
 local v3_adapter = require("apisix.admin.v3_adapter")
+local utils = require("apisix.admin.utils")
 local ngx = ngx
 local get_method = ngx.req.get_method
 local ngx_time = ngx.time
@@ -189,6 +190,14 @@ local function run()
     local code, data = resource[method](seg_id, req_body, seg_sub_path,
                                         uri_args)
     if code then
+        if method == "get" and plugin.enable_data_encryption then
+            if seg_res == "consumers" then
+                utils.decrypt_params(plugin.decrypt_conf, data, 
core.schema.TYPE_CONSUMER)
+            else
+                utils.decrypt_params(plugin.decrypt_conf, data)
+            end
+        end
+
         if v3_adapter.enable_v3() then
             core.response.set_header("X-API-VERSION", "v3")
         else
diff --git a/apisix/admin/plugins.lua b/apisix/admin/plugins.lua
index 380723163..26dcab719 100644
--- a/apisix/admin/plugins.lua
+++ b/apisix/admin/plugins.lua
@@ -23,12 +23,20 @@ local table_sort = table.sort
 local table_insert = table.insert
 local get_uri_args = ngx.req.get_uri_args
 local plugin_get_all = require("apisix.plugin").get_all
+local encrypt_conf = require("apisix.plugin").encrypt_conf
+local pairs = pairs
 
 local _M = {}
 
 
 function _M.check_schema(plugins_conf, schema_type)
-    return check_schema(plugins_conf, schema_type, false)
+    local ok, err = check_schema(plugins_conf, schema_type, false)
+    if ok then
+        for name, conf in pairs(plugins_conf) do
+            encrypt_conf(name, conf, schema_type)
+        end
+    end
+    return ok, err
 end
 
 
diff --git a/apisix/admin/utils.lua b/apisix/admin/utils.lua
index db73dda67..ee396d0d3 100644
--- a/apisix/admin/utils.lua
+++ b/apisix/admin/utils.lua
@@ -17,6 +17,8 @@
 local core    = require("apisix.core")
 local ngx_time = ngx.time
 local tonumber = tonumber
+local ipairs = ipairs
+local pairs = pairs
 
 
 local _M = {}
@@ -78,4 +80,28 @@ function _M.fix_count(body, id)
 end
 
 
+function _M.decrypt_params(decrypt_func, body, schema_type)
+    -- list
+    if body.list then
+        for _, route in ipairs(body.list) do
+            if route.value and route.value.plugins then
+                for name, conf in pairs(route.value.plugins) do
+                    decrypt_func(name, conf, schema_type)
+                end
+            end
+        end
+        return
+    end
+
+    -- node
+    local plugins = body.node and body.node.value
+                    and body.node.value.plugins
+
+    if plugins then
+        for name, conf in pairs(plugins) do
+            decrypt_func(name, conf, schema_type)
+        end
+    end
+end
+
 return _M
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index 4221b7387..d305f1798 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -20,6 +20,7 @@ local config_util   = require("apisix.core.config_util")
 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 ngx           = ngx
 local crc32         = ngx.crc32_short
 local ngx_exit      = ngx.exit
@@ -849,6 +850,71 @@ check_plugin_metadata = function(item)
 end
 
 
+local enable_data_encryption
+local function enable_gde()
+    if enable_data_encryption == nil then
+        enable_data_encryption =
+            core.table.try_read_attr(local_conf, "apisix", "data_encryption", 
"enable")
+        _M.enable_data_encryption = enable_data_encryption
+    end
+
+    return enable_data_encryption
+end
+
+
+local function get_plugin_schema_for_gde(name, schema_type)
+    if not enable_gde() then
+        return nil
+    end
+
+    local plugin_schema = local_plugins_hash and local_plugins_hash[name]
+    local schema
+    if schema_type == core.schema.TYPE_CONSUMER then
+        schema = plugin_schema.consumer_schema
+    else
+        schema = plugin_schema.schema
+    end
+
+    return schema
+end
+
+
+local function decrypt_conf(name, conf, schema_type)
+    local schema = get_plugin_schema_for_gde(name, schema_type)
+    if not schema then
+        return
+    end
+
+    for key, props in pairs(schema.properties) do
+        if props.type == "string" and props.encrypted and conf[key] then
+            local encrypted, err = apisix_ssl.aes_decrypt_pkey(conf[key], 
"data_encrypt")
+            if not encrypted then
+                core.log.warn("failed to decrypt the conf of plugin [", name,
+                               "] key [", key, "], err: ", err)
+            else
+                conf[key] = encrypted
+            end
+        end
+    end
+end
+_M.decrypt_conf = decrypt_conf
+
+
+local function encrypt_conf(name, conf, schema_type)
+    local schema = get_plugin_schema_for_gde(name, schema_type)
+    if not schema then
+        return
+    end
+
+    for key, props in pairs(schema.properties) do
+        if props.type == "string" and props.encrypted and conf[key] then
+            local encrypted = apisix_ssl.aes_encrypt_pkey(conf[key], 
"data_encrypt")
+            conf[key] = encrypted
+        end
+    end
+end
+_M.encrypt_conf = encrypt_conf
+
 
 local function check_schema(plugins_conf, schema_type, skip_disabled_plugin)
     for name, plugin_conf in pairs(plugins_conf) do
@@ -901,7 +967,15 @@ _M.stream_check_schema = stream_check_schema
 
 function _M.plugin_checker(item, schema_type)
     if item.plugins then
-        return check_schema(item.plugins, schema_type, true)
+        local ok, err = check_schema(item.plugins, schema_type, true)
+
+        if ok and enable_gde() then
+            -- decrypt conf
+            for name, conf in pairs(item.plugins) do
+                decrypt_conf(name, conf, schema_type)
+            end
+        end
+        return ok, err
     end
 
     return true
diff --git a/apisix/plugins/basic-auth.lua b/apisix/plugins/basic-auth.lua
index 5f984ed66..65d96e5c7 100644
--- a/apisix/plugins/basic-auth.lua
+++ b/apisix/plugins/basic-auth.lua
@@ -18,7 +18,6 @@ local core = require("apisix.core")
 local ngx = ngx
 local ngx_re = require("ngx.re")
 local consumer = require("apisix.consumer")
-
 local lrucache = core.lrucache.new({
     ttl = 300, count = 512
 })
@@ -39,7 +38,7 @@ local consumer_schema = {
     title = "work with consumer object",
     properties = {
         username = { type = "string" },
-        password = { type = "string" },
+        password = { type = "string", encrypted = true },
     },
     required = {"username", "password"},
 }
diff --git a/apisix/plugins/clickhouse-logger.lua 
b/apisix/plugins/clickhouse-logger.lua
index 026f0cfa9..28404f9ae 100644
--- a/apisix/plugins/clickhouse-logger.lua
+++ b/apisix/plugins/clickhouse-logger.lua
@@ -36,7 +36,7 @@ local schema = {
         endpoint_addr = core.schema.uri_def,
         endpoint_addrs = {items = core.schema.uri_def, type = "array", 
minItems = 1},
         user = {type = "string", default = ""},
-        password = {type = "string", default = ""},
+        password = {type = "string", default = "", encrypted = true},
         database = {type = "string", default = ""},
         logtable = {type = "string", default = ""},
         timeout = {type = "integer", minimum = 1, default = 3},
diff --git a/apisix/plugins/key-auth.lua b/apisix/plugins/key-auth.lua
index 5806025a6..63e41a084 100644
--- a/apisix/plugins/key-auth.lua
+++ b/apisix/plugins/key-auth.lua
@@ -40,7 +40,7 @@ local schema = {
 local consumer_schema = {
     type = "object",
     properties = {
-        key = {type = "string"},
+        key = { type = "string", encrypted = true },
     },
     required = {"key"},
 }
diff --git a/apisix/ssl.lua b/apisix/ssl.lua
index 26fa5c6ef..18898027a 100644
--- a/apisix/ssl.lua
+++ b/apisix/ssl.lua
@@ -56,52 +56,96 @@ function _M.server_name()
 end
 
 
-local _aes_128_cbc_with_iv_tbl
-local function get_aes_128_cbc_with_iv()
-    if _aes_128_cbc_with_iv_tbl == nil then
-        _aes_128_cbc_with_iv_tbl = core.table.new(2, 0)
-        local local_conf = core.config.local_conf()
-        local ivs = core.table.try_read_attr(local_conf, "apisix", "ssl", 
"key_encrypt_salt")
-        local type_ivs = type(ivs)
+local function init_iv_tbl(ivs)
+    local _aes_128_cbc_with_iv_tbl = core.table.new(2, 0)
+    local type_ivs = type(ivs)
 
-        if type_ivs == "table" then
-            for _, iv in ipairs(ivs) do
-                local aes_with_iv = assert(aes:new(iv, nil, aes.cipher(128, 
"cbc"), {iv = iv}))
-                core.table.insert(_aes_128_cbc_with_iv_tbl, aes_with_iv)
-            end
-        elseif type_ivs == "string" then
-            local aes_with_iv = assert(aes:new(ivs, nil, aes.cipher(128, 
"cbc"), {iv = ivs}))
+    if type_ivs == "table" then
+        for _, iv in ipairs(ivs) do
+            local aes_with_iv = assert(aes:new(iv, nil, aes.cipher(128, 
"cbc"), {iv = iv}))
             core.table.insert(_aes_128_cbc_with_iv_tbl, aes_with_iv)
         end
+    elseif type_ivs == "string" then
+        local aes_with_iv = assert(aes:new(ivs, nil, aes.cipher(128, "cbc"), 
{iv = ivs}))
+        core.table.insert(_aes_128_cbc_with_iv_tbl, aes_with_iv)
     end
 
     return _aes_128_cbc_with_iv_tbl
 end
 
 
-function _M.aes_encrypt_pkey(origin)
-    local aes_128_cbc_with_iv_tbl = get_aes_128_cbc_with_iv()
-    local aes_128_cbc_with_iv = aes_128_cbc_with_iv_tbl[1]
-    if aes_128_cbc_with_iv ~= nil and core.string.has_prefix(origin, "---") 
then
-        local encrypted = aes_128_cbc_with_iv:encrypt(origin)
-        if encrypted == nil then
-            core.log.error("failed to encrypt key[", origin, "] ")
-            return origin
-        end
+local _aes_128_cbc_with_iv_tbl_ssl
+local function get_aes_128_cbc_with_iv_ssl(local_conf)
+    if _aes_128_cbc_with_iv_tbl_ssl == nil then
+        local ivs = core.table.try_read_attr(local_conf, "apisix", "ssl", 
"key_encrypt_salt")
+        _aes_128_cbc_with_iv_tbl_ssl = init_iv_tbl(ivs)
+    end
+
+    return _aes_128_cbc_with_iv_tbl_ssl
+end
 
-        return ngx_encode_base64(encrypted)
+
+local _aes_128_cbc_with_iv_tbl_gde
+local function get_aes_128_cbc_with_iv_gde(local_conf)
+    if _aes_128_cbc_with_iv_tbl_gde == nil then
+        local ivs = core.table.try_read_attr(local_conf, "apisix", 
"data_encryption", "keyring")
+        _aes_128_cbc_with_iv_tbl_gde = init_iv_tbl(ivs)
     end
 
-    return origin
+    return _aes_128_cbc_with_iv_tbl_gde
 end
 
 
-local function aes_decrypt_pkey(origin)
-    if core.string.has_prefix(origin, "---") then
+
+local function encrypt(aes_128_cbc_with_iv, origin)
+    local encrypted = aes_128_cbc_with_iv:encrypt(origin)
+    if encrypted == nil then
+        core.log.error("failed to encrypt key[", origin, "] ")
         return origin
     end
 
-    local aes_128_cbc_with_iv_tbl = get_aes_128_cbc_with_iv()
+    return ngx_encode_base64(encrypted)
+end
+
+function _M.aes_encrypt_pkey(origin, field)
+    local local_conf = core.config.local_conf()
+
+    if not field then
+        -- default used by ssl
+        local aes_128_cbc_with_iv_tbl_ssl = 
get_aes_128_cbc_with_iv_ssl(local_conf)
+        local aes_128_cbc_with_iv_ssl = aes_128_cbc_with_iv_tbl_ssl[1]
+        if aes_128_cbc_with_iv_ssl ~= nil and core.string.has_prefix(origin, 
"---") then
+            return encrypt(aes_128_cbc_with_iv_ssl, origin)
+        end
+    else
+        if field == "data_encrypt" then
+            local aes_128_cbc_with_iv_tbl_gde = 
get_aes_128_cbc_with_iv_gde(local_conf)
+            local aes_128_cbc_with_iv_gde = aes_128_cbc_with_iv_tbl_gde[1]
+            if aes_128_cbc_with_iv_gde ~= nil then
+                return encrypt(aes_128_cbc_with_iv_gde, origin)
+            end
+        end
+    end
+
+    return origin
+end
+
+
+local function aes_decrypt_pkey(origin, field)
+    local local_conf = core.config.local_conf()
+    local aes_128_cbc_with_iv_tbl
+
+    if not field then
+        if core.string.has_prefix(origin, "---") then
+            return origin
+        end
+        aes_128_cbc_with_iv_tbl = get_aes_128_cbc_with_iv_ssl(local_conf)
+    else
+        if field == "data_encrypt" then
+            aes_128_cbc_with_iv_tbl = get_aes_128_cbc_with_iv_gde(local_conf)
+        end
+    end
+
     if #aes_128_cbc_with_iv_tbl == 0 then
         return origin
     end
@@ -119,10 +163,9 @@ local function aes_decrypt_pkey(origin)
         end
     end
 
-    core.log.error("decrypt ssl key failed")
-
-    return nil
+    return nil, "decrypt ssl key failed"
 end
+_M.aes_decrypt_pkey = aes_decrypt_pkey
 
 
 local function validate(cert, key)
@@ -136,8 +179,10 @@ local function validate(cert, key)
         return true
     end
 
-    key = aes_decrypt_pkey(key)
+    local err
+    key, err = aes_decrypt_pkey(key)
     if not key then
+        core.log.error(err)
         return nil, "failed to decrypt previous encrypted key"
     end
 
@@ -173,7 +218,12 @@ end
 local function parse_pem_priv_key(sni, pkey)
     core.log.debug("parsing priv key for sni: ", sni)
 
-    local parsed, err = ngx_ssl.parse_pem_priv_key(aes_decrypt_pkey(pkey))
+    local key, err = aes_decrypt_pkey(pkey)
+    if not key then
+        core.log.error(err)
+        return nil, err
+    end
+    local parsed, err = ngx_ssl.parse_pem_priv_key(key)
     return parsed, err
 end
 
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 6f9de38bd..f14db8ef2 100755
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -123,6 +123,13 @@ apisix:
   #  ip: 127.0.0.1
   #  port: 9090
   disable_sync_configuration_during_start: false  # safe exit. Remove this 
once the feature is stable
+  data_encryption:                # add `encrypted = true` in plugin schema to 
enable encryption
+    enable: false                 # if not set, the default value is `false`.
+    keyring:
+      - qeddd145sfvddff3          # If not set, will save origin value into 
etcd.
+                                  # If set this, the keyring should be an 
array whose elements are string, and the size is also 16, and it will encrypt 
fields with AES-128-CBC
+                                  # !!! So do not change it after encryption, 
it can't decrypt the fields have be saved if you change !!
+                                  # Only use the first key to encrypt, and 
decrypt in the order of the array.
 
 nginx_config:                     # config for render the template to generate 
nginx.conf
   #user: root                     # specifies the execution user of the worker 
process.
diff --git a/docs/en/latest/plugin-develop.md b/docs/en/latest/plugin-develop.md
index b47d9dfdf..c55c47bf1 100644
--- a/docs/en/latest/plugin-develop.md
+++ b/docs/en/latest/plugin-develop.md
@@ -216,7 +216,7 @@ end
 Note: the project has provided the public method "__core.schema.check__", 
which can be used directly to complete JSON
 verification.
 
-In addition, if the plugin needs to use some metadata, we can define the 
plugin `metadata_schema`, and then we can dynamically manage these metadata 
through the `admin api`. Example:
+In addition, if the plugin needs to use some metadata, we can define the 
plugin `metadata_schema`, and then we can dynamically manage these metadata 
through the `Admin API`. Example:
 
 ```lua
 local metadata_schema = {
@@ -293,6 +293,38 @@ function _M.check_schema(conf, schema_type)
 end
 ```
 
+### encrypted storage fields
+
+Specify the parameters to be stored encrypted. (Requires APISIX version >= 
3.1.0)
+
+Some plugins require parameters to be stored encrypted, such as the `password` 
parameter of the `basic-auth` plugin. This plugin needs to specify in the 
`schema` which parameters need to be stored encrypted.
+
+```lua
+password = { type = "string", encrypted = true },
+```
+
+Parameters can be stored encrypted by specifying `encrypted = true` in the 
`schema`. APISIX will provide the following functionality.
+
+- When adding and updating resources via the `Admin API`, APISIX automatically 
encrypts parameters with `encrypted = true` and stores them in etcd
+- When fetching resources via the `Admin API` and when running the plugin, 
APISIX automatically decrypts the `encrypted = true` parameter
+
+How to enable this feature?
+
+Enable `data_encryption` in `config.yaml`.
+
+```yaml
+apisix:
+    data_encryption:
+    enable: true
+    keyring:
+        - edd1c9f0985e76a2
+        - qeddd145sfvddff4
+```
+
+APISIX will try to decrypt the data with keys in the order of the keys in the 
keyring (only for parameters declared `encrypted = true`). If the decryption 
fails, the next key will be tried until the decryption succeeds.
+
+If none of the keys in `keyring` can decrypt the data, the original data is 
used.
+
 ## choose phase to run
 
 Determine which phase to run, generally access or rewrite. If you don't know 
the [OpenResty 
lifecycle](https://github.com/openresty/lua-nginx-module/blob/master/README.markdown#directives),
 it's
diff --git a/docs/en/latest/plugins/basic-auth.md 
b/docs/en/latest/plugins/basic-auth.md
index a55dd1467..25c574c9c 100644
--- a/docs/en/latest/plugins/basic-auth.md
+++ b/docs/en/latest/plugins/basic-auth.md
@@ -42,6 +42,8 @@ For Consumer:
 | username | string | True     | Unique username for a Consumer. If multiple 
Consumers use the same `username`, a request matching exception is raised. |
 | password | string | True     | Password of the user.                         
                                                                         |
 
+NOTE: The schema for `password` also defines `encrypted = true`, which means 
that the field will be stored encrypted in etcd. See [encrypted storage 
fields](../plugin-develop.md#encrypted-storage-fields).
+
 For Route:
 
 | Name             | Type    | Required | Default | Description                
                                            |
diff --git a/docs/en/latest/plugins/clickhouse-logger.md 
b/docs/en/latest/plugins/clickhouse-logger.md
index 22786f4b1..9de95ccf1 100644
--- a/docs/en/latest/plugins/clickhouse-logger.md
+++ b/docs/en/latest/plugins/clickhouse-logger.md
@@ -47,6 +47,8 @@ The `clickhouse-logger` Plugin is used to push logs to 
[ClickHouse](https://clic
 
 This Plugin supports using batch processors to aggregate and process entries 
(logs/data) in a batch. This avoids the need for frequently submitting the 
data. The batch processor submits data every `5` seconds or when the data in 
the queue reaches `1000`. See [Batch 
Processor](../batch-processor.md#configuration) for more information or setting 
your custom configuration.
 
+NOTE: The schema for `password` also defines `encrypted = true`, which means 
that the field will be stored encrypted in etcd. See [encrypted storage 
fields](../plugin-develop.md#encrypted-storage-fields).
+
 ## Metadata
 
 You can also set the format of the logs by configuring the Plugin metadata. 
The following configurations are available:
diff --git a/docs/en/latest/plugins/key-auth.md 
b/docs/en/latest/plugins/key-auth.md
index ea3ee098b..b188cbc27 100644
--- a/docs/en/latest/plugins/key-auth.md
+++ b/docs/en/latest/plugins/key-auth.md
@@ -41,6 +41,8 @@ For Consumer:
 |------|--------|-------------|----------------------------|
 | key  | string | required    | Unique key for a Consumer. |
 
+NOTE: The schema for `key` also defines `encrypted = true`, which means that 
the field will be stored encrypted in etcd. See [encrypted storage 
fields](../plugin-develop.md#encrypted-storage-fields).
+
 For Route:
 
 | Name   | Type   | Requirement | Default | Valid | Description                
                                                                                
                                                                                
                                                                                
   |
diff --git a/docs/zh/latest/plugin-develop.md b/docs/zh/latest/plugin-develop.md
index d9e570515..6012c323c 100644
--- a/docs/zh/latest/plugin-develop.md
+++ b/docs/zh/latest/plugin-develop.md
@@ -196,7 +196,7 @@ end
 
 注:项目已经提供了 __core.schema.check__ 公共方法,直接使用即可完成配置参数校验。
 
-另外,如果插件需要使用一些元数据,可以定义插件的 `metadata_schema` ,然后就可以通过 `admin api` 动态的管理这些元数据了。如:
+另外,如果插件需要使用一些元数据,可以定义插件的 `metadata_schema` ,然后就可以通过 `Admin API` 动态的管理这些元数据了。如:
 
 ```lua
 local metadata_schema = {
@@ -273,6 +273,38 @@ function _M.check_schema(conf, schema_type)
 end
 ```
 
+### 加密存储字段
+
+指定参数需要被加密存储(需要 APISIX 版本不小于 3.1)
+
+有些插件需要将参数加密存储,比如 `basic-auth` 插件的 `password` 参数。这个插件需要在 `schema` 
中指定哪些参数需要被加密存储。
+
+```lua
+password = { type = "string", encrypted = true },
+```
+
+通过在 `schema` 中指定 `encrypted = true`,可以将参数加密存储。APISIX 将提供以下功能:
+
+- 通过 `Admin API` 来新增和更新资源时,对于 `encrypted = true` 的参数,APISIX 会自动加密存储在 etcd 中
+- 通过 `Admin API` 来获取资源时,以及在运行插件时,对于 `encrypted = true` 的参数,APISIX 会自动解密
+
+如何开启该功能?
+
+在 `config.yaml` 中开启 `data_encryption`:
+
+```yaml
+apisix:
+    data_encryption:
+    enable: true
+    keyring:
+        - edd1c9f0985e76a2
+        - qeddd145sfvddff4
+```
+
+`keyring` 是一个数组,可以指定多个 key,APISIX 会按照 keyring 中 key 的顺序,依次尝试用 key 来解密数据(只对声明 
`encrypted = true` 的参数)。如果解密失败,会尝试下一个 key,直到解密成功。
+
+如果 `keyring` 中的 key 都无法解密数据,则使用原始数据。
+
 ## 确定执行阶段
 
 根据业务功能,确定你的插件需要在哪个阶段执行。 key-auth 是一个认证插件,所以需要在 rewrite 阶段执行。在 APISIX,只有认证逻辑可以在 
rewrite 阶段里面完成,其他需要在代理到上游之前执行的逻辑都是在 access 阶段完成的。
diff --git a/docs/zh/latest/plugins/basic-auth.md 
b/docs/zh/latest/plugins/basic-auth.md
index dc2c597bb..f19dcab57 100644
--- a/docs/zh/latest/plugins/basic-auth.md
+++ b/docs/zh/latest/plugins/basic-auth.md
@@ -42,6 +42,8 @@ Consumer 端:
 | username | string | 是   | Consumer 的用户名并且该用户名是唯一,如果多个 Consumer 使用了相同的 
`username`,将会出现请求匹配异常。|
 | password | string | 是   | 用户的密码。                                             
                                         |
 
+注意:`password` 的 schema 中还定义了 `encrypted = true`,这意味着该字段将会被加密存储在 etcd 中。具体参考 
[加密存储字段](../plugin-develop.md#加密存储字段)。
+
 Route 端:
 
 | 名称             | 类型     | 必选项 | 默认值  | 描述                                    
                        |
diff --git a/docs/zh/latest/plugins/clickhouse-logger.md 
b/docs/zh/latest/plugins/clickhouse-logger.md
index 9b0ba11e7..dc35db7e9 100644
--- a/docs/zh/latest/plugins/clickhouse-logger.md
+++ b/docs/zh/latest/plugins/clickhouse-logger.md
@@ -47,6 +47,8 @@ description: 本文介绍了 API 网关 Apache APISIX 如何使用 clickhouse-lo
 
 该插件支持使用批处理器来聚合并批量处理条目(日志/数据)。这样可以避免插件频繁地提交数据,默认情况下批处理器每 `5` 秒钟或队列中的数据达到 `1000` 
条时提交数据,如需了解批处理器相关参数设置,请参考 [Batch-Processor](../batch-processor.md#配置)。
 
+注意:`password` 的 schema 中还定义了 `encrypted = true`,这意味着该字段将会被加密存储在 etcd 中。具体参考 
[加密存储字段](../plugin-develop.md#加密存储字段)。
+
 ## 配置插件元数据
 
 `clickhouse-logger` 也支持自定义日志格式,与 [http-logger](./http-logger.md) 插件类似。
diff --git a/docs/zh/latest/plugins/key-auth.md 
b/docs/zh/latest/plugins/key-auth.md
index 8b4ba2146..8031854e7 100644
--- a/docs/zh/latest/plugins/key-auth.md
+++ b/docs/zh/latest/plugins/key-auth.md
@@ -41,6 +41,8 @@ Consumer 端:
 | ---- | ------ | ------ | 
-------------------------------------------------------------------------------------------------------------
 |
 | key  | string | 是     | 不同的 Consumer 应有不同的 `key`,它应当是唯一的。如果多个 Consumer 
使用了相同的 `key`,将会出现请求匹配异常。 |
 
+注意:`key` 的 schema 中还定义了 `encrypted = true`,这意味着该字段将会被加密存储在 etcd 中。具体参考 
[加密存储字段](../plugin-develop.md#加密存储字段)。
+
 Router 端:
 
 | 名称              | 类型   | 必选项 | 默认值 | 描述                                      
                                                                                
                                 |
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 2ab993152..00a500e08 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -338,7 +338,7 @@ 
qr/\[\{"name":"wolf-rbac","priority":2555\},\{"name":"ldap-auth","priority":2540
         }
     }
 --- response_body eval
-qr/\{"properties":\{"password":\{"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
 with consumer object","type":"object"\}/
+qr/\{"properties":\{"password":\{"encrypted":true,"type":"string"\},"username":\{"type":"string"\}\},"required":\["username","password"\],"title":"work
 with consumer object","type":"object"\}/
 
 
 
diff --git a/t/lib/server.lua b/t/lib/server.lua
index 72b1f30b1..603ca2ae6 100644
--- a/t/lib/server.lua
+++ b/t/lib/server.lua
@@ -577,4 +577,17 @@ function _M.go()
     return _M[action]()
 end
 
+
+function _M.clickhouse_logger_server()
+    ngx.req.read_body()
+    local data = ngx.req.get_body_data()
+    local headers = ngx.req.get_headers()
+    ngx.log(ngx.WARN, "clickhouse body: ", data)
+    for k, v in pairs(headers) do
+        ngx.log(ngx.WARN, "clickhouse headers: " .. k .. ":" .. v)
+    end
+    ngx.say("ok")
+end
+
+
 return _M
diff --git a/t/node/data_encrypt.t b/t/node/data_encrypt.t
new file mode 100644
index 000000000..8746c88d5
--- /dev/null
+++ b/t/node/data_encrypt.t
@@ -0,0 +1,571 @@
+#
+# 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 (!$block->request) {
+        $block->set_value("request", "GET /t");
+    }
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: sanity
+# the sensitive data is encrypted in etcd, and it is normal to read it from 
the admin API
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/consumers/foo',
+                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)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/foo'))
+            ngx.say(res.body.node.value.plugins["basic-auth"].password)
+
+        }
+    }
+--- response_body
+bar
+77+NmbYqNfN+oLm0aX5akg==
+
+
+
+=== TEST 2: enable basic auth plugin
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {}
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 3: verify
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOmJhcg==
+--- response_body
+hello world
+
+
+
+=== TEST 4: multiple auth plugins work well
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        },
+                        "key-auth": {
+                            "key": "auth-one"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+            local code, message, res = t('/apisix/admin/consumers/foo',
+                ngx.HTTP_GET
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            ngx.say("done")
+        }
+    }
+--- response_body
+done
+
+
+
+=== TEST 5: enable multiple auth plugins on route
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {},
+                        "key-auth": {}
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 6: verify
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /hello
+--- more_headers
+apikey: auth-one
+Authorization: Basic Zm9vOmJhcg==
+--- response_body
+hello world
+
+
+
+=== TEST 7: disable data_encryption
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: false
+        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": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/foo'))
+            ngx.say(res.body.node.value.plugins["basic-auth"].password)
+
+        }
+    }
+--- response_body
+bar
+
+
+
+=== TEST 8: etcd store unencrypted password, enable data_encryption, 
decryption fails, use original password
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local core = require("apisix.core")
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res, err = core.etcd.set("/consumers/foo2", 
core.json.decode([[{
+                "username":"foo2",
+                "plugins":{
+                    "basic-auth":{
+                        "username":"foo2",
+                        "password":"bar"
+                    }
+                }
+            }]]))
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/consumers/foo2',
+                ngx.HTTP_GET
+            )
+            res = core.json.decode(res)
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            ngx.say(res.value.plugins["basic-auth"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/foo2'))
+            ngx.say(res.body.node.value.plugins["basic-auth"].password)
+        }
+    }
+--- response_body
+bar
+bar
+--- error_log
+failed to decrypt the conf of plugin [basic-auth] key [password], err: decrypt 
ssl key failed
+
+
+
+=== TEST 9: etcd stores both encrypted and unencrypted data
+# enable data_encryption, decryption of encrypted data succeeds
+# decryption of unencrypted data fails, make sure it works well
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local core = require("apisix.core")
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res, err = core.etcd.set("/consumers/foo2", 
core.json.decode([[{
+                "username":"foo2",
+                "plugins":{
+                    "basic-auth":{
+                        "username":"foo2",
+                        "password":"bar"
+                    },
+                    "key-auth": {
+                        "key": "vU/ZHVJw7b0XscDJ1Fhtig=="
+                    }
+                }
+            }]]))
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/consumers/foo2',
+                ngx.HTTP_GET
+            )
+            res = core.json.decode(res)
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            ngx.say(res.value.plugins["basic-auth"].password)
+            ngx.say(res.value.plugins["key-auth"].key)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/foo2'))
+            ngx.say(res.body.node.value.plugins["basic-auth"].password)
+            ngx.say(res.body.node.value.plugins["key-auth"].key)
+        }
+    }
+--- response_body
+bar
+auth-two
+bar
+vU/ZHVJw7b0XscDJ1Fhtig==
+--- error_log
+failed to decrypt the conf of plugin [basic-auth] key [password], err: decrypt 
ssl key failed
+
+
+
+=== TEST 10: verify, use the foo2 consumer
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /hello
+--- more_headers
+apikey: auth-two
+Authorization: Basic Zm9vMjpiYXI=
+--- response_body
+hello world
+
+
+
+=== TEST 11: keyring rotate, encrypt with edd1c9f0985e76a2
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {}
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "uri": "/hello"
+                }]]
+                )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- response_body
+passed
+
+
+
+=== TEST 12: keyring rotate, decrypt with qeddd145sfvddff3 would fail, but 
encrypt with edd1c9f0985e76a2 would success
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - qeddd145sfvddff3
+            - edd1c9f0985e76a2
+--- request
+GET /hello
+--- more_headers
+Authorization: Basic Zm9vOmJhcg==
+--- response_body
+hello world
+
+
+
+=== TEST 13: search consumer list
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+
+            -- dletet exist consumers
+            t('/apisix/admin/consumers/foo', ngx.HTTP_DELETE)
+            t('/apisix/admin/consumers/foo2', ngx.HTTP_DELETE)
+
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "foo",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "foo",
+                            "password": "bar"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/consumers',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "test",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "test",
+                            "password": "test"
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/consumers',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            local pwds = {}
+            table.insert(pwds, 
res.list[1].value.plugins["basic-auth"].password)
+            table.insert(pwds, 
res.list[2].value.plugins["basic-auth"].password)
+
+            ngx.say(json.encode(pwds))
+        }
+    }
+--- response_body
+["bar","test"]
diff --git a/t/node/data_encrypt2.t b/t/node/data_encrypt2.t
new file mode 100644
index 000000000..a1c73729e
--- /dev/null
+++ b/t/node/data_encrypt2.t
@@ -0,0 +1,699 @@
+#
+# 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: data encryption work well with plugins that not the auth plugins
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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": {
+                            "clickhouse-logger": {
+                                "user": "default",
+                                "password": "abc123",
+                                "database": "default",
+                                "logtable": "t",
+                                "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                                "batch_max_size":1,
+                                "inactive_timeout":1
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]]
+            )
+
+            ngx.sleep(0.5)
+
+            -- get plugin conf from admin api, password is decrypted
+            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["clickhouse-logger"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/routes/1'))
+            ngx.say(res.body.node.value.plugins["clickhouse-logger"].password)
+        }
+    }
+--- response_body
+abc123
+7ipXoKyiZZUAgf3WWNPI5A==
+
+
+
+=== TEST 2: verify
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- error_log
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
+clickhouse headers: x-clickhouse-key:abc123
+clickhouse headers: x-clickhouse-user:default
+clickhouse headers: x-clickhouse-database:default
+--- wait: 5
+
+
+
+=== TEST 3: POST and get list
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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',
+                 ngx.HTTP_POST,
+                 [[{
+                        "plugins": {
+                            "clickhouse-logger": {
+                                "user": "default",
+                                "password": "abc123",
+                                "database": "default",
+                                "logtable": "t",
+                                "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                                "batch_max_size":1,
+                                "inactive_timeout":1
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]]
+            )
+
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/routes',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+
+
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            ngx.say(res.list[1].value.plugins["clickhouse-logger"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local id = res.list[1].value.id
+            local key = "/routes/" .. id
+            local res = assert(etcd.get(key))
+
+            ngx.say(res.body.node.value.plugins["clickhouse-logger"].password)
+        }
+    }
+--- response_body
+abc123
+7ipXoKyiZZUAgf3WWNPI5A==
+
+
+
+=== TEST 4: PATCH
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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": {
+                            "clickhouse-logger": {
+                                "user": "default",
+                                "password": "abc123",
+                                "database": "default",
+                                "logtable": "t",
+                                "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                                "batch_max_size":1,
+                                "inactive_timeout":1
+                            }
+                        },
+                        "upstream": {
+                            "nodes": {
+                                "127.0.0.1:1982": 1
+                            },
+                            "type": "roundrobin"
+                        },
+                        "uri": "/opentracing"
+                }]]
+            )
+
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/routes/1/plugins',
+                ngx.HTTP_PATCH,
+                [[{
+                        "clickhouse-logger": {
+                            "user": "default",
+                            "password": "def456",
+                            "database": "default",
+                            "logtable": "t",
+                            "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                            "batch_max_size":1,
+                            "inactive_timeout":1
+                        }
+                 }]]
+            )
+
+            ngx.sleep(0.1)
+            -- get plugin conf from admin api, password is decrypted
+            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["clickhouse-logger"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/routes/1'))
+            ngx.say(res.body.node.value.plugins["clickhouse-logger"].password)
+        }
+    }
+--- response_body
+def456
+3hlZu5mwUbqROm+cy0Vi9A==
+
+
+
+=== TEST 5: data encryption work well with services
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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/services/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "plugins": {
+                        "clickhouse-logger": {
+                            "user": "default",
+                            "password": "abc123",
+                            "database": "default",
+                            "logtable": "t",
+                            "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                            "batch_max_size":1,
+                            "inactive_timeout":1
+                        }
+                    },
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "service_id": "1",
+                    "uri": "/opentracing"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/services/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["clickhouse-logger"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/services/1'))
+            ngx.say(res.body.node.value.plugins["clickhouse-logger"].password)
+        }
+    }
+--- response_body
+abc123
+7ipXoKyiZZUAgf3WWNPI5A==
+
+
+
+=== TEST 6: verify
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- error_log
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
+clickhouse headers: x-clickhouse-key:abc123
+clickhouse headers: x-clickhouse-user:default
+clickhouse headers: x-clickhouse-database:default
+--- wait: 5
+
+
+
+=== TEST 7: data encryption work well with plugin_configs
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local code, err = t('/apisix/admin/plugin_configs/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "clickhouse-logger": {
+                            "user": "default",
+                            "password": "abc123",
+                            "database": "default",
+                            "logtable": "t",
+                            "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
+                            "batch_max_size":1,
+                            "inactive_timeout":1
+                        }
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/routes/1',
+                 ngx.HTTP_PUT,
+                 [[{
+                    "plugin_config_id": 1,
+                    "uri": "/opentracing",
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1982": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/plugin_configs/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["clickhouse-logger"].password)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/plugin_configs/1'))
+            ngx.say(res.body.node.value.plugins["clickhouse-logger"].password)
+        }
+    }
+--- response_body
+abc123
+7ipXoKyiZZUAgf3WWNPI5A==
+
+
+
+=== TEST 8: verify
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- request
+GET /opentracing
+--- response_body
+opentracing
+--- error_log
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
+clickhouse headers: x-clickhouse-key:abc123
+clickhouse headers: x-clickhouse-user:default
+clickhouse headers: x-clickhouse-database:default
+--- wait: 5
+
+
+
+=== TEST 9: data encryption work well with global rule
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: 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",
+                    "plugins": {
+                        "basic-auth": {
+                            "username": "test",
+                            "password": "test"
+                        }
+                    },
+                    "desc": "test description"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            local code, body = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/hello",
+                    "upstream": {
+                        "type": "roundrobin",
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            local code, body = t('/apisix/admin/global_rules/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "basic-auth": {}
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            -- sleep for data sync
+            ngx.sleep(0.5)
+            -- get plugin conf from admin api, password is decrypted
+            local code, message, res = t('/apisix/admin/consumers/test',
+                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)
+
+            -- get plugin conf from etcd, password is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/test'))
+            ngx.say(res.body.node.value.plugins["basic-auth"].password)
+
+            -- hit the route with authorization
+            local code, body = t('/hello',
+                ngx.HTTP_PUT,
+                nil,
+                nil,
+                {Authorization = "Basic dGVzdDp0ZXN0"}
+            )
+            if code ~= 200 then
+                ngx.status = code
+                return
+            end
+
+            -- delete global rule
+            t('/apisix/admin/global_rules/1',
+                ngx.HTTP_DELETE
+            )
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+test
+9QKrmTT3TkWGvjlIoe5XXw==
+passed
+
+
+
+=== TEST 10: data encryption work well with consumer groups
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require("toolkit.json")
+            local t = require("lib.test_admin").test
+            local etcd = require("apisix.core.etcd")
+            local code, body = t('/apisix/admin/consumer_groups/company_a',
+                ngx.HTTP_PUT,
+                [[{
+                    "plugins": {
+                        "limit-count": {
+                            "count": 2,
+                            "time_window": 60,
+                            "rejected_code": 503,
+                            "key": "remote_addr"
+                        }
+                    }
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+
+            ngx.sleep(0.1)
+
+            local code, body = t('/apisix/admin/consumers/foobar',
+                ngx.HTTP_PUT,
+                [[{
+                    "username": "foobar",
+                    "plugins": {
+                        "key-auth": {
+                            "key": "auth-two"
+                        }
+                    },
+                    "group_id": "company_a"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(body)
+                return
+            end
+            ngx.sleep(0.1)
+
+            -- get plugin conf from admin api, key is decrypted
+            local code, message, res = t('/apisix/admin/consumers/foobar',
+                ngx.HTTP_GET
+            )
+            res = json.decode(res)
+            if code >= 300 then
+                ngx.status = code
+                ngx.say(message)
+                return
+            end
+
+            ngx.say(res.value.plugins["key-auth"].key)
+
+            -- get plugin conf from etcd, key is encrypted
+            local etcd = require("apisix.core.etcd")
+            local res = assert(etcd.get('/consumers/foobar'))
+            ngx.say(res.body.node.value.plugins["key-auth"].key)
+        }
+    }
+--- response_body
+auth-two
+vU/ZHVJw7b0XscDJ1Fhtig==
+
+
+
+=== TEST 11: verify data encryption
+--- yaml_config
+apisix:
+    data_encryption:
+        enable: true
+        keyring:
+            - edd1c9f0985e76a2
+--- config
+    location /t {
+        content_by_lua_block {
+            local json = require "t.toolkit.json"
+            local t = require("lib.test_admin").test
+            local code, err = t('/apisix/admin/routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "uri": "/hello",
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1980": 1
+                        },
+                        "type": "roundrobin"
+                    },
+                    "plugins": {
+                        "key-auth": {}
+                    }
+                }]]
+            )
+            if code > 300 then
+                ngx.log(ngx.ERR, err)
+                return
+            end
+            ngx.sleep(0.1)
+
+            local http = require "resty.http"
+            local uri = "http://127.0.0.1:"; .. ngx.var.server_port
+                        .. "/hello"
+            local ress = {}
+            for i = 1, 3 do
+                local httpc = http.new()
+                local res, err = httpc:request_uri(uri, {
+                    method = "GET",
+                    headers = {
+                        ["apikey"] = "auth-two"
+                    }
+                })
+                if not res then
+                    ngx.say(err)
+                    return
+                end
+                table.insert(ress, res.status)
+            end
+            ngx.say(json.encode(ress))
+        }
+    }
+--- response_body
+[200,200,503]
diff --git a/t/plugin/clickhouse-logger.t b/t/plugin/clickhouse-logger.t
index bcb874768..ed2f0dc29 100644
--- a/t/plugin/clickhouse-logger.t
+++ b/t/plugin/clickhouse-logger.t
@@ -32,18 +32,7 @@ add_block_preprocessor(sub {
     my $http_config = $block->http_config // <<_EOC_;
     server {
         listen 10420;
-        location /clickhouse-logger/test {
-            content_by_lua_block {
-                ngx.req.read_body()
-                local data = ngx.req.get_body_data()
-                local headers = ngx.req.get_headers()
-                ngx.log(ngx.WARN, "clickhouse body: ", data)
-                for k, v in pairs(headers) do
-                    ngx.log(ngx.WARN, "clickhouse headers: " .. k .. ":" .. v)
-                end
-                ngx.say("ok")
-            }
-        }
+
         location /clickhouse-logger/test1 {
             content_by_lua_block {
                 ngx.req.read_body()
@@ -78,7 +67,7 @@ __DATA__
                                                  password = "a",
                                                  database = "default",
                                                  logtable = "t",
-                                                 endpoint_addr = 
"http://127.0.0.1:10420/clickhouse-logger/test";,
+                                                 endpoint_addr = 
"http://127.0.0.1:1980/clickhouse_logger_server";,
                                                  max_retry_count = 1,
                                                  name = "clickhouse logger",
                                                  ssl_verify = false
@@ -105,7 +94,7 @@ passed
                                                  password = "a",
                                                  database = "default",
                                                  logtable = "t",
-                                                 endpoint_addr = 
"http://127.0.0.1:10420/clickhouse-logger/test";
+                                                 endpoint_addr = 
"http://127.0.0.1:1980/clickhouse_logger_server";
                                                  })
 
             if not ok then
@@ -157,7 +146,7 @@ value should match only one schema, but matches none
                                 "password": "a",
                                 "database": "default",
                                 "logtable": "t",
-                                "endpoint_addr": 
"http://127.0.0.1:10420/clickhouse-logger/test";,
+                                "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";,
                                 "batch_max_size":1,
                                 "inactive_timeout":1
                             }
@@ -197,7 +186,7 @@ passed
                                 "password": "a",
                                 "database": "default",
                                 "logtable": "t",
-                                "endpoint_addrs": 
["http://127.0.0.1:10420/clickhouse-logger/test";,
+                                "endpoint_addrs": 
["http://127.0.0.1:1980/clickhouse_logger_server";,
                                                   
"http://127.0.0.1:10420/clickhouse-logger/test1";],
                                 "batch_max_size":1,
                                 "inactive_timeout":1
diff --git a/t/plugin/error-log-logger-clickhouse.t 
b/t/plugin/error-log-logger-clickhouse.t
index 25554fe6a..f6a328d7f 100644
--- a/t/plugin/error-log-logger-clickhouse.t
+++ b/t/plugin/error-log-logger-clickhouse.t
@@ -35,26 +35,6 @@ plugins:
 _EOC_
         $block->set_value("extra_yaml_config", $extra_yaml_config);
     }
-
-    my $http_config = $block->http_config // <<_EOC_;
-    server {
-        listen 10420;
-        location /error-logger-clickhouse/test {
-            content_by_lua_block {
-                ngx.req.read_body()
-                local data = ngx.req.get_body_data()
-                local headers = ngx.req.get_headers()
-                ngx.log(ngx.WARN, "clickhouse error log body: ", data)
-                for k, v in pairs(headers) do
-                    ngx.log(ngx.WARN, "clickhouse headers: " .. k .. ":" .. v)
-                end
-                ngx.say("ok")
-            }
-        }
-    }
-_EOC_
-
-    $block->set_value("http_config", $http_config);
 });
 
 run_tests();
@@ -74,7 +54,7 @@ __DATA__
                         password = "a",
                         database = "default",
                         logtable = "t",
-                        endpoint_addr = 
"http://127.0.0.1:10420/error-logger-clickhouse/test";
+                        endpoint_addr = 
"http://127.0.0.1:1980/clickhouse_logger_server";
                     }
                 },
                 core.schema.TYPE_METADATA
@@ -105,7 +85,7 @@ done
                                 "password": "a",
                                 "database": "default",
                                 "logtable": "t",
-                                "endpoint_addr": 
"http://127.0.0.1:10420/error-logger-clickhouse/test";
+                                "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";
                     },
                     "inactive_timeout": 1
                 }]]
@@ -117,7 +97,7 @@ done
 --- response_body
 --- error_log
 this is a warning message for test2
-clickhouse error log body: INSERT INTO t FORMAT JSONEachRow
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
 clickhouse headers: x-clickhouse-key:a
 clickhouse headers: x-clickhouse-user:default
 clickhouse headers: x-clickhouse-database:default
@@ -139,7 +119,7 @@ clickhouse headers: x-clickhouse-database:default
                         "password": "a",
                         "database": "default",
                         "logtable": "t",
-                        "endpoint_addr": 
"http://127.0.0.1:10420/error-logger-clickhouse/test";
+                        "endpoint_addr": 
"http://127.0.0.1:1980/clickhouse_logger_server";
                     },
                     "batch_max_size": 15,
                     "inactive_timeout": 1
@@ -152,7 +132,7 @@ clickhouse headers: x-clickhouse-database:default
 --- response_body
 --- error_log
 this is a warning message for test3
-clickhouse error log body: INSERT INTO t FORMAT JSONEachRow
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
 clickhouse headers: x-clickhouse-key:a
 clickhouse headers: x-clickhouse-user:default
 clickhouse headers: x-clickhouse-database:default
@@ -171,7 +151,7 @@ clickhouse headers: x-clickhouse-database:default
 --- response_body
 --- error_log
 this is a warning message for test4
-clickhouse error log body: INSERT INTO t FORMAT JSONEachRow
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
 clickhouse headers: x-clickhouse-key:a
 clickhouse headers: x-clickhouse-user:default
 clickhouse headers: x-clickhouse-database:default
@@ -190,7 +170,7 @@ clickhouse headers: x-clickhouse-database:default
 --- response_body
 --- error_log
 this is a warning message for test5
-clickhouse error log body: INSERT INTO t FORMAT JSONEachRow
+clickhouse body: INSERT INTO t FORMAT JSONEachRow
 clickhouse headers: x-clickhouse-key:a
 clickhouse headers: x-clickhouse-user:default
 clickhouse headers: x-clickhouse-database:default

Reply via email to