please don’t use qq email 刘维 <levy...@qq.com.invalid>于2022年11月26日 周六23:23写道:
> Background > > Nowadays, if a plugin needs to store sensitive information, it will design > a separate set of encryption mechanism, which causes the problem of > repeated development and cumbersome management. Now we consider introducing > Data Encryption function, hereinafter referred to as DE, to encrypt the > specified information, and any module/plugin that needs to encrypt > sensitive information only needs to specify the fields to be encrypted on > display, so that transparent encryption/decryption can be performed when > saving/reading the information. > > Benefits > > Unify the encryption mechanism of APISIX to avoid duplication of > development while improving product competitiveness > > Goals > > APISIX provides DE functionality to support the use of this capability in > plugins and elsewhere to protect sensitive information. > > The user scenarios are as follows > > > Turn on the DE function in the configuration file > > apisix: > enable_global_data_encryption: true > > set encrypted fields > > local schema = { > type = "object", > properties = { > username = { type = "string" }, > password = { type = "string", encrypted = true }, > <==== Here > }, > required = {"username", "password"}, > } > > Set the plugin > > curl http://127.0.0.1:9180/apisix/admin/consumers -H 'X-API-KEY: > edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' > { > "username": "foo", > "plugins": { > "basic-auth": { > "username": "foo", > "password": "bar" > <= Sensitive Information > } > } > }' > > The plugin data read directly from ETCD is encrypted > > etcdctl get /apisix/consumers/foo > { > "username":"foo", > "update_time":1668584767, > "create_time":1668584767, > "plugins":{ > "basic-auth":{ > "username":"foo", > > "password":"da61c0083b6d19ef3db2490d0da96a71572da0fa" <= > encrypted > } > } > } > > The data surface is read normally > > curl http://127.0.0.1:9180/apisix/admin/consumers -H 'X-API-KEY: > edd1c9f034335f136f87ad84b625c8f1' > { > "list": [ > { > "createdIndex": 8627, > "modifiedIndex": 8627, > "value": { > "username": "foo", > "update_time": 1668584767, > "create_time": 1668584767, > "plugins": { > "basic-auth": { > "username": "foo", > "password": "bar" > <= Unencrypted > } > } > }, > "key": "/apisix/consumers/foo" > } > ], > "total": 1 > } > Detailed Design > > > Add a field in the configuration file to enable data encryption > > apisix: > data_encryption: > # use `encrypted = {fields}` in plugin schema to enable encryption > enable: false > # If not set, the default value is `false`. > keyring: > - edd1c9f0985e76a2 > # If not set, will save origin value into etcd. > - qeddd145sfvddff3 > # 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. > > Specify the fields to be encrypted in the schema of the plugin > > local consumer_schema = { > type = "object", > title = "work with consumer object", > properties = { > username = { type = "string" }, > password = { type = "string", encrypted = true }, > <===== here > }, > required = {"username", "password"}, > } > > local schema = { > > } > > If data encryption is enabled, the corresponding fields of the following > plugins will be encrypted > > jwt-auth:secret,private_key > basic-auth:password > authz-keycloak:client_secret > authz-casdoor:client_secret > openid-connect:client_secret > hmac-auth: secret_key > csrf:key > rocketmq-logger:secret_key > clickhouse-logger:password > error-log-logger:clickhouse.password > sls-logger:access_key_secret > google-cloud-logging:auth_config.private_key > elasticsearch-logger:auth.password > tencent-cloud-cls:secret_key > kafka-proxy:sasl.password > > Encryption and Decryption > > > > Commonly used algorithms AES, ChaCha20, we use AES128CBC encryption > algorithm, the logic is the same as ssl encryption and decryption, that is, > only use the first key to encrypt, if it fails to take turns to use all > keys to decrypt, consider reusing the ssl encryption and decryption > function (https://github.com/apache/apisix/blob/master/apisix/ssl.lua) > > > > Currently, only those distributed through the apisix admin interface can > be encrypted. Many users have developed their own admin and directly > manipulated storage media such as etcd, so they need to implement the > encryption function themselves. > > > > After the encryption function is enabled, the data that was not encrypted > needs to be sent down again through the interface in order to be encrypted, > is a refresh interface provided? Consider not providing it for now > > > > Combining the above, the original string is used directly after decryption > failure > > > > Admin now only uses etcd, considering that the first version only supports > etcd and only supports encryption of plugin parameters > > > > Encryption: > > > Add encryption and decryption to check_single_plugin_schema, note that > there are 2 different types of schema within the plugin, namely: schema and > consumer_schema > local function check_single_plugin_schema(name, plugin_conf, schema_type, > skip_disabled_plugin, encrypt_or_decrypt) > ... > if plugin_obj.check_schema then > ... > end > > if schema_type == core.schema.TYPE_CONSUMER then > ok, err = encrypt(plugin_conf, > plugin_obj.consumer_schema, encrypt_or_decrypt) > else > ok, err = encrypt(plugin_conf, > plugin_obj.schema, encrypt_or_decrypt) > end > > return true > end > The encryption and decryption function iterates through the corresponding > schema, and if it finds the encrypted = true field, it encrypts and > decrypts the field in the corresponding configuration > local function encrypt(plugin_conf, schema, encrypt_or_decrypt) > if schema.type == "object" then > for name, obj in pairs(schema.properties) do > encrypt(plugin_conf[name], obj, > encrypt_or_decrypt) > end > elseif schema.type = "array" then > ... > end > > if schema.encrypted then > aes128(plugin_conf) > end > end > Add corresponding options in check_schema and stream_check_schema, now > directly called in admin, no need to modify, encrypt_or_decrypt is nil by > default, i.e. encrypt > local function check_schema(plugins_conf, schema_type, > skip_disabled_plugin, encrypt_or_decrypt) > for name, plugin_conf in pairs(plugins_conf) do > local ok, err = > check_single_plugin_schema(name, plugin_conf, > schema_type, > skip_disabled_plugin, encrypt_or_decrypt) > if not ok then > return false, err > end > end > > return true > end > plugin_checker and stream_plugin_checker are used for checksumming when > reading data from etcd, and are decrypted by default > <br class="Apple-interchange-newline"><div></div> > > > > > > > > function _M.plugin_checker(item, schema_type) > if item.plugins then > return check_schema(item.plugins, schema_type, > true, decrypt) <=== decrypt end > return true > > end -- Thanks, Ming Wen, Apache APISIX PMC Chair Twitter: _WenMing