Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: data encryption support more plugins #8487

Merged
merged 8 commits into from
Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apisix/admin/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ local function run()
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)
elseif seg_res == "plugin_metadata" then
utils.decrypt_params(plugin.decrypt_conf, data, core.schema.TYPE_METADATA)
else
utils.decrypt_params(plugin.decrypt_conf, data)
end
Expand Down
3 changes: 3 additions & 0 deletions apisix/admin/plugin_metadata.lua
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ local pcall = pcall
local require = require
local core = require("apisix.core")
local utils = require("apisix.admin.utils")
local encrypt_conf = require("apisix.plugin").encrypt_conf

local injected_mark = "injected metadata_schema"
local _M = {
Expand Down Expand Up @@ -73,6 +74,8 @@ local function check_conf(plugin_name, conf)
ok, err = plugin_object.check_schema(conf, core.schema.TYPE_METADATA)
end

encrypt_conf(plugin_name, conf, core.schema.TYPE_METADATA)

if not ok then
return nil, {error_msg = "invalid configuration: " .. err}
end
Expand Down
6 changes: 6 additions & 0 deletions apisix/admin/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ function _M.decrypt_params(decrypt_func, body, schema_type)
decrypt_func(name, conf, schema_type)
end
end

-- metadata
if schema_type == core.schema.TYPE_METADATA then
local conf = body.node and body.node.value
decrypt_func(conf.name, conf, schema_type)
end
end

return _M
97 changes: 79 additions & 18 deletions apisix/plugin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ local enable_debug = require("apisix.debug").enable_debug
local wasm = require("apisix.wasm")
local expr = require("resty.expr.v1")
local apisix_ssl = require("apisix.ssl")
local re_split = require("ngx.re").split
local ngx = ngx
local crc32 = ngx.crc32_short
local ngx_exit = ngx.exit
Expand Down Expand Up @@ -844,12 +845,6 @@ local function check_single_plugin_schema(name, plugin_conf, schema_type, skip_d
end


check_plugin_metadata = function(item)
return check_single_plugin_schema(item.id, item,
core.schema.TYPE_METADATA, true)
end


local enable_data_encryption
local function enable_gde()
if enable_data_encryption == nil then
Expand All @@ -868,9 +863,15 @@ local function get_plugin_schema_for_gde(name, schema_type)
end

local plugin_schema = local_plugins_hash and local_plugins_hash[name]
if not plugin_schema then
return nil
end

local schema
if schema_type == core.schema.TYPE_CONSUMER then
schema = plugin_schema.consumer_schema
elseif schema_type == core.schema.TYPE_METADATA then
schema = plugin_schema.metadata_schema
else
schema = plugin_schema.schema
end
Expand All @@ -882,17 +883,39 @@ end
local function decrypt_conf(name, conf, schema_type)
local schema = get_plugin_schema_for_gde(name, schema_type)
if not schema then
core.log.warn("failed to get schema for plugin: ", name)
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
if schema.encrypt_fields and core.table.nkeys(schema.encrypt_fields) > 0 then
for _, key in ipairs(schema.encrypt_fields) do
if conf[key] then
local decrypted, err = apisix_ssl.aes_decrypt_pkey(conf[key], "data_encrypt")
if not decrypted then
core.log.warn("failed to decrypt the conf of plugin [", name,
"] key [", key, "], err: ", err)
else
conf[key] = decrypted
end
elseif core.string.find(key, ".") then
-- decrypt fields has indents
local res, err = re_split(key, "\\.", "jo")
if not res then
core.log.warn("failed to split key [", key, "], err: ", err)
return
end

-- we only support two levels
if conf[res[1]] and conf[res[1]][res[2]] then
local decrypted, err = apisix_ssl.aes_decrypt_pkey(
conf[res[1]][res[2]], "data_encrypt")
if not decrypted then
core.log.warn("failed to decrypt the conf of plugin [", name,
"] key [", key, "], err: ", err)
else
conf[res[1]][res[2]] = decrypted
end
end
end
end
end
Expand All @@ -903,19 +926,57 @@ _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
core.log.warn("failed to get schema for plugin: ", name)
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
if schema.encrypt_fields and core.table.nkeys(schema.encrypt_fields) > 0 then
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
for _, key in ipairs(schema.encrypt_fields) do
if conf[key] then
local encrypted, err = apisix_ssl.aes_encrypt_pkey(conf[key], "data_encrypt")
if not encrypted then
core.log.warn("failed to encrypt the conf of plugin [", name,
"] key [", key, "], err: ", err)
else
conf[key] = encrypted
end
elseif core.string.find(key, ".") then
-- encrypt fields has indents
local res, err = re_split(key, "\\.", "jo")
if not res then
core.log.warn("failed to split key [", key, "], err: ", err)
return
end

-- we only support two levels
Copy link
Contributor

@monkeyDluffy6017 monkeyDluffy6017 Dec 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

too many limitations, maybe we should optimize this feature later when we have time

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why add this limitation, can we parse the conf recursively

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to optimize when the schema must be expressed in more than two levels.
For now, I think two levels are enough(In fact, I hate nesting more than two levels in a schema, it's unreadable.)

if conf[res[1]] and conf[res[1]][res[2]] then
local encrypted, err = apisix_ssl.aes_encrypt_pkey(
conf[res[1]][res[2]], "data_encrypt")
if not encrypted then
core.log.warn("failed to encrypt the conf of plugin [", name,
"] key [", key, "], err: ", err)
else
conf[res[1]][res[2]] = encrypted
end
end
end
end
end
end
_M.encrypt_conf = encrypt_conf


check_plugin_metadata = function(item)
local ok, err = check_single_plugin_schema(item.id, item,
core.schema.TYPE_METADATA, true)
if ok and enable_gde()then
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved
decrypt_conf(item.name, item, core.schema.TYPE_METADATA)
end

return ok, err
end


local function check_schema(plugins_conf, schema_type, skip_disabled_plugin)
for name, plugin_conf in pairs(plugins_conf) do
local ok, err = check_single_plugin_schema(name, plugin_conf,
Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/authz-casdoor.lua
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ local schema = {
client_secret = {type = "string"},
callback_url = {type = "string", pattern = "^[^%?]+[^/]$"}
},
encrypt_fields = {"client_secret"},
required = {
"callback_url", "endpoint_addr", "client_id", "client_secret"
}
Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/authz-keycloak.lua
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ local schema = {
maxLength = 4096
},
},
encrypt_fields = {"client_secret"},
required = {"client_id"},
allOf = {
-- Require discovery or token endpoint.
Expand Down
3 changes: 2 additions & 1 deletion apisix/plugins/basic-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ local consumer_schema = {
title = "work with consumer object",
properties = {
username = { type = "string" },
password = { type = "string", encrypted = true },
password = { type = "string" },
},
encrypt_fields = {"password"},
required = {"username", "password"},
}

Expand Down
3 changes: 2 additions & 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 = "", encrypted = true},
password = {type = "string", default = ""},
database = {type = "string", default = ""},
logtable = {type = "string", default = ""},
timeout = {type = "integer", minimum = 1, default = 3},
Expand All @@ -47,6 +47,7 @@ local schema = {
{required = {"endpoint_addr", "user", "password", "database", "logtable"}},
{required = {"endpoint_addrs", "user", "password", "database", "logtable"}}
},
encrypt_fields = {"password"},
}


Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/csrf.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ local schema = {
default = "apisix-csrf-token"
}
},
encrypt_fields = {"key"},
required = {"key"}
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/elasticsearch-logger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ local schema = {
default = true
}
},
encrypt_fields = {"auth.password"},
required = { "endpoint_addr", "field" },
}

