You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2022/12/01 01:35:32 UTC
[apisix] branch master updated: feat: Support store secrets in secrets manager for auth plugin via kms components (#8421)
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 0ed451a32 feat: Support store secrets in secrets manager for auth plugin via kms components (#8421)
0ed451a32 is described below
commit 0ed451a32e9b7ba9ee4750b6a02289ed26c8ae2b
Author: soulbird <zh...@outlook.com>
AuthorDate: Thu Dec 1 09:35:22 2022 +0800
feat: Support store secrets in secrets manager for auth plugin via kms components (#8421)
Co-authored-by: soulbird <zh...@gmail.com>
---
apisix/admin/kms.lua | 11 +-
apisix/consumer.lua | 3 +-
apisix/core/env.lua | 57 +++---
apisix/core/utils.lua | 56 ------
apisix/init.lua | 2 +
apisix/kms.lua | 239 ++++++++++++++++++++++
apisix/kms/vault.lua | 23 ++-
apisix/schema_def.lua | 15 --
docs/assets/images/kms.png | Bin 0 -> 125602 bytes
docs/en/latest/terminology/kms.md | 74 ++++++-
docs/zh/latest/terminology/kms.md | 70 ++++++-
t/config-center-yaml/kms.t | 413 ++++++++++++++++++++++++++++++++++++++
t/core/env.t | 38 ++--
t/core/utils.t | 111 ----------
t/plugin/key-auth.t | 84 +++++++-
15 files changed, 958 insertions(+), 238 deletions(-)
diff --git a/apisix/admin/kms.lua b/apisix/admin/kms.lua
index 895b60421..4be84a7e8 100644
--- a/apisix/admin/kms.lua
+++ b/apisix/admin/kms.lua
@@ -14,10 +14,14 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
+local require = require
+
local core = require("apisix.core")
local utils = require("apisix.admin.utils")
+
local type = type
local tostring = tostring
+local pcall = pcall
local _M = {
@@ -46,7 +50,12 @@ local function check_conf(id, conf, need_id, typ)
conf.id = id
core.log.info("conf: ", core.json.delay_encode(conf))
- local ok, err = core.schema.check(core.schema["kms_" .. typ], conf)
+ local ok, kms_service = pcall(require, "apisix.kms." .. typ)
+ if not ok then
+ return false, {error_msg = "invalid kms service: " .. typ}
+ end
+
+ local ok, err = core.schema.check(kms_service.schema, conf)
if not ok then
return nil, {error_msg = "invalid configuration: " .. err}
end
diff --git a/apisix/consumer.lua b/apisix/consumer.lua
index 32ff69275..bdeb78cd3 100644
--- a/apisix/consumer.lua
+++ b/apisix/consumer.lua
@@ -15,6 +15,7 @@
-- limitations under the License.
--
local core = require("apisix.core")
+local kms = require("apisix.kms")
local plugin = require("apisix.plugin")
local plugin_checker = require("apisix.plugin").plugin_checker
local error = error
@@ -103,7 +104,7 @@ local function create_consume_cache(consumers_conf, key_attr)
for _, consumer in ipairs(consumers_conf.nodes) do
core.log.info("consumer node: ", core.json.delay_encode(consumer))
local new_consumer = core.table.clone(consumer)
- new_consumer.auth_conf = core.utils.retrieve_secrets_ref(new_consumer.auth_conf)
+ new_consumer.auth_conf = kms.fetch_secrets(new_consumer.auth_conf)
consumer_names[new_consumer.auth_conf[key_attr]] = new_consumer
end
diff --git a/apisix/core/env.lua b/apisix/core/env.lua
index d8702904a..2bab04327 100644
--- a/apisix/core/env.lua
+++ b/apisix/core/env.lua
@@ -27,10 +27,13 @@ local find = string.find
local sub = string.sub
local str = ffi.string
-local _M = {}
-
local ENV_PREFIX = "$ENV://"
+local _M = {
+ PREFIX = ENV_PREFIX
+}
+
+
local apisix_env_vars = {}
ffi.cdef [[
@@ -39,32 +42,35 @@ ffi.cdef [[
function _M.init()
- local e = ffi.C.environ
- if not e then
- log.warn("could not access environment variables")
- return
- end
-
- local i = 0
- while e[i] ~= nil do
- local var = str(e[i])
- local p = find(var, "=")
- if p then
- apisix_env_vars[sub(var, 1, p - 1)] = sub(var, p + 1)
+ local e = ffi.C.environ
+ if not e then
+ log.warn("could not access environment variables")
+ return
end
- i = i + 1
- end
+ local i = 0
+ while e[i] ~= nil do
+ local var = str(e[i])
+ local p = find(var, "=")
+ if p then
+ apisix_env_vars[sub(var, 1, p - 1)] = sub(var, p + 1)
+ end
+
+ i = i + 1
+ end
end
-local function is_env_uri(env_uri)
+local function parse_env_uri(env_uri)
-- Avoid the error caused by has_prefix to cause a crash.
- return type(env_uri) == "string" and string.has_prefix(upper(env_uri), ENV_PREFIX)
-end
+ if type(env_uri) ~= "string" then
+ return nil, "error env_uri type: " .. type(env_uri)
+ end
+ if not string.has_prefix(upper(env_uri), ENV_PREFIX) then
+ return nil, "error env_uri prefix: " .. env_uri
+ end
-local function parse_env_uri(env_uri)
local path = sub(env_uri, #ENV_PREFIX + 1)
local idx = find(path, "/")
if not idx then
@@ -80,18 +86,17 @@ local function parse_env_uri(env_uri)
end
-function _M.get(env_uri)
- if not is_env_uri(env_uri) then
- return nil
+function _M.fetch_by_uri(env_uri)
+ local opts, err = parse_env_uri(env_uri)
+ if not opts then
+ return nil, err
end
- local opts = parse_env_uri(env_uri)
local main_value = apisix_env_vars[opts.key] or os.getenv(opts.key)
if main_value and opts.sub_key ~= "" then
local vt, err = json.decode(main_value)
if not vt then
- log.warn("decode failed, err: ", err, " value: ", main_value)
- return nil
+ return nil, "decode failed, err: " .. (err or "") .. ", value: " .. main_value
end
return vt[opts.sub_key]
end
diff --git a/apisix/core/utils.lua b/apisix/core/utils.lua
index 6b61cf020..f72996b78 100644
--- a/apisix/core/utils.lua
+++ b/apisix/core/utils.lua
@@ -25,8 +25,6 @@ local rfind_char = core_str.rfind_char
local table = require("apisix.core.table")
local log = require("apisix.core.log")
local string = require("apisix.core.string")
-local env = require("apisix.core.env")
-local lrucache = require("apisix.core.lrucache")
local dns_client = require("apisix.core.dns.client")
local ngx_re = require("ngx.re")
local ipmatcher = require("resty.ipmatcher")
@@ -37,7 +35,6 @@ local sub_str = string.sub
local str_byte = string.byte
local tonumber = tonumber
local tostring = tostring
-local pairs = pairs
local re_gsub = ngx.re.gsub
local type = type
local io_popen = io.popen
@@ -332,57 +329,4 @@ end
_M.resolve_var = resolve_var
-local secrets_lrucache = lrucache.new({
- ttl = 300, count = 512
-})
-
-local retrieve_secrets_ref
-do
- local retrieve_ref
- function retrieve_ref(refs)
- for k, v in pairs(refs) do
- local typ = type(v)
- if typ == "string" then
- refs[k] = env.get(v) or v
- elseif typ == "table" then
- retrieve_ref(v)
- end
- end
- return refs
- end
-
- local function retrieve(refs)
- log.info("retrieve secrets refs")
-
- local new_refs = table.deepcopy(refs)
- return retrieve_ref(new_refs)
- end
-
- function retrieve_secrets_ref(refs, cache, key, version)
- if not refs or type(refs) ~= "table" then
- return nil
- end
- if not cache then
- return retrieve(refs)
- end
- return secrets_lrucache(key, version, retrieve, refs)
- end
-end
--- Retrieve all secrets ref in the given table
----
--- Retrieve all secrets ref in the given table,
--- and then replace them with the values from the environment variables.
---
--- @function core.utils.retrieve_secrets_ref
--- @tparam table refs The table to be retrieved.
--- @tparam boolean cache Whether to use lrucache to cache results.
--- @tparam string key The cache key for lrucache.
--- @tparam string version The cache version for lrucache.
--- @treturn table The table after the reference is replaced.
--- @usage
--- local new_refs = core.utils.retrieve_secrets_ref(refs) -- "no cache"
--- local new_refs = core.utils.retrieve_secrets_ref(refs, true, key, ver) -- "cache"
-_M.retrieve_secrets_ref = retrieve_secrets_ref
-
-
return _M
diff --git a/apisix/init.lua b/apisix/init.lua
index fbb090bee..038eb578b 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -37,6 +37,7 @@ local admin_init = require("apisix.admin.init")
local get_var = require("resty.ngxvar").fetch
local router = require("apisix.router")
local apisix_upstream = require("apisix.upstream")
+local apisix_kms = require("apisix.kms")
local set_upstream = apisix_upstream.set_by_route
local apisix_ssl = require("apisix.ssl")
local upstream_util = require("apisix.utils.upstream")
@@ -150,6 +151,7 @@ function _M.http_init_worker()
plugin_config.init_worker()
require("apisix.consumer").init_worker()
consumer_group.init_worker()
+ apisix_kms.init_worker()
apisix_upstream.init_worker()
require("apisix.plugins.ext-plugin.init").init_worker()
diff --git a/apisix/kms.lua b/apisix/kms.lua
new file mode 100644
index 000000000..2da234863
--- /dev/null
+++ b/apisix/kms.lua
@@ -0,0 +1,239 @@
+--
+-- 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.
+--
+
+local require = require
+local core = require("apisix.core")
+local string = require("apisix.core.string")
+
+local find = string.find
+local sub = string.sub
+local upper = string.upper
+local byte = string.byte
+local type = type
+local pcall = pcall
+local pairs = pairs
+local ipairs = ipairs
+
+local _M = {}
+
+
+local KMS_PREFIX = "$KMS://"
+local kmss
+
+local function check_kms(conf)
+ local idx = find(conf.id or "", "/")
+ if not idx then
+ return false, "no kms id"
+ end
+ local service = sub(conf.id, 1, idx - 1)
+
+ local ok, kms_service = pcall(require, "apisix.kms." .. service)
+ if not ok then
+ return false, "kms service not exits, service: " .. service
+ end
+
+ return core.schema.check(kms_service.schema, conf)
+end
+
+
+local kms_lrucache = core.lrucache.new({
+ ttl = 300, count = 512
+})
+
+local function create_kms_kvs(values)
+ local kms_services = {}
+
+ for _, v in ipairs(values) do
+ local path = v.value.id
+ local idx = find(path, "/")
+ if not idx then
+ core.log.error("no kms id")
+ return nil
+ end
+
+ local service = sub(path, 1, idx - 1)
+ local id = sub(path, idx + 1)
+
+ if not kms_services[service] then
+ kms_services[service] = {}
+ end
+ kms_services[service][id] = v.value
+ end
+
+ return kms_services
+end
+
+
+ local function kms_kv(service, confid)
+ local kms_values
+ kms_values = core.config.fetch_created_obj("/kms")
+ if not kms_values or not kms_values.values then
+ return nil
+ end
+
+ local kms_services = kms_lrucache("kms_kv", kms_values.conf_version,
+ create_kms_kvs, kms_values.values)
+ return kms_services[service] and kms_services[service][confid]
+end
+
+
+function _M.kmss()
+ if not kmss then
+ return nil, nil
+ end
+
+ return kmss.values, kmss.conf_version
+end
+
+
+function _M.init_worker()
+ local cfg = {
+ automatic = true,
+ checker = check_kms,
+ }
+
+ kmss = core.config.new("/kms", cfg)
+end
+
+
+local function parse_kms_uri(kms_uri)
+ -- Avoid the error caused by has_prefix to cause a crash.
+ if type(kms_uri) ~= "string" then
+ return nil, "error kms_uri type: " .. type(kms_uri)
+ end
+
+ if not string.has_prefix(upper(kms_uri), KMS_PREFIX) then
+ return nil, "error kms_uri prefix: " .. kms_uri
+ end
+
+ local path = sub(kms_uri, #KMS_PREFIX + 1)
+ local idx1 = find(path, "/")
+ if not idx1 then
+ return nil, "error format: no kms service"
+ end
+ local service = sub(path, 1, idx1 - 1)
+
+ local idx2 = find(path, "/", idx1 + 1)
+ if not idx2 then
+ return nil, "error format: no kms conf id"
+ end
+ local confid = sub(path, idx1 + 1, idx2 - 1)
+
+ local key = sub(path, idx2 + 1)
+ if key == "" then
+ return nil, "error format: no kms key id"
+ end
+
+ local opts = {
+ service = service,
+ confid = confid,
+ key = key
+ }
+ return opts
+end
+
+
+local function fetch_by_uri(kms_uri)
+ local opts, err = parse_kms_uri(kms_uri)
+ if not opts then
+ return nil, err
+ end
+
+ local conf = kms_kv(opts.service, opts.confid)
+ if not conf then
+ return nil, "no kms conf, kms_uri: " .. kms_uri
+ end
+
+ local ok, sm = pcall(require, "apisix.kms." .. opts.service)
+ if not ok then
+ return nil, "no kms service: " .. opts.service
+ end
+
+ local value, err = sm.get(conf, opts.key)
+ if err then
+ return nil, err
+ end
+
+ return value
+end
+
+-- for test
+_M.fetch_by_uri = fetch_by_uri
+
+
+local function fetch(uri)
+ -- do a quick filter to improve retrieval speed
+ if byte(uri, 1, 1) ~= byte('$') then
+ return nil
+ end
+
+ local val, err
+ if string.has_prefix(upper(uri), core.env.PREFIX) then
+ val, err = core.env.fetch_by_uri(uri)
+ elseif string.has_prefix(upper(uri), KMS_PREFIX) then
+ val, err = fetch_by_uri(uri)
+ end
+
+ if err then
+ core.log.error("failed to fetch kms value: ", err)
+ return
+ end
+
+ return val
+end
+
+
+local secrets_lrucache = core.lrucache.new({
+ ttl = 300, count = 512
+})
+
+local fetch_secrets
+do
+ local retrieve_refs
+ function retrieve_refs(refs)
+ for k, v in pairs(refs) do
+ local typ = type(v)
+ if typ == "string" then
+ refs[k] = fetch(v) or v
+ elseif typ == "table" then
+ retrieve_refs(v)
+ end
+ end
+ return refs
+ end
+
+ local function retrieve(refs)
+ core.log.info("retrieve secrets refs")
+
+ local new_refs = core.table.deepcopy(refs)
+ return retrieve_refs(new_refs)
+ end
+
+ function fetch_secrets(refs, cache, key, version)
+ if not refs or type(refs) ~= "table" then
+ return nil
+ end
+ if not cache then
+ return retrieve(refs)
+ end
+ return secrets_lrucache(key, version, retrieve, refs)
+ end
+end
+
+_M.fetch_secrets = fetch_secrets
+
+return _M
diff --git a/apisix/kms/vault.lua b/apisix/kms/vault.lua
index 1343002fc..41111e2b7 100644
--- a/apisix/kms/vault.lua
+++ b/apisix/kms/vault.lua
@@ -18,15 +18,32 @@
--- Vault Tools.
-- Vault is an identity-based secrets and encryption management system.
-local core = require("apisix.core")
-local http = require("resty.http")
+local core = require("apisix.core")
+local http = require("resty.http")
local norm_path = require("pl.path").normpath
local sub = core.string.sub
local rfind_char = core.string.rfind_char
-local _M = {}
+
+local schema = {
+ type = "object",
+ properties = {
+ uri = core.schema.uri_def,
+ prefix = {
+ type = "string",
+ },
+ token = {
+ type = "string",
+ },
+ },
+ required = {"uri", "prefix", "token"},
+}
+
+local _M = {
+ schema = schema
+}
local function make_request_to_vault(conf, method, key, data)
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 0bdb56ecd..f7b117af9 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -692,21 +692,6 @@ _M.service = {
}
-_M.kms_vault = {
- type = "object",
- properties = {
- uri = _M.uri_def,
- prefix = {
- type = "string",
- },
- token = {
- type = "string",
- },
- },
- required = {"uri", "prefix", "token"},
-}
-
-
_M.consumer = {
type = "object",
properties = {
diff --git a/docs/assets/images/kms.png b/docs/assets/images/kms.png
new file mode 100644
index 000000000..54c10e9ec
Binary files /dev/null and b/docs/assets/images/kms.png differ
diff --git a/docs/en/latest/terminology/kms.md b/docs/en/latest/terminology/kms.md
index 56754bf84..7a7fac0a9 100644
--- a/docs/en/latest/terminology/kms.md
+++ b/docs/en/latest/terminology/kms.md
@@ -32,7 +32,13 @@ Secrets refer to any sensitive information required during the running process o
KMS allows users to store Secrets through some secrets management services (Vault, etc.) in APISIX, and read them according to the key when using them to ensure that **Secrets do not exist in plain text throughout the platform**.
-APISIX currently supports storing keys in environment variables.
+Its working principle is shown in the figure:
+![kms](../../../assets/images/kms.png)
+
+APISIX currently supports storing keys in the following ways:
+
+- [Environment Variables](#use-environment-variables-to-manage-secrets)
+- [HashiCorp Vault](#use-vault-to-manage-secrets)
You use KMS functions by specifying format variables in the consumer configuration of the following plugins, such as `key-auth`.
@@ -42,9 +48,9 @@ If a configuration item is: `key: "$ENV://ABC"`, when the actual value correspon
:::
-## Use environment variables to manage keys
+## Use environment variables to manage secrets
-Using environment variables to manage keys means that you can save key information in environment variables, and refer to environment variables through variables in a specific format when configuring plugins. APISIX supports referencing system environment variables and environment variables configured through the Nginx `env` directive.
+Using environment variables to manage secrets means that you can save key information in environment variables, and refer to environment variables through variables in a specific format when configuring plugins. APISIX supports referencing system environment variables and environment variables configured through the Nginx `env` directive.
### Usage
@@ -107,3 +113,65 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \
```
Through the above steps, the `key` configuration in the `key-auth` plugin can be saved in the environment variable instead of being displayed in plain text when configuring the plugin.
+
+## Use Vault to manage secrets
+
+Using Vault to manage secrets means that you can store secrets information in the Vault service and refer to it through variables in a specific format when configuring plugins. APISIX currently supports [Vault KV engine version V1](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1).
+
+### Usage
+
+```
+$KMS://$secretmanager/$id/$secret_id/$key
+```
+
+- secretmanager: secrets management service, could be the Vault, AWS, etc.
+- id: KMS resource ID, which needs to be consistent with the one specified when adding the KMS resource
+- secret_id: the secret ID in the secrets management service
+- key: the key corresponding to the secret in the secrets management service
+
+### Example: use in key-auth plugin
+
+Step 1: Create the corresponding key in the Vault, you can use the following command:
+
+```shell
+vault kv put apisix/jack auth-key=value
+```
+
+Step 2: Add KMS resources through the Admin API, configure the Vault address and other connection information:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/kms/vault/1 \
+-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "uri": "https://127.0.0.1:8200",
+ "prefix": "apisix",
+ "token": "root"
+}'
+```
+
+If you use APISIX Standalone mode, you can add the following configuration in `apisix.yaml` configuration file:
+
+```yaml
+kms:
+ - id: vault/1
+ prefix: apisix
+ token: root
+ uri: 127.0.0.1:8200
+```
+
+Step 3: Reference the KMS resource in the `key-auth` plugin and fill in the key information:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/consumers \
+-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "jack",
+ "plugins": {
+ "key-auth": {
+ "key": "$KMS://vault/1/jack/auth-key"
+ }
+ }
+}'
+```
+
+Through the above two steps, when the user request hits the `key-auth` plugin, the real value of the key in the Vault will be obtained through the KMS component.
diff --git a/docs/zh/latest/terminology/kms.md b/docs/zh/latest/terminology/kms.md
index 6ab4a8ca7..15404fbf3 100644
--- a/docs/zh/latest/terminology/kms.md
+++ b/docs/zh/latest/terminology/kms.md
@@ -32,7 +32,13 @@ Secrets 是指 APISIX 运行过程中所需的任何敏感信息,它可能是
KMS 允许用户在 APISIX 中通过一些密钥管理服务(Vault 等)来存储 Secrets,在使用的时候根据 key 进行读取,确保 Secrets 在整个平台中不以明文的形式存在。
-APISIX 目前支持将密钥存储在环境变量中。
+其工作原理如图所示:
+![kms](../../../assets/images/kms.png)
+
+APISIX 目前支持通过以下方式存储密钥:
+
+- [环境变量](#使用环境变量管理密钥)
+- [HashiCorp Vault](#使用-vault-管理密钥)
你可以在以下插件的 consumer 配置中通过指定格式的变量来使用 KMS 功能,比如 `key-auth` 插件。
@@ -107,3 +113,65 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \
```
通过以上步骤,可以将 `key-auth` 插件中的 key 配置保存在环境变量中,而不是在配置插件时明文显示。
+
+## 使用 Vault 管理密钥
+
+使用 Vault 来管理密钥意味着你可以将密钥信息保存在 Vault 服务中,在配置插件时通过特定格式的变量来引用。APISIX 目前支持对接 [Vault KV 引擎的 V1 版本](https://developer.hashicorp.com/vault/docs/secrets/kv/kv-v1)。
+
+### 引用方式
+
+```
+$KMS://$secretmanager/$id/$secret_id/$key
+```
+
+- secretmanager: 密钥管理服务,可以是 Vault、AWS 等
+- id:KMS 资源 ID, 需要与添加 KMS 资源时指定的 ID 保持一致
+- secret_id: 密钥管理服务中的密钥 ID
+- key: 密钥管理服务中密钥对应的 key
+
+### 示例:在 key-auth 插件中使用
+
+第一步:在 Vault 中创建对应的密钥,可以使用如下命令:
+
+```shell
+vault kv put apisix/jack auth-key=value
+```
+
+第二步:通过 Admin API 添加 KMS 资源,配置 Vault 的地址等连接信息:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/kms/vault/1 \
+-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "uri": "https://127.0.0.1:8200",
+ "prefix": "apisix",
+ "token": "root"
+}'
+```
+
+如果使用 APISIX Standalone 版本,则可以在 `apisix.yaml` 文件中添加如下配置:
+
+```yaml
+kms:
+ - id: vault/1
+ prefix: apisix
+ token: root
+ uri: 127.0.0.1:8200
+```
+
+第三步:在 `key-auth` 插件中引用 KMS 资源,填充秘钥信息:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/consumers \
+-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+ "username": "jack",
+ "plugins": {
+ "key-auth": {
+ "key": "$KMS://vault/1/jack/auth-key"
+ }
+ }
+}'
+```
+
+通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 KMS 组件获取到 key 在 Vault 中的真实值。
diff --git a/t/config-center-yaml/kms.t b/t/config-center-yaml/kms.t
new file mode 100644
index 000000000..84b2c4c77
--- /dev/null
+++ b/t/config-center-yaml/kms.t
@@ -0,0 +1,413 @@
+#
+# 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);
+log_level('info');
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ my $yaml_config = $block->yaml_config // <<_EOC_;
+apisix:
+ node_listen: 1984
+deployment:
+ role: data_plane
+ role_data_plane:
+ config_provider: yaml
+_EOC_
+
+ $block->set_value("yaml_config", $yaml_config);
+
+ if (!$block->apisix_yaml) {
+ my $routes = <<_EOC_;
+routes:
+ -
+ uri: /hello
+ upstream:
+ nodes:
+ "127.0.0.1:1980": 1
+ type: roundrobin
+#END
+_EOC_
+
+ $block->set_value("apisix_yaml", $routes);
+ }
+
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: validate kms/vault: wrong schema
+--- apisix_yaml
+kms:
+ - id: vault/1
+ prefix: kv/apisix
+ token: root
+ uri: 127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local values = kms.kmss()
+ ngx.say(#values)
+ }
+ }
+--- request
+GET /t
+--- response_body
+0
+--- error_log
+property "uri" validation failed: failed to match pattern "^[^\\/]+:\\/\\/([\\da-zA-Z.-]+|\\[[\\da-fA-F:]+\\])(:\\d+)?"
+
+
+
+=== TEST 2: validate kms: service not exits
+--- apisix_yaml
+kms:
+ - id: hhh/1
+ prefix: kv/apisix
+ token: root
+ uri: 127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local values = kms.kmss()
+ ngx.say(#values)
+ }
+ }
+--- request
+GET /t
+--- response_body
+0
+--- error_log
+kms service not exits
+
+
+
+=== TEST 3: load config normal
+--- apisix_yaml
+kms:
+ - id: vault/1
+ prefix: kv/apisix
+ token: root
+ uri: http://127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local values = kms.kmss()
+ ngx.say("len: ", #values)
+
+ ngx.say("id: ", values[1].value.id)
+ ngx.say("prefix: ", values[1].value.prefix)
+ ngx.say("token: ", values[1].value.token)
+ ngx.say("uri: ", values[1].value.uri)
+ }
+ }
+--- request
+GET /t
+--- response_body
+len: 1
+id: vault/1
+prefix: kv/apisix
+token: root
+uri: http://127.0.0.1:8200
+
+
+
+=== TEST 4: store secret into vault
+--- exec
+VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/apisix-key key=value
+--- response_body
+Success! Data written to: kv/apisix/apisix-key
+
+
+
+=== TEST 5: kms.fetch_by_uri: start with $kms://
+--- apisix_yaml
+kms:
+ - id: vault/1
+ prefix: kv/apisix
+ token: root
+ uri: http://127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local value = kms.fetch_by_uri("$kms://vault/1/apisix-key/key")
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 6: kms.fetch_by_uri: start with $KMS://
+--- apisix_yaml
+kms:
+ - id: vault/1
+ prefix: kv/apisix
+ token: root
+ uri: http://127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local value = kms.fetch_by_uri("$KMS://vault/1/apisix-key/key")
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+value
+
+
+
+=== TEST 7: kms.fetch_by_uri, wrong ref format: wrong type
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri(1)
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error kms_uri type: number
+
+
+
+=== TEST 8: kms.fetch_by_uri, wrong ref format: wrong prefix
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri("kms://")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error kms_uri prefix: kms://
+
+
+
+=== TEST 9: kms.fetch_by_uri, error format: no kms service
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri("$kms://")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no kms service
+
+
+
+=== TEST 10: kms.fetch_by_uri, error format: no kms conf id
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri("$kms://vault/")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no kms conf id
+
+
+
+=== TEST 11: kms.fetch_by_uri, error format: no kms key id
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri("$kms://vault/2/")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+error format: no kms key id
+
+
+
+=== TEST 12: kms.fetch_by_uri, no config
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local _, err = kms.fetch_by_uri("$kms://vault/2/bar")
+ ngx.say(err)
+ }
+ }
+--- request
+GET /t
+--- response_body
+no kms conf, kms_uri: $kms://vault/2/bar
+
+
+
+=== TEST 13: kms.fetch_by_uri, no sub key value
+--- apisix_yaml
+kms:
+ - id: vault/1
+ prefix: kv/apisix
+ token: root
+ uri: http://127.0.0.1:8200
+#END
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local value = kms.fetch_by_uri("$kms://vault/1/apisix-key/bar")
+ ngx.say(value)
+ }
+ }
+--- request
+GET /t
+--- response_body
+nil
+
+
+
+=== TEST 14: fetch_secrets env: no cache
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local refs = {
+ key = "jack",
+ secret = "$env://secret"
+ }
+ local new_refs = kms.fetch_secrets(refs)
+ assert(new_refs ~= refs)
+ ngx.say(refs.secret)
+ ngx.say(new_refs.secret)
+ ngx.say(new_refs.key)
+ }
+ }
+--- request
+GET /t
+--- response_body
+$env://secret
+apisix
+jack
+--- error_log_like
+qr/retrieve secrets refs/
+
+
+
+=== TEST 15: fetch_secrets env: cache
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local refs = {
+ key = "jack",
+ secret = "$env://secret"
+ }
+ local refs_1 = kms.fetch_secrets(refs, true, "key", 1)
+ local refs_2 = kms.fetch_secrets(refs, true, "key", 1)
+ assert(refs_1 == refs_2)
+ ngx.say(refs_1.secret)
+ ngx.say(refs_2.secret)
+ }
+ }
+--- request
+GET /t
+--- response_body
+apisix
+apisix
+--- grep_error_log eval
+qr/retrieve secrets refs/
+--- grep_error_log_out
+retrieve secrets refs
+
+
+
+=== TEST 16: fetch_secrets env: table nesting
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local refs = {
+ key = "jack",
+ user = {
+ username = "apisix",
+ passsword = "$env://secret"
+ }
+ }
+ local new_refs = kms.fetch_secrets(refs)
+ ngx.say(new_refs.user.passsword)
+ }
+ }
+--- request
+GET /t
+--- response_body
+apisix
+
+
+
+=== TEST 17: fetch_secrets: wrong refs type
+--- main_config
+env secret=apisix;
+--- config
+ location /t {
+ content_by_lua_block {
+ local kms = require("apisix.kms")
+ local refs = "wrong"
+ local new_refs = kms.fetch_secrets(refs)
+ ngx.say(new_refs)
+ }
+ }
+--- request
+GET /t
+--- response_body
+nil
diff --git a/t/core/env.t b/t/core/env.t
index 745fcef1a..2e14a4397 100644
--- a/t/core/env.t
+++ b/t/core/env.t
@@ -34,7 +34,7 @@ __DATA__
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$env://TEST_ENV_VAR")
+ local value = env.fetch_by_uri("$env://TEST_ENV_VAR")
ngx.say(value)
}
}
@@ -50,7 +50,7 @@ test-value
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://TEST_ENV_VAR")
+ local value = env.fetch_by_uri("$ENV://TEST_ENV_VAR")
ngx.say(value)
}
}
@@ -66,7 +66,7 @@ test-value
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://test_env_var")
+ local value = env.fetch_by_uri("$ENV://test_env_var")
ngx.say(value)
}
}
@@ -82,18 +82,18 @@ nil
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get(1)
- ngx.say(value)
+ local _, err = env.fetch_by_uri(1)
+ ngx.say(err)
- local value = env.get(true)
- ngx.say(value)
+ local _, err = env.fetch_by_uri(true)
+ ngx.say(err)
}
}
--- request
GET /t
--- response_body
-nil
-nil
+error env_uri type: number
+error env_uri type: boolean
@@ -102,14 +102,14 @@ nil
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("env://")
- ngx.say(value)
+ local _, err = env.fetch_by_uri("env://")
+ ngx.say(err)
}
}
--- request
GET /t
--- response_body
-nil
+error env_uri prefix: env://
@@ -118,9 +118,9 @@ nil
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://TEST_ENV_SUB_VAR/main")
+ local value = env.fetch_by_uri("$ENV://TEST_ENV_SUB_VAR/main")
ngx.say(value)
- local value = env.get("$ENV://TEST_ENV_SUB_VAR/sub")
+ local value = env.fetch_by_uri("$ENV://TEST_ENV_SUB_VAR/sub")
ngx.say(value)
}
}
@@ -137,14 +137,14 @@ sub_value
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://TEST_ENV_VAR/main")
- ngx.say(value)
+ local _, err = env.fetch_by_uri("$ENV://TEST_ENV_VAR/main")
+ ngx.say(err)
}
}
--- request
GET /t
--- response_body
-nil
+decode failed, err: Expected value but found invalid token at character 1, value: test-value
@@ -153,7 +153,7 @@ nil
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://TEST_ENV_VAR/no")
+ local value = env.fetch_by_uri("$ENV://TEST_ENV_VAR/no")
ngx.say(value)
}
}
@@ -171,7 +171,7 @@ env ngx_env=apisix-nice;
location /t {
content_by_lua_block {
local env = require("apisix.core.env")
- local value = env.get("$ENV://ngx_env")
+ local value = env.fetch_by_uri("$ENV://ngx_env")
ngx.say(value)
}
}
diff --git a/t/core/utils.t b/t/core/utils.t
index 9094ddc14..4e6b0d766 100644
--- a/t/core/utils.t
+++ b/t/core/utils.t
@@ -361,114 +361,3 @@ apisix:
GET /t
--- error_log
failed to parse domain: ipv6.local
-
-
-
-=== TEST 12: retrieve_secrets_ref: no cache
---- main_config
-env secret=apisix;
---- extra_init_by_lua
-require("apisix.core.env").init()
---- config
- location /t {
- content_by_lua_block {
- local core = require("apisix.core")
- local refs = {
- key = "jack",
- secret = "$env://secret"
- }
- local new_refs = core.utils.retrieve_secrets_ref(refs)
- assert(new_refs ~= refs)
- ngx.say(refs.secret)
- ngx.say(new_refs.secret)
- ngx.say(new_refs.key)
- }
- }
---- request
-GET /t
---- response_body
-$env://secret
-apisix
-jack
---- error_log_like
-qr/retrieve secrets refs/
-
-
-
-=== TEST 13: retrieve_secrets_ref: cache
---- main_config
-env secret=apisix;
---- extra_init_by_lua
-require("apisix.core.env").init()
---- config
- location /t {
- content_by_lua_block {
- local core = require("apisix.core")
- local refs = {
- key = "jack",
- secret = "$env://secret"
- }
- local refs_1 = core.utils.retrieve_secrets_ref(refs, true, "key", 1)
- local refs_2 = core.utils.retrieve_secrets_ref(refs, true, "key", 1)
- assert(refs_1 == refs_2)
- ngx.say(refs_1.secret)
- ngx.say(refs_2.secret)
- }
- }
---- request
-GET /t
---- response_body
-apisix
-apisix
---- grep_error_log eval
-qr/retrieve secrets refs/
---- grep_error_log_out
-retrieve secrets refs
-
-
-
-=== TEST 14: retrieve_secrets_ref: table nesting
---- main_config
-env secret=apisix;
---- extra_init_by_lua
-require("apisix.core.env").init()
---- config
- location /t {
- content_by_lua_block {
- local core = require("apisix.core")
- local refs = {
- key = "jack",
- user = {
- username = "apisix",
- passsword = "$env://secret"
- }
- }
- local new_refs = core.utils.retrieve_secrets_ref(refs)
- ngx.say(new_refs.user.passsword)
- }
- }
---- request
-GET /t
---- response_body
-apisix
-
-
-
-=== TEST 15: retrieve_secrets_ref: wrong refs type
---- main_config
-env secret=apisix;
---- extra_init_by_lua
-require("apisix.core.env").init()
---- config
- location /t {
- content_by_lua_block {
- local core = require("apisix.core")
- local refs = "wrong"
- local new_refs = core.utils.retrieve_secrets_ref(refs)
- ngx.say(new_refs)
- }
- }
---- request
-GET /t
---- response_body
-nil
diff --git a/t/plugin/key-auth.t b/t/plugin/key-auth.t
index 4f139bbfe..c9f78bb43 100644
--- a/t/plugin/key-auth.t
+++ b/t/plugin/key-auth.t
@@ -538,7 +538,7 @@ auth: auth-one
-=== TEST 26: change consumer with secrets ref
+=== TEST 26: change consumer with secrets ref: env
--- config
location /t {
content_by_lua_block {
@@ -568,10 +568,90 @@ passed
-=== TEST 27: verify auth request args should not hidden
+=== TEST 27: verify auth request
--- main_config
env test_auth=authone;
--- request
GET /hello?auth=authone
--- response_args
auth: authone
+
+
+
+=== TEST 28: put kms vault config
+--- request
+GET /t
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local etcd = require("apisix.core.etcd")
+ local code, body = t('/apisix/admin/kms/vault/test1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "http://127.0.0.1:8200",
+ "prefix" : "kv/apisix",
+ "token" : "root"
+ }]],
+ [[{
+ "value": {
+ "uri": "http://127.0.0.1:8200",
+ "prefix" : "kv/apisix",
+ "token" : "root"
+ },
+ "key": "/apisix/kms/vault/test1"
+ }]]
+ )
+
+ ngx.status = code
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 29: change consumer with secrets ref: vault
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/consumers',
+ ngx.HTTP_PUT,
+ [[{
+ "username": "jack",
+ "plugins": {
+ "key-auth": {
+ "key": "$kms://vault/test1/jack/auth-key"
+ }
+ }
+ }]]
+ )
+
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 30: store secret into vault
+--- exec
+VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/jack auth-key=authtwo
+--- response_body
+Success! Data written to: kv/apisix/jack
+
+
+
+=== TEST 31: verify auth request
+--- request
+GET /hello?auth=authtwo
+--- response_args
+auth: authtwo