Skip to content

Commit

Permalink
feat: support global data encryption of secret information (#8403)
Browse files Browse the repository at this point in the history
Fixes #8407
  • Loading branch information
tzssangglass authored Nov 30, 2022
1 parent bd6cbef commit 3d5128d
Show file tree
Hide file tree
Showing 23 changed files with 1,586 additions and 85 deletions.
9 changes: 9 additions & 0 deletions apisix/admin/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
10 changes: 9 additions & 1 deletion apisix/admin/plugins.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
26 changes: 26 additions & 0 deletions apisix/admin/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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
76 changes: 75 additions & 1 deletion apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions apisix/plugins/basic-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
Expand All @@ -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"},
}
Expand Down
2 changes: 1 addition & 1 deletion apisix/plugins/clickhouse-logger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
2 changes: 1 addition & 1 deletion apisix/plugins/key-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ local schema = {
local consumer_schema = {
type = "object",
properties = {
key = {type = "string"},
key = { type = "string", encrypted = true },
},
required = {"key"},
}
Expand Down
116 changes: 83 additions & 33 deletions apisix/ssl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 3d5128d

Please sign in to comment.