Expand Down
3 changes: 2 additions & 1 deletion apisix/plugins/error-log-logger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ local metadata_schema = {
{required = {"clickhouse"}},
-- for compatible with old schema
{required = {"host", "port"}}
}
},
encrypt_fields = {"clickhouse.password"},
}


Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/google-cloud-logging.lua
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ local schema = {
{ required = { "auth_config" } },
{ required = { "auth_file" } },
},
encrypt_fields = {"auth_config.private_key"},
}


Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/hmac-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ local consumer_schema = {
default = MAX_REQ_BODY,
},
},
encrypt_fields = {"secret_key"},
required = {"access_key", "secret_key"},
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/jwt-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ local consumer_schema = {
}
}
},
encrypt_fields = {"secret", "private_key"},
required = {"key"},
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/kafka-proxy.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ local schema = {
required = {"username", "password"},
},
},
encrypt_fields = {"sasl.password"},
}


Expand Down
3 changes: 2 additions & 1 deletion apisix/plugins/key-auth.lua
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ local schema = {
local consumer_schema = {
type = "object",
properties = {
key = { type = "string", encrypted = true },
key = { type = "string" },
},
encrypt_fields = {"key"},
required = {"key"},
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/openid-connect.lua
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ local schema = {
default = false
}
},
encrypt_fields = {"client_secret"},
required = {"client_id", "client_secret", "discovery"}
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/rocketmq-logger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ local schema = {
}
},
},
encrypt_fields = {"secret_key"},
required = {"nameserver_list", "topic"}
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/sls-logger.lua
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ local schema = {
access_key_id = {type = "string"},
access_key_secret = {type ="string"}
},
encrypt_fields = {"access_key_secret"},
required = {"host", "port", "project", "logstore", "access_key_id", "access_key_secret"}
}

