You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by GitBox <gi...@apache.org> on 2022/11/29 12:05:48 UTC

[GitHub] [apisix] spacewander commented on a diff in pull request #8403: feat: support global data encryption of secret information

spacewander commented on code in PR #8403:
URL: https://github.com/apache/apisix/pull/8403#discussion_r1034655502


##########
apisix/admin/utils.lua:
##########
@@ -78,4 +80,29 @@ function _M.fix_count(body, id)
 end
 
 
+function _M.decrypt_params(decrypt_func, body, schema_type)
+    -- list
+    if body.list and #body.list > 0 then
+        for _, route in ipairs(body.list) do
+            if route.value and route.value.plugins
+               and core.table.nkeys(route.value.plugins) > 0 then

Review Comment:
   We can use `core.table.isempty` for this case. In fact, we don't need to check the len of plugins.



##########
apisix/plugin.lua:
##########
@@ -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(name, schema_type)

Review Comment:
   ```suggestion
   local function get_plugin_schema_for_gde(name, schema_type)
   ```
   would be a more appropriate name



##########
apisix/plugin.lua:
##########
@@ -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(name, schema_type)
+    if not enable_gde() then
+        return false

Review Comment:
   ```suggestion
           return nil
   ```
   as the return type isn't boolean



##########
apisix/ssl.lua:
##########
@@ -173,7 +218,11 @@ 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)

Review Comment:
   Should return from here?



##########
t/node/data_encrypt.t:
##########
@@ -0,0 +1,581 @@
+#
+# 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");
+    }
+
+    if (!$block->no_error_log) {

Review Comment:
   We don't need this now (no_error_log is added by default)



##########
t/node/data_encrypt.t:
##########
@@ -0,0 +1,581 @@
+#
+# 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");
+    }
+
+    if (!$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+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)
+        }
+    }
+--- request
+GET /t
+--- 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)
+        }
+    }
+--- request
+GET /t
+--- 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))
+        }
+    }
+--- request
+GET /t

Review Comment:
   Ditto



##########
t/node/data_encrypt.t:
##########
@@ -0,0 +1,581 @@
+#
+# 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");
+    }
+
+    if (!$block->no_error_log) {
+        $block->set_value("no_error_log", "[error]\n[alert]");
+    }
+});
+
+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)
+        }
+    }
+--- request
+GET /t

Review Comment:
   `GET /t` is set at the top of this file



##########
docs/en/latest/plugin-develop.md:
##########
@@ -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.0.1)

Review Comment:
   ```suggestion
   Specify the parameters to be stored encrypted. (Requires APISIX version >= 3.1.0)
   ```



##########
docs/zh/latest/plugin-develop.md:
##########
@@ -273,6 +273,38 @@ function _M.check_schema(conf, schema_type)
 end
 ```
 
+### 加密存储字段
+
+指定参数需要被加密存储(需要 APISIX 版本大于 3.0.1)

Review Comment:
   ```suggestion
   指定参数需要被加密存储(需要 APISIX 版本不小于 3.1)
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@apisix.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org