From e74674649fde29cc59ae70be1f8a6f2266b3ba40 Mon Sep 17 00:00:00 2001 From: Billy Zhou Date: Mon, 5 Aug 2024 14:59:14 +0800 Subject: [PATCH 01/16] chore(grpc-transcode): adjust the position of enums in pb_option_def (#11448) --- apisix/plugins/grpc-transcode.lua | 4 +-- t/plugin/grpc-transcode3.t | 43 +++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/apisix/plugins/grpc-transcode.lua b/apisix/plugins/grpc-transcode.lua index a89008d39790..625018fb71fc 100644 --- a/apisix/plugins/grpc-transcode.lua +++ b/apisix/plugins/grpc-transcode.lua @@ -27,11 +27,11 @@ local plugin_name = "grpc-transcode" local pb_option_def = { { description = "enum as result", type = "string", - enum = {"int64_as_number", "int64_as_string", "int64_as_hexstring"}, + enum = {"enum_as_name", "enum_as_value"}, }, { description = "int64 as result", type = "string", - enum = {"enum_as_name", "enum_as_value"}, + enum = {"int64_as_number", "int64_as_string", "int64_as_hexstring"}, }, { description ="default values option", type = "string", diff --git a/t/plugin/grpc-transcode3.t b/t/plugin/grpc-transcode3.t index 0a8ddf54ded6..93a3d626766d 100644 --- a/t/plugin/grpc-transcode3.t +++ b/t/plugin/grpc-transcode3.t @@ -576,3 +576,46 @@ location /t { } --- response_body passed + + + +=== TEST 14: pb_option - check the matchings between enum and category +--- config + location /t { + content_by_lua_block { + local ngx_re_match = ngx.re.match + local plugin = require("apisix.plugins.grpc-transcode") + + local pb_option_def = plugin.schema.properties.pb_option.items.anyOf + + local patterns = { + [[^enum_as_.+$]], + [[^int64_as_.+$]], + [[^.+_default_.+$]], + [[^.+_hooks$]], + } + + local function check_pb_option_enum_category() + for i, category in ipairs(pb_option_def) do + for _, enum in ipairs(category.enum) do + if not ngx_re_match(enum, patterns[i], "jo") then + return ([[mismatch between enum("%s") and category("%s")]]):format( + enum, category.description) + end + end + end + end + + local err = check_pb_option_enum_category() + if err then + ngx.say(err) + return + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +done From 4c87264d0d18efafb1abc3ebad4fd88cbd49f5a9 Mon Sep 17 00:00:00 2001 From: opencmit2 <112474703+opencmit2@users.noreply.github.com> Date: Wed, 7 Aug 2024 20:32:56 +0800 Subject: [PATCH 02/16] docs: correct plugin name in basic-auth documentation (#11481) --- docs/en/latest/plugins/basic-auth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/latest/plugins/basic-auth.md b/docs/en/latest/plugins/basic-auth.md index 5073cff95cc5..a25df3a2196d 100644 --- a/docs/en/latest/plugins/basic-auth.md +++ b/docs/en/latest/plugins/basic-auth.md @@ -136,7 +136,7 @@ HTTP/1.1 401 Unauthorized ## Delete Plugin -To remove the `jwt-auth` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect. +To remove the `basic-auth` Plugin, you can delete the corresponding JSON configuration from the Plugin configuration. APISIX will automatically reload and you do not have to restart for this to take effect. ```shell curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d ' From a6b0d0076f10ed1d5006e179c2283d4048c18256 Mon Sep 17 00:00:00 2001 From: Shreemaan Abhishek Date: Wed, 14 Aug 2024 11:58:28 +0545 Subject: [PATCH 03/16] chore: release 3.10.0 (#11472) --- .asf.yaml | 4 +++ CHANGELOG.md | 53 +++++++++++++++++++++++++++++++ apisix/core/version.lua | 2 +- docs/en/latest/building-apisix.md | 2 +- docs/en/latest/config.json | 2 +- docs/zh/latest/building-apisix.md | 2 +- docs/zh/latest/config.json | 2 +- 7 files changed, 62 insertions(+), 5 deletions(-) diff --git a/.asf.yaml b/.asf.yaml index ef0571a9aa86..5d657124b314 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -53,6 +53,10 @@ github: dismiss_stale_reviews: true require_code_owner_reviews: true required_approving_review_count: 3 + release/3.10: + required_pull_request_reviews: + require_code_owner_reviews: true + required_approving_review_count: 3 release/3.9: required_pull_request_reviews: require_code_owner_reviews: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 62e13fb88093..b173ca6ef300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ title: Changelog ## Table of Contents +- [3.10.0](#3100) - [3.9.0](#390) - [3.8.0](#380) - [3.7.0](#370) @@ -76,6 +77,58 @@ title: Changelog - [0.7.0](#070) - [0.6.0](#060) +## 3.10.0 + +### Change + +- remove `core.grpc` module [#11427](https://github.com/apache/apisix/pull/11427) +- add max req/resp body size attributes [#11133](https://github.com/apache/apisix/pull/11133) +- autogenerate admin api key if not passed [#11080](https://github.com/apache/apisix/pull/11080) +- enable sensitive fields encryption by default [#11076](https://github.com/apache/apisix/pull/11076) +- support more sensitive fields for encryption [#11095](https://github.com/apache/apisix/pull/11095) + +### Plugins + +- allow set headers in introspection request [#11090](https://github.com/apache/apisix/pull/11090) + +### Bugfixes + +- Fix: etcd sync data checker should work [#11457](https://github.com/apache/apisix/pull/11457) +- Fix: plugin metadata add id value for etcd checker [#11452](https://github.com/apache/apisix/pull/11452) +- Fix: allow trailing period in SNI and CN for SSL [#11414](https://github.com/apache/apisix/pull/11414) +- Fix: filter out illegal INT(string) formats [#11367](https://github.com/apache/apisix/pull/11367) +- Fix: make the message clearer when API key is missing [#11370](https://github.com/apache/apisix/pull/11370) +- Fix: report consumer username tag in datadog [#11354](https://github.com/apache/apisix/pull/11354) +- Fix: after updating the header, get the old value from the ctx.var [#11329](https://github.com/apache/apisix/pull/11329) +- Fix: ssl key rotation caused request failure [#11305](https://github.com/apache/apisix/pull/11305) +- Fix: validation fails causing etcd events not to be handled correctly [#11268](https://github.com/apache/apisix/pull/11268) +- Fix: stream route matcher is nil after first match [#11269](https://github.com/apache/apisix/pull/11269) +- Fix: rectify the way to fetch secret resource by id [#11164](https://github.com/apache/apisix/pull/11164) +- Fix: multi-auth raise 500 error when use default conf [#11145](https://github.com/apache/apisix/pull/11145) +- Fix: avoid overwriting `Access-Control-Expose-Headers` response header [#11136](https://github.com/apache/apisix/pull/11136) +- Fix: close session in case of error to avoid blocked session [#11089](https://github.com/apache/apisix/pull/11089) +- Fix: restore `pb.state` appropriately [#11135](https://github.com/apache/apisix/pull/11135) +- Fix: add a default limit of 100 for `get_headers()` [#11140](https://github.com/apache/apisix/pull/11140) +- Fix: disable features when prometheus plugin is turned off [#11117](https://github.com/apache/apisix/pull/11117) +- Fix: add post request headers only if auth request method is POST [#11021](https://github.com/apache/apisix/pull/11021) +- Fix: core.request.header return strings instead of table [#11127](https://github.com/apache/apisix/pull/11127) +- Fix: brotli partial response [#11087](https://github.com/apache/apisix/pull/11087) +- Fix: the port value greater than 65535 should not be allowed [#11043](https://github.com/apache/apisix/pull/11043) + +### Core + +- upgrade openresty version to 1.25.3.2 [#11419](https://github.com/apache/apisix/pull/11419) +- move config-default.yaml to hardcoded lua file [#11343](https://github.com/apache/apisix/pull/11343) +- warn log when sending requests to external services insecurely [#11403](https://github.com/apache/apisix/pull/11403) +- update casbin to 1.41.9 [#11400](https://github.com/apache/apisix/pull/11400) +- update lua-resty-t1k to 1.1.5 [#11391](https://github.com/apache/apisix/pull/11391) +- support store ssl.keys ssl.certs in secrets mamager [#11339](https://github.com/apache/apisix/pull/11339) +- move tinyyaml to lyaml [#11312](https://github.com/apache/apisix/pull/11312) +- support hcv namespace [#11277](https://github.com/apache/apisix/pull/11277) +- add discovery k8s dump data interface [#11111](https://github.com/apache/apisix/pull/11111) +- make fetch_secrets use cache for performance [#11201](https://github.com/apache/apisix/pull/11201) +- replace 'string.len' with '#' [#11078](https://github.com/apache/apisix/pull/11078) + ## 3.9.0 ### Change diff --git a/apisix/core/version.lua b/apisix/core/version.lua index cf6ddb86c880..91fe77c523ed 100644 --- a/apisix/core/version.lua +++ b/apisix/core/version.lua @@ -20,5 +20,5 @@ -- @module core.version return { - VERSION = "3.9.0" + VERSION = "3.10.0" } diff --git a/docs/en/latest/building-apisix.md b/docs/en/latest/building-apisix.md index f042bbabd74d..5ab13227a4ab 100644 --- a/docs/en/latest/building-apisix.md +++ b/docs/en/latest/building-apisix.md @@ -48,7 +48,7 @@ To build and package APISIX for a specific platform, see [apisix-build-tools](ht First of all, we need to specify the version `APISIX_VERSION` to be installed: ```shell -APISIX_VERSION='3.9.0' +APISIX_VERSION='3.10.0' ``` Then, you can run the following command to clone the APISIX source code from Github: diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index cd6aeb94b444..928aec3b2b62 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -1,5 +1,5 @@ { - "version": "3.9.0", + "version": "3.10.0", "sidebar": [ { "type": "category", diff --git a/docs/zh/latest/building-apisix.md b/docs/zh/latest/building-apisix.md index 2d835db0032d..3460b93796ae 100644 --- a/docs/zh/latest/building-apisix.md +++ b/docs/zh/latest/building-apisix.md @@ -47,7 +47,7 @@ import TabItem from '@theme/TabItem'; 首先,我们需要指定需要安装的版本`APISIX_VERSION`: ```shell -APISIX_VERSION='3.9.0' +APISIX_VERSION='3.10.0' ``` 然后,你可以运行以下命令,从 Github 克隆 APISIX 源码: diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json index 09d8130ff759..4e35f1c4d848 100644 --- a/docs/zh/latest/config.json +++ b/docs/zh/latest/config.json @@ -1,5 +1,5 @@ { - "version": "3.9.0", + "version": "3.10.0", "sidebar": [ { "type": "category", From 8ebd7f2ddf121967977aa052770d8da5c14f56ef Mon Sep 17 00:00:00 2001 From: HaiYan Date: Thu, 15 Aug 2024 17:23:56 +0800 Subject: [PATCH 04/16] Update lua-resty-kafka to 0.23-0 (#11463) Co-authored-by: Abhishek Choudhary --- apisix-master-0.rockspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index 6a65f502e0b7..ac8da1e61474 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -54,7 +54,7 @@ dependencies = { "nginx-lua-prometheus-api7 = 0.20240201-1", "jsonschema = 0.9.8", "lua-resty-ipmatcher = 0.6.1", - "lua-resty-kafka = 0.22-0", + "lua-resty-kafka = 0.23-0", "lua-resty-logger-socket = 2.0.1-0", "skywalking-nginx-lua = 0.6.0", "base64 = 1.5-2", From 0e47dce757d964efcb2bfd77e2dba29456a86c9e Mon Sep 17 00:00:00 2001 From: maclong1989 <814742806@qq.com> Date: Mon, 26 Aug 2024 10:47:48 +0800 Subject: [PATCH 05/16] docs: fix word spell error in monitor-api-health-check.md (#11522) --- docs/en/latest/tutorials/monitor-api-health-check.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/latest/tutorials/monitor-api-health-check.md b/docs/en/latest/tutorials/monitor-api-health-check.md index e90142b89cfe..84edad340c75 100644 --- a/docs/en/latest/tutorials/monitor-api-health-check.md +++ b/docs/en/latest/tutorials/monitor-api-health-check.md @@ -120,7 +120,7 @@ To generate some metrics, you try to send few requests to the route we created i curl -i -X GET "http://localhost:9080/" ``` -If you run the above requests a couple of times, you can see from responses that APISX routes some requests to `node1` and others to `node2`. That’s how Gateway load balancing works! +If you run the above requests a couple of times, you can see from responses that APISIX routes some requests to `node1` and others to `node2`. That’s how Gateway load balancing works! ```bash HTTP/1.1 200 OK From 4ecc741100c4968f31717fac3d7de32cff2b2ff5 Mon Sep 17 00:00:00 2001 From: Traky Deng Date: Mon, 26 Aug 2024 10:53:35 +0800 Subject: [PATCH 06/16] docs: update for `max_req_body_bytes ` and `max_resp_body_bytes ` attributes in `kafka-logger` plugin (#11505) * correct description for include_resp_body and improve the description * add the attributes to zh doc * fix typo --- docs/en/latest/plugins/kafka-logger.md | 4 ++-- docs/zh/latest/plugins/kafka-logger.md | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/en/latest/plugins/kafka-logger.md b/docs/en/latest/plugins/kafka-logger.md index fa70eb129935..a1a717c5e95e 100644 --- a/docs/en/latest/plugins/kafka-logger.md +++ b/docs/en/latest/plugins/kafka-logger.md @@ -55,10 +55,10 @@ It might take some time to receive the log data. It will be automatically sent a | log_format | object | False | | | Log format declared as key value pairs in JSON format. Values only support strings. [APISIX](../apisix-variable.md) or [Nginx](http://nginx.org/en/docs/varindex.html) variables can be used by prefixing the string with `$`. | | include_req_body | boolean | False | false | [false, true] | When set to `true` includes the request body in the log. If the request body is too big to be kept in the memory, it can't be logged due to Nginx's limitations. | | include_req_body_expr | array | False | | | Filter for when the `include_req_body` attribute is set to `true`. Request body is only logged when the expression set here evaluates to `true`. See [lua-resty-expr](https://github.com/api7/lua-resty-expr) for more. | -| max_req_body_bytes | integer | False | 524288 | >=1 | Request bodies within this size will be pushed to kafka, if the size exceeds the configured value it will be truncated before pushing to Kafka. | +| max_req_body_bytes | integer | False | 524288 | >=1 | Maximum request body allowed in bytes. Request bodies falling within this limit will be pushed to Kafka. If the size exceeds the configured value, the body will be truncated before being pushed to Kafka. | | include_resp_body | boolean | False | false | [false, true] | When set to `true` includes the response body in the log. | | include_resp_body_expr | array | False | | | Filter for when the `include_resp_body` attribute is set to `true`. Response body is only logged when the expression set here evaluates to `true`. See [lua-resty-expr](https://github.com/api7/lua-resty-expr) for more. | -| max_resp_body_bytes | integer | False | 524288 | >=1 | Request bodies within this size will be pushed to kafka, if the size exceeds the configured value it will be truncated before pushing to Kafka. | +| max_resp_body_bytes | integer | False | 524288 | >=1 | Maximum response body allowed in bytes. Response bodies falling within this limit will be pushed to Kafka. If the size exceeds the configured value, the body will be truncated before being pushed to Kafka. | | cluster_name | integer | False | 1 | [0,...] | Name of the cluster. Used when there are two or more Kafka clusters. Only works if the `producer_type` attribute is set to `async`. | | producer_batch_num | integer | optional | 200 | [1,...] | `batch_num` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka). The merge message and batch is send to the server. Unit is message count. | | producer_batch_size | integer | optional | 1048576 | [0,...] | `batch_size` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) in bytes. | diff --git a/docs/zh/latest/plugins/kafka-logger.md b/docs/zh/latest/plugins/kafka-logger.md index 1fdfe3b59a34..e708a21b87e5 100644 --- a/docs/zh/latest/plugins/kafka-logger.md +++ b/docs/zh/latest/plugins/kafka-logger.md @@ -53,8 +53,10 @@ description: API 网关 Apache APISIX 的 kafka-logger 插件用于将日志作 | log_format | object | 否 | | | 以 JSON 格式的键值对来声明日志格式。对于值部分,仅支持字符串。如果是以 `$` 开头,则表明是要获取 [APISIX 变量](../apisix-variable.md) 或 [NGINX 内置变量](http://nginx.org/en/docs/varindex.html)。 | | include_req_body | boolean | 否 | false | [false, true] | 当设置为 `true` 时,包含请求体。**注意**:如果请求体无法完全存放在内存中,由于 NGINX 的限制,APISIX 无法将它记录下来。| | include_req_body_expr | array | 否 | | | 当 `include_req_body` 属性设置为 `true` 时进行过滤。只有当此处设置的表达式计算结果为 `true` 时,才会记录请求体。更多信息,请参考 [lua-resty-expr](https://github.com/api7/lua-resty-expr)。 | +| max_req_body_bytes | integer | 否 | 524288 | >=1 | 允许的最大请求正文(以字节为单位)。在此限制内的请求体将被推送到 Kafka。如果大小超过配置值,则正文在推送到 Kafka 之前将被截断。 | | include_resp_body | boolean | 否 | false | [false, true] | 当设置为 `true` 时,包含响应体。 | | include_resp_body_expr | array | 否 | | | 当 `include_resp_body` 属性设置为 `true` 时进行过滤。只有当此处设置的表达式计算结果为 `true` 时才会记录响应体。更多信息,请参考 [lua-resty-expr](https://github.com/api7/lua-resty-expr)。| +| max_resp_body_bytes | integer | 否 | 524288 | >=1 | 允许的最大响应正文(以字节为单位)。低于此限制的响应主体将被推送到 Kafka。如果大小超过配置值,则正文在推送到 Kafka 之前将被截断。 | | cluster_name | integer | 否 | 1 | [0,...] | Kafka 集群的名称,当有两个及以上 Kafka 集群时使用。只有当 `producer_type` 设为 `async` 模式时才可以使用该属性。| | producer_batch_num | integer | 否 | 200 | [1,...] | 对应 [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) 中的 `batch_num` 参数,聚合消息批量提交,单位为消息条数。 | | producer_batch_size | integer | 否 | 1048576 | [0,...] | 对应 [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) 中的 `batch_size` 参数,单位为字节。 | From a6f085328d7aa69d3eacc1cca401d92dc9addd69 Mon Sep 17 00:00:00 2001 From: Traky Deng Date: Mon, 26 Aug 2024 10:54:28 +0800 Subject: [PATCH 07/16] docs: update docs for the removal of `config-default.yaml` (master) (#11503) * update reference for the config-default.yaml file * resolve mdlint errs * resolve zh lint err --- docs/en/latest/FAQ.md | 6 +-- docs/en/latest/benchmark.md | 2 +- .../latest/customize-nginx-configuration.md | 4 +- docs/en/latest/install-dependencies.md | 2 +- docs/en/latest/installation-guide.md | 8 +--- docs/en/latest/plugin-develop.md | 44 +++++++++---------- docs/en/latest/plugins/aws-lambda.md | 2 +- docs/en/latest/plugins/azure-functions.md | 2 +- docs/en/latest/plugins/inspect.md | 27 +++++++----- docs/en/latest/pubsub.md | 8 +++- docs/en/latest/terminology/plugin.md | 39 +++++++++++++--- docs/zh/latest/FAQ.md | 6 +-- docs/zh/latest/benchmark.md | 2 +- .../latest/customize-nginx-configuration.md | 4 +- docs/zh/latest/install-dependencies.md | 2 +- docs/zh/latest/installation-guide.md | 7 +-- docs/zh/latest/plugin-develop.md | 44 +++++++++++-------- docs/zh/latest/terminology/plugin.md | 39 +++++++++++++--- 18 files changed, 157 insertions(+), 91 deletions(-) diff --git a/docs/en/latest/FAQ.md b/docs/en/latest/FAQ.md index 8a669b0f9c2d..a19b5e96fbfe 100644 --- a/docs/en/latest/FAQ.md +++ b/docs/en/latest/FAQ.md @@ -756,15 +756,15 @@ deployment: password: etcd_password # password for etcd ``` -For other ETCD configurations, such as expiration times, retries, and so on, you can refer to the `ETCD` section in the `conf/config-default.yaml` file. +For other ETCD configurations, such as expiration times, retries, and so on, you can refer to the `etcd` section in the sample configuration `conf/config.yaml.example` file. -## What is the difference between SSLs and tls.client_cert in upstream configurations, and ssl_trusted_certificate in config-default.yaml? +## What is the difference between SSLs, `tls.client_cert` in upstream configurations, and `ssl_trusted_certificate` in `config.yaml`? The `ssls` is managed through the `/apisix/admin/ssls` API. It's used for managing TLS certificates. These certificates may be used during TLS handshake (between Apache APISIX and its clients). Apache APISIX uses Server Name Indication (SNI) to differentiate between certificates of different domains. The `tls.client_cert`, `tls.client_key`, and `tls.client_cert_id` in upstream are used for mTLS communication with the upstream. -The `ssl_trusted_certificate` in config-default.yaml configures a trusted CA certificate. It is used for verifying some certificates signed by private authorities within APISIX, to avoid APISIX rejects the certificate. Note that it is not used to trust the certificates of APISIX upstream, because APISIX does not verify the legality of the upstream certificates. Therefore, even if the upstream uses an invalid TLS certificate, it can still be accessed without configuring a root certificate. +The `ssl_trusted_certificate` in `config.yaml` configures a trusted CA certificate. It is used for verifying some certificates signed by private authorities within APISIX, to avoid APISIX rejects the certificate. Note that it is not used to trust the certificates of APISIX upstream, because APISIX does not verify the legality of the upstream certificates. Therefore, even if the upstream uses an invalid TLS certificate, it can still be accessed without configuring a root certificate. ## Where can I find more answers? diff --git a/docs/en/latest/benchmark.md b/docs/en/latest/benchmark.md index bc86cf97d77d..8ddddc6bad05 100644 --- a/docs/en/latest/benchmark.md +++ b/docs/en/latest/benchmark.md @@ -140,7 +140,7 @@ For more reference on how to run the benchmark test, you can see this [PR](https :::tip -If you want to run the benchmark with a large number of connections, You may have to update the **keepalive** config in the [conf/config-default.yaml](https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L242). Connections exceeding this number will become short connections. You can run the following command to test the benchmark with a large number of connections: +If you want to run the benchmark with a large number of connections, You may have to update the [**keepalive**](https://github.com/apache/apisix/blob/master/conf/config.yaml.example#L241) config by adding the configuration to [`config.yaml`](https://github.com/apache/apisix/blob/master/conf/config.yaml) and reload APISIX. Connections exceeding this number will become short connections. You can run the following command to test the benchmark with a large number of connections: ```bash wrk -t200 -c5000 -d30s http://127.0.0.1:9080/hello diff --git a/docs/en/latest/customize-nginx-configuration.md b/docs/en/latest/customize-nginx-configuration.md index e5098b043e55..9a6baa512d0d 100644 --- a/docs/en/latest/customize-nginx-configuration.md +++ b/docs/en/latest/customize-nginx-configuration.md @@ -21,11 +21,11 @@ title: Customize Nginx configuration # --> -The Nginx configuration used by APISIX is generated via the template file `apisix/cli/ngx_tpl.lua` and the options from `conf/config-default.yaml` / `conf/config.yaml`. +The Nginx configuration used by APISIX is generated via the template file `apisix/cli/ngx_tpl.lua` and the parameters in `apisix/cli/config.lua` and `conf/config.yaml`. You can take a look at the generated Nginx configuration in `conf/nginx.conf` after running `./bin/apisix start`. -If you want to customize the Nginx configuration, please read through the `nginx_config` in `conf/config-default.yaml`. You can override the default value in the `conf/config.yaml`. For instance, you can inject some snippets in the `conf/nginx.conf` via configuring the `xxx_snippet` entries: +If you want to customize the Nginx configuration, please read through the `nginx_config` in `conf/config.default.example`. You can override the default value in the `conf/config.yaml`. For instance, you can inject some snippets in the `conf/nginx.conf` via configuring the `xxx_snippet` entries: ```yaml ... diff --git a/docs/en/latest/install-dependencies.md b/docs/en/latest/install-dependencies.md index 83545f2cfdc6..049c1a69dee7 100644 --- a/docs/en/latest/install-dependencies.md +++ b/docs/en/latest/install-dependencies.md @@ -25,7 +25,7 @@ title: Install Dependencies - Since v2.0 Apache APISIX would not support the v2 protocol storage to etcd anymore, and the minimum etcd version supported is v3.4.0. What's more, etcd v3 uses gRPC as the messaging protocol, while Apache APISIX uses HTTP(S) to communicate with etcd cluster, so be sure the [etcd gRPC gateway](https://etcd.io/docs/v3.4.0/dev-guide/api_grpc_gateway/) is enabled. -- Now by default Apache APISIX uses HTTP protocol to talk with etcd cluster, which is insecure. Please configure certificate and corresponding private key for your etcd cluster, and use "https" scheme explicitly in the etcd endpoints list in your Apache APISIX configuration, if you want to keep the data secure and integral. See the etcd section in `conf/config-default.yaml` for more details. +- Now by default Apache APISIX uses HTTP protocol to talk with etcd cluster, which is insecure. Please configure certificate and corresponding private key for your etcd cluster, and use "https" scheme explicitly in the etcd endpoints list in your Apache APISIX configuration, if you want to keep the data secure and integral. See the etcd section in `conf/config.yaml.example` for more details. - If it is OpenResty 1.19, APISIX will use OpenResty's built-in LuaJIT to run `bin/apisix`; otherwise it will use Lua 5.1. If you encounter `luajit: lj_asm_x86.h:2819: asm_loop_ fixup: Assertion '((intptr_t)target & 15) == 0' failed`, this is a problem with the low version of OpenResty's built-in LuaJIT under certain compilation conditions. diff --git a/docs/en/latest/installation-guide.md b/docs/en/latest/installation-guide.md index 779f65b33b01..b3cc514864a1 100644 --- a/docs/en/latest/installation-guide.md +++ b/docs/en/latest/installation-guide.md @@ -271,7 +271,7 @@ You can configure your APISIX deployment in two ways: apisix start -c ``` -APISIX will use the configurations added in this configuration file and will fall back to the default configuration if anything is not configured. +APISIX will use the configurations added in this configuration file and will fall back to the default configuration if anything is not configured. The default configurations can be found in `apisix/cli/config.lua` and should not be modified. For example, to configure the default listening port to be `8000` without changing other configurations, your configuration file could look like this: @@ -297,12 +297,6 @@ deployment: :::warning -APISIX's default configuration can be found in `conf/config-default.yaml` file and it should not be modified. It is bound to the source code and the configuration should only be changed by the methods mentioned above. - -::: - -:::warning - The `conf/nginx.conf` file is automatically generated and should not be modified. ::: diff --git a/docs/en/latest/plugin-develop.md b/docs/en/latest/plugin-develop.md index 406dce0e6e74..ff9cfae79a0f 100644 --- a/docs/en/latest/plugin-develop.md +++ b/docs/en/latest/plugin-develop.md @@ -117,36 +117,36 @@ local _M = { Note: The priority of the new plugin cannot be same to any existing ones, you can use the `/v1/schema` method of [control API](./control-api.md#get-v1schema) to view the priority of all plugins. In addition, plugins with higher priority value will be executed first in a given phase (see the definition of `phase` in [choose-phase-to-run](#choose-phase-to-run)). For example, the priority of example-plugin is 0 and the priority of ip-restriction is 3000. Therefore, the ip-restriction plugin will be executed first, then the example-plugin plugin. It's recommended to use priority 1 ~ 99 for your plugin unless you want it to run before some builtin plugins. -In the "__conf/config-default.yaml__" configuration file, the enabled plugins (all specified by plugin name) are listed. +By default, most APISIX plugins are [enabled](https://github.com/apache/apisix/blob/master/apisix/cli/config.lua): -```yaml -plugins: # plugin list - - limit-req - - limit-count - - limit-conn - - key-auth - - prometheus - - node-status - - jwt-auth - - zipkin - - ip-restriction - - grpc-transcode - - serverless-pre-function - - serverless-post-function - - openid-connect - - proxy-rewrite - - redirect +```lua title="apisix/cli/config.lua" +local _M = { + ... + plugins = { + "real-ip", + "ai", + "client-control", + "proxy-control", + "request-id", + "zipkin", + "ext-plugin-pre-req", + "fault-injection", + "mocking", + "serverless-pre-function", + ... + }, ... +} ``` Note: the order of the plugins is not related to the order of execution. -To enable your plugin, copy this plugin list into `conf/config.yaml`, and add your plugin name. For instance: +To enable your custom plugin, add the list of plugins into `conf/config.yaml` and append your plugin name. For instance: ```yaml -plugins: # copied from config-default.yaml - ... - - your-plugin +plugins: # see `conf/config.yaml.example` for an example + - ... # add existing plugins + - your-plugin # add your custom plugin ``` If your plugin has a new code directory of its own, and you need to redistribute it with the APISIX source code, you will need to modify the `Makefile` to create directory, such as: diff --git a/docs/en/latest/plugins/aws-lambda.md b/docs/en/latest/plugins/aws-lambda.md index 2cfca463749b..f9344859d43c 100644 --- a/docs/en/latest/plugins/aws-lambda.md +++ b/docs/en/latest/plugins/aws-lambda.md @@ -104,7 +104,7 @@ Server: APISIX/2.10.2 "Hello, APISIX!" ``` -Another example of a request where the client communicates with APISIX via HTTP/2 is shown below (make sure you have configured `enable_http2: true` for a in your default configuration file (`config-default.yaml`). You can do this by uncommenting the port `9081` from the field `apisix.node_listen`): +Another example of a request where the client communicates with APISIX via HTTP/2 is shown below. Before proceeding, make sure you have configured `enable_http2: true` in your configuration file `config.yaml` for port `9081` and reloaded APISIX. See [`config.yaml.example`](https://github.com/apache/apisix/blob/master/conf/config.yaml.example) for the example configuration. ```shell curl -i -XGET --http2 --http2-prior-knowledge localhost:9081/aws\?name=APISIX diff --git a/docs/en/latest/plugins/azure-functions.md b/docs/en/latest/plugins/azure-functions.md index f68b6f3b497f..07663eb07e37 100644 --- a/docs/en/latest/plugins/azure-functions.md +++ b/docs/en/latest/plugins/azure-functions.md @@ -119,7 +119,7 @@ Server: APISIX/2.10.2 Hello, APISIX ``` -Another example of a request where the client communicates with APISIX via HTTP/2 is shown below (make sure you have configured `enable_http2: true` in your default configuration file (`config-default.yaml`). You can do this by uncommenting the port `9081` from the field `apisix.node_listen`): +Another example of a request where the client communicates with APISIX via HTTP/2 is shown below. Before proceeding, make sure you have configured `enable_http2: true` in your configuration file `config.yaml` for port `9081` and reloaded APISIX. See [`config.yaml.example`](https://github.com/apache/apisix/blob/master/conf/config.yaml.example) for the example configuration. ```shell curl -i -XGET --http2 --http2-prior-knowledge http://localhost:9081/azure\?name=APISIX diff --git a/docs/en/latest/plugins/inspect.md b/docs/en/latest/plugins/inspect.md index cbd876ceb687..9f5b3fe0843f 100644 --- a/docs/en/latest/plugins/inspect.md +++ b/docs/en/latest/plugins/inspect.md @@ -87,16 +87,23 @@ The `info` is a hash table which contains below keys: ## Enable Plugin -Plugin is enabled by default (`conf/config-default.yaml`): - -```yaml title="conf/config-default.yaml" -plugins: - - inspect - -plugin_attr: - inspect: - delay: 3 - hooks_file: "/usr/local/apisix/plugin_inspect_hooks.lua" +Plugin is enabled by default: + +```yaml title="apisix/cli/config.lua" +local _M = { + plugins = { + "inspect", + ... + }, + plugin_attr = { + inspect = { + delay = 3, + hooks_file = "/usr/local/apisix/plugin_inspect_hooks.lua" + }, + ... + }, + ... +} ``` ## Example usage diff --git a/docs/en/latest/pubsub.md b/docs/en/latest/pubsub.md index 62112fb4596c..ec756229e71d 100644 --- a/docs/en/latest/pubsub.md +++ b/docs/en/latest/pubsub.md @@ -106,9 +106,13 @@ Add the required fields to the plugin schema definition and write them to the co The `kafka-proxy` plugin [kafka-proxy.lua](https://github.com/apache/apisix/blob/master/apisix/plugins/kafka-proxy.lua). -Add this plugin to the list of plugins in the APISIX configuration file. +Add this plugin to [the existing list of plugins](https://github.com/apache/apisix/blob/master/apisix/cli/config.yaml.example) in the APISIX configuration file [`config.yaml`](https://github.com/apache/apisix/blob/master/conf/config.yaml). For instance: -The plugins list [config-default.yaml](https://github.com/apache/apisix/blob/master/conf/config-default.yaml). +```yaml title="conf/config.yaml" +plugins: # see `conf/config.yaml.example` for an example + - ... # add existing plugins + - kafka-proxy +``` #### Results diff --git a/docs/en/latest/terminology/plugin.md b/docs/en/latest/terminology/plugin.md index 66fff7afdd3c..0312c6622900 100644 --- a/docs/en/latest/terminology/plugin.md +++ b/docs/en/latest/terminology/plugin.md @@ -38,22 +38,51 @@ If existing APISIX Plugins do not meet your needs, you can also write your own p ## Plugins installation -APISIX comes with a default configuration file called `config-default.yaml` and a user-defined configuration file called `config.yaml`. These files are located in the `conf` directory. If the same key (e.g. `plugins`) exists in both files, the configuration values for the key in `config.yaml` will overwrite those in `config-default.yaml`. +By default, most APISIX plugins are [installed](https://github.com/apache/apisix/blob/master/apisix/cli/config.lua): + +```lua title="apisix/cli/config.lua" +local _M = { + ... + plugins = { + "real-ip", + "ai", + "client-control", + "proxy-control", + "request-id", + "zipkin", + "ext-plugin-pre-req", + "fault-injection", + "mocking", + "serverless-pre-function", + ... + }, + ... +} +``` -The `plugins` block is where you can declare the Plugins loaded to your APISIX instance: +If you would like to make adjustments to plugins installation, add the customized `plugins` configuration to `config.yaml`. For example: ```yaml plugins: - - real-ip # loaded + - real-ip # installed + - ai + - real-ip - ai - client-control - proxy-control - request-id - zipkin - # - skywalking # not loaded -... + - ext-plugin-pre-req + - fault-injection + # - mocking # not install + - serverless-pre-function + ... # other plugins ``` +See `config.yaml.example`(https://github.com/apache/apisix/blob/master/conf/config.yaml.example) for a complete configuration reference. + +You should reload APISIX for configuration changes to take effect. + ## Plugins execution lifecycle An installed plugin is first initialized. The configuration of the plugin is then checked against the defined [JSON Schema](https://json-schema.org) to make sure the plugins configuration schema is correct. diff --git a/docs/zh/latest/FAQ.md b/docs/zh/latest/FAQ.md index 181588ba0de9..d605d091b9c1 100644 --- a/docs/zh/latest/FAQ.md +++ b/docs/zh/latest/FAQ.md @@ -760,15 +760,15 @@ deployment: password: etcd_password # password for etcd ``` -关于 ETCD 的其他配置,比如过期时间、重试次数等等,你可以参考 `conf/config-default.yaml` 文件中的 `ETCD` 部分。 +关于 ETCD 的其他配置,比如过期时间、重试次数等等,你可以参考 `conf/config.yaml.example` 文件中的 `etcd` 部分。 -## SSLs 对象与 `upstream` 对象中的 `tls.client_cert` 以及 `config-default.yaml` 中的 `ssl_trusted_certificate` 区别是什么? +## SSLs 对象与 `upstream` 对象中的 `tls.client_cert` 以及 `config.yaml` 中的 `ssl_trusted_certificate` 区别是什么? Admin API 中 `/apisix/admin/ssls` 用于管理 SSL 对象,如果 APISIX 需要接收来自外网的 HTTPS 请求,那就需要用到存放在这里的证书完成握手。SSL 对象中支持配置多个证书,不同域名的证书 APISIX 将使用 Server Name Indication (SNI) 进行区分。 Upstream 对象中的 `tls.client_cert`、`tls.client_key` 与 `tls.client_cert_id` 用于存放客户端的证书,适用于需要与上游进行 [mTLS 通信](https://apisix.apache.org/zh/docs/apisix/tutorials/client-to-apisix-mtls/)的情况。 -`config-default.yaml` 中的 `ssl_trusted_certificate` 用于配置一个受信任的根证书。它仅用于在 APISIX 内部访问某些具有自签名证书的服务时,避免提示拒绝对方的 SSL 证书。注意:它不用于信任 APISIX 上游的证书,因为 APISIX 不会验证上游证书的合法性。因此,即使上游使用了无效的 TLS 证书,APISIX 仍然可以与其通信,而无需配置根证书。 +`config.yaml` 中的 `ssl_trusted_certificate` 用于配置一个受信任的根证书。它仅用于在 APISIX 内部访问某些具有自签名证书的服务时,避免提示拒绝对方的 SSL 证书。注意:它不用于信任 APISIX 上游的证书,因为 APISIX 不会验证上游证书的合法性。因此,即使上游使用了无效的 TLS 证书,APISIX 仍然可以与其通信,而无需配置根证书。 ## 如果在使用 APISIX 过程中遇到问题,我可以在哪里寻求更多帮助? diff --git a/docs/zh/latest/benchmark.md b/docs/zh/latest/benchmark.md index 874fd9c7ce13..2ceda912d7d6 100644 --- a/docs/zh/latest/benchmark.md +++ b/docs/zh/latest/benchmark.md @@ -139,7 +139,7 @@ wrk -d 60 --latency http://127.0.0.1:9080/hello :::tip -如果你想测试大量连接的基准测试,你可能需要更新 [`./conf/config-default.yaml`](https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L242) 中的 **keepalive** 配置项,否则超过配置数量的连接将成为短连接。你可以使用以下命令运行大量连接的基准测试: +如果您想使用大量连接运行基准测试,您可能需要更新 [**keepalive**](https://github.com/apache/apisix/blob/master/conf/config.yaml.example#L241) 配置,将配置添加到 [`config.yaml`](https://github.com/apache/apisix/blob/master/conf/config.yaml) 并重新加载 APISIX。否则超过配置数量的连接将成为短连接。你可以使用以下命令运行大量连接的基准测试: ```bash wrk -t200 -c5000 -d30s http://127.0.0.1:9080/hello diff --git a/docs/zh/latest/customize-nginx-configuration.md b/docs/zh/latest/customize-nginx-configuration.md index 78880d262dbc..0817f63fccd4 100644 --- a/docs/zh/latest/customize-nginx-configuration.md +++ b/docs/zh/latest/customize-nginx-configuration.md @@ -21,11 +21,11 @@ title: 自定义 Nginx 配置 # --> -APISIX 会通过 `apisix/cli/ngx_tpl.lua` 这个模板和 `conf/config-default.yaml` 加 `conf/config.yaml` 的配置生成 Nginx 配置文件。 +APISIX 使用的 Nginx 配置是通过模板文件 `apisix/cli/ngx_tpl.lua` 以及 `apisix/cli/config.lua` 和`conf/config.yaml` 中的参数生成的。 在执行完 `./bin/apisix start`,你可以在 `conf/nginx.conf` 看到生成的 Nginx 配置文件。 -在自定义 Nginx 配置文件之前,烦请仔细阅读 `conf/config-default.yaml`。你可以在 `conf/config.yaml` 里面覆盖掉默认值。举个例子,你可以通过 `xxx_snippet` 之类的配置,在 `conf/nginx.conf` 里面注入你的自定义配置: +如果你需要自定义 Nginx 配置,请阅读 `conf/config.default.example` 中的 `nginx_config`。你可以在 `conf/config.yaml` 中覆盖默认值。例如,你可以在 `conf/nginx.conf` 中通过配置 `xxx_snippet` 条目注入一些代码片段: ```yaml ... diff --git a/docs/zh/latest/install-dependencies.md b/docs/zh/latest/install-dependencies.md index c7d36f5112d7..c545c8cc93a9 100644 --- a/docs/zh/latest/install-dependencies.md +++ b/docs/zh/latest/install-dependencies.md @@ -25,7 +25,7 @@ title: 安装依赖 - Apache APISIX 从 v2.0 开始不再支持 `v2` 版本的 etcd,并且 etcd 最低支持版本为 v3.4.0,因此请使用 etcd 3.4.0+。更重要的是,因为 etcd v3 使用 gRPC 作为消息传递协议,而 Apache APISIX 使用 HTTP(S) 与 etcd 集群通信,因此请确保启用 [etcd gRPC gateway](https://etcd.io/docs/v3.4.0/dev-guide/api_grpc_gateway/) 功能。 -- 目前 Apache APISIX 默认使用 HTTP 协议与 etcd 集群通信,这并不安全,如果希望保障数据的安全性和完整性。请为您的 etcd 集群配置证书及对应私钥,并在您的 Apache APISIX etcd endpoints 配置列表中明确使用 `https` 协议前缀。请查阅 `conf/config-default.yaml` 中 etcd 一节相关的配置来了解更多细节。 +- 目前 Apache APISIX 默认使用 HTTP 协议与 etcd 集群通信,这并不安全,如果希望保障数据的安全性和完整性。请为您的 etcd 集群配置证书及对应私钥,并在您的 Apache APISIX etcd endpoints 配置列表中明确使用 `https` 协议前缀。请查阅 `conf/config.yaml.example` 中 `etcd` 一节相关的配置来了解更多细节。 - 如果是 OpenResty 1.19,APISIX 会使用 OpenResty 内置的 LuaJIT 来运行 `bin/apisix`;否则会使用 Lua 5.1。如果运行过程中遇到 `luajit: lj_asm_x86.h:2819: asm_loop_fixup: Assertion '((intptr_t)target & 15) == 0' failed`,这是低版本 OpenResty 内置的 LuaJIT 在特定编译条件下的问题。 diff --git a/docs/zh/latest/installation-guide.md b/docs/zh/latest/installation-guide.md index df06485fe802..59cdf20405ef 100644 --- a/docs/zh/latest/installation-guide.md +++ b/docs/zh/latest/installation-guide.md @@ -265,7 +265,7 @@ brew services start etcd ### 配置 APISIX -通过修改本地 `./conf/config.yaml` 文件,或者在启动 APISIX 时使用 `-c` 或 `--config` 添加文件路径参数 `apisix start -c `,完成对 APISIX 服务本身的基本配置。 +通过修改本地 `./conf/config.yaml` 文件,或者在启动 APISIX 时使用 `-c` 或 `--config` 添加文件路径参数 `apisix start -c `,完成对 APISIX 服务本身的基本配置。默认配置不应修改,可以在 `apisix/cli/config.lua` 中找到。 比如将 APISIX 默认监听端口修改为 8000,其他配置保持默认,在 `./conf/config.yaml` 中只需这样配置: @@ -291,11 +291,6 @@ deployment: :::warning -APISIX 的默认配置可以在 `./conf/config-default.yaml` 文件中看到,该文件与 APISIX 源码强绑定,请不要手动修改 `./conf/config-default.yaml` 文件。如果需要自定义任何配置,都应在 `./conf/config.yaml` 文件中完成。 -::: - -:::warning - 请不要手动修改 APISIX 安装目录下的 `./conf/nginx.conf` 文件。当 APISIX 启动时,会根据 `config.yaml` 的配置自动生成新的 `nginx.conf` 并自动启动服务。 ::: diff --git a/docs/zh/latest/plugin-develop.md b/docs/zh/latest/plugin-develop.md index c425a8d2cc97..c893088f454a 100644 --- a/docs/zh/latest/plugin-develop.md +++ b/docs/zh/latest/plugin-develop.md @@ -109,30 +109,38 @@ local _M = { 注:新插件的优先级(priority 属性)不能与现有插件的优先级相同,您可以使用 [control API](./control-api.md#get-v1schema) 的 `/v1/schema` 方法查看所有插件的优先级。另外,同一个阶段里面,优先级 ( priority ) 值大的插件,会优先执行,比如 `example-plugin` 的优先级是 0,`ip-restriction` 的优先级是 3000,所以在每个阶段,会先执行 `ip-restriction` 插件,再去执行 `example-plugin` 插件。这里的“阶段”的定义,参见后续的 [确定执行阶段](#确定执行阶段) 这一节。对于你的插件,建议采用 1 到 99 之间的优先级。 -在 __conf/config-default.yaml__ 配置文件中,列出了启用的插件(都是以插件名指定的): +默认情况下,大多数 APISIX 插件都是[启用](https://github.com/apache/apisix/blob/master/apisix/cli/config.lua)的状态: -```yaml -plugins: # plugin list - - limit-req - - limit-count - - limit-conn - - key-auth - - prometheus - - node-status - - jwt-auth - - zipkin - - ip-restriction - - grpc-transcode - - serverless-pre-function - - serverless-post-function - - openid-connect - - proxy-rewrite - - redirect +```lua title="apisix/cli/config.lua" +local _M = { ... + plugins = { + "real-ip", + "ai", + "client-control", + "proxy-control", + "request-id", + "zipkin", + "ext-plugin-pre-req", + "fault-injection", + "mocking", + "serverless-pre-function", + ... + }, + ... +} ``` 注:先后顺序与执行顺序无关。 +要启用您的自定义插件,请将插件列表添加到 `conf/config.yaml` 并附加您的插件名称。例如: + +```yaml +plugins: # 请参阅 `conf/config.yaml.example` 示例 + - ... # 添加现有插件 + - your-plugin # 添加您的自定义插件 +``` + 特别需要注意的是,如果你的插件有新建自己的代码目录,那么就需要修改 Makefile 文件,新增创建文件夹的操作,比如: ``` diff --git a/docs/zh/latest/terminology/plugin.md b/docs/zh/latest/terminology/plugin.md index b8a224233a97..ba42e814cd17 100644 --- a/docs/zh/latest/terminology/plugin.md +++ b/docs/zh/latest/terminology/plugin.md @@ -37,22 +37,51 @@ APISIX 提供了许多现有的插件,可以定制和编排以满足你的需 ## 插件安装 -APISIX 附带一个`config-default.yaml`的默认配置文件和一个 `config.yaml` 的用户自定义配置文件。这些文件位于`conf`目录中。如果两个文件中都存在相同的键 (例如`plugins`),则`config.yaml`文件中该键的配置值将覆盖`config-default.yaml`文件中的配置值。 +默认情况下,大多数 APISIX 插件都已[安装](https://github.com/apache/apisix/blob/master/apisix/cli/config.lua): + +```lua title="apisix/cli/config.lua" +local _M = { + ... + plugins = { + "real-ip", + "ai", + "client-control", + "proxy-control", + "request-id", + "zipkin", + "ext-plugin-pre-req", + "fault-injection", + "mocking", + "serverless-pre-function", + ... + }, + ... +} +``` -例如: +如果您想调整插件安装,请将自定义的 `plugins` 配置添加到 `config.yaml` 中。例如: ```yaml plugins: - - real-ip # 安装 + - real-ip # 安装 + - ai + - real-ip - ai - client-control - proxy-control - request-id - zipkin - # - skywalking # 未安装 -... + - ext-plugin-pre-req + - fault-injection + # - mocking # 不安装 + - serverless-pre-function + ... # 其它插件 ``` +完整配置参考请参见 [`config.yaml.example`](https://github.com/apache/apisix/blob/master/conf/config.yaml.example)。 + +重新加载 APISIX 以使配置更改生效。 + ## 插件执行生命周期 安装的插件首先会被初始化。然后会检查插件的配置,以确保插件配置遵循定义的[JSON Schema](https://json-schema.org)。 From c1601f89cde1461d3ae1dcfcf83c387951cf4e5b Mon Sep 17 00:00:00 2001 From: Traky Deng Date: Tue, 27 Aug 2024 13:15:55 +0800 Subject: [PATCH 08/16] docs: clarify `gzip_buffers` unit size (#11526) --- docs/en/latest/plugins/gzip.md | 4 ++-- docs/zh/latest/plugins/gzip.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/en/latest/plugins/gzip.md b/docs/en/latest/plugins/gzip.md index e8ff94162021..a8a16dc61edf 100644 --- a/docs/en/latest/plugins/gzip.md +++ b/docs/en/latest/plugins/gzip.md @@ -46,8 +46,8 @@ This Plugin requires APISIX to run on [APISIX-Runtime](../FAQ.md#how-do-i-build- | min_length | integer | False | 20 | >= 1 | Dynamically sets the `gzip_min_length` directive. | | comp_level | integer | False | 1 | [1, 9] | Dynamically sets the `gzip_comp_level` directive. | | http_version | number | False | 1.1 | 1.1, 1.0 | Dynamically sets the `gzip_http_version` directive. | -| buffers.number | integer | False | 32 | >= 1 | Dynamically sets the `gzip_buffers` directive. | -| buffers.size | integer | False | 4096 | >= 1 | Dynamically sets the `gzip_buffers` directive. | +| buffers.number | integer | False | 32 | >= 1 | Dynamically sets the `gzip_buffers` directive parameter `number`. | +| buffers.size | integer | False | 4096 | >= 1 | Dynamically sets the `gzip_buffers` directive parameter `size`. The unit is in bytes. | | vary | boolean | False | false | | Dynamically sets the `gzip_vary` directive. | ## Enable Plugin diff --git a/docs/zh/latest/plugins/gzip.md b/docs/zh/latest/plugins/gzip.md index a9546d7932cb..05a49b634d28 100644 --- a/docs/zh/latest/plugins/gzip.md +++ b/docs/zh/latest/plugins/gzip.md @@ -46,8 +46,8 @@ description: 本文介绍了关于 Apache APISIX `gzip` 插件的基本信息及 | min_length | integer | 否 | 20 | >= 1 | 动态设置 [`gzip_min_length`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_min_length) 指令。 | | comp_level | integer | 否 | 1 | [1, 9] | 动态设置 [`gzip_comp_level`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_comp_level) 指令。 | | http_version | number | 否 | 1.1 | 1.1, 1.0 | 动态设置 [`gzip_http_version`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_http_version) 指令。 | -| buffers.number | integer | 否 | 32 | >= 1 | 动态设置 [`gzip_buffers`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_buffers) 指令。 | -| buffers.size | integer | 否 | 4096 | >= 1 | 动态设置 [`gzip_buffers`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_buffers) 指令。 | +| buffers.number | integer | 否 | 32 | >= 1 | 动态设置 [`gzip_buffers`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_buffers) 指令 参数 `number`。 | +| buffers.size | integer | 否 | 4096 | >= 1 | 动态设置 [`gzip_buffers`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_buffers) 指令参数 `size`。单位为字节。 | | vary | boolean | 否 | false | | 动态设置 [`gzip_vary`](https://nginx.org/en/docs/http/ngx_http_gzip_module.html#gzip_vary) 指令。 | ## 启用插件 From 9c81c93635e079900088ae3e4d92e3e957f16ba1 Mon Sep 17 00:00:00 2001 From: HuanXin-Chen <111850224+HuanXin-Chen@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:41:19 +0800 Subject: [PATCH 09/16] feat: support aws secret manager (#11417) --- apisix-master-0.rockspec | 3 +- apisix/secret/aws.lua | 135 ++++++++++++ ci/init-common-test-service.sh | 6 + ci/pod/docker-compose.common.yml | 9 + docs/en/latest/terminology/secret.md | 105 ++++++++- docs/zh/latest/terminology/secret.md | 104 +++++++++ t/secret/aws.t | 316 +++++++++++++++++++++++++++ 7 files changed, 676 insertions(+), 2 deletions(-) create mode 100644 apisix/secret/aws.lua create mode 100644 t/secret/aws.t diff --git a/apisix-master-0.rockspec b/apisix-master-0.rockspec index ac8da1e61474..913a4defe39d 100644 --- a/apisix-master-0.rockspec +++ b/apisix-master-0.rockspec @@ -81,7 +81,8 @@ dependencies = { "lua-resty-ldap = 0.1.0-0", "lua-resty-t1k = 1.1.5", "brotli-ffi = 0.3-1", - "lua-ffi-zlib = 0.6-0" + "lua-ffi-zlib = 0.6-0", + "api7-lua-resty-aws == 2.0.1-1", } build = { diff --git a/apisix/secret/aws.lua b/apisix/secret/aws.lua new file mode 100644 index 000000000000..e194fff0889f --- /dev/null +++ b/apisix/secret/aws.lua @@ -0,0 +1,135 @@ +-- +-- 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. +-- + +--- AWS Tools. +local core = require("apisix.core") +local http = require("resty.http") +local aws = require("resty.aws") + +local sub = core.string.sub +local find = core.string.find +local env = core.env +local unpack = unpack + +local schema = { + type = "object", + properties = { + access_key_id = { + type = "string", + }, + secret_access_key = { + type = "string", + }, + session_token = { + type = "string", + }, + region = { + type = "string", + default = "us-east-1", + }, + endpoint_url = core.schema.uri_def, + }, + required = {"access_key_id", "secret_access_key"}, +} + +local _M = { + schema = schema +} + +local function make_request_to_aws(conf, key) + local aws_instance = aws() + + local region = conf.region + + local access_key_id = env.fetch_by_uri(conf.access_key_id) or conf.access_key_id + + local secret_access_key = env.fetch_by_uri(conf.secret_access_key) or conf.secret_access_key + + local session_token = env.fetch_by_uri(conf.session_token) or conf.session_token + + local credentials = aws_instance:Credentials({ + accessKeyId = access_key_id, + secretAccessKey = secret_access_key, + sessionToken = session_token, + }) + + local default_endpoint = "https://secretsmanager." .. region .. ".amazonaws.com" + local scheme, host, port, _, _ = unpack(http:parse_uri(conf.endpoint_url or default_endpoint)) + local endpoint = scheme .. "://" .. host + + local sm = aws_instance:SecretsManager({ + credentials = credentials, + endpoint = endpoint, + region = region, + port = port, + }) + + local res, err = sm:getSecretValue({ + SecretId = key, + VersionStage = "AWSCURRENT", + }) + + if not res then + return nil, err + end + + if res.status ~= 200 then + local data = core.json.encode(res.body) + if data then + return nil, "invalid status code " .. res.status .. ", " .. data + end + + return nil, "invalid status code " .. res.status + end + + return res.body.SecretString +end + +-- key is the aws secretId +function _M.get(conf, key) + core.log.info("fetching data from aws for key: ", key) + + local idx = find(key, '/') + + local main_key = idx and sub(key, 1, idx - 1) or key + if main_key == "" then + return nil, "can't find main key, key: " .. key + end + + local sub_key = idx and sub(key, idx + 1) or nil + + core.log.info("main: ", main_key, sub_key and ", sub: " .. sub_key or "") + + local res, err = make_request_to_aws(conf, main_key) + if not res then + return nil, "failed to retrtive data from aws secret manager: " .. err + end + + if not sub_key then + return res + end + + local data, err = core.json.decode(res) + if not data then + return nil, "failed to decode result, res: " .. res .. ", err: " .. err + end + + return data[sub_key] +end + + +return _M diff --git a/ci/init-common-test-service.sh b/ci/init-common-test-service.sh index 7a54cd49a2b6..602f01a4ad23 100755 --- a/ci/init-common-test-service.sh +++ b/ci/init-common-test-service.sh @@ -19,3 +19,9 @@ # prepare vault kv engine sleep 3s docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault secrets enable -path=kv -version=1 kv" + +# prepare localstack +sleep 3s +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name apisix-key --description 'APISIX Secret' --secret-string '{\"jack\":\"value\"}'" +sleep 3s +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name apisix-mysql --description 'APISIX Secret' --secret-string 'secret'" diff --git a/ci/pod/docker-compose.common.yml b/ci/pod/docker-compose.common.yml index 222dc1e1eed4..67504cbe8565 100644 --- a/ci/pod/docker-compose.common.yml +++ b/ci/pod/docker-compose.common.yml @@ -102,3 +102,12 @@ services: VAULT_DEV_ROOT_TOKEN_ID: root VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200 command: [ "vault", "server", "-dev" ] + + + ## LocalStack + localstack: + image: localstack/localstack + container_name: localstack + restart: unless-stopped + ports: + - "127.0.0.1:4566:4566" # LocalStack Gateway diff --git a/docs/en/latest/terminology/secret.md b/docs/en/latest/terminology/secret.md index bc233f3d9ce1..e27ee79fc1ac 100644 --- a/docs/en/latest/terminology/secret.md +++ b/docs/en/latest/terminology/secret.md @@ -38,7 +38,8 @@ Its working principle is shown in the figure: APISIX currently supports storing secrets in the following ways: - [Environment Variables](#use-environment-variables-to-manage-secrets) -- [HashiCorp Vault](#use-vault-to-manage-secrets) +- [HashiCorp Vault](#use-hashicorp-vault-to-manage-secrets) +- [AWS Secrets Manager](#use-aws-secrets-manager-to-manage-secrets) You can use APISIX Secret functions by specifying format variables in the consumer configuration of the following plugins, such as `key-auth`. @@ -190,3 +191,105 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ``` 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 APISIX Secret component. + +## Use AWS Secrets Manager to manage secrets + +Managing secrets with AWS Secrets Manager is a secure and convenient way to store and manage sensitive information. This method allows you to save secret information in AWS Secrets Manager and reference these secrets in a specific format when configuring APISIX plugins. + +APISIX currently supports two authentication methods: using [long-term credentials](https://docs.aws.amazon.com/sdkref/latest/guide/access-iam-users.html) and [short-term credentials](https://docs.aws.amazon.com/sdkref/latest/guide/access-temp-idc.html). + +### Usage + +``` +$secret://$manager/$id/$secret_name/$key +``` + +- manager: secrets management service, could be the HashiCorp Vault, AWS, etc. +- id: APISIX Secrets resource ID, which needs to be consistent with the one specified when adding the APISIX Secrets resource +- secret_name: the secret name in the secrets management service +- key: get the value of a property when the value of the secret is a JSON string + +### Required Parameters + +| Name | Required | Default Value | Description | +| --- | --- | --- | --- | +| access_key_id | True | | AWS Access Key ID | +| secret_access_key | True | | AWS Secret Access Key | +| session_token | False | | Temporary access credential information | +| region | False | us-east-1 | AWS Region | +| endpoint_url | False | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager URL | + +### Example: use in key-auth plugin + +Here, we use the key-auth plugin as an example to demonstrate how to manage secrets through AWS Secrets Manager. + +Step 1: Create the corresponding key in the AWS secrets manager. Here, [localstack](https://www.localstack.cloud/) is used for as the example environment, and you can use the following command: + +```shell +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" +``` + +Step 2: Add APISIX Secrets resources through the Admin API, configure the connection information such as the address of AWS Secrets Manager. + +You can store the critical key information in environment variables to ensure the configuration information is secure, and reference it where it is used: + +```shell +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_SESSION_TOKEN= +export AWS_REGION= +``` + +Alternatively, you can also specify all the information directly in the configuration: + +```shell +curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" +}' +``` + +If you use APISIX Standalone mode, you can add the following configuration in `apisix.yaml` configuration file: + +```yaml +secrets: + - id: aws/1 + endpoint_url: http://127.0.0.1:4566 + region: us-east-1 + access_key_id: access + secret_access_key: secret + session_token: token +``` + +Step 3: Reference the APISIX Secrets 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: $admin_key" -X PUT -d ' +{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/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 APISIX Secret component. + +### Verification + +You can verify this with the following command: + +```shell +#Replace the following your_route with the actual route path. +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +``` + +This will verify whether the `key-auth` plugin is correctly using the key from AWS Secrets Manager. diff --git a/docs/zh/latest/terminology/secret.md b/docs/zh/latest/terminology/secret.md index 100a44475eb3..03dbf0c1be2b 100644 --- a/docs/zh/latest/terminology/secret.md +++ b/docs/zh/latest/terminology/secret.md @@ -39,6 +39,7 @@ APISIX 目前支持通过以下方式存储密钥: - [环境变量](#使用环境变量管理密钥) - [HashiCorp Vault](#使用-vault-管理密钥) +- [AWS Secrets Manager](#使用-aws-secrets-manager-管理密钥) 你可以在以下插件的 consumer 配置中通过指定格式的变量来使用 APISIX Secret 功能,比如 `key-auth` 插件。 @@ -191,3 +192,106 @@ curl http://127.0.0.1:9180/apisix/admin/consumers \ ``` 通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 Vault 中的真实值。 + +## 使用 AWS Secrets Manager 管理密钥 + +使用 AWS Secrets Manager 管理密钥是一种安全且便捷的方式来存储和管理敏感信息。通过这种方式,你可以将密钥信息保存在 AWS Secret Manager 中,并在配置 APISIX 插件时通过特定的格式引用这些密钥。 + +APISIX 目前支持两种访问方式: [长期凭证的访问方式](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-iam-users.html) 和 [短期凭证的访问方式](https://docs.aws.amazon.com/zh_cn/sdkref/latest/guide/access-temp-idc.html)。 + +### 引用方式 + +在 APISIX 中引用密钥时,可以使用以下格式: + +``` +$secret://$manager/$id/$secret_name/$key +``` + +- manager: 密钥管理服务,可以是 Vault、AWS 等 +- APISIX Secret 资源 ID,需要与添加 APISIX Secret 资源时指定的 ID 保持一致 +- secret_name: 密钥管理服务中的密钥名称 +- key:当密钥的值是 JSON 字符串时,获取某个属性的值 + +### 相关参数 + +| 名称 | 必选项 | 默认值 | 描述 | +| --- | --- | --- | --- | +| access_key_id | 是 | | AWS 访问密钥 ID | +| secret_access_key | 是 | | AWS 访问密钥 | +| session_token | 否 | | 临时访问凭证信息 | +| region | 否 | us-east-1 | AWS 区域 | +| endpoint_url | 否 | https://secretsmanager.{region}.amazonaws.com | AWS Secret Manager 地址 | + +### 示例:在 key-auth 插件中使用 + +这里以 key-auth 插件的使用为例,展示如何通过 AWS Secret Manager 管理密钥: + +第一步:在 AWS Secret Manager 中创建对应的密钥,这里使用 [localstack](https://www.localstack.cloud/) 模拟,可以使用如下命令: + +```shell +docker exec -i localstack sh -c "awslocal secretsmanager create-secret --name jack --description 'APISIX Secret' --secret-string '{\"auth-key\":\"value\"}'" +``` + +第二步:通过 Admin API 添加 Secret 资源,配置 AWS Secret Manager 的地址等连接信息: + +你可以在环境变量中存储关键密钥信息,保证配置信息是安全的,在使用到地方进行引用: + +```shell +export AWS_ACCESS_KEY_ID= +export AWS_SECRET_ACCESS_KEY= +export AWS_SESSION_TOKEN= +``` + +当然,你也可以通过直接在配置中指定所有信息内容: + +```shell +curl http://127.0.0.1:9180/apisix/admin/secrets/aws/1 \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" +}' +``` + +如果使用 APISIX Standalone 版本,则可以在 `apisix.yaml` 文件中添加如下配置: + +```yaml +secrets: + - id: aws/1 + endpoint_url: http://127.0.0.1:4566 + region: us-east-1 + access_key_id: access + secret_access_key: secret + session_token: token +``` + +第三步:在 `key-auth` 插件中引用 APISIX Secret 资源,填充秘钥信息: + +```shell +curl http://127.0.0.1:9180/apisix/admin/consumers \ +-H "X-API-KEY: $admin_key" -X PUT -d ' +{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/1/jack/auth-key" + } + } +}' +``` + +通过上面两步操作,当用户请求命中 `key-auth` 插件时,会通过 APISIX Secret 组件获取到 key 在 AWS Secret Manager 中的真实值。 + +### 验证 + +你可以通过如下指令进行验证: + +```shell +# 示例:将下面的 your_route 替换为实际的路由路径 +curl -i http://127.0.0.1:9080/your_route -H 'apikey: value' +``` + +这将验证 key-auth 插件是否正确地使用 AWS Secret Manager 中的密钥。 diff --git a/t/secret/aws.t b/t/secret/aws.t new file mode 100644 index 000000000000..ae0e09b63398 --- /dev/null +++ b/t/secret/aws.t @@ -0,0 +1,316 @@ +# +# 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. +# + +BEGIN { + $ENV{AWS_REGION} = "us-east-1"; + $ENV{AWS_ACCESS_KEY_ID} = "access"; + $ENV{AWS_SECRET_ACCESS_KEY} = "secret"; + $ENV{AWS_SESSION_TOKEN} = "token"; +} + +use t::APISIX 'no_plan'; + +repeat_each(1); +no_long_string(); +no_root_location(); +log_level("info"); + +run_tests; + +__DATA__ + +=== TEST 1: sanity +--- request +GET /t +--- config + location /t { + content_by_lua_block { + local test_case = { + {access_key_id = "access"}, + {secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = 1234}, + {access_key_id = 1234, secret_access_key = "secret"}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token"}, + {access_key_id = "access", secret_access_key = "secret", session_token = 1234}, + {access_key_id = "access", secret_access_key = "secret", region = "us-east-1"}, + {access_key_id = "access", secret_access_key = "secret", region = 1234}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = "http://127.0.0.1:4566"}, + {access_key_id = "access", secret_access_key = "secret", endpoint_url = 1234}, + {access_key_id = "access", secret_access_key = "secret", session_token = "token", endpoint_url = "http://127.0.0.1:4566", region = "us-east-1"}, + } + local aws = require("apisix.secret.aws") + local core = require("apisix.core") + local metadata_schema = aws.schema + + for _, conf in ipairs(test_case) do + local ok, err = core.schema.check(metadata_schema, conf) + ngx.say(ok and "done" or err) + end + } + } +--- response_body +property "secret_access_key" is required +property "access_key_id" is required +done +property "secret_access_key" validation failed: wrong type: expected string, got number +property "access_key_id" validation failed: wrong type: expected string, got number +done +property "session_token" validation failed: wrong type: expected string, got number +done +property "region" validation failed: wrong type: expected string, got number +done +property "endpoint_url" validation failed: wrong type: expected string, got number +done + + + +=== TEST 2: check key: no main key +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "/apisix") + if err then + return ngx.say(err) + end + + ngx.say("done") + } + } +--- request +GET /t +--- response_body +can't find main key, key: /apisix + + + +=== TEST 3: error aws endpoint_url +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:8080", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "apisix-key/jack") + if err then + return ngx.say(err) + end + ngx.say("done") + } + } +--- request +GET /t +--- response_body +failed to retrtive data from aws secret manager: SecretsManager:getSecretValue() failed to connect to 'http://127.0.0.1:8080': connection refused +--- timeout: 6 + + + +=== TEST 4: get value from aws (status ~= 200) +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "apisix-error-key/jack") + if err then + return ngx.say("err") + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +err + + + +=== TEST 5: get json value from aws +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "access", + secret_access_key = "secret", + session_token = "token", + } + local data, err = aws.get(conf, "apisix-key/jack") + if err then + return ngx.say(err) + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value + + + +=== TEST 6: get json value from aws using env var +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "$ENV://AWS_ACCESS_KEY_ID", + secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY", + session_token = "$ENV://AWS_SESSION_TOKEN", + } + local data, err = aws.get(conf, "apisix-key/jack") + if err then + return ngx.say(err) + end + ngx.say("value") + } + } +--- request +GET /t +--- response_body +value + + + +=== TEST 7: get string value from aws +--- config + location /t { + content_by_lua_block { + local aws = require("apisix.secret.aws") + local conf = { + endpoint_url = "http://127.0.0.1:4566", + region = "us-east-1", + access_key_id = "$ENV://AWS_ACCESS_KEY_ID", + secret_access_key = "$ENV://AWS_SECRET_ACCESS_KEY", + session_token = "$ENV://AWS_SESSION_TOKEN", + } + local data, err = aws.get(conf, "apisix-mysql") + if err then + return ngx.say(err) + end + ngx.say(data) + } + } +--- request +GET /t +--- response_body +secret + + + +=== TEST 8: add secret && consumer && check +--- request +GET /t +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + -- put secret aws config + local code, body = t('/apisix/admin/secrets/aws/mysecret', + ngx.HTTP_PUT, + [[{ + "endpoint_url": "http://127.0.0.1:4566", + "region": "us-east-1", + "access_key_id": "access", + "secret_access_key": "secret", + "session_token": "token" + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + -- change consumer with secrets ref: aws + code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/mysecret/jack/key" + } + } + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + + local secret = require("apisix.secret") + local value = secret.fetch_by_uri("$secret://aws/mysecret/jack/key") + + + local code, body = t('/apisix/admin/secrets/aws/mysecret', ngx.HTTP_DELETE) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + code, body = t('/apisix/admin/consumers', + ngx.HTTP_PUT, + [[{ + "username": "jack", + "plugins": { + "key-auth": { + "key": "$secret://aws/mysecret/jack/key" + } + } + }]] + ) + if code >= 300 then + ngx.status = code + return ngx.say(body) + end + + local secret = require("apisix.secret") + local value = secret.fetch_by_uri("$secret://aws/mysecret/jack/key") + if value then + ngx.say("secret value: ", value) + end + ngx.say("all done") + } + } +--- response_body +all done From e775640f79923b4480283a3aea6486c3208dff82 Mon Sep 17 00:00:00 2001 From: Shreemaan Abhishek Date: Thu, 29 Aug 2024 13:28:53 +0545 Subject: [PATCH 10/16] feat: ai-prompt-template plugin (#11517) --- apisix/cli/config.lua | 1 + apisix/plugins/ai-prompt-template.lua | 146 +++++++ conf/config.yaml.example | 1 + docs/en/latest/config.json | 1 + docs/en/latest/plugins/ai-prompt-template.md | 102 +++++ t/admin/plugins.t | 1 + t/plugin/ai-prompt-template.t | 403 +++++++++++++++++++ 7 files changed, 655 insertions(+) create mode 100644 apisix/plugins/ai-prompt-template.lua create mode 100644 docs/en/latest/plugins/ai-prompt-template.md create mode 100644 t/plugin/ai-prompt-template.t diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua index 94843621a74b..7f15542b1d7e 100644 --- a/apisix/cli/config.lua +++ b/apisix/cli/config.lua @@ -213,6 +213,7 @@ local _M = { "authz-keycloak", "proxy-cache", "body-transformer", + "ai-prompt-template", "proxy-mirror", "proxy-rewrite", "workflow", diff --git a/apisix/plugins/ai-prompt-template.lua b/apisix/plugins/ai-prompt-template.lua new file mode 100644 index 000000000000..0a092c3f77c0 --- /dev/null +++ b/apisix/plugins/ai-prompt-template.lua @@ -0,0 +1,146 @@ +-- +-- 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 body_transformer = require("apisix.plugins.body-transformer") +local ipairs = ipairs + +local prompt_schema = { + properties = { + role = { + type = "string", + enum = { "system", "user", "assistant" } + }, + content = { + type = "string", + minLength = 1, + } + }, + required = { "role", "content" } +} + +local prompts = { + type = "array", + minItems = 1, + items = prompt_schema +} + +local schema = { + type = "object", + properties = { + templates = { + type = "array", + minItems = 1, + items = { + type = "object", + properties = { + name = { + type = "string", + minLength = 1, + }, + template = { + type = "object", + properties = { + model = { + type = "string", + minLength = 1, + }, + messages = prompts + } + } + }, + required = {"name", "template"} + } + }, + }, + required = {"templates"}, +} + + +local _M = { + version = 0.1, + priority = 1060, + name = "ai-prompt-template", + schema = schema, +} + +local templates_lrucache = core.lrucache.new({ + ttl = 300, count = 256 +}) + +local templates_json_lrucache = core.lrucache.new({ + ttl = 300, count = 256 +}) + +function _M.check_schema(conf) + return core.schema.check(schema, conf) +end + + +local function get_request_body_table() + local body, err = core.request.get_body() + if not body then + return nil, { message = "could not get body: " .. err } + end + + local body_tab, err = core.json.decode(body) + if not body_tab then + return nil, { message = "could not get parse JSON request body: ", err } + end + + return body_tab +end + + +local function find_template(conf, template_name) + for _, template in ipairs(conf.templates) do + if template.name == template_name then + return template.template + end + end + return nil +end + +function _M.rewrite(conf, ctx) + local body_tab, err = get_request_body_table() + if not body_tab then + return 400, err + end + local template_name = body_tab.template_name + if not template_name then + return 400, { message = "template name is missing in request." } + end + + local template = templates_lrucache(template_name, conf, find_template, conf, template_name) + if not template then + return 400, { message = "template: " .. template_name .. " not configured." } + end + + local template_json = templates_json_lrucache(template, template, core.json.encode, template) + core.log.info("sending template to body_transformer: ", template_json) + return body_transformer.rewrite( + { + request = { + template = template_json, + input_format = "json" + } + }, + ctx + ) +end + + +return _M diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 5a490a4bb4b3..5d22418caeb5 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -476,6 +476,7 @@ plugins: # plugin list (sorted by priority) #- error-log-logger # priority: 1091 - proxy-cache # priority: 1085 - body-transformer # priority: 1080 + - ai-prompt-template # priority: 1060 - proxy-mirror # priority: 1010 - proxy-rewrite # priority: 1008 - workflow # priority: 1006 diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index 928aec3b2b62..0998ec730cd1 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -91,6 +91,7 @@ "plugins/proxy-rewrite", "plugins/grpc-transcode", "plugins/grpc-web", + "plugins/ai-prompt-template", "plugins/fault-injection", "plugins/mocking", "plugins/degraphql", diff --git a/docs/en/latest/plugins/ai-prompt-template.md b/docs/en/latest/plugins/ai-prompt-template.md new file mode 100644 index 000000000000..9ca4e1f70aa9 --- /dev/null +++ b/docs/en/latest/plugins/ai-prompt-template.md @@ -0,0 +1,102 @@ +--- +title: ai-prompt-template +keywords: + - Apache APISIX + - API Gateway + - Plugin + - ai-prompt-template +description: This document contains information about the Apache APISIX ai-prompt-template Plugin. +--- + + + +## Description + +The `ai-prompt-template` plugin simplifies access to LLM providers, such as OpenAI and Anthropic, and their models by predefining the request format +using a template, which only allows users to pass customized values into template variables. + +## Plugin Attributes + +| **Field** | **Required** | **Type** | **Description** | +| ------------------------------------- | ------------ | -------- | --------------------------------------------------------------------------------------------------------------------------- | +| `templates` | Yes | Array | An array of template objects | +| `templates.name` | Yes | String | Name of the template. | +| `templates.template.model` | Yes | String | Model of the AI Model, for example `gpt-4` or `gpt-3.5`. See your LLM provider API documentation for more available models. | +| `templates.template.messages.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) | +| `templates.template.messages.content` | Yes | String | Content of the message. | + +## Example usage + +Create a route with the `ai-prompt-template` plugin like so: + +```shell +curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ + -H "X-API-KEY: ${ADMIN_API_KEY}" \ + -d '{ + "uri": "/v1/chat/completions", + "upstream": { + "type": "roundrobin", + "nodes": { + "api.openai.com:443": 1 + }, + "scheme": "https", + "pass_host": "node" + }, + "plugins": { + "ai-prompt-template": { + "templates": [ + { + "name": "level of detail", + "template": { + "model": "gpt-4", + "messages": [ + { + "role": "user", + "content": "Explain about {{ topic }} in {{ level }}." + } + ] + } + } + ] + } + } + }' +``` + +Now send a request: + +```shell +curl http://127.0.0.1:9080/v1/chat/completions -i -XPOST -H 'Content-Type: application/json' -d '{ + "template_name": "level of detail", + "topic": "psychology", + "level": "brief" +}' -H "Authorization: Bearer " +``` + +Then the request body will be modified to something like this: + +```json +{ + "model": "some model", + "messages": [ + { "role": "user", "content": "Explain about psychology in brief." } + ] +} +``` diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 911205f48cb4..547b1a316d56 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -93,6 +93,7 @@ opa authz-keycloak proxy-cache body-transformer +ai-prompt-template proxy-mirror proxy-rewrite workflow diff --git a/t/plugin/ai-prompt-template.t b/t/plugin/ai-prompt-template.t new file mode 100644 index 000000000000..050e0f246268 --- /dev/null +++ b/t/plugin/ai-prompt-template.t @@ -0,0 +1,403 @@ +# +# 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) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } + +}); + +run_tests(); + +__DATA__ + +=== TEST 1: sanity +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-template": { + "templates":[ + { + "name": "programming question", + "template": { + "model": "some model", + "messages": [ + { "role": "system", "content": "You are a {{ language }} programmer." }, + { "role": "user", "content": "Write a {{ program_name }} program." } + ] + } + }, + { + "name": "level of detail", + "template": { + "model": "some model", + "messages": [ + { "role": "user", "content": "Explain about {{ topic }} in {{ level }}." } + ] + } + } + ] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 2: no templates +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-template": { + "templates":[] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- error_code: 400 +--- response_body eval +qr/.*property \\"templates\\" validation failed: expect array to have at least 1 items.*/ + + + +=== TEST 3: test template insertion +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("apisix.core.json") + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "template_name": "programming question", + "language": "python", + "program_name": "quick sort" + }]], + [[{ + "model": "some model", + "messages": [ + { "role": "system", "content": "You are a python programmer." }, + { "role": "user", "content": "Write a quick sort program." } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 4: multiple templates +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-template": { + "templates":[ + { + "name": "programming question", + "template": { + "model": "some model", + "messages": [ + { "role": "system", "content": "You are a {{ language }} programmer." }, + { "role": "user", "content": "Write a {{ program_name }} program." } + ] + } + }, + { + "name": "level of detail", + "template": { + "model": "some model", + "messages": [ + { "role": "user", "content": "Explain about {{ topic }} in {{ level }}." } + ] + } + } + ] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 5: test second template +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("apisix.core.json") + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "template_name": "level of detail", + "topic": "psychology", + "level": "brief" + }]], + [[{ + "model": "some model", + "messages": [ + { "role": "user", "content": "Explain about psychology in brief." } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 6: missing template items +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("apisix.core.json") + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "template_name": "level of detail", + "topic-missing": "psychology", + "level-missing": "brief" + }]], + [[{ + "model": "some model", + "messages": [ + { "role": "user", "content": "Explain about in ." } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 7: request body contains non-existent template +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("apisix.core.json") + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "template_name": "random", + "some-key": "some-value" + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + ngx.say("passed") + } + } +--- error_code: 400 +--- response_body eval +qr/.*template: random not configured.*/ + + + +=== TEST 8: request body contains non-existent template +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local json = require("apisix.core.json") + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "missing-template-name": "haha" + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + ngx.say("passed") + } + } +--- error_code: 400 +--- response_body eval +qr/.*template name is missing in request.*/ + + + +=== TEST 9: (cache test) same template name in different routes +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + for i = 1, 5, 1 do + local code = t('/apisix/admin/routes/' .. i, + ngx.HTTP_PUT, + [[{ + "uri": "/]] .. i .. [[", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-template": { + "templates":[ + { + "name": "same name", + "template": { + "model": "some model", + "messages": [ + { "role": "system", "content": "Field: {{ field }} in route]] .. i .. [[." } + ] + } + } + ] + }, + "proxy-rewrite": { + "uri": "/echo" + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + ngx.say("failed") + return + end + end + + for i = 1, 5, 1 do + local code, body = t('/' .. i, + ngx.HTTP_POST, + [[{ + "template_name": "same name", + "field": "foo" + }]], + [[{ + "model": "some model", + "messages": [ + { "role": "system", "content": "Field: foo in route]] .. i .. [[." } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say(body) + return + end + end + ngx.status = 200 + ngx.say("passed") + } + } + +--- response_body +passed From 33879168349b08ba70f5097629840dba181e1def Mon Sep 17 00:00:00 2001 From: Shreemaan Abhishek Date: Fri, 30 Aug 2024 09:24:15 +0545 Subject: [PATCH 11/16] feat: ai-prompt-decorator plugin (#11515) --- apisix/cli/config.lua | 1 + apisix/plugins/ai-prompt-decorator.lua | 117 +++++++ apisix/plugins/ai-prompt-template.lua | 2 +- conf/config.yaml.example | 3 +- docs/en/latest/config.json | 1 + docs/en/latest/plugins/ai-prompt-decorator.md | 109 +++++++ t/admin/plugins.t | 1 + t/plugin/ai-prompt-decorator.t | 293 ++++++++++++++++++ 8 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 apisix/plugins/ai-prompt-decorator.lua create mode 100644 docs/en/latest/plugins/ai-prompt-decorator.md create mode 100644 t/plugin/ai-prompt-decorator.t diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua index 7f15542b1d7e..6ab10c9256cd 100644 --- a/apisix/cli/config.lua +++ b/apisix/cli/config.lua @@ -214,6 +214,7 @@ local _M = { "proxy-cache", "body-transformer", "ai-prompt-template", + "ai-prompt-decorator", "proxy-mirror", "proxy-rewrite", "workflow", diff --git a/apisix/plugins/ai-prompt-decorator.lua b/apisix/plugins/ai-prompt-decorator.lua new file mode 100644 index 000000000000..10b36e82cd1d --- /dev/null +++ b/apisix/plugins/ai-prompt-decorator.lua @@ -0,0 +1,117 @@ +-- +-- 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 ngx = ngx +local pairs = pairs +local EMPTY = {} + +local prompt_schema = { + properties = { + role = { + type = "string", + enum = { "system", "user", "assistant" } + }, + content = { + type = "string", + minLength = 1, + } + }, + required = { "role", "content" } +} + +local prompts = { + type = "array", + items = prompt_schema +} + +local schema = { + type = "object", + properties = { + prepend = prompts, + append = prompts, + }, + anyOf = { + { required = { "prepend" } }, + { required = { "append" } }, + { required = { "append", "prepend" } }, + }, +} + + +local _M = { + version = 0.1, + priority = 1070, + name = "ai-prompt-decorator", + schema = schema, +} + + +function _M.check_schema(conf) + return core.schema.check(schema, conf) +end + + +local function get_request_body_table() + local body, err = core.request.get_body() + if not body then + return nil, { message = "could not get body: " .. err } + end + + local body_tab, err = core.json.decode(body) + if not body_tab then + return nil, { message = "could not get parse JSON request body: " .. err } + end + + return body_tab +end + + +local function decorate(conf, body_tab) + local new_messages = conf.prepend or EMPTY + for _, message in pairs(body_tab.messages) do + core.table.insert_tail(new_messages, message) + end + + for _, message in pairs(conf.append or EMPTY) do + core.table.insert_tail(new_messages, message) + end + + body_tab.messages = new_messages +end + + +function _M.rewrite(conf, ctx) + local body_tab, err = get_request_body_table() + if not body_tab then + return 400, err + end + + if not body_tab.messages then + return 400, "messages missing from request body" + end + decorate(conf, body_tab) -- will decorate body_tab in place + + local new_jbody, err = core.json.encode(body_tab) + if not new_jbody then + return 500, { message = "failed to parse modified JSON request body: " .. err } + end + + ngx.req.set_body_data(new_jbody) +end + + +return _M diff --git a/apisix/plugins/ai-prompt-template.lua b/apisix/plugins/ai-prompt-template.lua index 0a092c3f77c0..d2c36693c8a5 100644 --- a/apisix/plugins/ai-prompt-template.lua +++ b/apisix/plugins/ai-prompt-template.lua @@ -72,7 +72,7 @@ local schema = { local _M = { version = 0.1, - priority = 1060, + priority = 1071, name = "ai-prompt-template", schema = schema, } diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 5d22418caeb5..17b385216a88 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -476,7 +476,8 @@ plugins: # plugin list (sorted by priority) #- error-log-logger # priority: 1091 - proxy-cache # priority: 1085 - body-transformer # priority: 1080 - - ai-prompt-template # priority: 1060 + - ai-prompt-template # priority: 1071 + - ai-prompt-decorator # priority: 1070 - proxy-mirror # priority: 1010 - proxy-rewrite # priority: 1008 - workflow # priority: 1006 diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json index 0998ec730cd1..2195688a365c 100644 --- a/docs/en/latest/config.json +++ b/docs/en/latest/config.json @@ -87,6 +87,7 @@ "type": "category", "label": "Transformation", "items": [ + "plugins/ai-prompt-decorator", "plugins/response-rewrite", "plugins/proxy-rewrite", "plugins/grpc-transcode", diff --git a/docs/en/latest/plugins/ai-prompt-decorator.md b/docs/en/latest/plugins/ai-prompt-decorator.md new file mode 100644 index 000000000000..44ee59e74bde --- /dev/null +++ b/docs/en/latest/plugins/ai-prompt-decorator.md @@ -0,0 +1,109 @@ +--- +title: ai-prompt-decorator +keywords: + - Apache APISIX + - API Gateway + - Plugin + - ai-prompt-decorator +description: This document contains information about the Apache APISIX ai-prompt-decorator Plugin. +--- + + + +## Description + +The `ai-prompt-decorator` plugin simplifies access to LLM providers, such as OpenAI and Anthropic, and their models by appending or prepending prompts into the request. + +## Plugin Attributes + +| **Field** | **Required** | **Type** | **Description** | +| ----------------- | --------------- | -------- | --------------------------------------------------- | +| `prepend` | Conditionally\* | Array | An array of prompt objects to be prepended | +| `prepend.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) | +| `prepend.content` | Yes | String | Content of the message. Minimum length: 1 | +| `append` | Conditionally\* | Array | An array of prompt objects to be appended | +| `append.role` | Yes | String | Role of the message (`system`, `user`, `assistant`) | +| `append.content` | Yes | String | Content of the message. Minimum length: 1 | + +\* **Conditionally Required**: At least one of `prepend` or `append` must be provided. + +## Example usage + +Create a route with the `ai-prompt-decorator` plugin like so: + +```shell +curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \ + -H "X-API-KEY: ${ADMIN_API_KEY}" \ + -d '{ + "uri": "/v1/chat/completions", + "plugins": { + "ai-prompt-decorator": { + "prepend":[ + { + "role": "system", + "content": "I have exams tomorrow so explain conceptually and briefly" + } + ], + "append":[ + { + "role": "system", + "content": "End the response with an analogy." + } + ] + } + }, + "upstream": { + "type": "roundrobin", + "nodes": { + "api.openai.com:443": 1 + }, + "pass_host": "node", + "scheme": "https" + } + }' +``` + +Now send a request: + +```shell +curl http://127.0.0.1:9080/v1/chat/completions -i -XPOST -H 'Content-Type: application/json' -d '{ + "model": "gpt-4", + "messages": [{ "role": "user", "content": "What is TLS Handshake?" }] +}' -H "Authorization: Bearer " +``` + +Then the request body will be modified to something like this: + +```json +{ + "model": "gpt-4", + "messages": [ + { + "role": "system", + "content": "I have exams tomorrow so explain conceptually and briefly" + }, + { "role": "user", "content": "What is TLS Handshake?" }, + { + "role": "system", + "content": "End the response with an analogy." + } + ] +} +``` diff --git a/t/admin/plugins.t b/t/admin/plugins.t index 547b1a316d56..ef43ea9f3965 100644 --- a/t/admin/plugins.t +++ b/t/admin/plugins.t @@ -94,6 +94,7 @@ authz-keycloak proxy-cache body-transformer ai-prompt-template +ai-prompt-decorator proxy-mirror proxy-rewrite workflow diff --git a/t/plugin/ai-prompt-decorator.t b/t/plugin/ai-prompt-decorator.t new file mode 100644 index 000000000000..15f40eeeda10 --- /dev/null +++ b/t/plugin/ai-prompt-decorator.t @@ -0,0 +1,293 @@ +# +# 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) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /t"); + } + +}); + +run_tests(); + +__DATA__ + +=== TEST 1: sanity: configure prepend only +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-decorator": { + "prepend":[ + { + "role": "system", + "content": "some content" + } + ] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 2: test prepend +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "messages": [ + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" } + ] + }]], + [[{ + "messages": [ + { "role": "system", "content": "some content" }, + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say("failed") + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 3: sanity: configure append only +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-decorator": { + "append":[ + { + "role": "system", + "content": "some content" + } + ] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 4: test append +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "messages": [ + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" } + ] + }]], + [[{ + "messages": [ + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" }, + { "role": "system", "content": "some content" } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say("failed") + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 5: sanity: configure append and prepend both +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-decorator": { + "append":[ + { + "role": "system", + "content": "some append" + } + ], + "prepend":[ + { + "role": "system", + "content": "some prepend" + } + ] + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body +passed + + + +=== TEST 6: test append +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body, actual_resp = t('/echo', + ngx.HTTP_POST, + [[{ + "messages": [ + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" } + ] + }]], + [[{ + "messages": [ + { "role": "system", "content": "some prepend" }, + { "role": "system", "content": "You are a mathematician" }, + { "role": "user", "content": "What is 1+1?" }, + { "role": "system", "content": "some append" } + ] + }]] + ) + if code >= 300 then + ngx.status = code + ngx.say("failed") + return + end + ngx.say("passed") + } + } +--- response_body +passed + + + +=== TEST 7: sanity: configure neither append nor prepend should fail +--- config + location /t { + content_by_lua_block { + local t = require("lib.test_admin").test + local code, body = t('/apisix/admin/routes/1', + ngx.HTTP_PUT, + [[{ + "uri": "/echo", + "upstream": { + "type": "roundrobin", + "nodes": { + "127.0.0.1:1980": 1 + } + }, + "plugins": { + "ai-prompt-decorator": { + } + } + }]] + ) + + if code >= 300 then + ngx.status = code + end + ngx.say(body) + } +} +--- response_body_eval +qr/.*failed to check the configuration of plugin ai-prompt-decorator err.*/ +--- error_code: 400 From 6b25be489b9bccea14d7de0bb0e828c77fc0e888 Mon Sep 17 00:00:00 2001 From: MrLinMH <66994884+MrLinMH@users.noreply.github.com> Date: Thu, 5 Sep 2024 05:58:37 -0400 Subject: [PATCH 12/16] docs: correct rate in `limit-req` plugin example (#11550) Co-authored-by: Menghai --- docs/en/latest/plugins/limit-req.md | 10 +++++----- docs/zh/latest/plugins/limit-req.md | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/en/latest/plugins/limit-req.md b/docs/en/latest/plugins/limit-req.md index 9c5aafbb38aa..f5aa3e6ecc7a 100644 --- a/docs/en/latest/plugins/limit-req.md +++ b/docs/en/latest/plugins/limit-req.md @@ -77,7 +77,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X P "uri": "/index.html", "plugins": { "limit-req": { - "rate": 1, + "rate": 3, "burst": 2, "rejected_code": 503, "key_type": "var", @@ -101,7 +101,7 @@ You can also configure the `key_type` to `var_combination` as shown: "uri": "/index.html", "plugins": { "limit-req": { - "rate": 1, + "rate": 3, "burst": 2, "rejected_code": 503, "key_type": "var_combination", @@ -130,8 +130,8 @@ curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X "key": "auth-jack" }, "limit-req": { - "rate": 1, - "burst": 3, + "rate": 3, + "burst": 2, "rejected_code": 403, "key": "consumer_name" } @@ -164,7 +164,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X P ## Example usage -Once you have configured the Plugin as shown above, you can test it out. The above configuration limits to 1 request per second. If the number of requests is greater than 1 but less than 3, a delay will be added. And if the number of requests per second exceeds 3, it will be rejected. +Once you have configured the Plugin as shown above, you can test it out. The above configuration limits to 3 request per second. If the number of requests is greater than 3 but less than 5, a delay will be added. And if the number of requests per second exceeds 5, it will be rejected. Now if you send a request: diff --git a/docs/zh/latest/plugins/limit-req.md b/docs/zh/latest/plugins/limit-req.md index 7297491d637a..564e38edc275 100644 --- a/docs/zh/latest/plugins/limit-req.md +++ b/docs/zh/latest/plugins/limit-req.md @@ -81,7 +81,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 \ "uri": "/index.html", "plugins": { "limit-req": { - "rate": 1, + "rate": 3, "burst": 2, "rejected_code": 503, "key_type": "var", @@ -97,7 +97,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 \ }' ``` -上述示例表示,APISIX 将客户端的 IP 地址作为限制请求速率的条件,当请求速率小于 3 次每秒(`rate`)时,请求正常;当请求速率大于 3 次每秒(`rate`),小于 5 次每秒(`rate + burst`)时,将会对超出部分的请求进行延迟处理;当请求速率大于 5 次每秒(`rate + burst`)时,超出规定数量的请求将返回 HTTP 状态码 `503`。 +上述示例表示,APISIX 将客户端的 IP 地址作为限制请求速率的条件,当请求速率小于等于 3 次每秒(`rate`)时,请求正常;当请求速率大于 3 次每秒(`rate`),小于等于 5 次每秒(`rate + burst`)时,将会对超出部分的请求进行延迟处理;当请求速率大于 5 次每秒(`rate + burst`)时,超出规定数量的请求将返回 HTTP 状态码 `503`。 你也可以设置 `key_type` 的值为 `var_combination`: @@ -107,7 +107,7 @@ curl http://127.0.0.1:9180/apisix/admin/routes/1 \ "uri": "/index.html", "plugins": { "limit-req": { - "rate": 1, + "rate": 3, "burst": 2, "rejected_code": 503, "key_type": "var_combination", From a628ba145d000cc9e48ced9c9c41bc8911d560bb Mon Sep 17 00:00:00 2001 From: MrLinMH <66994884+MrLinMH@users.noreply.github.com> Date: Thu, 5 Sep 2024 22:01:50 -0400 Subject: [PATCH 13/16] chore: add `http.lua_shared_dict.prometheus-metrics` annotation in `config.yaml.example` file (#11552) --- conf/config.yaml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/config.yaml.example b/conf/config.yaml.example index 17b385216a88..da125f77daa2 100644 --- a/conf/config.yaml.example +++ b/conf/config.yaml.example @@ -252,7 +252,7 @@ nginx_config: # Config for render the template to generate n internal-status: 10m plugin-limit-req: 10m plugin-limit-count: 10m - prometheus-metrics: 10m + prometheus-metrics: 10m # In production, less than 50m is recommended plugin-limit-conn: 10m upstream-healthcheck: 10m worker-events: 10m From b37ae50f7b92273e539810124d938fc0efdfe471 Mon Sep 17 00:00:00 2001 From: Shreemaan Abhishek Date: Thu, 12 Sep 2024 13:19:32 +0545 Subject: [PATCH 14/16] fix(ci): foo.com is no longer a bad gateway (#11570) --- t/plugin/traffic-split3.t | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/t/plugin/traffic-split3.t b/t/plugin/traffic-split3.t index e1cc7dd3846a..e7096094a050 100644 --- a/t/plugin/traffic-split3.t +++ b/t/plugin/traffic-split3.t @@ -251,7 +251,7 @@ location /t { name = "upstream_A", type = "roundrobin", nodes = { - {host = "foo.com", port = 80, weight = 0} + {host = "test.com", port = 80, weight = 0} } }, weight = 2 @@ -287,7 +287,7 @@ passed GET /server_port --- error_code: 502 --- error_log eval -qr/dns resolver domain: foo.com to \d+.\d+.\d+.\d+/ +qr/dns resolver domain: test.com to \d+.\d+.\d+.\d+/ From bffa9c88fc185f805870b2101f17d5384ad654d2 Mon Sep 17 00:00:00 2001 From: Billy Zhou Date: Thu, 12 Sep 2024 17:07:46 +0800 Subject: [PATCH 15/16] fix: confusion when substituting ENV in config file (#11545) --- apisix/cli/ops.lua | 2 +- t/cli/cli_envsubst_confusion.t | 111 +++++++++++++++++++++++++++++++++ t/cli/test_main.sh | 12 ++-- t/cli/test_standalone.sh | 2 +- 4 files changed, 119 insertions(+), 8 deletions(-) create mode 100644 t/cli/cli_envsubst_confusion.t diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua index 1ee068d6bc9e..16547fce360c 100644 --- a/apisix/cli/ops.lua +++ b/apisix/cli/ops.lua @@ -696,7 +696,7 @@ Please modify "admin_key" in conf/config.yaml . for name, value in pairs(exported_vars) do if value then - table_insert(sys_conf["envs"], name .. "=" .. value) + table_insert(sys_conf["envs"], name) end end end diff --git a/t/cli/cli_envsubst_confusion.t b/t/cli/cli_envsubst_confusion.t new file mode 100644 index 000000000000..16d65e0ef5e8 --- /dev/null +++ b/t/cli/cli_envsubst_confusion.t @@ -0,0 +1,111 @@ +# +# 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); + +$ENV{SOME_STRING_VALUE_BUT_DIFFERENT} = 'astringvaluebutdifferent'; +$ENV{SOME_STRING_VALUE} = 'astringvalue'; + +our $yaml_config = <<_EOC_; +apisix: + node_listen: 1984 +deployment: + role: data_plane + role_data_plane: + config_provider: yaml +_EOC_ + +our $apisix_yaml = <<_EOC_; +upstreams: + - id: 1 + nodes: + - host: 127.0.0.1 + port: 1980 + weight: 1 +routes: + - uri: /hello + upstream_id: 1 + plugins: + response-rewrite: + headers: + set: + X-Some-String-Value-But-Different: Different \${{SOME_STRING_VALUE_BUT_DIFFERENT}} + X-Some-String-Value: \${{SOME_STRING_VALUE}} +#END +_EOC_ + +our $response_headers_correct = <<_EOC_; +X-Some-String-Value-But-Different: Different astringvaluebutdifferent +X-Some-String-Value: astringvalue +_EOC_ + +our $response_headers_INCORRECT = <<_EOC_; +X-Some-String-Value-But-Different: Different astringvalue +X-Some-String-Value: astringvalue +_EOC_ + +add_block_preprocessor(sub { + my ($block) = @_; + + if (!$block->request) { + $block->set_value("request", "GET /hello"); + } +}); + +run_tests(); + +__DATA__ + +=== TEST 1: assignment style, the PREFIX 1st - incorrect +--- main_config +env SOME_STRING_VALUE=astringvalue; +env SOME_STRING_VALUE_BUT_DIFFERENT=astringvaluebutdifferent; +--- yaml_config eval: $::yaml_config +--- apisix_yaml eval: $::apisix_yaml +--- response_headers eval: $::response_headers_INCORRECT + + + +=== TEST 2: assignment style, the DIFF 1st - correct +--- main_config +env SOME_STRING_VALUE_BUT_DIFFERENT=astringvaluebutdifferent; +env SOME_STRING_VALUE=astringvalue; +--- yaml_config eval: $::yaml_config +--- apisix_yaml eval: $::apisix_yaml +--- response_headers eval: $::response_headers_correct + + + +=== TEST 3: declaration style, the PREFIX 1st - correct +--- main_config +env SOME_STRING_VALUE; +env SOME_STRING_VALUE_BUT_DIFFERENT; +--- yaml_config eval: $::yaml_config +--- apisix_yaml eval: $::apisix_yaml +--- response_headers eval: $::response_headers_correct + + + +=== TEST 4: declaration style, the DIFF 1st - also correct +--- main_config +env SOME_STRING_VALUE_BUT_DIFFERENT; +env SOME_STRING_VALUE; +--- yaml_config eval: $::yaml_config +--- apisix_yaml eval: $::apisix_yaml +--- response_headers eval: $::response_headers_correct diff --git a/t/cli/test_main.sh b/t/cli/test_main.sh index 9637000176bb..62c128c94380 100755 --- a/t/cli/test_main.sh +++ b/t/cli/test_main.sh @@ -348,7 +348,7 @@ deployment: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if ! grep "env ETCD_HOST=127.0.0.1;" conf/nginx.conf > /dev/null; then +if ! grep "env ETCD_HOST;" conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -369,7 +369,7 @@ nginx_config: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if grep "env ETCD_HOST=127.0.0.1;" conf/nginx.conf > /dev/null; then +if grep "env ETCD_HOST=.*;" conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -394,7 +394,7 @@ nginx_config: ETCD_HOST=127.0.0.1 ETCD_PORT=2379 make init -if grep "env ETCD_HOST=127.0.0.1;" conf/nginx.conf > /dev/null; then +if grep "env ETCD_HOST;" conf/nginx.conf > /dev/null; then echo "failed: support environment variables in local_conf" exit 1 fi @@ -414,7 +414,7 @@ tests: make init -if ! grep "env TEST_ENV=1.1.1.1;" conf/nginx.conf > /dev/null; then +if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then echo "failed: should use default value when environment not set" exit 1 fi @@ -426,7 +426,7 @@ tests: make init -if ! grep "env TEST_ENV=very-long-domain-with-many-symbols.absolutely-non-exists-123ss.com:1234/path?param1=value1;" conf/nginx.conf > /dev/null; then +if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then echo "failed: should use default value when environment not set" exit 1 fi @@ -438,7 +438,7 @@ tests: TEST_ENV=127.0.0.1 make init -if ! grep "env TEST_ENV=127.0.0.1;" conf/nginx.conf > /dev/null; then +if ! grep "env TEST_ENV;" conf/nginx.conf > /dev/null; then echo "failed: should use environment variable when environment is set" exit 1 fi diff --git a/t/cli/test_standalone.sh b/t/cli/test_standalone.sh index 57b665294ce4..d5844a2ce654 100755 --- a/t/cli/test_standalone.sh +++ b/t/cli/test_standalone.sh @@ -54,7 +54,7 @@ routes: # check for resolve variables var_test_path=/test make init -if ! grep "env var_test_path=/test;" conf/nginx.conf > /dev/null; then +if ! grep "env var_test_path;" conf/nginx.conf > /dev/null; then echo "failed: failed to resolve variables" exit 1 fi From b5ea128fe37dbe1fa8de5b46e3c06468804f2a3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E7=91=9E=E4=B8=9C?= Date: Fri, 13 Sep 2024 09:39:15 +0800 Subject: [PATCH 16/16] chore: code style(multi-auth) (#11508) --- apisix/plugins/multi-auth.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apisix/plugins/multi-auth.lua b/apisix/plugins/multi-auth.lua index 755846fe1f1e..8557344741d4 100644 --- a/apisix/plugins/multi-auth.lua +++ b/apisix/plugins/multi-auth.lua @@ -50,7 +50,7 @@ function _M.check_schema(conf) local auth = require("apisix.plugins." .. auth_plugin_name) if auth == nil then return false, auth_plugin_name .. " plugin did not found" - else + else if auth.type ~= 'auth' then return false, auth_plugin_name .. " plugin is not supported" end