diff --git a/apisix/admin/init.lua b/apisix/admin/init.lua index 5827f83978fb..02c8cd9d60a2 100644 --- a/apisix/admin/init.lua +++ b/apisix/admin/init.lua @@ -55,6 +55,7 @@ local resources = { plugin_metadata = require("apisix.admin.plugin_metadata"), plugin_configs = require("apisix.admin.plugin_config"), consumer_groups = require("apisix.admin.consumer_group"), + kms = require("apisix.admin.kms"), } diff --git a/apisix/admin/kms.lua b/apisix/admin/kms.lua new file mode 100644 index 000000000000..895b60421dc8 --- /dev/null +++ b/apisix/admin/kms.lua @@ -0,0 +1,203 @@ +-- +-- 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 core = require("apisix.core") +local utils = require("apisix.admin.utils") +local type = type +local tostring = tostring + + +local _M = { + need_v3_filter = true, +} + + +local function check_conf(id, conf, need_id, typ) + if not conf then + return nil, {error_msg = "missing configurations"} + end + + id = id or conf.id + if need_id and not id then + return nil, {error_msg = "missing id"} + end + + if not need_id and id then + return nil, {error_msg = "wrong id, do not need it"} + end + + if need_id and conf.id and tostring(conf.id) ~= tostring(id) then + return nil, {error_msg = "wrong id"} + end + + conf.id = id + + core.log.info("conf: ", core.json.delay_encode(conf)) + local ok, err = core.schema.check(core.schema["kms_" .. typ], conf) + if not ok then + return nil, {error_msg = "invalid configuration: " .. err} + end + + return true +end + + +local function split_typ_and_id(id, sub_path) + local uri_segs = core.utils.split_uri(sub_path) + local typ = id + local id = nil + if #uri_segs > 0 then + id = uri_segs[1] + end + return typ, id +end + + +function _M.put(id, conf, sub_path) + local typ, id = split_typ_and_id(id, sub_path) + if not id then + return 400, {error_msg = "no kms id in uri"} + end + + local ok, err = check_conf(typ .. "/" .. id, conf, true, typ) + if not ok then + return 400, err + end + + local key = "/kms/" .. typ .. "/" .. id + + local ok, err = utils.inject_conf_with_prev_conf("kms", key, conf) + if not ok then + return 503, {error_msg = err} + end + + local res, err = core.etcd.set(key, conf) + if not res then + core.log.error("failed to put kms [", key, "]: ", err) + return 503, {error_msg = err} + end + + return res.status, res.body +end + + +function _M.get(id, conf, sub_path) + local typ, id = split_typ_and_id(id, sub_path) + + local key = "/kms/" + if id then + key = key .. typ + key = key .. "/" .. id + end + + local res, err = core.etcd.get(key, not id) + if not res then + core.log.error("failed to get kms [", key, "]: ", err) + return 503, {error_msg = err} + end + + utils.fix_count(res.body, id) + return res.status, res.body +end + + +function _M.delete(id, conf, sub_path) + local typ, id = split_typ_and_id(id, sub_path) + if not id then + return 400, {error_msg = "no kms id in uri"} + end + + local key = "/kms/" .. typ .. "/" .. id + + local res, err = core.etcd.delete(key) + if not res then + core.log.error("failed to delete kms [", key, "]: ", err) + return 503, {error_msg = err} + end + + return res.status, res.body +end + + +function _M.patch(id, conf, sub_path) + local uri_segs = core.utils.split_uri(sub_path) + if #uri_segs < 2 then + return 400, {error_msg = "no kms id and/or sub path in uri"} + end + local typ = id + id = uri_segs[1] + sub_path = core.table.concat(uri_segs, "/", 2) + + if not id then + return 400, {error_msg = "missing kms id"} + end + + if not conf then + return 400, {error_msg = "missing new configuration"} + end + + if not sub_path or sub_path == "" then + if type(conf) ~= "table" then + return 400, {error_msg = "invalid configuration"} + end + end + + local key = "/kms/" .. typ .. "/" .. id + local res_old, err = core.etcd.get(key) + if not res_old then + core.log.error("failed to get kms [", key, "]: ", err) + return 503, {error_msg = err} + end + + if res_old.status ~= 200 then + return res_old.status, res_old.body + end + core.log.info("key: ", key, " old value: ", + core.json.delay_encode(res_old, true)) + + local node_value = res_old.body.node.value + local modified_index = res_old.body.node.modifiedIndex + + if sub_path and sub_path ~= "" then + local code, err, node_val = core.table.patch(node_value, sub_path, conf) + node_value = node_val + if code then + return code, err + end + utils.inject_timestamp(node_value, nil, true) + else + node_value = core.table.merge(node_value, conf) + utils.inject_timestamp(node_value, nil, conf) + end + + core.log.info("new conf: ", core.json.delay_encode(node_value, true)) + + local ok, err = check_conf(typ .. "/" .. id, node_value, true, typ) + if not ok then + return 400, {error_msg = err} + end + + local res, err = core.etcd.atomic_set(key, node_value, nil, modified_index) + if not res then + core.log.error("failed to set new kms[", key, "]: ", err) + return 503, {error_msg = err} + end + + return res.status, res.body +end + + +return _M diff --git a/apisix/constants.lua b/apisix/constants.lua index 9f9b62fc3ba0..40ab71a2b414 100644 --- a/apisix/constants.lua +++ b/apisix/constants.lua @@ -33,6 +33,7 @@ return { ["/protos"] = true, ["/plugin_configs"] = true, ["/consumer_groups"] = true, + ["/kms"] = true, }, STREAM_ETCD_DIRECTORY = { ["/upstreams"] = true, diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua index f7b117af93da..0bdb56ecd0e0 100644 --- a/apisix/schema_def.lua +++ b/apisix/schema_def.lua @@ -692,6 +692,21 @@ _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/en/latest/admin-api.md b/docs/en/latest/admin-api.md index 7dfa98751633..668befc03b98 100644 --- a/docs/en/latest/admin-api.md +++ b/docs/en/latest/admin-api.md @@ -37,7 +37,7 @@ The Admin API lets users control their deployed Apache APISIX instance. The [arc When APISIX is started, the Admin API will listen on port `9180` by default and take the API prefixed with `/apisix/admin`. -Therefore, to avoid conflicts between your designed API and `/apisix/admin`, you can modify the configuration file [`/conf/config.yaml`](https://github.com/apache/apisix/blob/master/ conf/config.yaml) to modify the default listening port. +Therefore, to avoid conflicts between your designed API and `/apisix/admin`, you can modify the configuration file [`/conf/config.yaml`](https://github.com/apache/apisix/blob/master/conf/config.yaml) to modify the default listening port. APISIX supports setting the IP access allowlist of Admin API to prevent APISIX from being illegally accessed and attacked. You can configure the IP addresses to allow access in the `deployment.admin.allow_admin` option in the `./conf/config.yaml` file. @@ -45,7 +45,7 @@ The `X-API-KEY` shown below refers to the `deployment.admin.admin_key.key` in th :::tip -It is recommended that you modify the default listening port, IP access allowlist and Admin API token of the Admin API to ensure the security of your API. +For security reasons, please modify the default `admin_key`, and check the `allow_admin` IP access list. ::: @@ -153,6 +153,7 @@ Resources that support paging queries: - SSL - Stream Route - Upstream +- kms ### Support filtering query @@ -1348,3 +1349,65 @@ Stream Route resource request address: /apisix/admin/stream_routes/{id} | protocol.conf | False | Configuration | Protocol-specific configuration. | | To learn more about filtering in stream proxies, check [this](./stream-proxy.md#more-route-match-options) document. + +## kms + +kms means `Secrets Management`, which could use any secret manager supported, e.g. `vault`. + +### kms API + +kms resource request address:: /apisix/admin/kms/{secretmanager}/{id} + +### Request Methods + +| Method | Request URI | Request Body | Description | +| ------ | ---------------------------------- | ------------ | ------------------------------------------------- | +| GET | /apisix/admin/kms | NULL | Fetches a list of all kms. | +| GET | /apisix/admin/kms/{secretmanager}/{id} | NULL | Fetches specified kms by id. | +| PUT | /apisix/admin/kms/{secretmanager} | {...} | Create new kms configuration. | +| DELETE | /apisix/admin/kms/{secretmanager}/{id} | NULL | Removes the kms with the specified id. | +| PATCH | /apisix/admin/kms/{secretmanager}/{id} | {...} | Updates the selected attributes of the specified, existing kms. To delete an attribute, set value of attribute set to null. | +| PATCH | /apisix/admin/kms/{secretmanager}/{id}/{path} | {...} | Updates the attribute specified in the path. The values of other attributes remain unchanged. | + +### Request Body Parameters + +When `{secretmanager}` is `vault`: + +| Parameter | Required | Type | Description | Example | +| ----------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | +| uri | True | URI | URI of the vault server. | | +| prefix | True | string | key prefix +| token | True | string | vault token. | | + +Example Configuration: + +```shell +{ + "uri": "https://localhost/vault", + "prefix": "/apisix/kv", + "token": "343effad" +} +``` + +Example API usage: + +```shell +curl -i http://127.0.0.1:9180/apisix/admin/kms/vault/test2 \ +-H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' +{ + "uri": "http://xxx/get", + "prefix" : "apisix", + "token" : "apisix" +}' +``` + +```shell +HTTP/1.1 200 OK +... + +{"key":"\/apisix\/kms\/vault\/test2","value":{"id":"vault\/test2","token":"apisix","prefix":"apisix","update_time":1669625828,"create_time":1669625828,"uri":"http:\/\/xxx\/get"}} +``` + +### Response Parameters + +Currently, the response is returned from etcd. diff --git a/docs/en/latest/terminology/api-gateway.md b/docs/en/latest/terminology/api-gateway.md index 435c2260a8b9..0a9e3d69b18c 100644 --- a/docs/en/latest/terminology/api-gateway.md +++ b/docs/en/latest/terminology/api-gateway.md @@ -1,5 +1,10 @@ --- title: API Gateway +keywords: + - APISIX + - API Gateway + - Gateway +description: This article mainly introduces the role of the API gateway and why it is needed. --- -对于 API 网关通常可以用请求域名、客户端 IP 地址等字段识别到某类请求方, -然后进行插件过滤并转发请求到指定上游,但有时候这个深度不够。 +## 描述 + +Consumer 是某类服务的消费者,需要与用户认证配合才可以使用。当不同的消费者请求同一个 API 时,APISIX 会根据当前请求的用户信息,对应不同的 Plugin 或 Upstream 配置。如果 [Route](./route.md)、[Service](./service.md)、[Consumer](./consumer.md) 和 [Plugin Config](./plugin-config.md) 都绑定了相同的插件,只有消费者的插件配置会生效。插件配置的优先级由高到低的顺序是:Consumer > Route > Plugin Config > Service。 + +对于 API 网关而言,一般情况可以通过请求域名、客户端 IP 地址等字段识别到某类请求方,然后进行插件过滤并转发请求到指定上游。但有时候该方式达不到用户需求,因此 APISIX 支持了 Consumer 对象。 ![Consumer](../../../assets/images/consumer-who.png) 如上图所示,作为 API 网关,需要知道 API Consumer(消费方)具体是谁,这样就可以对不同 API Consumer 配置不同规则。 -| 名称 | 必选项 | 说明 | -| -------- | ---- | -------------------------------------------------------------------------------------------------------------------------------- | -| username | 是 | Consumer 名称。 | -| plugins | 否 | 该 Consumer 对应的插件配置,它的优先级是最高的:Consumer > Route > Service。对于具体插件配置,可以参考 [Plugins](plugin.md) 章节。 | +## 配置选项 + +定义 Consumer 的字段如下: + +| 名称 | 必选项 | 描述 | +| -------- | ---- | ------------------------------------------------------------------------------| +| username | 是 | Consumer 名称。 | +| plugins | 否 | Consumer 对应的插件配置。详细信息,请参考 [Plugins](./plugin.md)。 | + +## 识别消费者 在 APISIX 中,识别 Consumer 的过程如下图: ![Consumer Internal](../../../assets/images/consumer-internal.png) -1. 授权认证:比如有 [key-auth](../plugins/key-auth.md)、[JWT](../plugins/jwt-auth.md) 等。 -2. 获取 consumer_name:通过授权认证,即可自然获取到对应的 Consumer name,它是 Consumer 对象的唯一识别标识。 +1. 授权认证:比如有 [key-auth](../plugins/key-auth.md)、[JWT](../plugins/jwt-auth.md) 等; +2. 获取 consumer_name:通过授权认证,即可自然获取到对应的 Consumer name,它是 Consumer 对象的唯一识别标识; 3. 获取 Consumer 上绑定的 Plugin 或 Upstream 信息:完成对不同 Consumer 做不同配置的效果。 -概括一下,Consumer 是某类服务的消费者,需与用户认证体系配合才能使用。 -比如不同的 Consumer 请求同一个 API,网关服务根据当前请求用户信息,对应不同的 Plugin 或 Upstream 配置。 +你可以参考 [key-auth](../plugins/key-auth.md) 认证授权插件的调用逻辑,进一步理解 Consumer 概念和使用。 -此外,大家也可以参考 [key-auth](../plugins/key-auth.md) 认证授权插件的调用逻辑,辅助大家来进一步理解 Consumer 概念和使用。 +:::note 注意 -如何对某个 Consumer 开启指定插件,可以看下面例子: +如需了解更多关于 Consumer 对象的信息,你可以参考 [Admin API Consumer](../admin-api.md#consumer) 资源介绍。 -```shell -# 创建 Consumer ,指定认证插件 key-auth ,并开启特定插件 limit-count -$ curl http://127.0.0.1:9180/apisix/admin/consumers -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "username": "jack", - "plugins": { - "key-auth": { - "key": "auth-one" - }, - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" +::: + +## 使用示例 + +以下示例介绍了如何对某个 Consumer 开启指定插件: + +1. 创建 Consumer,指定认证插件 `key-auth`,并开启特定插件 `limit-count`。 + + ```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": "auth-one" + }, + "limit-count": { + "count": 2, + "time_window": 60, + "rejected_code": 503, + "key": "remote_addr" + } } - } -}' - -# 创建 Router,设置路由规则和启用插件配置 -$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "plugins": { - "key-auth": {} - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 + }' + ``` + +2. 创建路由,设置路由规则和启用插件配置。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "plugins": { + "key-auth": {} }, - "type": "roundrobin" - }, - "uri": "/hello" -}' - -# 发测试请求,前两次返回正常,没达到限速阈值 -$ curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I -... - -$ curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I -... - -# 第三次测试返回 503,请求被限制 -$ curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I -HTTP/1.1 503 Service Temporarily Unavailable -... - -``` - -结合 [consumer-restriction](../plugins/consumer-restriction.md) 插件,限制 jack 对该 route 的访问 - -```shell -# 设置黑名单,禁止 jack 访问该 API - -$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "plugins": { - "key-auth": {}, - "consumer-restriction": { - "blacklist": [ - "jack" - ] - } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" }, - "type": "roundrobin" - }, - "uri": "/hello" -}' + "uri": "/hello" + }' + ``` + +3. 测试插件。 + + 连续发送三次测试请求,前两次返回正常,没达到限速阈值。 + + ```shell + curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I + ``` + + 第三次测试返回 `503`,请求被限制: + + ```shell + HTTP/1.1 503 Service Temporarily Unavailable + ... + ``` + +通过 [consumer-restriction](../plugins/consumer-restriction.md) 插件,限制用户 `jack` 对该 Route 的访问。 + +1. 设置黑名单,禁止 jack 访问该 API。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "plugins": { + "key-auth": {}, + "consumer-restriction": { + "blacklist": [ + "jack" + ] + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + }, + "type": "roundrobin" + }, + "uri": "/hello" + }' + ``` + +2. 通过以下命令访问该路由,均返回 `403`,`jack` 被禁止访问。 + + ```shell + curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I + ``` -# 反复测试,均返回 403,jack 被禁止访问 -$ curl http://127.0.0.1:9080/hello -H 'apikey: auth-one' -I -HTTP/1.1 403 -... + 返回结果: -``` + ``` + HTTP/1.1 403 + ... + ``` diff --git a/docs/zh/latest/terminology/upstream.md b/docs/zh/latest/terminology/upstream.md index 88bc251a1a69..65ef3b5e6135 100644 --- a/docs/zh/latest/terminology/upstream.md +++ b/docs/zh/latest/terminology/upstream.md @@ -1,5 +1,11 @@ --- title: Upstream +keywords: + - APISIX + - API 网关 + - 上游 + - Upstream +description: 本文介绍了 Apache APISIX Upstream 对象的作用以及如何使用 Upstream。 --- -Upstream 是虚拟主机抽象,对给定的多个服务节点按照配置规则进行负载均衡。Upstream 的地址信息可以直接配置到 `Route`(或 `Service`) 上,当 Upstream 有重复时,就需要用“引用”方式避免重复了。 +## 描述 -![Upstream 示例](../../../assets/images/upstream-example.png) +Upstream(也称之为上游)是对虚拟主机抽象,即应用层服务或节点的抽象。你可以通过 Upstream 对象对多个服务节点按照配置规则进行负载均衡。 -如上图所示,通过创建 Upstream 对象,在 `Route` 用 ID 方式引用,就可以确保只维护一个对象的值了。 +上游的地址信息可以直接配置到[路由](./route.md)(或[服务](./service.md))中。 -Upstream 的配置可以被直接绑定在指定 `Route` 中,也可以被绑定在 `Service` 中,不过 `Route` 中的配置优先级更高。这里的优先级行为与 `Plugin` 非常相似。 +![Upstream 示例](../../../assets/images/upstream-example.png) -### 配置参数 +如上图所示,当多个路由(或服务)使用该上游时,你可以单独创建上游对象,在路由中通过使用 `upstream_id` 的方式引用资源,减轻维护压力。 -APISIX 的 Upstream 除了基本的负载均衡算法选择外,还支持对上游做主被动健康检查、重试等逻辑,具体看这个 [链接](../admin-api.md#upstream)。 +你也可以将上游的信息直接配置在指定路由或服务中,不过路由中的配置优先级更高,优先级行为与[插件](./plugin.md) 非常相似。 -创建上游对象用例: +## 配置参数 -```shell -curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "type": "chash", - "key": "remote_addr", - "nodes": { - "127.0.0.1:80": 1, - "foo.com:80": 2 - } -}' -``` - -上游对象创建后,均可以被具体 `Route` 或 `Service` 引用,例如: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "uri": "/index.html", - "upstream_id": 1 -}' -``` +APISIX 的 Upstream 对象除了基本的负载均衡算法外,还支持对上游做主被动健康检查、重试等逻辑。更多信息,请参考 [Admin API 中的 Upstream 资源](../admin-api.md#upstream)。 -为了方便使用,也可以直接把上游地址直接绑到某个 `Route` 或 `Service` ,例如: +1. 创建上游对象用例。 -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "uri": "/index.html", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" - } - }, - "upstream": { - "type": "roundrobin", + ```shell + curl http://127.0.0.1:9180/apisix/admin/upstreams/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "type": "chash", + "key": "remote_addr", "nodes": { - "127.0.0.1:1980": 1 - } - } -}' -``` - -下面是一个配置了健康检查的示例: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "uri": "/index.html", - "plugins": { - "limit-count": { - "count": 2, - "time_window": 60, - "rejected_code": 503, - "key": "remote_addr" + "127.0.0.1:80": 1, + "httpbin.org:80": 2 } - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1 + }' + ``` + + 上游对象创建后,可以被路由或服务引用。 + +2. 在路由中使用创建的上游对象。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "uri": "/index.html", + "upstream_id": 1 + }' + ``` + +3. 为方便使用,你也可以直接把上游信息直接配置在某个路由或服务。 + +以下示例是将上游信息直接配置在路由中: + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "uri": "/index.html", + "plugins": { + "limit-count": { + "count": 2, + "time_window": 60, + "rejected_code": 503, + "key": "remote_addr" + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } } - "type": "roundrobin", - "retries": 2, - "checks": { - "active": { - "http_path": "/status", - "host": "foo.com", - "healthy": { - "interval": 2, - "successes": 1 - }, - "unhealthy": { - "interval": 1, - "http_failures": 2 + }' + ``` + +## 使用示例 + +- 配置健康检查的示例。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "uri": "/index.html", + "plugins": { + "limit-count": { + "count": 2, + "time_window": 60, + "rejected_code": 503, + "key": "remote_addr" + } + }, + "upstream": { + "nodes": { + "127.0.0.1:1980": 1 + } + "type": "roundrobin", + "retries": 2, + "checks": { + "active": { + "http_path": "/status", + "host": "foo.com", + "healthy": { + "interval": 2, + "successes": 1 + }, + "unhealthy": { + "interval": 1, + "http_failures": 2 + } } } } - } -}' -``` + }' + ``` -更多细节可以参考 [健康检查的文档](../health-check.md)。 + 更多信息,请参考[健康检查的文档](../tutorials/health-check.md)。 -下面是几个使用不同 `hash_on` 类型的配置示例: +以下是使用不同 [`hash_on`](../admin-api.md#upstream-body-request-methods) 类型的配置示例: -#### Consumer +### Consumer -创建一个 consumer 对象: +1. 创建一个 Consumer 对象。 -```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": "auth-jack" + ```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": "auth-jack" + } } - } -}' -``` + }' + ``` -新建路由,打开 `key-auth` 插件认证,`upstream` 的 `hash_on` 类型为 `consumer`: +2. 创建路由,启用 `key-auth` 插件,配置 `upstream.hash_on` 的类型为 `consumer`。 -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "plugins": { - "key-auth": {} - }, - "upstream": { - "nodes": { - "127.0.0.1:1980": 1, - "127.0.0.1:1981": 1 + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "plugins": { + "key-auth": {} }, - "type": "chash", - "hash_on": "consumer" - }, - "uri": "/server_port" -}' -``` - -测试请求,认证通过后的 `consumer_name` 将作为负载均衡哈希算法的哈希值: - -```shell -curl http://127.0.0.1:9080/server_port -H "apikey: auth-jack" -``` - -##### Cookie - -新建路由和 `Upstream`,`hash_on` 类型为 `cookie`: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "uri": "/hash_on_cookie", - "upstream": { - "key": "sid", - "type": "chash", - "hash_on": "cookie", - "nodes": { - "127.0.0.1:1980": 1, - "127.0.0.1:1981": 1 + "upstream": { + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1 + }, + "type": "chash", + "hash_on": "consumer" + }, + "uri": "/server_port" + }' + ``` + +3. 测试请求,认证通过后的 `consumer_name` 将作为负载均衡哈希算法的哈希值。 + + ```shell + curl http://127.0.0.1:9080/server_port -H "apikey: auth-jack" + ``` + +### Cookie + +1. 创建路由并配置 `upstream.hash_on` 的类型为 `cookie`。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "uri": "/hash_on_cookie", + "upstream": { + "key": "sid", + "type": "chash", + "hash_on": "cookie", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1 + } } - } -}' -``` - -客户端请求携带 `Cookie`: - -```shell - curl http://127.0.0.1:9080/hash_on_cookie -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -H "Cookie: sid=3c183a30cffcda1408daf1c61d47b274" -``` - -##### Header - -新建路由和 `Upstream`,`hash_on` 类型为 `header`,`key` 为 `content-type`: - -```shell -curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' -{ - "uri": "/hash_on_header", - "upstream": { - "key": "content-type", - "type": "chash", - "hash_on": "header", - "nodes": { - "127.0.0.1:1980": 1, - "127.0.0.1:1981": 1 + }' + ``` + +2. 客户端请求携带 `Cookie`。 + + ```shell + curl http://127.0.0.1:9080/hash_on_cookie \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ + -H "Cookie: sid=3c183a30cffcda1408daf1c61d47b274" + ``` + +### Header + +1. 创建路由并配置 `upstream.hash_on` 的类型为 `header`,`key` 为 `content-type`。 + + ```shell + curl http://127.0.0.1:9180/apisix/admin/routes/1 \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d ' + { + "uri": "/hash_on_header", + "upstream": { + "key": "content-type", + "type": "chash", + "hash_on": "header", + "nodes": { + "127.0.0.1:1980": 1, + "127.0.0.1:1981": 1 + } } - } -}' -``` + }' + ``` -客户端请求携带 `content-type` 的 `header`: +2. 客户端请求携带 `content-type` 的 `header`。 ```shell - curl http://127.0.0.1:9080/hash_on_header -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -H "Content-Type: application/json" + curl http://127.0.0.1:9080/hash_on_header \ + -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \ + -H "Content-Type: application/json" ``` diff --git a/t/admin/kms.t b/t/admin/kms.t new file mode 100644 index 000000000000..8290f77f58e8 --- /dev/null +++ b/t/admin/kms.t @@ -0,0 +1,217 @@ +# +# 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"); + } +}); + +run_tests; + +__DATA__ + +=== TEST 1: PUT +--- 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:12800/get", + "prefix" : "apisix", + "token" : "apisix" + }]], + [[{ + "value": { + "uri": "http://127.0.0.1:12800/get", + "prefix" : "apisix", + "token" : "apisix" + }, + "key": "/apisix/kms/vault/test1" + }]] + ) + + ngx.status = code + ngx.say(body) + + local res = assert(etcd.get('/kms/vault/test1')) + local create_time = res.body.node.value.create_time + assert(create_time ~= nil, "create_time is nil") + local update_time = res.body.node.value.update_time + assert(update_time ~= nil, "update_time is nil") + } + } +--- response_body +passed + + + +=== TEST 2: GET +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/kms/vault/test1', + ngx.HTTP_GET, + nil, + [[{ + "value": { + "uri": "http://127.0.0.1:12800/get", + "prefix" : "apisix", + "token" : "apisix" + }, + "key": "/apisix/kms/vault/test1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 3: GET all +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/kms', + ngx.HTTP_GET, + nil, + [[{ + "total": 1, + "list": [ + { + "key": "/apisix/kms/vault/test1", + "value": { + "uri": "http://127.0.0.1:12800/get", + "prefix" : "apisix", + "token" : "apisix" + } + } + ] + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 4: PATCH +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local etcd = require("apisix.core.etcd") + local res = assert(etcd.get('/kms/vault/test1')) + local prev_create_time = res.body.node.value.create_time + assert(prev_create_time ~= nil, "create_time is nil") + local prev_update_time = res.body.node.value.update_time + assert(prev_update_time ~= nil, "update_time is nil") + ngx.sleep(1) + + local code, body = t('/apisix/admin/kms/vault/test1/token', + ngx.HTTP_PATCH, + [["unknown"]], + [[{ + "value": { + "uri": "http://127.0.0.1:12800/get", + "prefix" : "apisix", + "token" : "unknown" + }, + "key": "/apisix/kms/vault/test1" + }]] + ) + + ngx.status = code + ngx.say(body) + + local res = assert(etcd.get('/kms/vault/test1')) + assert(res.body.node.value.token == "unknown") + } + } +--- response_body +passed + + + +=== TEST 5: DELETE +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/kms/vault/test1', + ngx.HTTP_DELETE + ) + ngx.say(body) + } + } +--- response_body +passed + + + +=== TEST 6: PUT with invalid format +--- 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": "/get", + "prefix" : "apisix", + "token" : "apisix" + }]], + [[{ + "value": { + "uri": "http://127.0.0.1:12800/get", + "prefix" : "apisix", + "token" : "apisix" + }, + "key": "/apisix/kms/vault/test1" + }]] + ) + + ngx.status = code + ngx.say(body) + } + } +--- error_code: 400 +--- response_body eval +qr/validation failed: failed to match pattern/