Expand Down
1 change: 1 addition & 0 deletions apisix/plugins/tencent-cloud-cls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ local schema = {
include_resp_body = { type = "boolean", default = false },
global_tag = { type = "object" },
},
encrypt_fields = {"secret_key"},
required = { "cls_host", "cls_topic", "secret_id", "secret_key" }
}

Expand Down
2 changes: 1 addition & 1 deletion conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ 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
data_encryption: # add `encrypt_fields = { $field },` 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.
Expand Down
18 changes: 13 additions & 5 deletions docs/en/latest/plugin-develop.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,13 +300,21 @@ 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 },
encrypt_fields = {"password"}
```

Parameters can be stored encrypted by specifying `encrypted = true` in the `schema`. APISIX will provide the following functionality.
If it is a nested parameter, such as the `clickhouse.password` parameter of the `error-log-logger` plugin, it needs to be separated by `.`:

- 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
```lua
encrypt_fields = {"clickhouse.password"}
```

Currently only two levels of nesting are supported.
tzssangglass marked this conversation as resolved.
Show resolved Hide resolved

Parameters can be stored encrypted by specifying `encrypt_fields = {"password"}` in the `schema`. APISIX will provide the following functionality.

- When adding and updating resources via the `Admin API`, APISIX automatically encrypts the parameters declared in `encrypt_fields` and stores them in etcd
- When fetching resources via the `Admin API` and when running the plugin, APISIX automatically decrypts the parameters declared in `encrypt_fields`

How to enable this feature?

Expand All @@ -321,7 +329,7 @@ apisix:
- 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.
APISIX will try to decrypt the data with keys in the order of the keys in the keyring (only for parameters declared in `encrypt_fields`). 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.

Expand Down
2 changes: 2 additions & 0 deletions docs/en/latest/plugins/authz-casdoor.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ The `authz-casdoor` Plugin can be used to add centralized authentication with [C
| client_secret | string | True | Client secret in Casdoor. |
| callback_url | string | True | Callback URL used to receive state and code. |

NOTE: `encrypt_fields = {"client_secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).

:::info IMPORTANT

`endpoint_addr` and `callback_url` should not end with '/'.
Expand Down
2 changes: 2 additions & 0 deletions docs/en/latest/plugins/authz-keycloak.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Refer to [Authorization Services Guide](https://www.keycloak.org/docs/latest/aut
| access_denied_redirect_uri | string | False | | [1, 2048] | URI to redirect the user to instead of returning an error message like `"error_description":"not_authorized"`. |
| password_grant_token_generation_incoming_uri | string | False | | /api/token | Set this to generate token using the password grant type. The Plugin will compare incoming request URI to this value. |

NOTE: `encrypt_fields = {"client_secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).

### Discovery and endpoints

It is recommended to use the `discovery` attribute as the `authz-keycloak` Plugin can discover the Keycloak API endpoints from it.
Expand Down
Loading