feat(apisix): add Cloudron package

- Implements Apache APISIX packaging for Cloudron platform.
- Includes Dockerfile, CloudronManifest.json, and start.sh.
- Configured to use Cloudron's etcd addon.

🤖 Generated with Gemini CLI
Co-Authored-By: Gemini <noreply@google.com>
This commit is contained in:
2025-09-04 09:42:47 -05:00
parent f7bae09f22
commit 54cc5f7308
1608 changed files with 388342 additions and 0 deletions

View File

@@ -0,0 +1,775 @@
---
title: FAQ
keywords:
- Apache APISIX
- API Gateway
- FAQ
description: This article lists solutions to common problems when using Apache APISIX.
---
<!--
#
# 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.
#
-->
## Why do I need a new API gateway?
As organizations move towards cloud native microservices, there is a need for an API gateway that is performant, flexible, secure and scalable.
APISIX outperforms other API gateways in these metrics while being platform agnostic and fully dynamic delivering features like supporting multiple protocols, fine-grained routing and multi-language support.
## How does Apache APISIX differ from other API gateways?
Apache APISIX differs in the following ways:
- It uses etcd to save and synchronize configurations rather than relational databases like PostgreSQL or MySQL. The real-time event notification system in etcd is easier to scale than in these alternatives. This allows APISIX to synchronize the configuration in real-time, makes the code concise and avoids a single point of failure.
- Fully dynamic.
- Supports [hot loading of Plugins](./terminology/plugin.md#hot-reload).
## What is the performance impact of using Apache APISIX?
Apache APISIX delivers the best performance among other API gateways with a single-core QPS of 18,000 with an average delay of 0.2 ms.
Specific results of the performance benchmarks can be found [here](benchmark.md).
## Which platforms does Apache APISIX support?
Apache APISIX is platform agnostic and avoids vendor lock-in. It is built for cloud native environments and can run on bare-metal machines to Kubernetes. It even support Apple Silicon chips.
## What does it mean by "Apache APISIX is fully dynamic"?
Apache APISIX is fully dynamic in the sense that it doesn't require restarts to change its behavior.
It does the following dynamically:
- Reloading Plugins
- Proxy rewrites
- Proxy mirror
- Response rewrites
- Health checks
- Traffic split
## Does Apache APISIX have a user interface?
Yes. Apache APISIX has an experimental feature called [Apache APISIX Dashboard](https://github.com/apache/apisix-dashboard), which is independent from Apache APISIX. To work with Apache APISIX through a user interface, you can deploy the Apache APISIX Dashboard.
## Can I write my own Plugins for Apache APISIX?
Yes. Apache APISIX is flexible and extensible through the use of custom Plugins that can be specific to user needs.
You can write your own Plugins by referring to [How to write your own Plugins](plugin-develop.md).
## Why does Apache APISIX use etcd for the configuration center?
In addition to the basic functionality of storing the configurations, Apache APISIX also needs a storage system that supports these features:
1. Distributed deployments in clusters.
2. Guarded transactions by comparisons.
3. Multi-version concurrency control.
4. Notifications and watch streams.
5. High performance with minimum read/write latency.
etcd provides these features and more making it ideal over other databases like PostgreSQL and MySQL.
To learn more on how etcd compares with other alternatives see this [comparison chart](https://etcd.io/docs/latest/learning/why/#comparison-chart).
## When installing Apache APISIX dependencies with LuaRocks, why does it cause a timeout or result in a slow or unsuccessful installation?
This is likely because the LuaRocks server used is blocked.
To solve this you can use https_proxy or use the `--server` flag to specify a faster LuaRocks server.
You can run the command below to see the available servers (needs LuaRocks 3.0+):
```shell
luarocks config rocks_servers
```
Mainland China users can use `luarocks.cn` as the LuaRocks server. You can use this wrapper with the Makefile to set this up:
```bash
make deps ENV_LUAROCKS_SERVER=https://luarocks.cn
```
If this does not solve your problem, you can try getting a detailed log by using the `--verbose` or `-v` flag to diagnose the problem.
## How do I build the APISIX-Runtime environment?
Some functions need to introduce additional NGINX modules, which requires APISIX to run on APISIX-Runtime. If you need these functions, you can refer to the code in [api7/apisix-build-tools](https://github.com/api7/apisix-build-tools) to build your own APISIX-Runtime environment.
## How can I make a gray release with Apache APISIX?
Let's take an example query `foo.com/product/index.html?id=204&page=2` and consider that you need to make a gray release based on the `id` in the query string with this condition:
1. Group A: `id <= 1000`
2. Group B: `id > 1000`
There are two different ways to achieve this in Apache APISIX:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
1. Using the `vars` field in a [Route](terminology/route.md):
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"vars": [
["arg_id", "<=", "1000"]
],
"plugins": {
"redirect": {
"uri": "/test?group_id=1"
}
}
}'
curl -i http://127.0.0.1:9180/apisix/admin/routes/2 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"vars": [
["arg_id", ">", "1000"]
],
"plugins": {
"redirect": {
"uri": "/test?group_id=2"
}
}
}'
```
All the available operators of the current `lua-resty-radixtree` are listed [here](https://github.com/api7/lua-resty-radixtree#operator-list).
2. Using the [traffic-split](plugins/traffic-split.md) Plugin.
## How do I redirect HTTP traffic to HTTPS with Apache APISIX?
For example, you need to redirect traffic from `http://foo.com` to `https://foo.com`.
Apache APISIX provides several different ways to achieve this:
1. Setting `http_to_https` to `true` in the [redirect](plugins/redirect.md) Plugin:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/hello",
"host": "foo.com",
"plugins": {
"redirect": {
"http_to_https": true
}
}
}'
```
2. Advanced routing with `vars` in the redirect Plugin:
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/hello",
"host": "foo.com",
"vars": [
[
"scheme",
"==",
"http"
]
],
"plugins": {
"redirect": {
"uri": "https://$host$request_uri",
"ret_code": 301
}
}
}'
```
3. Using the `serverless` Plugin:
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/hello",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": ["return function() if ngx.var.scheme == \"http\" and ngx.var.host == \"foo.com\" then ngx.header[\"Location\"] = \"https://foo.com\" .. ngx.var.request_uri; ngx.exit(ngx.HTTP_MOVED_PERMANENTLY); end; end"]
}
}
}'
```
To test this serverless Plugin:
```shell
curl -i -H 'Host: foo.com' http://127.0.0.1:9080/hello
```
The response should be:
```
HTTP/1.1 301 Moved Permanently
Date: Mon, 18 May 2020 02:56:04 GMT
Content-Type: text/html
Content-Length: 166
Connection: keep-alive
Location: https://foo.com/hello
Server: APISIX web server
<html>
<head><title>301 Moved Permanently</title></head>
<body>
<center><h1>301 Moved Permanently</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
## How do I change Apache APISIX's log level?
By default the log level of Apache APISIX is set to `warn`. You can set this to `info` to trace the messages printed by `core.log.info`.
For this, you can set the `error_log_level` parameter in your configuration file (conf/config.yaml) as shown below and reload Apache APISIX.
```yaml
nginx_config:
error_log_level: "info"
```
## How do I reload my custom Plugins for Apache APISIX?
All Plugins in Apache APISIX are hot reloaded.
You can learn more about hot reloading of Plugins [here](./terminology/plugin.md#hot-reload).
## How do I configure Apache APISIX to listen on multiple ports when handling HTTP or HTTPS requests?
By default, Apache APISIX listens only on port 9080 when handling HTTP requests.
To configure Apache APISIX to listen on multiple ports, you can:
1. Modify the parameter `node_listen` in `conf/config.yaml`:
```
apisix:
node_listen:
- 9080
- 9081
- 9082
```
Similarly for HTTPS requests, modify the parameter `ssl.listen` in `conf/config.yaml`:
```
apisix:
ssl:
enable: true
listen:
- port: 9443
- port: 9444
- port: 9445
```
2. Reload or restart Apache APISIX.
## After uploading the SSL certificate, why can't the corresponding route be accessed through HTTPS + IP?
If you directly use HTTPS + IP address to access the server, the server will use the IP address to compare with the bound SNI. Since the SSL certificate is bound to the domain name, the corresponding resource cannot be found in the SNI, so that the certificate will be verified. The authentication fails, and the user cannot access the gateway via HTTPS + IP.
You can implement this function by setting the `fallback_sni` parameter in the configuration file and configuring the domain name. When the user uses HTTPS + IP to access the gateway, when the SNI is empty, it will fall back to the default SNI to achieve HTTPS + IP access to the gateway.
```yaml title="./conf/config.yaml"
apisix
ssl
fallback_sni: "${your sni}"
```
## How does Apache APISIX achieve millisecond-level configuration synchronization?
Apache APISIX uses etcd for its configuration center. etcd provides subscription functions like [watch](https://github.com/api7/lua-resty-etcd/blob/master/api_v3.md#watch) and [watchdir](https://github.com/api7/lua-resty-etcd/blob/master/api_v3.md#watchdir) that can monitor changes to specific keywords or directories.
In Apache APISIX, we use [etcd.watchdir](https://github.com/api7/lua-resty-etcd/blob/master/api_v3.md#watchdir) to monitor changes in a directory.
If there is no change in the directory being monitored, the process will be blocked until it times out or run into any errors.
If there are changes in the directory being monitored, etcd will return this new data within milliseconds and Apache APISIX will update the cache memory.
## How do I customize the Apache APISIX instance id?
By default, Apache APISIX reads the instance id from `conf/apisix.uid`. If this is not found and no id is configured, Apache APISIX will generate a `uuid` for the instance id.
To specify a meaningful id to bind Apache APISIX to your internal system, set the `id` in your `conf/config.yaml` file:
```yaml
apisix:
id: "your-id"
```
## Why are there errors saying "failed to fetch data from etcd, failed to read etcd dir, etcd key: xxxxxx" in the error.log?
Please follow the troubleshooting steps described below:
1. Make sure that there aren't any networking issues between Apache APISIX and your etcd deployment in your cluster.
2. If your network is healthy, check whether you have enabled the [gRPC gateway](https://etcd.io/docs/v3.4/dev-guide/api_grpc_gateway/) for etcd. The default state depends on whether you used command line options or a configuration file to start the etcd server.
- If you used command line options, gRPC gateway is enabled by default. You can enable it manually as shown below:
```sh
etcd --enable-grpc-gateway --data-dir=/path/to/data
```
**Note**: This flag is not shown while running `etcd --help`.
- If you used a configuration file, gRPC gateway is disabled by default. You can manually enable it as shown below:
In `etcd.json`:
```json
{
"enable-grpc-gateway": true,
"data-dir": "/path/to/data"
}
```
In `etcd.conf.yml`:
```yml
enable-grpc-gateway: true
```
**Note**: This distinction was eliminated by etcd in their latest master branch but wasn't backported to previous versions.
## How do I setup high availability Apache APISIX clusters?
Apache APISIX can be made highly available by adding a load balancer in front of it as APISIX's data plane is stateless and can be scaled when needed.
The control plane of Apache APISIX is highly available as it relies only on an etcd cluster.
## Why does the `make deps` command fail when installing Apache APISIX from source?
When executing `make deps` to install Apache APISIX from source, you can get an error as shown below:
```shell
$ make deps
......
Error: Failed installing dependency: https://luarocks.org/luasec-0.9-1.src.rock - Could not find header file for OPENSSL
No file openssl/ssl.h in /usr/local/include
You may have to install OPENSSL in your system and/or pass OPENSSL_DIR or OPENSSL_INCDIR to the luarocks command.
Example: luarocks install luasec OPENSSL_DIR=/usr/local
make: *** [deps] Error 1
```
This is caused by the missing OpenResty openssl development kit. To install it, refer [installing dependencies](install-dependencies.md).
## How do I access the APISIX Dashboard through Apache APISIX proxy?
You can follow the steps below to configure this:
1. Configure different ports for Apache APISIX proxy and Admin API. Or, disable the Admin API.
```yaml
deployment:
admin:
admin_listen: # use a separate port
ip: 127.0.0.1
port: 9180
```
2. Add a proxy Route for the Apache APISIX dashboard:
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uris":[ "/*" ],
"name":"apisix_proxy_dashboard",
"upstream":{
"nodes":[
{
"host":"127.0.0.1",
"port":9000,
"weight":1
}
],
"type":"roundrobin"
}
}'
```
**Note**: The Apache APISIX Dashboard is listening on `127.0.0.1:9000`.
## How do I use regular expressions (regex) for matching `uri` in a Route?
You can use the `vars` field in a Route for matching regular expressions:
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/*",
"vars": [
["uri", "~~", "^/[a-z]+$"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
And to test this request:
```shell
# uri matched
$ curl http://127.0.0.1:9080/hello -i
HTTP/1.1 200 OK
...
# uri didn't match
$ curl http://127.0.0.1:9080/12ab -i
HTTP/1.1 404 Not Found
...
```
For more info on using `vars` refer to [lua-resty-expr](https://github.com/api7/lua-resty-expr).
## Does the Upstream node support configuring a [FQDN](https://en.wikipedia.org/wiki/Fully_qualified_domain_name) address?
Yes. The example below shows configuring the FQDN `httpbin.default.svc.cluster.local` (a Kubernetes service):
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/ip",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.default.svc.cluster.local": 1
}
}
}'
```
To test this Route:
```shell
$ curl http://127.0.0.1:9080/ip -i
HTTP/1.1 200 OK
...
```
## What is the `X-API-KEY` of the Admin API? Can it be modified?
`X-API-KEY` of the Admin API refers to the `apisix.admin_key.key` in your `conf/config.yaml` file. It is the access token for the Admin API.
By default, it is set to `edd1c9f034335f136f87ad84b625c8f1` and can be modified by changing the parameter in your `conf/config.yaml` file:
```yaml
apisix:
admin_key
-
name: "admin"
key: newkey
role: admin
```
Now, to access the Admin API:
```shell
$ curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: newkey' -X PUT -d '
{
"uris":[ "/*" ],
"name":"admin-token-test",
"upstream":{
"nodes":[
{
"host":"127.0.0.1",
"port":1980,
"weight":1
}
],
"type":"roundrobin"
}
}'
HTTP/1.1 200 OK
......
```
**Note**: By using the default token, you could be exposed to security risks. It is required to update it when deploying to a production environment.
## How do I allow all IPs to access Apache APISIX's Admin API?
By default, Apache APISIX only allows IPs in the range `127.0.0.0/24` to access the Admin API.
To allow IPs in all ranges, you can update your configuration file as show below and restart or reload Apache APISIX.
```yaml
deployment:
admin:
allow_admin:
- 0.0.0.0/0
```
**Note**: This should only be used in non-production environments to allow all clients to access Apache APISIX and is not safe for production environments. Always authorize specific IP addresses or address ranges for production environments.
## How do I auto renew SSL certificates with acme.sh?
You can run the commands below to achieve this:
```bash
curl --output /root/.acme.sh/renew-hook-update-apisix.sh --silent https://gist.githubusercontent.com/anjia0532/9ebf8011322f43e3f5037bc2af3aeaa6/raw/65b359a4eed0ae990f9188c2afa22bacd8471652/renew-hook-update-apisix.sh
```
```bash
chmod +x /root/.acme.sh/renew-hook-update-apisix.sh
```
```bash
acme.sh --issue --staging -d demo.domain --renew-hook "/root/.acme.sh/renew-hook-update-apisix.sh -h http://apisix-admin:port -p /root/.acme.sh/demo.domain/demo.domain.cer -k /root/.acme.sh/demo.domain/demo.domain.key -a xxxxxxxxxxxxx"
```
```bash
acme.sh --renew --domain demo.domain
```
You can check [this post](https://juejin.cn/post/6965778290619449351) for a more detailed instruction on setting this up.
## How do I strip a prefix from a path before forwarding to Upstream in Apache APISIX?
To strip a prefix from a path in your route, like to take `/foo/get` and strip it to `/get`, you can use the [proxy-rewrite](plugins/proxy-rewrite.md) Plugin:
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/foo/*",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/foo/(.*)","/$1"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
And to test this configuration:
```shell
curl http://127.0.0.1:9080/foo/get -i
HTTP/1.1 200 OK
...
{
...
"url": "http://127.0.0.1/get"
}
```
## How do I fix the error `unable to get local issuer certificate` in Apache APISIX?
You can manually set the path to your certificate by adding it to your `conf/config.yaml` file as shown below:
```yaml
apisix:
ssl:
ssl_trusted_certificate: /path/to/certs/ca-certificates.crt
```
**Note**: When you are trying to connect TLS services with cosocket and if APISIX does not trust the peer's TLS certificate, you should set the parameter `apisix.ssl.ssl_trusted_certificate`.
For example, if you are using Nacos for service discovery in APISIX, and Nacos has TLS enabled (configured host starts with `https://`), you should set `apisix.ssl.ssl_trusted_certificate` and use the same CA certificate as Nacos.
## How do I fix the error `module 'resty.worker.events' not found` in Apache APISIX?
This error is caused by installing Apache APISIX in the `/root` directory. The worker process would by run by the user "nobody" and it would not have enough permissions to access the files in the `/root` directory.
To fix this, you can change the APISIX installation directory to the recommended directory: `/usr/local`.
## What is the difference between `plugin-metadata` and `plugin-configs` in Apache APISIX?
The differences between the two are described in the table below:
| `plugin-metadata` | `plugin-config` |
| ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Metadata of a Plugin shared by all configuration instances of the Plugin. | Collection of configuration instances of multiple different Plugins. |
| Used when there are property changes that needs to be propagated across all configuration instances of a Plugin. | Used when you need to reuse a common set of configuration instances so that it can be extracted to a `plugin-config` and bound to different Routes. |
| Takes effect on all the entities bound to the configuration instances of the Plugin. | Takes effect on Routes bound to the `plugin-config`. |
## After deploying Apache APISIX, how to detect the survival of the APISIX data plane?
You can create a route named `health-info` and enable the [fault-injection](https://apisix.apache.org/docs/apisix/plugins/fault-injection/) plugin (where YOUR-TOKEN is the user's token; 127.0.0.1 is the IP address of the control plane, which can be modified by yourself):
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/health-info \
-H 'X-API-KEY: YOUR-TOKEN' -X PUT -d '
{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 200,
"body": "fine"
}
}
},
"uri": "/status"
}'
````
Verification:
Access the `/status` of the Apache APISIX data plane to detect APISIX. If the response code is 200, it means APISIX is alive.
:::note
This method only detects whether the APISIX data plane is alive or not. It does not mean that the routing and other functions of APISIX are normal. These require more routing-level detection.
:::
## What are the scenarios with high APISIX latency related to [etcd](https://etcd.io/) and how to fix them?
etcd is the data storage component of apisix, and its stability is related to the stability of APISIX.
In actual scenarios, if APISIX uses a certificate to connect to etcd through HTTPS, the following two problems of high latency for data query or writing may occur:
1. Query or write data through APISIX Admin API.
2. In the monitoring scenario, Prometheus crawls the APISIX data plane Metrics API timeout.
These problems related to higher latency seriously affect the service stability of APISIX, and the reason why such problems occur is mainly because etcd provides two modes of operation: HTTP (HTTPS) and gRPC. And APISIX uses the HTTP (HTTPS) protocol to operate etcd by default.
In this scenario, etcd has a bug about HTTP/2: if etcd is operated over HTTPS (HTTP is not affected), the upper limit of HTTP/2 connections is the default `250` in Golang. Therefore, when the number of APISIX data plane nodes is large, once the number of connections between all APISIX nodes and etcd exceeds this upper limit, the response of APISIX API interface will be very slow.
In Golang, the default upper limit of HTTP/2 connections is `250`, the code is as follows:
```go
package http2
import ...
const (
prefaceTimeout = 10 * time.Second
firstSettingsTimeout = 2 * time.Second // should be in-flight with preface anyway
handlerChunkWriteSize = 4 << 10
defaultMaxStreams = 250 // TODO: make this 100 as the GFE seems to?
maxQueuedControlFrames = 10000
)
```
etcd officially maintains two main branches, `3.4` and `3.5`. In the `3.4` series, the recently released `3.4.20` version has fixed this issue. As for the `3.5` version, the official is preparing to release the `3.5.5` version a long time ago, but it has not been released as of now (2022.09.13). So, if you are using etcd version less than `3.5.5`, you can refer to the following ways to solve this problem:
1. Change the communication method between APISIX and etcd from HTTPS to HTTP.
2. Roll back the etcd to `3.4.20`.
3. Clone the etcd source code and compile the `release-3.5` branch directly (this branch has fixed the problem of HTTP/2 connections, but the new version has not been released yet).
The way to recompile etcd is as follows:
```shell
git checkout release-3.5
make GOOS=linux GOARCH=amd64
```
The compiled binary is in the bin directory, replace it with the etcd binary of your server environment, and then restart etcd:
For more information, please refer to:
- [when etcd node have many http long polling connections, it may cause etcd to respond slowly to http requests.](https://github.com/etcd-io/etcd/issues/14185)
- [bug: when apisix starts for a while, its communication with etcd starts to time out](https://github.com/apache/apisix/issues/7078)
- [the prometheus metrics API is tool slow](https://github.com/apache/apisix/issues/7353)
- [Support configuring `MaxConcurrentStreams` for http2](https://github.com/etcd-io/etcd/pull/14169)
Another solution is to switch to an experimental gRPC-based configuration synchronization. This requires setting `use_grpc: true` in the configuration file `conf/config.yaml`:
```yaml
etcd:
use_grpc: true
host:
- "http://127.0.0.1:2379"
prefix: "/apisix"
```
## Why is the file-logger logging garbled?
If you are using the `file-logger` plugin but getting garbled logs, one possible reason is your upstream response has returned a compressed response body. You can fix this by setting the accept-encoding in the request header to not receive compressed responses using the [proxy-rewirte](https://apisix.apache.org/docs/apisix/plugins/proxy-rewrite/) plugin:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H 'X-API-KEY: YOUR-TOKEN' -X PUT -d '
{
"methods":[
"GET"
],
"uri":"/test/index.html",
"plugins":{
"proxy-rewrite":{
"headers":{
"set":{
"accept-encoding":"gzip;q=0,deflate,sdch"
}
}
}
},
"upstream":{
"type":"roundrobin",
"nodes":{
"127.0.0.1:80":1
}
}
}'
```
## How does APISIX configure ETCD with authentication?
Suppose you have an ETCD cluster that enables the auth. To access this cluster, you need to configure the correct username and password for Apache APISIX in `conf/config.yaml`:
```yaml
deployment:
etcd:
host:
- "http://127.0.0.1:2379"
user: etcd_user # username for etcd
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 sample configuration `conf/config.yaml.example` file.
## 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.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?
You can find more answers on:
- [Apache APISIX Slack Channel](/docs/general/join/#join-the-slack-channel)
- [Ask questions on APISIX mailing list](/docs/general/join/#subscribe-to-the-mailing-list)
- [GitHub Issues](https://github.com/apache/apisix/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) and [GitHub Discussions](https://github.com/apache/apisix/discussions)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
---
title: APISIX variable
keywords:
- Apache APISIX
- API Gateway
- APISIX variable
description: This article describes the variables supported by Apache APISIX.
---
<!--
#
# 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.
#
-->
## Description
Besides [NGINX variable](http://nginx.org/en/docs/varindex.html), APISIX also provides
additional variables.
## List of variables
| Variable Name | Origin | Description | Example |
|-------------------- | ---------- | ----------------------------------------------------------------------------------- | ------------- |
| balancer_ip | core | The IP of picked upstream server. | 192.168.1.2 |
| balancer_port | core | The port of picked upstream server. | 80 |
| consumer_name | core | Username of Consumer. | |
| consumer_group_id | core | Group ID of Consumer. | |
| graphql_name | core | The [operation name](https://graphql.org/learn/queries/#operation-name) of GraphQL. | HeroComparison |
| graphql_operation | core | The operation type of GraphQL. | mutation |
| graphql_root_fields | core | The top level fields of GraphQL. | ["hero"] |
| mqtt_client_id | mqtt-proxy | The client id in MQTT protocol. | |
| route_id | core | Id of Route. | |
| route_name | core | Name of Route. | |
| service_id | core | Id of Service. | |
| service_name | core | Name of Service. | |
| redis_cmd_line | Redis | The content of Redis command. | |
| resp_body | core | In the logger plugin, if some of the plugins support logging of response body, for example by configuring `include_resp_body: true`, then this variable can be used in the log format. | |
| rpc_time | xRPC | Time spent at the rpc request level. | |
You can also register your own [variable](./plugin-develop.md#register-custom-variable).

View File

@@ -0,0 +1,51 @@
---
title: Architecture
keywords:
- API Gateway
- Apache APISIX
- APISIX architecture
description: Architecture of Apache APISIX—the Cloud Native API Gateway.
---
<!--
#
# 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.
#
-->
APISIX is built on top of Nginx and [ngx_lua](https://github.com/openresty/lua-nginx-module) leveraging the power offered by LuaJIT. See [Why Apache APISIX chose Nginx and Lua to build API Gateway?](https://apisix.apache.org/blog/2021/08/25/why-apache-apisix-chose-nginx-and-lua/).
![flow-software-architecture](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/flow-software-architecture.png)
APISIX has two main parts:
1. APISIX core, Lua plugin, multi-language Plugin runtime, and the WASM plugin runtime.
2. Built-in Plugins that adds features for observability, security, traffic control, etc.
The APISIX core handles the important functions like matching Routes, load balancing, service discovery, configuration management, and provides a management API. It also includes APISIX Plugin runtime supporting Lua and multilingual Plugins (Go, Java , Python, JavaScript, etc) including the experimental WASM Plugin runtime.
APISIX also has a set of [built-in Plugins](https://apisix.apache.org/docs/apisix/plugins/batch-requests) that adds features like authentication, security, observability, etc. They are written in Lua.
## Request handling process
The diagram below shows how APISIX handles an incoming request and applies corresponding Plugins:
![flow-load-plugin](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/flow-load-plugin.png)
## Plugin hierarchy
The chart below shows the order in which different types of Plugin are applied to a request:
![flow-plugin-internal](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/flow-plugin-internal.png)

View File

@@ -0,0 +1,276 @@
---
title: Running APISIX in AWS with AWS CDK
---
<!--
#
# 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.
#
-->
[APISIX](https://github.com/apache/apisix) is a cloud-native microservices API gateway, delivering the ultimate performance, security, open source and scalable platform for all your APIs and microservices.
## Architecture
This reference architecture walks you through building **APISIX** as a serverless container API Gateway on top of AWS Fargate with AWS CDK.
![Apache APISIX Serverless Architecture](../../assets/images/aws-fargate-cdk.png)
## Generate an AWS CDK project with `projen`
```bash
$ mkdir apisix-aws
$ cd $_
$ npx projen new awscdk-app-ts
```
update the `.projenrc.js` with the following content:
```js
const { AwsCdkTypeScriptApp } = require('projen');
const project = new AwsCdkTypeScriptApp({
cdkVersion: "1.70.0",
name: "apisix-aws",
cdkDependencies: [
'@aws-cdk/aws-ec2',
'@aws-cdk/aws-ecs',
'@aws-cdk/aws-ecs-patterns',
]
});
project.synth();
```
update the project:
```ts
$ npx projen
```
## update `src/main.ts`
```ts
import * as cdk from '@aws-cdk/core';
import { Vpc, Port } from '@aws-cdk/aws-ec2';
import { Cluster, ContainerImage, TaskDefinition, Compatibility } from '@aws-cdk/aws-ecs';
import { ApplicationLoadBalancedFargateService, NetworkLoadBalancedFargateService } from '@aws-cdk/aws-ecs-patterns';
export class ApiSixStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const vpc = Vpc.fromLookup(this, 'VPC', {
isDefault: true
})
const cluster = new Cluster(this, 'Cluster', {
vpc
})
/**
* ApiSix service
*/
const taskDefinition = new TaskDefinition(this, 'TaskApiSix', {
compatibility: Compatibility.FARGATE,
memoryMiB: '512',
cpu: '256'
})
taskDefinition
.addContainer('apisix', {
image: ContainerImage.fromRegistry('iresty/apisix'),
})
.addPortMappings({
containerPort: 9080
})
taskDefinition
.addContainer('etcd', {
image: ContainerImage.fromRegistry('gcr.azk8s.cn/etcd-development/etcd:v3.3.12'),
// image: ContainerImage.fromRegistry('gcr.io/etcd-development/etcd:v3.3.12'),
})
.addPortMappings({
containerPort: 2379
})
const svc = new ApplicationLoadBalancedFargateService(this, 'ApiSixService', {
cluster,
taskDefinition,
})
svc.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '30')
svc.targetGroup.configureHealthCheck({
interval: cdk.Duration.seconds(5),
healthyHttpCodes: '404',
healthyThresholdCount: 2,
unhealthyThresholdCount: 3,
timeout: cdk.Duration.seconds(4)
})
/**
* PHP service
*/
const taskDefinitionPHP = new TaskDefinition(this, 'TaskPHP', {
compatibility: Compatibility.FARGATE,
memoryMiB: '512',
cpu: '256'
})
taskDefinitionPHP
.addContainer('php', {
image: ContainerImage.fromRegistry('abiosoft/caddy:php'),
})
.addPortMappings({
containerPort: 2015
})
const svcPHP = new NetworkLoadBalancedFargateService(this, 'PhpService', {
cluster,
taskDefinition: taskDefinitionPHP,
assignPublicIp: true,
})
// allow Fargate task behind NLB to accept all traffic
svcPHP.service.connections.allowFromAnyIpv4(Port.tcp(2015))
svcPHP.targetGroup.setAttribute('deregistration_delay.timeout_seconds', '30')
svcPHP.loadBalancer.setAttribute('load_balancing.cross_zone.enabled', 'true')
new cdk.CfnOutput(this, 'ApiSixDashboardURL', {
value: `http://${svc.loadBalancer.loadBalancerDnsName}/apisix/dashboard/`
})
}
}
const devEnv = {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
};
const app = new cdk.App();
new ApiSixStack(app, 'apisix-stack-dev', { env: devEnv });
app.synth();
```
## Deploy the APISIX Stack with AWS CDK
```bash
$ cdk diff
$ cdk deploy
```
On deployment complete, some outputs will be returned:
```bash
Outputs:
apiSix.PhpServiceLoadBalancerDNS5E5BAB1B = apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
apiSix.ApiSixDashboardURL = http://apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com/apisix/dashboard/
apiSix.ApiSixServiceLoadBalancerDNSD4E5B8CB = apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com
apiSix.ApiSixServiceServiceURLF6EC7872 = http://apiSi-ApiSi-1TM103DN35GRY-1477666967.us-west-2.elb.amazonaws.com
```
Open the `apiSix.ApiSixDashboardURL` from your browser and you will see the login prompt.
### Configure the upstream nodes
All upstream nodes are running as **AWS Fargate** tasks and registered to the **NLB(Network Load Balancer)** exposing multiple static IP addresses. We can query the IP addresses by **nslookup** the **apiSix.PhpServiceLoadBalancerDNS5E5BAB1B** like this:
```bash
$ nslookup apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
Server: 192.168.31.1
Address: 192.168.31.1#53
Non-authoritative answer:
Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
Address: 44.224.124.213
Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
Address: 18.236.43.167
Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
Address: 35.164.164.178
Name: apiSi-PhpSe-FOL2MM4TW7G8-09029e095ab36fcc.elb.us-west-2.amazonaws.com
Address: 44.226.102.63
```
Configure the IP addresses returned as your upstream nodes in your **APISIX** dashboard followed by the **Services** and **Routes** configuration. Let's say we have a `/index.php` as the URI for the first route for our first **Service** from the **Upstream** IP addresses.
![upstream with AWS NLB IP addresses](../../assets/images/aws-nlb-ip-addr.png)
![service with created upstream](../../assets/images/aws-define-service.png)
![define route with service and uri](../../assets/images/aws-define-route.png)
## Validation
OK. Let's test the `/index.php` on `{apiSix.ApiSixServiceServiceURL}/index.php`
![Testing Apache APISIX on AWS Fargate](../../assets/images/aws-caddy-php-welcome-page.png)
Now we have been successfully running **APISIX** in AWS Fargate as serverless container API Gateway service.
## Clean up
```bash
$ cdk destroy
```
## Running APISIX in AWS China Regions
update `src/main.ts`
```js
taskDefinition
.addContainer('etcd', {
image: ContainerImage.fromRegistry('gcr.azk8s.cn/etcd-development/etcd:v3.3.12'),
// image: ContainerImage.fromRegistry('gcr.io/etcd-development/etcd:v3.3.12'),
})
.addPortMappings({
containerPort: 2379
})
```
_(read [here](https://github.com/iresty/docker-apisix/blob/9a731f698171f4838e9bc0f1c05d6dda130ca89b/example/docker-compose.yml#L18-L19) for more reference)_
Run `cdk deploy` and specify your preferred AWS region in China.
```bash
# let's say we have another AWS_PROFILE for China regions called 'cn'
# make sure you have aws configure --profile=cn properly.
#
# deploy to NingXia region
$ cdk deploy --profile cn -c region=cn-northwest-1
# deploy to Beijing region
$ cdk deploy --profile cn -c region=cn-north-1
```
In the following case, we got the `Outputs` returned for **AWS Ningxia region(cn-northwest-1)**:
```bash
Outputs:
apiSix.PhpServiceLoadBalancerDNS5E5BAB1B = apiSi-PhpSe-1760FFS3K7TXH-562fa1f7f642ec24.elb.cn-northwest-1.amazonaws.com.cn
apiSix.ApiSixDashboardURL = http://apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn/apisix/dashboard/
apiSix.ApiSixServiceLoadBalancerDNSD4E5B8CB = apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn
apiSix.ApiSixServiceServiceURLF6EC7872 = http://apiSi-ApiSi-123HOROQKWZKA-1268325233.cn-northwest-1.elb.amazonaws.com.cn
```
Open the `apiSix.ApiSixDashboardURL` URL and log in to configure your **APISIX** in AWS China region.
_TBD_
## Decouple APISIX and etcd3 on AWS
For high availability and state consistency consideration, you might be interested to decouple the **etcd3** as a separate cluster from **APISIX** not only for performance but also high availability and fault tolerance yet with highly reliable state consistency.
_TBD_

View File

@@ -0,0 +1,149 @@
---
title: Batch Processor
---
<!--
#
# 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.
#
-->
The batch processor can be used to aggregate entries(logs/any data) and process them in a batch.
When the batch_max_size is set to 1 the processor will execute each entry immediately. Setting the batch max size more
than 1 will start aggregating the entries until it reaches the max size or the timeout expires.
## Configurations
The only mandatory parameter to create a batch processor is a function. The function will be executed when the batch reaches the max size
or when the buffer duration exceeds.
| Name | Type | Requirement | Default | Valid | Description |
| ---------------- | ------- | ----------- | ------- | ------- | ------------------------------------------------------------ |
| name | string | optional | logger's name | ["http logger",...] | A unique identifier used to identify the batch processor, which defaults to the name of the logger plug-in that calls the batch processor, such as plug-in "http logger" 's `name` is "http logger. |
| batch_max_size | integer | optional | 1000 | [1,...] | Sets the maximum number of logs sent in each batch. When the number of logs reaches the set maximum, all logs will be automatically pushed to the HTTP/HTTPS service. |
| inactive_timeout | integer | optional | 5 | [1,...] | The maximum time to refresh the buffer (in seconds). When the maximum refresh time is reached, all logs will be automatically pushed to the HTTP/HTTPS service regardless of whether the number of logs in the buffer reaches the maximum number set. |
| buffer_duration | integer | optional | 60 | [1,...] | Maximum age in seconds of the oldest entry in a batch before the batch must be processed. |
| max_retry_count | integer | optional | 0 | [0,...] | Maximum number of retries before removing the entry from the processing pipeline when an error occurs. |
| retry_delay | integer | optional | 1 | [0,...] | Number of seconds the process execution should be delayed if the execution fails. |
The following code shows an example of how to use batch processor in your plugin:
```lua
local bp_manager_mod = require("apisix.utils.batch-processor-manager")
...
local plugin_name = "xxx-logger"
local batch_processor_manager = bp_manager_mod.new(plugin_name)
local schema = {...}
local _M = {
...
name = plugin_name,
schema = batch_processor_manager:wrap_schema(schema),
}
...
function _M.log(conf, ctx)
local entry = {...} -- data to log
if batch_processor_manager:add_entry(conf, entry) then
return
end
-- create a new processor if not found
-- entries is an array table of entry, which can be processed in batch
local func = function(entries)
-- serialize to json array core.json.encode(entries)
-- process/send data
return true
-- return false, err_msg, first_fail if failed
-- first_fail(optional) indicates first_fail-1 entries have been successfully processed
-- and during processing of entries[first_fail], the error occurred. So the batch processor
-- only retries for the entries having index >= first_fail as per the retry policy.
end
batch_processor_manager:add_entry_to_new_processor(conf, entry, ctx, func)
end
```
The batch processor's configuration will be set inside the plugin's configuration.
For example:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"http-logger": {
"uri": "http://mockbin.org/bin/:ID",
"batch_max_size": 10,
"max_retry_count": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
If your plugin only uses one global batch processor,
you can also use the processor directly:
```lua
local entry = {...} -- data to log
if log_buffer then
log_buffer:push(entry)
return
end
local config_bat = {
name = config.name,
retry_delay = config.retry_delay,
...
}
local err
-- entries is an array table of entry, which can be processed in batch
local func = function(entries)
...
return true
-- return false, err_msg, first_fail if failed
end
log_buffer, err = batch_processor:new(func, config_bat)
if not log_buffer then
core.log.warn("error when creating the batch processor: ", err)
return
end
log_buffer:push(entry)
```
Note: Please make sure the batch max size (entry count) is within the limits of the function execution.
The timer to flush the batch runs based on the `inactive_timeout` configuration. Thus, for optimal usage,
keep the `inactive_timeout` smaller than the `buffer_duration`.

View File

@@ -0,0 +1,151 @@
---
title: Benchmark
---
<!--
#
# 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.
#
-->
### Benchmark Environments
n1-highcpu-8 (8 vCPUs, 7.2 GB memory) on Google Cloud
But we **only** used 4 cores to run APISIX, and left 4 cores for system and [wrk](https://github.com/wg/wrk),
which is the HTTP benchmarking tool.
### Benchmark Test for reverse proxy
Only used APISIX as the reverse proxy server, with no logging, limit rate, or other plugins enabled,
and the response size was 1KB.
#### QPS
The x-axis means the size of CPU core, and the y-axis is QPS.
![benchmark-1](../../assets/images/benchmark-1.jpg)
#### Latency
Note the y-axis latency in **microsecond(μs)** not millisecond.
![latency-1](../../assets/images/latency-1.jpg)
#### Flame Graph
The result of Flame Graph:
![flamegraph-1](../../assets/images/flamegraph-1.jpg)
And if you want to run the benchmark test in your machine, you should run another Nginx to listen 80 port.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:80": 1,
"127.0.0.2:80": 1
}
}
}'
```
then run wrk:
```shell
wrk -d 60 --latency http://127.0.0.1:9080/hello
```
### Benchmark Test for reverse proxy, enabled 2 plugins
Only used APISIX as the reverse proxy server, enabled the limit rate and prometheus plugins,
and the response size was 1KB.
#### QPS
The x-axis means the size of CPU core, and the y-axis is QPS.
![benchmark-2](../../assets/images/benchmark-2.jpg)
#### Latency
Note the y-axis latency in **microsecond(μs)** not millisecond.
![latency-2](../../assets/images/latency-2.jpg)
#### Flame Graph
The result of Flame Graph:
![flamegraph-2](../../assets/images/flamegraph-2.jpg)
And if you want to run the benchmark test in your machine, you should run another Nginx to listen 80 port.
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {
"limit-count": {
"count": 999999999,
"time_window": 60,
"rejected_code": 503,
"key": "remote_addr"
},
"prometheus":{}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:80": 1,
"127.0.0.2:80": 1
}
}
}'
```
then run wrk:
```shell
wrk -d 60 --latency http://127.0.0.1:9080/hello
```
For more reference on how to run the benchmark test, you can see this [PR](https://github.com/apache/apisix/pull/6136) and this [script](https://gist.github.com/membphis/137db97a4bf64d3653aa42f3e016bd01).
:::tip
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
```
For more details, you can refer to [Module ngx_http_upstream_module](http://nginx.org/en/docs/http/ngx_http_upstream_module.html).
:::

View File

@@ -0,0 +1,119 @@
---
id: build-apisix-dev-environment-devcontainers
title: Build development environment with Dev Containers
description: This paper introduces how to quickly start the APISIX API Gateway development environment using Dev Containers.
---
<!--
#
# 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.
#
-->
Previously, building and developing APISIX on Linux or macOS required developers to install its runtime environment and toolchain themselves, and developers might not be familiar with them.
As it needs to support multiple operating systems and CPU ISAs, the process has inherent complexities in how to find and install dependencies and toolchains.
:::note
The tutorial can be used as an alternative to a [bare-metal environment](building-apisix.md) or a [macOS container development environment](build-apisix-dev-environment-on-mac.md).
It only requires that you have an environment running Docker or a similar alternative (the docker/docker compose command is required), and no other dependent components need to be installed on your host machine.
:::
## Supported systems and CPU ISA
- Linux
- AMD64
- ARM64
- Windows (with WSL2 supported)
- AMD64
- macOS
- ARM64
- AMD64
## Quick Setup of Apache APISIX Development Environment
### Implementation Idea
We use Dev Containers to build development environment, and when we open an APISIX project using the IDE, we have access to the container-driven runtime environment.
There the etcd is ready and we can start APISIX directly.
### Steps
:::note
The following uses Visual Studio Code, which has built-in integration with Dev Containers.
In theory you could also use any other editor or IDE that integrates with Dev Containers.
:::
First, clone the APISIX source code, open project in Visual Studio Code.
```shell
git clone https://github.com/apache/apisix.git
cd apisix
code . # VSCode needs to be in the PATH environment variable, you can also open the project directory manually in the UI.
```
Next, switch to Dev Containers. Open the VSCode Command Palette, and execute `Dev Containers: Reopen in Container`.
![VSCode Command open in container](../../assets/images/build-devcontainers-vscode-command.png)
VSCode will open the Dev Containers project in a new window, where it will build the runtime and install the toolchain according to the Dockerfile before starting the connection and finally installing the APISIX dependencies.
:::note
This process requires a reliable network connection, and it will access Docker Hub, GitHub, and some other sites. You will need to ensure the network connection yourself, otherwise the container build may fail.
:::
Wait some minutes, depending on the internet connection or computer performance, it may take from a few minutes to tens of minutes, you can click on the Progress Bar in the bottom right corner to view a live log where you will be able to check unusual stuck.
If you encounter any problems, you can search or ask questions in [GitHub Issues](https://github.com/apache/apisix/issues) or [GitHub Discussions](https://github.com/apache/apisix/discussions), and community members will respond as promptly as possible.
![VSCode dev containers building progress bar](../../assets/images/build-devcontainers-vscode-progressbar.png)
When the process in the terminal is complete, the development environment is ready, and even etcd is ready.
Start APISIX with the following command:
```shell
make run
```
Now you can start writing code and test cases, and testing tools are available:
```shell
export TEST_NGINX_BINARY=openresty
# run all tests
make test
# or run a specify test case file
FLUSH_ETCD=1 prove -Itest-nginx/lib -I. -r t/admin/api.t
```
## FAQ
### Where's the code? When I delete the container, are the changes lost?
It will be on your host, which is where you cloned the APISIX source code, and the container uses the volume to mount the code into the container. Containers contain only the runtime environment, not the source code, so no changes will be lost whether you close or delete the container.
And, the `git` is already installed in the container, so you can commit a change directly there.

View File

@@ -0,0 +1,94 @@
---
id: build-apisix-dev-environment-on-mac
title: Build development environment on Mac
description: This paper introduces how to use Docker to quickly build the development environment of API gateway Apache APISIX on Mac.
---
<!--
#
# 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.
#
-->
If you want to quickly build and develop APISIX on your Mac platform, you can refer to this tutorial.
:::note
This tutorial is suitable for situations where you need to quickly start development on the Mac platform, if you want to go further and have a better development experience, the better choice is the Linux-based virtual machine, or directly use this kind of system as your development environment.
You can see the specific supported systems [here](install-dependencies.md#install).
:::
## Quick Setup of Apache APISIX Development Environment
### Implementation Idea
We use Docker to build the test environment of Apache APISIX. When the container starts, we can mount the source code of Apache APISIX into the container, and then we can build and run test cases in the container.
### Implementation Steps
First, clone the APISIX source code, build an image that can run test cases, and compile the Apache APISIX.
```shell
git clone https://github.com/apache/apisix.git
cd apisix
docker build -t apisix-dev-env -f example/build-dev-image.dockerfile .
```
Next, start Etcd:
```shell
docker run -d --name etcd-apisix --net=host pachyderm/etcd:v3.5.2
```
Mount the APISIX directory and start the development environment container:
```shell
docker run -d --name apisix-dev-env --net=host -v $(pwd):/apisix:rw apisix-dev-env:latest
```
Finally, enter the container, build the Apache APISIX runtime, and configure the test environment:
```shell
docker exec -it apisix-dev-env make deps
docker exec -it apisix-dev-env ln -s /usr/bin/openresty /usr/bin/nginx
```
### Run and Stop APISIX
```shell
docker exec -it apisix-dev-env make run
docker exec -it apisix-dev-env make stop
```
:::note
If you encounter an error message like `nginx: [emerg] bind() to unix:/apisix/logs/worker_events.sock failed (95: Operation not supported)` while running `make run`, please use this solution.
Change the `File Sharing` settings of your Docker-Desktop:
![Docker-Desktop File Sharing Setting](../../assets/images/update-docker-desktop-file-sharing.png)
Changing to either `gRPC FUSE` or `osxfs` can resolve this issue.
:::
### Run Specific Test Cases
```shell
docker exec -it apisix-dev-env prove t/admin/routes.t
```

View File

@@ -0,0 +1,267 @@
---
id: building-apisix
title: Building APISIX from source
keywords:
- API Gateway
- Apache APISIX
- Code Contribution
- Building APISIX
description: Guide for building and running APISIX locally for development.
---
<!--
#
# 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.
#
-->
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
If you are looking to setup a development environment or contribute to APISIX, this guide is for you.
If you are looking to quickly get started with APISIX, check out the other [installation methods](./installation-guide.md).
:::note
To build an APISIX docker image from source code, see [build image from source code](https://apisix.apache.org/docs/docker/build/#build-an-image-from-customizedpatched-source-code).
To build and package APISIX for a specific platform, see [apisix-build-tools](https://github.com/api7/apisix-build-tools) instead.
:::
## Building APISIX from source
First of all, we need to specify the branch to be built:
```shell
APISIX_BRANCH='release/3.13'
```
Then, you can run the following command to clone the APISIX source code from Github:
```shell
git clone --depth 1 --branch ${APISIX_BRANCH} https://github.com/apache/apisix.git apisix-${APISIX_BRANCH}
```
Alternatively, you can also download the source package from the [Downloads](https://apisix.apache.org/downloads/) page. Note that source packages here are not distributed with test cases.
Before installation, install [OpenResty](https://openresty.org/en/installation.html).
Next, navigate to the directory, install dependencies, and build APISIX.
```shell
cd apisix-${APISIX_BRANCH}
make deps
make install
```
This will install the runtime-dependent Lua libraries and `apisix-runtime` the `apisix` CLI tool.
:::note
If you get an error message like `Could not find header file for LDAP/PCRE/openssl` while running `make deps`, use this solution.
`luarocks` supports custom compile-time dependencies (See: [Config file format](https://github.com/luarocks/luarocks/wiki/Config-file-format)). You can use a third-party tool to install the missing packages and add its installation directory to the `luarocks`' variables table. This method works on macOS, Ubuntu, CentOS, and other similar operating systems.
The solution below is for macOS but it works similarly for other operating systems:
1. Install `openldap` by running:
```shell
brew install openldap
```
2. Locate the installation directory by running:
```shell
brew --prefix openldap
```
3. Add this path to the project configuration file by any of the two methods shown below:
1. You can use the `luarocks config` command to set `LDAP_DIR`:
```shell
luarocks config variables.LDAP_DIR /opt/homebrew/cellar/openldap/2.6.1
```
2. You can also change the default configuration file of `luarocks`. Open the file `~/.luaorcks/config-5.1.lua` and add the following:
```shell
variables = { LDAP_DIR = "/opt/homebrew/cellar/openldap/2.6.1", LDAP_INCDIR = "/opt/homebrew/cellar/openldap/2.6.1/include", }
```
`/opt/homebrew/cellar/openldap/` is default path `openldap` is installed on Apple Silicon macOS machines. For Intel machines, the default path is `/usr/local/opt/openldap/`.
:::
To uninstall the APISIX runtime, run:
```shell
make uninstall
make undeps
```
:::danger
This operation will remove the files completely.
:::
## Installing etcd
APISIX uses [etcd](https://github.com/etcd-io/etcd) to save and synchronize configuration. Before running APISIX, you need to install etcd on your machine. Installation methods based on your operating system are mentioned below.
<Tabs
groupId="os"
defaultValue="linux"
values={[
{label: 'Linux', value: 'linux'},
{label: 'macOS', value: 'mac'},
]}>
<TabItem value="linux">
```shell
ETCD_VERSION='3.4.18'
wget https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
tar -xvf etcd-v${ETCD_VERSION}-linux-amd64.tar.gz && \
cd etcd-v${ETCD_VERSION}-linux-amd64 && \
sudo cp -a etcd etcdctl /usr/bin/
nohup etcd >/tmp/etcd.log 2>&1 &
```
</TabItem>
<TabItem value="mac">
```shell
brew install etcd
brew services start etcd
```
</TabItem>
</Tabs>
## Running and managing APISIX server
To initialize the configuration file, within the APISIX directory, run:
```shell
apisix init
```
:::tip
You can run `apisix help` to see a list of available commands.
:::
You can then test the created configuration file by running:
```shell
apisix test
```
Finally, you can run the command below to start APISIX:
```shell
apisix start
```
To stop APISIX, you can use either the `quit` or the `stop` subcommand.
`apisix quit` will gracefully shutdown APISIX. It will ensure that all received requests are completed before stopping.
```shell
apisix quit
```
Where as, the `apisix stop` command does a force shutdown and discards all pending requests.
```shell
apisix stop
```
## Building runtime for APISIX
Some features of APISIX requires additional Nginx modules to be introduced into OpenResty.
To use these features, you need to build a custom distribution of OpenResty (apisix-runtime). See [apisix-build-tools](https://github.com/api7/apisix-build-tools) for setting up your build environment and building it.
## Running tests
The steps below show how to run the test cases for APISIX:
1. Install [cpanminus](https://metacpan.org/pod/App::cpanminus#INSTALLATION), the package manager for Perl.
2. Install the [test-nginx](https://github.com/openresty/test-nginx) dependencies with `cpanm`:
```shell
sudo cpanm --notest Test::Nginx IPC::Run > build.log 2>&1 || (cat build.log && exit 1)
```
3. Clone the test-nginx source code locally:
```shell
git clone https://github.com/openresty/test-nginx.git
```
4. Append the current directory to Perl's module directory by running:
```shell
export PERL5LIB=.:$PERL5LIB
```
You can specify the Nginx binary path by running:
```shell
TEST_NGINX_BINARY=/usr/local/bin/openresty prove -Itest-nginx/lib -r t
```
5. Run the tests by running:
```shell
make test
```
:::note
Some tests rely on external services and system configuration modification. See [ci/linux_openresty_common_runner.sh](https://github.com/apache/apisix/blob/master/ci/linux_openresty_common_runner.sh) for a complete test environment build.
:::
### Troubleshooting
These are some common troubleshooting steps for running APISIX test cases.
#### Configuring Nginx path
For the error `Error unknown directive "lua_package_path" in /API_ASPIX/apisix/t/servroot/conf/nginx.conf`, ensure that OpenResty is set to the default Nginx and export the path as follows:
- Linux default installation path:
```shell
export PATH=/usr/local/openresty/nginx/sbin:$PATH
```
#### Running a specific test case
To run a specific test case, use the command below:
```shell
prove -Itest-nginx/lib -r t/plugin/openid-connect.t
```
See [testing framework](./internal/testing-framework.md) for more details.

View File

@@ -0,0 +1,328 @@
---
title: Certificate
---
<!--
#
# 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.
#
-->
`APISIX` supports to load multiple SSL certificates by TLS extension Server Name Indication (SNI).
### Single SNI
It is most common for an SSL certificate to contain only one domain. We can create an `ssl` object. Here is a simple case, creates a `ssl` object and `route` object.
* `cert`: PEM-encoded public certificate of the SSL key pair.
* `key`: PEM-encoded private key of the SSL key pair.
* `snis`: Hostname(s) to associate with this certificate as SNIs. To set this attribute this certificate must have a valid private key associated with it.
The following is an example of configuring an SSL certificate with a single SNI in APISIX.
Create an SSL object with the certificate and key valid for the SNI:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/ssls/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"cert" : "'"$(cat t/certs/apisix.crt)"'",
"key": "'"$(cat t/certs/apisix.key)"'",
"snis": ["test.com"]
}'
```
Create a Router object:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/get",
"hosts": ["test.com"],
"methods": ["GET"],
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
```
Send a request to verify:
```shell
curl --resolve 'test.com:9443:127.0.0.1' https://test.com:9443/get -k -vvv
* Added test.com:9443:127.0.0.1 to DNS cache
* About to connect() to test.com port 9443 (#0)
* Trying 127.0.0.1...
* Connected to test.com (127.0.0.1) port 9443 (#0)
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=CN; ST=GuangDong; L=ZhuHai; O=iresty; CN=test.com
* start date: Jun 24 22:18:05 2019 GMT
* expire date: May 31 22:18:05 2119 GMT
* issuer: C=CN; ST=GuangDong; L=ZhuHai; O=iresty; CN=test.com
* SSL certificate verify result: self-signed certificate (18), continuing anyway.
> GET /get HTTP/2
> Host: test.com:9443
> user-agent: curl/7.81.0
> accept: */*
```
### wildcard SNI
An SSL certificate could also be valid for a wildcard domain like `*.test.com`, which means it is valid for any domain of that pattern, including `www.test.com` and `mail.test.com`.
The following is an example of configuring an SSL certificate with a wildcard SNI in APISIX.
Create an SSL object with the certificate and key valid for the SNI:
```shell
curl http://127.0.0.1:9180/apisix/admin/ssls/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"cert" : "'"$(cat t/certs/apisix.crt)"'",
"key": "'"$(cat t/certs/apisix.key)"'",
"snis": ["*.test.com"]
}'
```
Create a Router object:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/get",
"hosts": ["*.test.com"],
"methods": ["GET"],
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
```
Send a request to verify:
```shell
curl --resolve 'www.test.com:9443:127.0.0.1' https://www.test.com:9443/get -k -vvv
* Added www.test.com:9443:127.0.0.1 to DNS cache
* Hostname www.test.com was found in DNS cache
* Trying 127.0.0.1:9443...
* Connected to www.test.com (127.0.0.1) port 9443 (#0)
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=CN; ST=GuangDong; L=ZhuHai; O=iresty; CN=test.com
* start date: Jun 24 22:18:05 2019 GMT
* expire date: May 31 22:18:05 2119 GMT
* issuer: C=CN; ST=GuangDong; L=ZhuHai; O=iresty; CN=test.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
> GET /get HTTP/2
> Host: www.test.com:9443
> user-agent: curl/7.74.0
> accept: */*
```
### multiple domain
If your SSL certificate may contain more than one domain, like `www.test.com` and `mail.test.com`, then you can add them into the `snis` array. For example:
```json
{
"snis": ["www.test.com", "mail.test.com"]
}
```
### multiple certificates for a single domain
If you want to configure multiple certificate for a single domain, for
instance, supporting both the
[ECC](https://en.wikipedia.org/wiki/Elliptic-curve_cryptography)
and RSA key-exchange algorithm, then just configure the extra certificates (the
first certificate and private key should be still put in `cert` and `key`) and
private keys by `certs` and `keys`.
* `certs`: PEM-encoded certificate array.
* `keys`: PEM-encoded private key array.
`APISIX` will pair certificate and private key with the same indice as a SSL key
pair. So the length of `certs` and `keys` must be same.
### set up multiple CA certificates
APISIX currently uses CA certificates in several places, such as [Protect Admin API](./mtls.md#protect-admin-api), [etcd with mTLS](./mtls.md#etcd-with-mtls), and [Deployment Modes](./deployment-modes.md).
In these places, `ssl_trusted_certificate` or `trusted_ca_cert` will be used to set up the CA certificate, but these configurations will eventually be translated into [lua_ssl_trusted_certificate](https://github.com/openresty/lua-nginx-module#lua_ssl_trusted_certificate) directive in OpenResty.
If you need to set up different CA certificates in different places, then you can package these CA certificates into a CA bundle file and point to this file when you need to set up CAs. This will avoid the problem that the generated `lua_ssl_trusted_certificate` has multiple locations and overwrites each other.
The following is a complete example to show how to set up multiple CA certificates in APISIX.
Suppose we let client and APISIX Admin API, APISIX and ETCD communicate with each other using mTLS protocol, and currently there are two CA certificates, `foo_ca.crt` and `bar_ca.crt`, and use each of these two CA certificates to issue client and server certificate pairs, `foo_ca.crt` and its issued certificate pair are used to protect Admin API, and `bar_ca.crt` and its issued certificate pair are used to protect ETCD.
The following table details the configurations involved in this example and what they do:
| Configuration | Type | Description |
| ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| foo_ca.crt | CA cert | Issues the secondary certificate required for the client to communicate with the APISIX Admin API over mTLS. |
| foo_client.crt | cert | A certificate issued by `foo_ca.crt` and used by the client to prove its identity when accessing the APISIX Admin API. |
| foo_client.key | key | Issued by `foo_ca.crt`, used by the client, the key file required to access the APISIX Admin API. |
| foo_server.crt | cert | Issued by `foo_ca.crt`, used by APISIX, corresponding to the `admin_api_mtls.admin_ssl_cert` configuration entry. |
| foo_server.key | key | Issued by `foo_ca.crt`, used by APISIX, corresponding to the `admin_api_mtls.admin_ssl_cert_key` configuration entry. |
| admin.apisix.dev | doname | Common Name used in issuing `foo_server.crt` certificate, through which the client accesses APISIX Admin API |
| bar_ca.crt | CA cert | Issues the secondary certificate required for APISIX to communicate with ETCD over mTLS. |
| bar_etcd.crt | cert | Issued by `bar_ca.crt` and used by ETCD, corresponding to the `-cert-file` option in the ETCD startup command. |
| bar_etcd.key | key | Issued by `bar_ca.crt` and used by ETCD, corresponding to the `--key-file` option in the ETCD startup command. |
| bar_apisix.crt | cert | Issued by `bar_ca.crt`, used by APISIX, corresponding to the `etcd.tls.cert` configuration entry. |
| bar_apisix.key | key | Issued by `bar_ca.crt`, used by APISIX, corresponding to the `etcd.tls.key` configuration entry. |
| etcd.cluster.dev | key | Common Name used in issuing `bar_etcd.crt` certificate, which is used as SNI when APISIX communicates with ETCD over mTLS. corresponds to `etcd.tls.sni` configuration item. |
| apisix.ca-bundle | CA bundle | Merged from `foo_ca.crt` and `bar_ca.crt`, replacing `foo_ca.crt` and `bar_ca.crt`. |
1. Create CA bundle files
```shell
cat /path/to/foo_ca.crt /path/to/bar_ca.crt > apisix.ca-bundle
```
2. Start the ETCD cluster and enable client authentication
Start by writing a `goreman` configuration named `Procfile-single-enable-mtls`, the content as:
```text
# Use goreman to run `go get github.com/mattn/goreman`
etcd1: etcd --name infra1 --listen-client-urls https://127.0.0.1:12379 --advertise-client-urls https://127.0.0.1:12379 --listen-peer-urls http://127.0.0.1:12380 --initial-advertise-peer-urls http://127.0.0.1:12380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle
etcd2: etcd --name infra2 --listen-client-urls https://127.0.0.1:22379 --advertise-client-urls https://127.0.0.1:22379 --listen-peer-urls http://127.0.0.1:22380 --initial-advertise-peer-urls http://127.0.0.1:22380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle
etcd3: etcd --name infra3 --listen-client-urls https://127.0.0.1:32379 --advertise-client-urls https://127.0.0.1:32379 --listen-peer-urls http://127.0.0.1:32380 --initial-advertise-peer-urls http://127.0.0.1:32380 --initial-cluster-token etcd-cluster-1 --initial-cluster 'infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380' --initial-cluster-state new --cert-file /path/to/bar_etcd.crt --key-file /path/to/bar_etcd.key --client-cert-auth --trusted-ca-file /path/to/apisix.ca-bundle
```
Use `goreman` to start the ETCD cluster:
```shell
goreman -f Procfile-single-enable-mtls start > goreman.log 2>&1 &
```
3. Update `config.yaml`
```yaml title="conf/config.yaml"
deployment:
admin:
admin_key
- name: admin
key: edd1c9f034335f136f87ad84b625c8f1
role: admin
admin_listen:
ip: 127.0.0.1
port: 9180
https_admin: true
admin_api_mtls:
admin_ssl_ca_cert: /path/to/apisix.ca-bundle
admin_ssl_cert: /path/to/foo_server.crt
admin_ssl_cert_key: /path/to/foo_server.key
apisix:
ssl:
ssl_trusted_certificate: /path/to/apisix.ca-bundle
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
host:
- "https://127.0.0.1:12379"
- "https://127.0.0.1:22379"
- "https://127.0.0.1:32379"
tls:
cert: /path/to/bar_apisix.crt
key: /path/to/bar_apisix.key
sni: etcd.cluster.dev
```
4. Test APISIX Admin API
Start APISIX, if APISIX starts successfully and there is no abnormal output in `logs/error.log`, it means that mTLS communication between APISIX and ETCD is normal.
Use curl to simulate a client, communicate with APISIX Admin API with mTLS, and create a route:
```shell
curl -vvv \
--resolve 'admin.apisix.dev:9180:127.0.0.1' https://admin.apisix.dev:9180/apisix/admin/routes/1 \
--cert /path/to/foo_client.crt \
--key /path/to/foo_client.key \
--cacert /path/to/apisix.ca-bundle \
-H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/get",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
A successful mTLS communication between curl and the APISIX Admin API is indicated if the following SSL handshake process is output:
```shell
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
```
5. Verify APISIX proxy
```shell
curl http://127.0.0.1:9080/get -i
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 298
Connection: keep-alive
Date: Tue, 26 Jul 2022 16:31:00 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/2.14.1
...
```
APISIX proxied the request to the `/get` path of the upstream `httpbin.org` and returned `HTTP/1.1 200 OK`. The whole process is working fine using CA bundle instead of CA certificate.

View File

@@ -0,0 +1,428 @@
{
"version": "3.13.0",
"sidebar": [
{
"type": "category",
"label": "Getting Started",
"items": [
"getting-started/README",
"getting-started/configure-routes",
"getting-started/load-balancing",
"getting-started/key-authentication",
"getting-started/rate-limiting"
]
},
{
"type": "doc",
"id": "installation-guide"
},
{
"type": "doc",
"id": "architecture-design/apisix"
},
{
"type": "category",
"label": "Tutorials",
"items": [
"tutorials/expose-api",
"tutorials/protect-api",
{
"type": "category",
"label": "Observability",
"items": [
"tutorials/observe-your-api",
"tutorials/health-check",
"tutorials/monitor-api-health-check"
]
},
"tutorials/manage-api-consumers",
"tutorials/cache-api-responses",
"tutorials/add-multiple-api-versions",
"tutorials/client-to-apisix-mtls",
"tutorials/websocket-authentication",
"tutorials/keycloak-oidc"
]
},
{
"type": "category",
"label": "Terminology",
"items": [
"terminology/api-gateway",
"terminology/consumer",
"terminology/consumer-group",
"terminology/credential",
"terminology/global-rule",
"terminology/plugin",
"terminology/plugin-config",
"terminology/plugin-metadata",
"terminology/route",
"terminology/router",
"terminology/script",
"terminology/service",
"terminology/upstream",
"terminology/secret"
]
},
{
"type": "category",
"label": "Plugins",
"items": [
{
"type": "category",
"label": "AI",
"items": [
"plugins/ai-proxy",
"plugins/ai-proxy-multi",
"plugins/ai-rate-limiting",
"plugins/ai-prompt-guard",
"plugins/ai-aws-content-moderation",
"plugins/ai-prompt-decorator",
"plugins/ai-prompt-template",
"plugins/ai-rag",
"plugins/ai-request-rewrite"
]
},
{
"type": "category",
"label": "General",
"items": [
"plugins/batch-requests",
"plugins/redirect",
"plugins/echo",
"plugins/gzip",
"plugins/brotli",
"plugins/real-ip",
"plugins/server-info",
"plugins/ext-plugin-pre-req",
"plugins/ext-plugin-post-req",
"plugins/ext-plugin-post-resp",
"plugins/inspect",
"plugins/ocsp-stapling"
]
},
{
"type": "category",
"label": "Transformation",
"items": [
"plugins/response-rewrite",
"plugins/proxy-rewrite",
"plugins/grpc-transcode",
"plugins/grpc-web",
"plugins/fault-injection",
"plugins/mocking",
"plugins/degraphql",
"plugins/body-transformer",
"plugins/attach-consumer-label"
]
},
{
"type": "category",
"label": "Authentication",
"items": [
"plugins/key-auth",
"plugins/jwt-auth",
"plugins/jwe-decrypt",
"plugins/basic-auth",
"plugins/authz-keycloak",
"plugins/authz-casdoor",
"plugins/wolf-rbac",
"plugins/openid-connect",
"plugins/cas-auth",
"plugins/hmac-auth",
"plugins/authz-casbin",
"plugins/ldap-auth",
"plugins/opa",
"plugins/forward-auth",
"plugins/multi-auth"
]
},
{
"type": "category",
"label": "Security",
"items": [
"plugins/cors",
"plugins/uri-blocker",
"plugins/ip-restriction",
"plugins/ua-restriction",
"plugins/referer-restriction",
"plugins/consumer-restriction",
"plugins/csrf",
"plugins/public-api",
"plugins/gm",
"plugins/chaitin-waf"
]
},
{
"type": "category",
"label": "Traffic",
"items": [
"plugins/limit-req",
"plugins/limit-conn",
"plugins/limit-count",
"plugins/proxy-cache",
"plugins/request-validation",
"plugins/proxy-mirror",
"plugins/api-breaker",
"plugins/traffic-split",
"plugins/request-id",
"plugins/proxy-control",
"plugins/client-control",
"plugins/workflow"
]
},
{
"type": "category",
"label": "Observability",
"items": [
{
"type": "category",
"label": "Tracers",
"items": [
"plugins/zipkin",
"plugins/skywalking",
"plugins/opentelemetry"
]
},
{
"type": "category",
"label": "Metrics",
"items": [
"plugins/prometheus",
"plugins/node-status",
"plugins/datadog"
]
},
{
"type": "category",
"label": "Loggers",
"items": [
"plugins/http-logger",
"plugins/skywalking-logger",
"plugins/tcp-logger",
"plugins/kafka-logger",
"plugins/rocketmq-logger",
"plugins/udp-logger",
"plugins/clickhouse-logger",
"plugins/syslog",
"plugins/log-rotate",
"plugins/error-log-logger",
"plugins/sls-logger",
"plugins/google-cloud-logging",
"plugins/splunk-hec-logging",
"plugins/file-logger",
"plugins/loggly",
"plugins/elasticsearch-logger",
"plugins/tencent-cloud-cls",
"plugins/loki-logger",
"plugins/lago"
]
}
]
},
{
"type": "category",
"label": "Serverless",
"items": [
"plugins/serverless",
"plugins/azure-functions",
"plugins/openwhisk",
"plugins/aws-lambda",
"plugins/openfunction"
]
},
{
"type": "category",
"label": "Other protocols",
"items": [
"plugins/dubbo-proxy",
"plugins/mqtt-proxy",
"plugins/kafka-proxy",
"plugins/http-dubbo"
]
}
]
},
{
"type": "category",
"label": "API",
"items": [
{
"type": "doc",
"id": "admin-api"
},
{
"type": "doc",
"id": "control-api"
},
{
"type": "doc",
"id": "status-api"
}
]
},
{
"type": "category",
"label": "Development",
"items": [
{
"type": "doc",
"id": "build-apisix-dev-environment-devcontainers"
},
{
"type": "doc",
"id": "building-apisix"
},
{
"type": "doc",
"id": "build-apisix-dev-environment-on-mac"
},
{
"type": "doc",
"id": "support-fips-in-apisix"
},
{
"type": "doc",
"id": "external-plugin"
},
{
"type": "doc",
"id": "wasm"
},
{
"type": "link",
"label": "CODE_STYLE",
"href": "https://github.com/apache/apisix/blob/master/CODE_STYLE.md"
},
{
"type": "category",
"label": "internal",
"items": [
"internal/plugin-runner",
"internal/testing-framework"
]
},
{
"type": "doc",
"id": "plugin-develop"
},
{
"type": "doc",
"id": "debug-mode"
}
]
},
{
"type": "doc",
"id": "deployment-modes"
},
{
"type": "doc",
"id": "FAQ"
},
{
"type": "category",
"label": "Others",
"items": [
{
"type": "category",
"label": "Discovery",
"items": [
"discovery",
"discovery/dns",
"discovery/consul",
"discovery/consul_kv",
"discovery/nacos",
"discovery/eureka",
"discovery/control-plane-service-discovery",
"discovery/kubernetes"
]
},
{
"type": "category",
"label": "PubSub",
"items": [
"pubsub",
"pubsub/kafka"
]
},
{
"type": "category",
"label": "xRPC",
"items": [
"xrpc/redis",
"xrpc"
]
},
{
"type": "doc",
"id": "router-radixtree"
},
{
"type": "doc",
"id": "stream-proxy"
},
{
"type": "doc",
"id": "grpc-proxy"
},
{
"type": "doc",
"id": "customize-nginx-configuration"
},
{
"type": "doc",
"id": "certificate"
},
{
"type": "doc",
"id": "batch-processor"
},
{
"type": "doc",
"id": "benchmark"
},
{
"type": "doc",
"id": "install-dependencies"
},
{
"type": "doc",
"id": "apisix-variable"
},
{
"type": "doc",
"id": "aws"
},
{
"type": "doc",
"id": "mtls"
},
{
"type": "doc",
"id": "debug-function"
},
{
"type": "doc",
"id": "profile"
},
{
"type": "doc",
"id": "ssl-protocol"
},
{
"type": "doc",
"id": "http3"
}
]
},
{
"type": "link",
"label": "CHANGELOG",
"href": "https://github.com/apache/apisix/blob/master/CHANGELOG.md"
},
{
"type": "doc",
"id": "upgrade-guide-from-2.15.x-to-3.0.0"
}
]
}

View File

@@ -0,0 +1,555 @@
---
title: Control API
---
<!--
#
# 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.
#
-->
In Apache APISIX, the control API is used to:
* Expose the internal state of APISIX.
* Control the behavior of a single, isolated APISIX data plane.
To change the default endpoint (`127.0.0.1:9090`) of the Control API server, change the `ip` and `port` in the `control` section in your configuration file (`conf/config.yaml`):
```yaml
apisix:
...
enable_control: true
control:
ip: "127.0.0.1"
port: 9090
```
To enable parameter matching in plugin's control API, add `router: 'radixtree_uri_with_parameter'` to the control section.
**Note**: Never configure the control API server to listen to public traffic.
## Control API Added via Plugins
[Plugins](./terminology/plugin.md) can be enabled to add its control API.
Some Plugins have their own control APIs. See the documentation of the specific Plugin to learn more.
## Plugin Independent Control API
The supported APIs are listed below.
### GET /v1/schema
Introduced in [v2.2](https://github.com/apache/apisix/releases/tag/2.2).
Returns the JSON schema used by the APISIX instance:
```json
{
"main": {
"route": {
"properties": {...}
},
"upstream": {
"properties": {...}
},
...
},
"plugins": {
"example-plugin": {
"consumer_schema": {...},
"metadata_schema": {...},
"schema": {...},
"type": ...,
"priority": 0,
"version": 0.1
},
...
},
"stream-plugins": {
"mqtt-proxy": {
...
},
...
}
}
```
**Note**: Only the enabled `plugins` are returned and they may lack fields like `consumer_schema` or `type` depending on how they were defined.
### GET /v1/healthcheck
Introduced in [v2.3](https://github.com/apache/apisix/releases/tag/2.3).
Returns a [health check](./tutorials/health-check.md) of the APISIX instance.
```json
[
{
"nodes": [
{
"ip": "52.86.68.46",
"counter": {
"http_failure": 0,
"success": 0,
"timeout_failure": 0,
"tcp_failure": 0
},
"port": 80,
"status": "healthy"
},
{
"ip": "100.24.156.8",
"counter": {
"http_failure": 5,
"success": 0,
"timeout_failure": 0,
"tcp_failure": 0
},
"port": 80,
"status": "unhealthy"
}
],
"name": "/apisix/routes/1",
"type": "http"
}
]
```
Each of the returned objects contain the following fields:
* name: resource id, where the health checker is reporting from.
* type: health check type: `["http", "https", "tcp"]`.
* nodes: target nodes of the health checker.
* nodes[i].ip: ip address.
* nodes[i].port: port number.
* nodes[i].status: health check result: `["healthy", "unhealthy", "mostly_healthy", "mostly_unhealthy"]`.
* nodes[i].counter.success: success health check count.
* nodes[i].counter.http_failure: http failures count.
* nodes[i].counter.tcp_failure: tcp connect/read/write failures count.
* nodes[i].counter.timeout_failure: timeout count.
You can also use `/v1/healthcheck/$src_type/$src_id` to get the health status of specific nodes.
For example, `GET /v1/healthcheck/upstreams/1` returns:
```json
{
"nodes": [
{
"ip": "52.86.68.46",
"counter": {
"http_failure": 0,
"success": 2,
"timeout_failure": 0,
"tcp_failure": 0
},
"port": 80,
"status": "healthy"
},
{
"ip": "100.24.156.8",
"counter": {
"http_failure": 5,
"success": 0,
"timeout_failure": 0,
"tcp_failure": 0
},
"port": 80,
"status": "unhealthy"
}
],
"type": "http"
"name": "/apisix/routes/1"
}
```
:::note
Only when one upstream is satisfied by the conditions below,
its status is shown in the result list:
* The upstream is configured with a health checker
* The upstream has served requests in any worker process
:::
If you use browser to access the control API URL, then you will get the HTML output:
![Health Check Status Page](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/health_check_status_page.png)
### POST /v1/gc
Introduced in [v2.8](https://github.com/apache/apisix/releases/tag/2.8).
Triggers a full garbage collection in the HTTP subsystem.
**Note**: When stream proxy is enabled, APISIX runs another Lua VM for the stream subsystem. Full garbage collection is not triggered in this VM.
### GET /v1/routes
Introduced in [v2.10.0](https://github.com/apache/apisix/releases/tag/2.10.0).
Returns all configured [Routes](./terminology/route.md):
```json
[
{
"value": {
"priority": 0,
"uris": [
"/hello"
],
"id": "1",
"upstream": {
"scheme": "http",
"pass_host": "pass",
"nodes": [
{
"port": 1980,
"host": "127.0.0.1",
"weight": 1
}
],
"type": "roundrobin",
"hash_on": "vars"
},
"status": 1
},
"clean_handlers": {},
"has_domain": false,
"orig_modifiedIndex": 1631193445,
"modifiedIndex": 1631193445,
"key": "/routes/1"
}
]
```
### GET /v1/route/{route_id}
Introduced in [v2.10.0](https://github.com/apache/apisix/releases/tag/2.10.0).
Returns the Route with the specified `route_id`:
```json
{
"value": {
"priority": 0,
"uris": [
"/hello"
],
"id": "1",
"upstream": {
"scheme": "http",
"pass_host": "pass",
"nodes": [
{
"port": 1980,
"host": "127.0.0.1",
"weight": 1
}
],
"type": "roundrobin",
"hash_on": "vars"
},
"status": 1
},
"clean_handlers": {},
"has_domain": false,
"orig_modifiedIndex": 1631193445,
"modifiedIndex": 1631193445,
"key": "/routes/1"
}
```
### GET /v1/services
Introduced in [v2.11.0](https://github.com/apache/apisix/releases/tag/2.11.0).
Returns all the Services:
```json
[
{
"has_domain": false,
"clean_handlers": {},
"modifiedIndex": 671,
"key": "/apisix/services/200",
"createdIndex": 671,
"value": {
"upstream": {
"scheme": "http",
"hash_on": "vars",
"pass_host": "pass",
"type": "roundrobin",
"nodes": [
{
"port": 1980,
"weight": 1,
"host": "127.0.0.1"
}
]
},
"create_time": 1634552648,
"id": "200",
"plugins": {
"limit-count": {
"key": "remote_addr",
"time_window": 60,
"redis_timeout": 1000,
"allow_degradation": false,
"show_limit_quota_header": true,
"policy": "local",
"count": 2,
"rejected_code": 503
}
},
"update_time": 1634552648
}
}
]
```
### GET /v1/service/{service_id}
Introduced in [v2.11.0](https://github.com/apache/apisix/releases/tag/2.11.0).
Returns the Service with the specified `service_id`:
```json
{
"has_domain": false,
"clean_handlers": {},
"modifiedIndex": 728,
"key": "/apisix/services/5",
"createdIndex": 728,
"value": {
"create_time": 1634554563,
"id": "5",
"upstream": {
"scheme": "http",
"hash_on": "vars",
"pass_host": "pass",
"type": "roundrobin",
"nodes": [
{
"port": 1980,
"weight": 1,
"host": "127.0.0.1"
}
]
},
"update_time": 1634554563
}
}
```
### GET /v1/upstreams
Introduced in [v2.11.0](https://github.com/apache/apisix/releases/tag/2.11.0).
Dumps all Upstreams:
```json
[
{
"value":{
"scheme":"http",
"pass_host":"pass",
"nodes":[
{
"host":"127.0.0.1",
"port":80,
"weight":1
},
{
"host":"foo.com",
"port":80,
"weight":2
}
],
"hash_on":"vars",
"update_time":1634543819,
"key":"remote_addr",
"create_time":1634539759,
"id":"1",
"type":"chash"
},
"has_domain":true,
"key":"\/apisix\/upstreams\/1",
"clean_handlers":{
},
"createdIndex":938,
"modifiedIndex":1225
}
]
```
### GET /v1/upstream/{upstream_id}
Introduced in [v2.11.0](https://github.com/apache/apisix/releases/tag/2.11.0).
Dumps the Upstream with the specified `upstream_id`:
```json
{
"value":{
"scheme":"http",
"pass_host":"pass",
"nodes":[
{
"host":"127.0.0.1",
"port":80,
"weight":1
},
{
"host":"foo.com",
"port":80,
"weight":2
}
],
"hash_on":"vars",
"update_time":1634543819,
"key":"remote_addr",
"create_time":1634539759,
"id":"1",
"type":"chash"
},
"has_domain":true,
"key":"\/apisix\/upstreams\/1",
"clean_handlers":{
},
"createdIndex":938,
"modifiedIndex":1225
}
```
### GET /v1/plugin_metadatas
Introduced in [v3.0.0](https://github.com/apache/apisix/releases/tag/3.0.0).
Dumps all plugin_metadatas:
```json
[
{
"log_format": {
"upstream_response_time": "$upstream_response_time"
},
"id": "file-logger"
},
{
"ikey": 1,
"skey": "val",
"id": "example-plugin"
}
]
```
### GET /v1/plugin_metadata/{plugin_name}
Introduced in [v3.0.0](https://github.com/apache/apisix/releases/tag/3.0.0).
Dumps the metadata with the specified `plugin_name`:
```json
{
"log_format": {
"upstream_response_time": "$upstream_response_time"
},
"id": "file-logger"
}
```
### PUT /v1/plugins/reload
Introduced in [v3.9.0](https://github.com/apache/apisix/releases/tag/3.9.0)
Triggers a hot reload of the plugins.
```shell
curl "http://127.0.0.1:9090/v1/plugins/reload" -X PUT
```
### GET /v1/discovery/{service}/dump
Get memory dump of discovered service endpoints and configuration details:
```json
{
"endpoints": [
{
"endpoints": [
{
"value": "{\"https\":[{\"host\":\"172.18.164.170\",\"port\":6443,\"weight\":50},{\"host\":\"172.18.164.171\",\"port\":6443,\"weight\":50},{\"host\":\"172.18.164.172\",\"port\":6443,\"weight\":50}]}",
"name": "default/kubernetes"
},
{
"value": "{\"metrics\":[{\"host\":\"172.18.164.170\",\"port\":2379,\"weight\":50},{\"host\":\"172.18.164.171\",\"port\":2379,\"weight\":50},{\"host\":\"172.18.164.172\",\"port\":2379,\"weight\":50}]}",
"name": "kube-system/etcd"
},
{
"value": "{\"http-85\":[{\"host\":\"172.64.89.2\",\"port\":85,\"weight\":50}]}",
"name": "test-ws/testing"
}
],
"id": "first"
}
],
"config": [
{
"default_weight": 50,
"id": "first",
"client": {
"token": "xxx"
},
"service": {
"host": "172.18.164.170",
"port": "6443",
"schema": "https"
},
"shared_size": "1m"
}
]
}
```
## GET /v1/discovery/{service}/show_dump_file
Get configured services details.
```json
{
"services": {
"service_a": [
{
"host": "172.19.5.12",
"port": 8000,
"weight": 120
},
{
"host": "172.19.5.13",
"port": 8000,
"weight": 120
}
]
},
"expire": 0,
"last_update": 1615877468
}
```

View File

@@ -0,0 +1,63 @@
---
title: Customize Nginx configuration
---
<!--
#
# 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.
#
-->
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.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
...
# put this in config.yaml:
nginx_config:
main_configuration_snippet: |
daemon on;
http_configuration_snippet: |
server
{
listen 45651;
server_name _;
access_log off;
location /ysec_status {
req_status_show;
allow 127.0.0.1;
deny all;
}
}
chunked_transfer_encoding on;
http_server_configuration_snippet: |
set $my "var";
http_admin_configuration_snippet: |
log_format admin "$request_time $pipe";
http_end_configuration_snippet: |
server_names_hash_bucket_size 128;
stream_configuration_snippet: |
tcp_nodelay off;
...
```
Pay attention to the indent of `nginx_config` and sub indent of the sub entries, the incorrect indent may cause `./bin/apisix start` to fail to generate Nginx configuration in `conf/nginx.conf`.

View File

@@ -0,0 +1,162 @@
---
title: Debug Function
---
<!--
#
# 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.
#
-->
## `5xx` response status code
Similar `5xx` status codes such as 500, 502, 503, etc., are the status codes in response to a server error. When a request has a `5xx` status code; it may come from `APISIX` or `Upstream`. How to identify the source of these response status codes is a very meaningful thing. It can quickly help us determine the problem. (When modifying the configuration `show_upstream_status_in_response_header` in `conf/config.yaml` to `true`, all upstream status codes will be returned, not only `5xx` status.)
## How to identify the source of the `5xx` response status code
In the response header of the request, through the response header of `X-APISIX-Upstream-Status`, we can effectively identify the source of the `5xx` status code. When the `5xx` status code comes from `Upstream`, the response header `X-APISIX-Upstream-Status` can be seen in the response header, and the value of this response header is the response status code. When the `5xx` status code is derived from `APISIX`, there is no response header information of `X-APISIX-Upstream-Status` in the response header. That is, only when the status code of `5xx` is derived from Upstream will the `X-APISIX-Upstream-Status` response header appear.
## Example
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
>Example 1: `502` response status code comes from `Upstream` (IP address is not available)
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"upstream": {
"nodes": {
"127.0.0.1:1": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
Test:
```shell
$ curl http://127.0.0.1:9080/hello -v
......
< HTTP/1.1 502 Bad Gateway
< Date: Wed, 25 Nov 2020 14:40:22 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 154
< Connection: keep-alive
< Server: APISIX/2.0
< X-APISIX-Upstream-Status: 502
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
It has a response header of `X-APISIX-Upstream-Status: 502`.
>Example 2: `502` response status code comes from `APISIX`
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 500,
"body": "Fault Injection!\n"
}
}
},
"uri": "/hello"
}'
```
Test
```shell
$ curl http://127.0.0.1:9080/hello -v
......
< HTTP/1.1 500 Internal Server Error
< Date: Wed, 25 Nov 2020 14:50:20 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: APISIX/2.0
<
Fault Injection!
```
There is no response header for `X-APISIX-Upstream-Status`.
>Example 3: `Upstream` has multiple nodes, and all nodes are unavailable
```shell
$ curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"nodes": {
"127.0.0.3:1": 1,
"127.0.0.2:1": 1,
"127.0.0.1:1": 1
},
"retries": 2,
"type": "roundrobin"
}'
```
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/hello",
"upstream_id": "1"
}'
```
Test
```shell
$ curl http://127.0.0.1:9080/hello -v
< HTTP/1.1 502 Bad Gateway
< Date: Wed, 25 Nov 2020 15:07:34 GMT
< Content-Type: text/html; charset=utf-8
< Content-Length: 154
< Connection: keep-alive
< Server: APISIX/2.0
< X-APISIX-Upstream-Status: 502, 502, 502
<
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
It has a response header of `X-APISIX-Upstream-Status: 502, 502, 502`.

View File

@@ -0,0 +1,140 @@
---
id: debug-mode
title: Debug mode
keywords:
- API gateway
- Apache APISIX
- Debug mode
description: Guide for enabling debug mode in Apache APISIX.
---
<!--
#
# 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.
#
-->
You can use APISIX's debug mode to troubleshoot your configuration.
## Basic debug mode
You can enable the basic debug mode by adding this line to your debug configuration file (`conf/debug.yaml`):
```yaml title="conf/debug.yaml"
basic:
enable: true
#END
```
APISIX loads the configurations of `debug.yaml` on startup and then checks if the file is modified on an interval of 1 second. If the file is changed, APISIX automatically applies the configuration changes.
:::note
For APISIX releases prior to v2.10, basic debug mode is enabled by setting `apisix.enable_debug = true` in your configuration file (`conf/config.yaml`).
:::
If you have configured two Plugins `limit-conn` and `limit-count` on the Route `/hello`, you will receive a response with the header `Apisix-Plugins: limit-conn, limit-count` when you enable the basic debug mode.
```shell
curl http://127.0.0.1:1984/hello -i
```
```shell
HTTP/1.1 200 OK
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Apisix-Plugins: limit-conn, limit-count
X-RateLimit-Limit: 2
X-RateLimit-Remaining: 1
Server: openresty
hello world
```
:::info IMPORTANT
If the debug information cannot be included in a response header (for example, when the Plugin is in a stream subsystem), the debug information will be logged as an error log at a `warn` level.
:::
## Advanced debug mode
You can configure advanced options in debug mode by modifying your debug configuration file (`conf/debug.yaml`).
The following configurations are available:
| Key | Required | Default | Description |
|---------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------|
| hook_conf.enable | True | false | Enables/disables hook debug trace. i.e. if enabled, will print the target module function's inputs or returned value. |
| hook_conf.name | True | | Module list name of the hook that enabled the debug trace. |
| hook_conf.log_level | True | warn | Log level for input arguments & returned values. |
| hook_conf.is_print_input_args | True | true | When set to `true` enables printing input arguments. |
| hook_conf.is_print_return_value | True | true | When set to `true` enables printing returned values. |
:::note
A checker would check every second for changes to the configuration file. It will only check a file if the file was updated based on its last modification time.
You can add an `#END` flag to indicate to the checker to only look for changes until that point.
:::
The example below shows how you can configure advanced options in debug mode:
```yaml title="conf/debug.yaml"
hook_conf:
enable: false # Enables/disables hook debug trace
name: hook_phase # Module list name of the hook that enabled the debug trace
log_level: warn # Log level for input arguments & returned values
is_print_input_args: true # When set to `true` enables printing input arguments
is_print_return_value: true # When set to `true` enables printing returned values
hook_phase: # Module function list, Name: hook_phase
apisix: # Referenced module name
- http_access_phase # Function namesArray
- http_header_filter_phase
- http_body_filter_phase
- http_log_phase
#END
```
### Dynamically enable advanced debug mode
You can also enable advanced debug mode only on particular requests.
The example below shows how you can enable it on requests with the header `X-APISIX-Dynamic-Debug`:
```yaml title="conf/debug.yaml"
http_filter:
enable: true # Enable/disable advanced debug mode dynamically
enable_header_name: X-APISIX-Dynamic-Debug # Trace for the request with this header
...
#END
```
This will enable the advanced debug mode only for requests like:
```shell
curl 127.0.0.1:9090/hello --header 'X-APISIX-Dynamic-Debug: foo'
```
:::note
The `apisix.http_access_phase` module cannot be hooked for this dynamic rule as the advanced debug mode is enabled based on the request.
:::

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,307 @@
---
title: Integration service discovery registry
---
<!--
#
# 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.
#
-->
## Summary
When system traffic changes, the number of servers of the upstream service also increases or decreases, or the server needs to be replaced due to its hardware failure. If the gateway maintains upstream service information through configuration, the maintenance costs in the microservices architecture pattern are unpredictable. Furthermore, due to the untimely update of these information, will also bring a certain impact for the business, and the impact of human error operation can not be ignored. So it is very necessary for the gateway to automatically get the latest list of service instances through the service registry。As shown in the figure below
![discovery through service registry](../../assets/images/discovery.png)
1. When the service starts, it will report some of its information, such as the service name, IP, port and other information to the registry. The services communicate with the registry using a mechanism such as a heartbeat, and if the registry and the service are unable to communicate for a long time, the instance will be cancel.When the service goes offline, the registry will delete the instance information.
2. The gateway gets service instance information from the registry in near-real time.
3. When the user requests the service through the gateway, the gateway selects one instance from the registry for proxy.
## How to extend the discovery client?
### Basic steps
It is very easy for APISIX to extend the discovery client, the basic steps are as follows
1. Add the implementation of registry client in the 'apisix/discovery/' directory;
2. Implement the `_M.init_worker()` function for initialization and the `_M.nodes(service_name)` function for obtaining the list of service instance nodes;
3. If you need the discovery module to export the debugging information online, implement the `_M.dump_data()` function;
4. Convert the registry data into data in APISIX;
### the example of Eureka
#### Implementation of Eureka client
First, create a directory `eureka` under `apisix/discovery`;
After that, add [`init.lua`](https://github.com/apache/apisix/blob/master/apisix/discovery/init.lua) in the `apisix/discovery/eureka` directory;
Then implement the `_M.init_worker()` function for initialization and the `_M.nodes(service_name)` function for obtaining the list of service instance nodes in `init.lua`:
```lua
local _M = {
version = 1.0,
}
function _M.nodes(service_name)
... ...
end
function _M.init_worker()
... ...
end
function _M.dump_data()
return {config = your_config, services = your_services, other = ... }
end
return _M
```
Finally, provide the schema for YAML configuration in the `schema.lua` under `apisix/discovery/eureka`.
#### How convert Eureka's instance data to APISIX's node?
Here's an example of Eureka's data
```json
{
"applications": {
"application": [
{
"name": "USER-SERVICE", # service name
"instance": [
{
"instanceId": "192.168.1.100:8761",
"hostName": "192.168.1.100",
"app": "USER-SERVICE", # service name
"ipAddr": "192.168.1.100", # IP address
"status": "UP",
"overriddenStatus": "UNKNOWN",
"port": {
"$": 8761,
"@enabled": "true"
},
"securePort": {
"$": 443,
"@enabled": "false"
},
"metadata": {
"management.port": "8761",
"weight": 100 # Setting by 'eureka.instance.metadata-map.weight' of the spring boot application
},
"homePageUrl": "http://192.168.1.100:8761/",
"statusPageUrl": "http://192.168.1.100:8761/actuator/info",
"healthCheckUrl": "http://192.168.1.100:8761/actuator/health",
... ...
}
]
}
]
}
}
```
Deal with the Eureka's instance data need the following steps :
1. select the UP instance. When the value of `overriddenStatus` is "UP" or the value of `overriddenStatus` is "UNKNOWN" and the value of `status` is "UP".
2. Host. The `ipAddr` is the IP address of instance; and must be IPv4 or IPv6.
3. Port. If the value of `port["@enabled"]` is equal to "true", using the value of `port["\$"]`, If the value of `securePort["@enabled"]` is equal to "true", using the value of `securePort["\$"]`.
4. Weight. `local weight = metadata.weight or local_conf.eureka.weight or 100`
The result of this example is as follows:
```json
[
{
"host" : "192.168.1.100",
"port" : 8761,
"weight" : 100,
"metadata" : {
"management.port": "8761"
}
}
]
```
## Configuration for discovery client
### Initial service discovery
Add the following configuration to `conf/config.yaml` to add different service discovery clients for dynamic selection during use:
```yaml
discovery:
eureka:
...
```
This name should be consistent with the file name of the implementation registry in the `apisix/discovery/` directory.
The supported discovery client: Eureka.
### Configuration for Eureka
Add following configuration in `conf/config.yaml`
```yaml
discovery:
eureka:
host: # it's possible to define multiple eureka hosts addresses of the same eureka cluster.
- "http://${username}:${password}@${eureka_host1}:${eureka_port1}"
- "http://${username}:${password}@${eureka_host2}:${eureka_port2}"
prefix: "/eureka/"
fetch_interval: 30 # 30s
weight: 100 # default weight for node
timeout:
connect: 2000 # 2000ms
send: 2000 # 2000ms
read: 5000 # 5000ms
```
## Upstream setting
### L7
Here is an example of routing a request with a URL of "/user/*" to a service which named "user-service" and use eureka discovery client in the registry :
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/user/*",
"upstream": {
"service_name": "USER-SERVICE",
"type": "roundrobin",
"discovery_type": "eureka"
}
}'
HTTP/1.1 201 Created
Date: Sat, 31 Aug 2019 01:17:15 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX web server
{"node":{"value":{"uri":"\/user\/*","upstream": {"service_name": "USER-SERVICE", "type": "roundrobin", "discovery_type": "eureka"}},"createdIndex":61925,"key":"\/apisix\/routes\/1","modifiedIndex":61925}}
```
Because the upstream interface URL may have conflict, usually in the gateway by prefix to distinguish:
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/a/*",
"plugins": {
"proxy-rewrite" : {
"regex_uri": ["^/a/(.*)", "/${1}"]
}
},
"upstream": {
"service_name": "A-SERVICE",
"type": "roundrobin",
"discovery_type": "eureka"
}
}'
$ curl http://127.0.0.1:9180/apisix/admin/routes/2 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/b/*",
"plugins": {
"proxy-rewrite" : {
"regex_uri": ["^/b/(.*)", "/${1}"]
}
},
"upstream": {
"service_name": "B-SERVICE",
"type": "roundrobin",
"discovery_type": "eureka"
}
}'
```
Suppose both A-SERVICE and B-SERVICE provide a `/test` API. The above configuration allows access to A-SERVICE's `/test` API through `/a/test` and B-SERVICE's `/test` API through `/b/test`.
**Notice**When configuring `upstream.service_name`, `upstream.nodes` will no longer take effect, but will be replaced by 'nodes' obtained from the registry.
### L4
Eureka service discovery also supports use in L4, the configuration method is similar to L7.
```shell
$ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"remote_addr": "127.0.0.1",
"upstream": {
"scheme": "tcp",
"discovery_type": "eureka",
"service_name": "APISIX-EUREKA",
"type": "roundrobin"
}
}'
HTTP/1.1 200 OK
Date: Fri, 30 Dec 2022 03:52:19 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.0.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: *
Access-Control-Max-Age: 3600
X-API-VERSION: v3
{"key":"\/apisix\/stream_routes\/1","value":{"remote_addr":"127.0.0.1","upstream":{"hash_on":"vars","type":"roundrobin","discovery_type":"eureka","scheme":"tcp","pass_host":"pass","service_name":"APISIX-EUREKA"},"id":"1","create_time":1672106762,"update_time":1672372339}}
```
## Embedded control api for debugging
Sometimes we need the discovery client to export online data snapshot in memory when running for debugging, and if you implement the `_M. dump_data()` function:
```lua
function _M.dump_data()
return {config = local_conf.discovery.eureka, services = applications}
end
```
Then you can call its control api as below:
```shell
GET /v1/discovery/{discovery_type}/dump
```
eg:
```shell
curl http://127.0.0.1:9090/v1/discovery/eureka/dump
```

View File

@@ -0,0 +1,344 @@
---
title: consul
---
<!--
#
# 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.
#
-->
## Summary
APACHE APISIX supports Consul as a service discovery
## Configuration for discovery client
### Configuration for Consul
First of all, we need to add following configuration in `conf/config.yaml` :
```yaml
discovery:
consul:
servers: # make sure service name is unique in these consul servers
- "http://127.0.0.1:8500" # `http://127.0.0.1:8500` and `http://127.0.0.1:8600` are different clusters
- "http://127.0.0.1:8600" # `consul` service is default skip service
token: "..." # if your consul cluster has enabled acl access control, you need to specify the token
skip_services: # if you need to skip special services
- "service_a"
timeout:
connect: 1000 # default 2000 ms
read: 1000 # default 2000 ms
wait: 60 # default 60 sec
weight: 1 # default 1
fetch_interval: 5 # default 3 sec, only take effect for keepalive: false way
keepalive: true # default true, use the long pull way to query consul servers
sort_type: "origin" # default origin
default_service: # you can define default service when missing hit
host: "127.0.0.1"
port: 20999
metadata:
fail_timeout: 1 # default 1 ms
weight: 1 # default 1
max_fails: 1 # default 1
dump: # if you need, when registered nodes updated can dump into file
path: "logs/consul.dump"
expire: 2592000 # unit sec, here is 30 day
```
And you can config it in short by default value:
```yaml
discovery:
consul:
servers:
- "http://127.0.0.1:8500"
```
The `keepalive` has two optional values:
- `true`, default and recommend value, use the long pull way to query consul servers
- `false`, not recommend, it would use the short pull way to query consul servers, then you can set the `fetch_interval` for fetch interval
The `sort_type` has four optional values:
- `origin`, not sorting
- `host_sort`, sort by host
- `port_sort`, sort by port
- `combine_sort`, with the precondition that hosts are ordered, ports are also ordered.
#### Dump Data
When we need reload `apisix` online, as the `consul` module maybe loads data from CONSUL slower than load routes from ETCD, and would get the log at the moment before load successfully from consul:
```
http_access_phase(): failed to set upstream: no valid upstream node
```
So, we import the `dump` function for `consul` module. When reload, would load the dump file before from consul; when the registered nodes in consul been updated, would dump the upstream nodes into file automatically.
The `dump` has three optional values now:
- `path`, the dump file save path
- support relative path, eg: `logs/consul.dump`
- support absolute path, eg: `/tmp/consul.dump`
- make sure the dump file's parent path exist
- make sure the `apisix` has the dump file's read-write access permission,eg: add below config in `conf/config.yaml`
```yaml
nginx_config: # config for render the template to generate nginx.conf
user: root # specifies the execution user of the worker process.
```
- `load_on_init`, default value is `true`
- if `true`, just try to load the data from the dump file before loading data from consul when starting, does not care the dump file exists or not
- if `false`, ignore loading data from the dump file
- Whether `true` or `false`, we don't need to prepare a dump file for apisix at anytime
- `expire`, unit sec, avoiding load expired dump data when load
- default `0`, it is unexpired forever
- recommend 2592000, which is 30 days(equals 3600 \* 24 \* 30)
### Register Http API Services
Now, register nodes into consul:
```shell
curl -X PUT 'http://127.0.0.1:8500/v1/agent/service/register' \
-d '{
"ID": "service_a1",
"Name": "service_a",
"Tags": ["primary", "v1"],
"Address": "127.0.0.1",
"Port": 8000,
"Meta": {
"service_a_version": "4.0"
},
"EnableTagOverride": false,
"Weights": {
"Passing": 10,
"Warning": 1
}
}'
curl -X PUT 'http://127.0.0.1:8500/v1/agent/service/register' \
-d '{
"ID": "service_a1",
"Name": "service_a",
"Tags": ["primary", "v1"],
"Address": "127.0.0.1",
"Port": 8002,
"Meta": {
"service_a_version": "4.0"
},
"EnableTagOverride": false,
"Weights": {
"Passing": 10,
"Warning": 1
}
}'
```
In some cases, same service name might exist in different consul servers.
To avoid confusion, use the full consul key url path as service name in practice.
### Port Handling
When APISIX retrieves service information from Consul, it handles port values as follows:
- If the service registration includes a valid port number, that port will be used.
- If the port is `nil` (not specified) or `0`, APISIX will default to port `80` for HTTP services.
### Upstream setting
#### L7
Here is an example of routing a request with a URL of "/*" to a service which named "service_a" and use consul discovery client in the registry :
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/*",
"upstream": {
"service_name": "service_a",
"type": "roundrobin",
"discovery_type": "consul"
}
}'
```
The format response as below:
```json
{
"key": "/apisix/routes/1",
"value": {
"uri": "/*",
"priority": 0,
"id": "1",
"upstream": {
"scheme": "http",
"type": "roundrobin",
"hash_on": "vars",
"discovery_type": "consul",
"service_name": "service_a",
"pass_host": "pass"
},
"create_time": 1669267329,
"status": 1,
"update_time": 1669267329
}
}
```
You could find more usage in the `apisix/t/discovery/consul.t` file.
#### L4
Consul service discovery also supports use in L4, the configuration method is similar to L7.
```shell
$ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"remote_addr": "127.0.0.1",
"upstream": {
"scheme": "tcp",
"service_name": "service_a",
"type": "roundrobin",
"discovery_type": "consul"
}
}'
```
You could find more usage in the `apisix/t/discovery/stream/consul.t` file.
## Debugging API
It also offers control api for debugging.
### Memory Dump API
```shell
GET /v1/discovery/consul/dump
```
For example:
```shell
# curl http://127.0.0.1:9090/v1/discovery/consul/dump | jq
{
"config": {
"fetch_interval": 3,
"timeout": {
"wait": 60,
"connect": 6000,
"read": 6000
},
"weight": 1,
"servers": [
"http://172.19.5.30:8500",
"http://172.19.5.31:8500"
],
"keepalive": true,
"default_service": {
"host": "172.19.5.11",
"port": 8899,
"metadata": {
"fail_timeout": 1,
"weight": 1,
"max_fails": 1
}
},
"skip_services": [
"service_d"
]
},
"services": {
"service_a": [
{
"host": "127.0.0.1",
"port": 30513,
"weight": 1
},
{
"host": "127.0.0.1",
"port": 30514,
"weight": 1
}
],
"service_b": [
{
"host": "172.19.5.51",
"port": 50051,
"weight": 1
}
],
"service_c": [
{
"host": "127.0.0.1",
"port": 30511,
"weight": 1
},
{
"host": "127.0.0.1",
"port": 30512,
"weight": 1
}
]
}
}
```
### Show Dump File API
It offers another control api for dump file view now. Maybe would add more api for debugging in future.
```shell
GET /v1/discovery/consul/show_dump_file
```
For example:
```shell
curl http://127.0.0.1:9090/v1/discovery/consul/show_dump_file | jq
{
"services": {
"service_a": [
{
"host": "172.19.5.12",
"port": 8000,
"weight": 120
},
{
"host": "172.19.5.13",
"port": 8000,
"weight": 120
}
]
},
"expire": 0,
"last_update": 1615877468
}
```

View File

@@ -0,0 +1,314 @@
---
title: consul_kv
---
<!--
#
# 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.
#
-->
## Summary
For users that are using [nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module) and Consul KV as a service discovery, like the Weibo Mobile Team, this may be needed.
Thanks to @fatman-x guy, who developed this module, called `consul_kv`, and its worker process data flow is below:
![consul kv module data flow diagram](https://user-images.githubusercontent.com/548385/107141841-6ced3e00-6966-11eb-8aa4-bc790a4ad113.png)
## Configuration for discovery client
### Configuration for Consul KV
Add following configuration in `conf/config.yaml` :
```yaml
discovery:
consul_kv:
servers:
- "http://127.0.0.1:8500"
- "http://127.0.0.1:8600"
token: "..." # if your consul cluster has enabled acl access control, you need to specify the token
prefix: "upstreams"
skip_keys: # if you need to skip special keys
- "upstreams/unused_api/"
timeout:
connect: 1000 # default 2000 ms
read: 1000 # default 2000 ms
wait: 60 # default 60 sec
weight: 1 # default 1
fetch_interval: 5 # default 3 sec, only take effect for keepalive: false way
keepalive: true # default true, use the long pull way to query consul servers
default_server: # you can define default server when missing hit
host: "127.0.0.1"
port: 20999
metadata:
fail_timeout: 1 # default 1 ms
weight: 1 # default 1
max_fails: 1 # default 1
dump: # if you need, when registered nodes updated can dump into file
path: "logs/consul_kv.dump"
expire: 2592000 # unit sec, here is 30 day
```
And you can config it in short by default value:
```yaml
discovery:
consul_kv:
servers:
- "http://127.0.0.1:8500"
```
The `keepalive` has two optional values:
- `true`, default and recommend value, use the long pull way to query consul servers
- `false`, not recommend, it would use the short pull way to query consul servers, then you can set the `fetch_interval` for fetch interval
#### Dump Data
When we need reload `apisix` online, as the `consul_kv` module maybe loads data from CONSUL slower than load routes from ETCD, and would get the log at the moment before load successfully from consul:
```
http_access_phase(): failed to set upstream: no valid upstream node
```
So, we import the `dump` function for `consul_kv` module. When reload, would load the dump file before from consul; when the registered nodes in consul been updated, would dump the upstream nodes into file automatically.
The `dump` has three optional values now:
- `path`, the dump file save path
- support relative path, eg: `logs/consul_kv.dump`
- support absolute path, eg: `/tmp/consul_kv.bin`
- make sure the dump file's parent path exist
- make sure the `apisix` has the dump file's read-write access permission,eg: `chown www:root conf/upstream.d/`
- `load_on_init`, default value is `true`
- if `true`, just try to load the data from the dump file before loading data from consul when starting, does not care the dump file exists or not
- if `false`, ignore loading data from the dump file
- Whether `true` or `false`, we don't need to prepare a dump file for apisix at anytime
- `expire`, unit sec, avoiding load expired dump data when load
- default `0`, it is unexpired forever
- recommend 2592000, which is 30 days(equals 3600 \* 24 \* 30)
### Register Http API Services
Service register Key&Value template:
```
Key: {Prefix}/{Service_Name}/{IP}:{Port}
Value: {"weight": <Num>, "max_fails": <Num>, "fail_timeout": <Num>}
```
The register consul key use `upstreams` as prefix by default. The http api service name called `webpages` for example, and you can also use `webpages/oneteam/hello` as service name. The api instance of node's ip and port make up new key: `<IP>:<Port>`.
Now, register nodes into consul:
```shell
curl \
-X PUT \
-d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \
http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.19.5.12:8000
curl \
-X PUT \
-d ' {"weight": 1, "max_fails": 2, "fail_timeout": 1}' \
http://127.0.0.1:8500/v1/kv/upstreams/webpages/172.19.5.13:8000
```
In some case, same keys exist in different consul servers.
To avoid confusion, use the full consul key url path as service name in practice.
### Upstream setting
#### L7
Here is an example of routing a request with a URL of "/*" to a service which named "http://127.0.0.1:8500/v1/kv/upstreams/webpages/" and use consul_kv discovery client in the registry :
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/*",
"upstream": {
"service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/",
"type": "roundrobin",
"discovery_type": "consul_kv"
}
}'
```
The format response as below:
```json
{
"node": {
"value": {
"priority": 0,
"update_time": 1612755230,
"upstream": {
"discovery_type": "consul_kv",
"service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/",
"hash_on": "vars",
"type": "roundrobin",
"pass_host": "pass"
},
"id": "1",
"uri": "/*",
"create_time": 1612755230,
"status": 1
},
"key": "/apisix/routes/1"
}
}
```
You could find more usage in the `apisix/t/discovery/consul_kv.t` file.
#### L4
Consul_kv service discovery also supports use in L4, the configuration method is similar to L7.
```shell
$ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"remote_addr": "127.0.0.1",
"upstream": {
"scheme": "tcp",
"service_name": "http://127.0.0.1:8500/v1/kv/upstreams/webpages/",
"type": "roundrobin",
"discovery_type": "consul_kv"
}
}'
```
You could find more usage in the `apisix/t/discovery/stream/consul_kv.t` file.
## Debugging API
It also offers control api for debugging.
### Memory Dump API
```shell
GET /v1/discovery/consul_kv/dump
```
For example:
```shell
# curl http://127.0.0.1:9090/v1/discovery/consul_kv/dump | jq
{
"config": {
"fetch_interval": 3,
"timeout": {
"wait": 60,
"connect": 6000,
"read": 6000
},
"prefix": "upstreams",
"weight": 1,
"servers": [
"http://172.19.5.30:8500",
"http://172.19.5.31:8500"
],
"keepalive": true,
"default_service": {
"host": "172.19.5.11",
"port": 8899,
"metadata": {
"fail_timeout": 1,
"weight": 1,
"max_fails": 1
}
},
"skip_keys": [
"upstreams/myapi/gateway/apisix/"
]
},
"services": {
"http://172.19.5.31:8500/v1/kv/upstreams/webpages/": [
{
"host": "127.0.0.1",
"port": 30513,
"weight": 1
},
{
"host": "127.0.0.1",
"port": 30514,
"weight": 1
}
],
"http://172.19.5.30:8500/v1/kv/upstreams/1614480/grpc/": [
{
"host": "172.19.5.51",
"port": 50051,
"weight": 1
}
],
"http://172.19.5.30:8500/v1/kv/upstreams/webpages/": [
{
"host": "127.0.0.1",
"port": 30511,
"weight": 1
},
{
"host": "127.0.0.1",
"port": 30512,
"weight": 1
}
]
}
}
```
### Show Dump File API
It offers another control api for dump file view now. Maybe would add more api for debugging in future.
```shell
GET /v1/discovery/consul_kv/show_dump_file
```
For example:
```shell
curl http://127.0.0.1:9090/v1/discovery/consul_kv/show_dump_file | jq
{
"services": {
"http://172.19.5.31:8500/v1/kv/upstreams/1614480/webpages/": [
{
"host": "172.19.5.12",
"port": 8000,
"weight": 120
},
{
"host": "172.19.5.13",
"port": 8000,
"weight": 120
}
]
},
"expire": 0,
"last_update": 1615877468
}
```

View File

@@ -0,0 +1,72 @@
---
title: Control Plane Service Discovery
keywords:
- API Gateway
- Apache APISIX
- ZooKeeper
- Nacos
- APISIX-Seed
description: This documentation describes implementing service discovery through Nacos and ZooKeeper on the API Gateway APISIX Control Plane.
---
<!--
#
# 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.
#
-->
This document describes how to implement service discovery with Nacos and Zookeeper on the APISIX Control Plane.
## APISIX-Seed Architecture
Apache APISIX has supported Data Plane service discovery in the early days, and now APISIX also supports Control Plane service discovery through the [APISIX-Seed](https://github.com/api7/apisix-seed) project. The following figure shows the APISIX-Seed architecture diagram.
![control-plane-service-discovery](../../../assets/images/control-plane-service-discovery.png)
The specific information represented by the figures in the figure is as follows:
1. Register an upstream with APISIX and specify the service discovery type. APISIX-Seed will watch APISIX resource changes in etcd, filter discovery types, and obtain service names.
2. APISIX-Seed subscribes the specified service name to the service registry to obtain changes to the corresponding service.
3. After the client registers the service with the service registry, APISIX-Seed will obtain the new service information and write the updated service node into etcd;
4. When the corresponding resources in etcd change, APISIX worker will refresh the latest service node information to memory.
:::note
It should be noted that after the introduction of APISIX-Seed, if the service of the registry changes frequently, the data in etcd will also change frequently. So, it is best to set the `--auto-compaction` option when starting etcd to compress the history periodically to avoid etcd eventually exhausting its storage space. Please refer to [revisions](https://etcd.io/docs/v3.5/learning/api/#revisions).
:::
## Why APISIX-Seed
- Network topology becomes simpler
APISIX does not need to maintain a network connection with each registry, and only needs to pay attention to the configuration information in etcd. This will greatly simplify the network topology.
- Total data volume about upstream service becomes smaller
Due to the characteristics of the registry, APISIX may store the full amount of registry service data in the worker, such as consul_kv. By introducing APISIX-Seed, each process of APISIX will not need to additionally cache upstream service-related information.
- Easier to manage
Service discovery configuration needs to be configured once per APISIX instance. By introducing APISIX-Seed, Apache APISIX will be in different to the configuration changes of the service registry.
## Supported service registry
ZooKeeper and Nacos are currently supported, and more service registries will be supported in the future. For more information, please refer to: [APISIX Seed](https://github.com/api7/apisix-seed#apisix-seed-for-apache-apisix).
- If you want to enable control plane ZooKeeper service discovery, please refer to: [ZooKeeper Deployment Tutorial](https://github.com/api7/apisix-seed/blob/main/docs/en/latest/zookeeper.md).
- If you want to enable control plane Nacos service discovery, please refer to: [Nacos Deployment Tutorial](https://github.com/api7/apisix-seed/blob/main/docs/en/latest/nacos.md).

View File

@@ -0,0 +1,155 @@
---
title: DNS
---
<!--
#
# 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.
#
-->
## service discovery via DNS
Some service discovery system, like Consul, support exposing service information
via DNS. Therefore we can use this way to discover service directly. Both L4 and L7 are supported.
First of all, we need to configure the address of DNS servers:
```yaml
# add this to config.yaml
discovery:
dns:
servers:
- "127.0.0.1:8600" # use the real address of your dns server
```
Unlike configuring the domain in the Upstream's `nodes` field, service discovery via
DNS will return all records. For example, with upstream configuration:
```json
{
"id": 1,
"discovery_type": "dns",
"service_name": "test.consul.service",
"type": "roundrobin"
}
```
and `test.consul.service` be resolved as `1.1.1.1` and `1.1.1.2`, this result will be the same as:
```json
{
"id": 1,
"type": "roundrobin",
"nodes": [
{"host": "1.1.1.1", "weight": 1},
{"host": "1.1.1.2", "weight": 1}
]
}
```
Note that all the IPs from `test.consul.service` share the same weight.
The resolved records will be cached according to their TTL.
For service whose record is not in the cache, we will query it in the order of `SRV -> A -> AAAA -> CNAME` by default.
When we refresh the cache record, we will try from the last previously successful type.
We can also customize the order by modifying the configuration file.
```yaml
# add this to config.yaml
discovery:
dns:
servers:
- "127.0.0.1:8600" # use the real address of your dns server
order: # order in which to try different dns record types when resolving
- last # "last" will try the last previously successful type for a hostname.
- SRV
- A
- AAAA
- CNAME
```
If you want to specify the port for the upstream server, you can add it to the `service_name`:
```json
{
"id": 1,
"discovery_type": "dns",
"service_name": "test.consul.service:1980",
"type": "roundrobin"
}
```
Another way to do it is via the SRV record, see below.
### SRV record
By using SRV record you can specify the port and the weight of a service.
Assumed you have the SRV record like this:
```
; under the section of blah.service
A 300 IN A 1.1.1.1
B 300 IN A 1.1.1.2
B 300 IN A 1.1.1.3
; name TTL type priority weight port
srv 86400 IN SRV 10 60 1980 A
srv 86400 IN SRV 20 20 1981 B
```
Upstream configuration like:
```json
{
"id": 1,
"discovery_type": "dns",
"service_name": "srv.blah.service",
"type": "roundrobin"
}
```
is the same as:
```json
{
"id": 1,
"type": "roundrobin",
"nodes": [
{"host": "1.1.1.1", "port": 1980, "weight": 60, "priority": -10},
{"host": "1.1.1.2", "port": 1981, "weight": 10, "priority": -20},
{"host": "1.1.1.3", "port": 1981, "weight": 10, "priority": -20}
]
}
```
Note that two records of domain B split the weight evenly.
For SRV record, nodes with lower priority are chosen first, so the final priority is negative.
As for 0 weight SRV record, the [RFC 2782](https://www.ietf.org/rfc/rfc2782.txt) says:
> Domain administrators SHOULD use Weight 0 when there isn't any server
selection to do, to make the RR easier to read for humans (less
noisy). In the presence of records containing weights greater
than 0, records with weight 0 should have a very small chance of
being selected.
We treat weight 0 record has a weight of 1 so the node "have a very small chance of
being selected", which is also the common way to treat this type of record.
For SRV record which has port 0, we will fallback to use the upstream protocol's default port.
You can also specify the port in the "service_name" field directly, like "srv.blah.service:8848".

View File

@@ -0,0 +1,25 @@
---
title: eureka
---
<!--
#
# 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.
#
-->
Apache APISIX supports service discovery via Eureka. For the details, please start your
reading from [Supported discovery registries](../discovery.md#supported-discovery-registries).

View File

@@ -0,0 +1,406 @@
---
title: Kubernetes
keywords:
- Kubernetes
- Apache APISIX
- Service discovery
- Cluster
- API Gateway
description: This article introduce how to perform service discovery based on Kubernetes in Apache APISIX and summarize related issues.
---
<!--
#
# 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.
#
-->
## Summary
The [_Kubernetes_](https://kubernetes.io/) service discovery [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) real-time changes of [_Endpoints_](https://kubernetes.io/docs/concepts/services-networking/service/) resources, then store theirs value into `ngx.shared.DICT`.
Discovery also provides a node query interface in accordance with the [_APISIX Discovery Specification_](../discovery.md).
## How To Use
Kubernetes service discovery both support single-cluster and multi-cluster modes, applicable to the case where the service is distributed in single or multiple Kubernetes clusters.
### Single-Cluster Mode Configuration
A detailed configuration for single-cluster mode Kubernetes service discovery is as follows:
```yaml
discovery:
kubernetes:
service:
# apiserver schema, options [http, https]
schema: https #default https
# apiserver host, options [ipv4, ipv6, domain, environment variable]
host: ${KUBERNETES_SERVICE_HOST} #default ${KUBERNETES_SERVICE_HOST}
# apiserver port, options [port number, environment variable]
port: ${KUBERNETES_SERVICE_PORT} #default ${KUBERNETES_SERVICE_PORT}
client:
# serviceaccount token or token_file
token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#token: |-
# eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
# 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI
default_weight: 50 # weight assigned to each discovered endpoint. default 50, minimum 0
# kubernetes discovery support namespace_selector
# you can use one of [equal, not_equal, match, not_match] filter namespace
namespace_selector:
# only save endpoints with namespace equal default
equal: default
# only save endpoints with namespace not equal default
#not_equal: default
# only save endpoints with namespace match one of [default, ^my-[a-z]+$]
#match:
#- default
#- ^my-[a-z]+$
# only save endpoints with namespace not match one of [default, ^my-[a-z]+$ ]
#not_match:
#- default
#- ^my-[a-z]+$
# kubernetes discovery support label_selector
# for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
label_selector: |-
first="a",second="b"
# reserved lua shared memory size,1m memory can store about 1000 pieces of endpoint
shared_size: 1m #default 1m
# if watch_endpoint_slices setting true, watch apiserver with endpointslices instead of endpoints
watch_endpoint_slices: false #default false
```
If the Kubernetes service discovery runs inside a pod, you can use minimal configuration:
```yaml
discovery:
kubernetes: { }
```
If the Kubernetes service discovery runs outside a pod, you need to create or select a specified [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/), then get its token value, and use following configuration:
```yaml
discovery:
kubernetes:
service:
schema: https
host: # enter apiserver host value here
port: # enter apiserver port value here
client:
token: # enter serviceaccount token value here
#token_file: # enter file path here
```
### Single-Cluster Mode Query Interface
The Kubernetes service discovery provides a query interface in accordance with the [_APISIX Discovery Specification_](../discovery.md).
**function:**
nodes(service_name)
**description:**
nodes() function attempts to look up the ngx.shared.DICT for nodes corresponding to service_name, \
service_name should match pattern: _[namespace]/[name]:[portName]_
+ namespace: The namespace where the Kubernetes endpoints is located
+ name: The name of the Kubernetes endpoints
+ portName: The `ports.name` value in the Kubernetes endpoints, if there is no `ports.name`, use `targetPort`, `port` instead. If `ports.name` exists, then port number cannot be used.
**return value:**
if the Kubernetes endpoints value is as follows:
```yaml
apiVersion: v1
kind: Endpoints
metadata:
name: plat-dev
namespace: default
subsets:
- addresses:
- ip: "10.5.10.109"
- ip: "10.5.10.110"
ports:
- port: 3306
name: port
```
a nodes("default/plat-dev:port") call will get follow result:
```
{
{
host="10.5.10.109",
port= 3306,
weight= 50,
},
{
host="10.5.10.110",
port= 3306,
weight= 50,
},
}
```
### Multi-Cluster Mode Configuration
A detailed configuration for multi-cluster mode Kubernetes service discovery is as follows:
```yaml
discovery:
kubernetes:
- id: release # a custom name refer to the cluster, pattern ^[a-z0-9]{1,8}
service:
# apiserver schema, options [http, https]
schema: https #default https
# apiserver host, options [ipv4, ipv6, domain, environment variable]
host: "1.cluster.com"
# apiserver port, options [port number, environment variable]
port: "6443"
client:
# serviceaccount token or token_file
token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
#token: |-
# eyJhbGciOiJSUzI1NiIsImtpZCI6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEif
# 6Ikx5ME1DNWdnbmhQNkZCNlZYMXBsT3pYU3BBS2swYzBPSkN3ZnBESGpkUEEifeyJhbGciOiJSUzI1NiIsImtpZCI
default_weight: 50 # weight assigned to each discovered endpoint. default 50, minimum 0
# kubernetes discovery support namespace_selector
# you can use one of [equal, not_equal, match, not_match] filter namespace
namespace_selector:
# only save endpoints with namespace equal default
equal: default
# only save endpoints with namespace not equal default
#not_equal: default
# only save endpoints with namespace match one of [default, ^my-[a-z]+$]
#match:
#- default
#- ^my-[a-z]+$
# only save endpoints with namespace not match one of [default, ^my-[a-z]+$]
#not_match:
#- default
#- ^my-[a-z]+$
# kubernetes discovery support label_selector
# for the expression of label_selector, please refer to https://kubernetes.io/docs/concepts/overview/working-with-objects/labels
label_selector: |-
first="a",second="b"
# reserved lua shared memory size,1m memory can store about 1000 pieces of endpoint
shared_size: 1m #default 1m
# if watch_endpoint_slices setting true, watch apiserver with endpointslices instead of endpoints
watch_endpoint_slices: false #default false
```
Multi-Kubernetes service discovery does not fill default values for service and client fields, you need to fill them according to the cluster configuration.
### Multi-Cluster Mode Query Interface
The Kubernetes service discovery provides a query interface in accordance with the [_APISIX Discovery Specification_](../discovery.md).
**function:**
nodes(service_name)
**description:**
nodes() function attempts to look up the ngx.shared.DICT for nodes corresponding to service_name, \
service_name should match pattern: _[id]/[namespace]/[name]:[portName]_
+ id: value defined in service discovery configuration
+ namespace: The namespace where the Kubernetes endpoints is located
+ name: The name of the Kubernetes endpoints
+ portName: The `ports.name` value in the Kubernetes endpoints, if there is no `ports.name`, use `targetPort`, `port` instead. If `ports.name` exists, then port number cannot be used.
**return value:**
if the Kubernetes endpoints value is as follows:
```yaml
apiVersion: v1
kind: Endpoints
metadata:
name: plat-dev
namespace: default
subsets:
- addresses:
- ip: "10.5.10.109"
- ip: "10.5.10.110"
ports:
- port: 3306
name: port
```
a nodes("release/default/plat-dev:port") call will get follow result:
```
{
{
host="10.5.10.109",
port= 3306,
weight= 50,
},
{
host="10.5.10.110",
port= 3306,
weight= 50,
},
}
```
## Q&A
**Q: Why only support configuration token to access _Kubernetes APIServer_?**
A: Usually, we will use three ways to complete the authentication of _Kubernetes APIServer_:
+ mTLS
+ Token
+ Basic authentication
Because lua-resty-http does not currently support mTLS, and basic authentication is not recommended, so currently only the token authentication method is implemented.
**Q: APISIX inherits Nginx's multiple process model, does it mean that each nginx worker process will [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) kubernetes endpoints resources?**
A: The Kubernetes service discovery only uses privileged processes to [_List-Watch_](https://kubernetes.io/docs/reference/using-api/api-concepts/) Kubernetes endpoints resources, then store theirs value into `ngx.shared.DICT`, worker processes get results by querying `ngx.shared.DICT`.
**Q: What permissions do [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) require?**
A: ServiceAccount requires the permissions of cluster-level [ get, list, watch ] endpoints resources, the declarative definition is as follows:
```yaml
kind: ServiceAccount
apiVersion: v1
metadata:
name: apisix-test
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: apisix-test
rules:
- apiGroups: [ "" ]
resources: [ endpoints,endpointslices ]
verbs: [ get,list,watch ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: apisix-test
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: apisix-test
subjects:
- kind: ServiceAccount
name: apisix-test
namespace: default
```
**Q: How to get [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) token value?**
A: Assume your [_ServiceAccount_](https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/) located in namespace apisix and name is Kubernetes-discovery, you can use the following steps to get token value.
1. Get secret name. You can execute the following command, the output of the first column is the secret name we want:
```shell
kubectl -n apisix get secrets | grep kubernetes-discovery
```
2. Get token value. Assume secret resources name is kubernetes-discovery-token-c64cv, you can execute the following command, the output is the service account token value we want:
```shell
kubectl -n apisix get secret kubernetes-discovery-token-c64cv -o jsonpath={.data.token} | base64 -d
```
## Debugging API
It also offers control api for debugging.
### Memory Dump API
To query/list the nodes discoverd by kubernetes discovery, you can query the /v1/discovery/kubernetes/dump control API endpoint like so:
```shell
GET /v1/discovery/kubernetes/dump
```
Which will yield the following response:
```
{
"endpoints": [
{
"endpoints": [
{
"value": "{\"https\":[{\"host\":\"172.18.164.170\",\"port\":6443,\"weight\":50},{\"host\":\"172.18.164.171\",\"port\":6443,\"weight\":50},{\"host\":\"172.18.164.172\",\"port\":6443,\"weight\":50}]}",
"name": "default/kubernetes"
},
{
"value": "{\"metrics\":[{\"host\":\"172.18.164.170\",\"port\":2379,\"weight\":50},{\"host\":\"172.18.164.171\",\"port\":2379,\"weight\":50},{\"host\":\"172.18.164.172\",\"port\":2379,\"weight\":50}]}",
"name": "kube-system/etcd"
},
{
"value": "{\"http-85\":[{\"host\":\"172.64.89.2\",\"port\":85,\"weight\":50}]}",
"name": "test-ws/testing"
}
],
"id": "first"
}
],
"config": [
{
"default_weight": 50,
"id": "first",
"client": {
"token": "xxx"
},
"service": {
"host": "172.18.164.170",
"port": "6443",
"schema": "https"
},
"shared_size": "1m"
}
]
}
```

View File

@@ -0,0 +1,280 @@
---
title: nacos
---
<!--
#
# 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.
#
-->
## Service discovery via Nacos
The performance of this module needs to be improved:
1. send the request parallelly.
### Configuration for Nacos
Add following configuration in `conf/config.yaml` :
```yaml
discovery:
nacos:
host:
- "http://${username}:${password}@${host1}:${port1}"
prefix: "/nacos/v1/"
fetch_interval: 30 # default 30 sec
# `weight` is the `default_weight` that will be attached to each discovered node that
# doesn't have a weight explicitly provided in nacos results
weight: 100 # default 100
timeout:
connect: 2000 # default 2000 ms
send: 2000 # default 2000 ms
read: 5000 # default 5000 ms
```
And you can config it in short by default value:
```yaml
discovery:
nacos:
host:
- "http://192.168.33.1:8848"
```
### Upstream setting
#### L7
Here is an example of routing a request with an URI of "/nacos/*" to a service which named "http://192.168.33.1:8848/nacos/v1/ns/instance/list?serviceName=APISIX-NACOS" and use nacos discovery client in the registry:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/nacos/*",
"upstream": {
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos"
}
}'
```
The formatted response as below:
```json
{
"node": {
"key": "\/apisix\/routes\/1",
"value": {
"id": "1",
"create_time": 1615796097,
"status": 1,
"update_time": 1615799165,
"upstream": {
"hash_on": "vars",
"pass_host": "pass",
"scheme": "http",
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos"
},
"priority": 0,
"uri": "\/nacos\/*"
}
}
}
```
#### L4
Nacos service discovery also supports use in L4, the configuration method is similar to L7.
```shell
$ curl http://127.0.0.1:9180/apisix/admin/stream_routes/1 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"remote_addr": "127.0.0.1",
"upstream": {
"scheme": "tcp",
"discovery_type": "nacos",
"service_name": "APISIX-NACOS",
"type": "roundrobin"
}
}'
```
### discovery_args
| Name | Type | Requirement | Default | Valid | Description |
| ------------ | ------ | ----------- | ------- | ----- | ------------------------------------------------------------ |
| namespace_id | string | optional | public | | This parameter is used to specify the namespace of the corresponding service |
| group_name | string | optional | DEFAULT_GROUP | | This parameter is used to specify the group of the corresponding service |
#### Specify the namespace
Example of routing a request with an URI of "/nacosWithNamespaceId/*" to a service with name, namespaceId "http://192.168.33.1:8848/nacos/v1/ns/instance/list?serviceName=APISIX-NACOS&namespaceId=test_ns" and use nacos discovery client in the registry:
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/2 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/nacosWithNamespaceId/*",
"upstream": {
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "test_ns"
}
}
}'
```
The formatted response as below:
```json
{
"node": {
"key": "\/apisix\/routes\/2",
"value": {
"id": "2",
"create_time": 1615796097,
"status": 1,
"update_time": 1615799165,
"upstream": {
"hash_on": "vars",
"pass_host": "pass",
"scheme": "http",
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "test_ns"
}
},
"priority": 0,
"uri": "\/nacosWithNamespaceId\/*"
}
}
}
```
#### Specify the group
Example of routing a request with an URI of "/nacosWithGroupName/*" to a service with name, groupName "http://192.168.33.1:8848/nacos/v1/ns/instance/list?serviceName=APISIX-NACOS&groupName=test_group" and use nacos discovery client in the registry:
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/3 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/nacosWithGroupName/*",
"upstream": {
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"group_name": "test_group"
}
}
}'
```
The formatted response as below:
```json
{
"node": {
"key": "\/apisix\/routes\/3",
"value": {
"id": "3",
"create_time": 1615796097,
"status": 1,
"update_time": 1615799165,
"upstream": {
"hash_on": "vars",
"pass_host": "pass",
"scheme": "http",
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"group_name": "test_group"
}
},
"priority": 0,
"uri": "\/nacosWithGroupName\/*"
}
}
}
```
#### Specify the namespace and group
Example of routing a request with an URI of "/nacosWithNamespaceIdAndGroupName/*" to a service with name, namespaceId, groupName "http://192.168.33.1:8848/nacos/v1/ns/instance/list?serviceName=APISIX-NACOS&namespaceId=test_ns&groupName=test_group" and use nacos discovery client in the registry:
```shell
$ curl http://127.0.0.1:9180/apisix/admin/routes/4 -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"uri": "/nacosWithNamespaceIdAndGroupName/*",
"upstream": {
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "test_ns",
"group_name": "test_group"
}
}
}'
```
The formatted response as below:
```json
{
"node": {
"key": "\/apisix\/routes\/4",
"value": {
"id": "4",
"create_time": 1615796097,
"status": 1,
"update_time": 1615799165,
"upstream": {
"hash_on": "vars",
"pass_host": "pass",
"scheme": "http",
"service_name": "APISIX-NACOS",
"type": "roundrobin",
"discovery_type": "nacos",
"discovery_args": {
"namespace_id": "test_ns",
"group_name": "test_group"
}
},
"priority": 0,
"uri": "\/nacosWithNamespaceIdAndGroupName\/*"
}
}
}
```

View File

@@ -0,0 +1,204 @@
---
title: HMAC Generate Signature Examples
---
<!--
#
# 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.
#
-->
## Python 3
```python
import base64
import hashlib
import hmac
secret = bytes('the shared secret key here', 'utf-8')
message = bytes('this is signature string', 'utf-8')
hash = hmac.new(secret, message, hashlib.sha256)
# to lowercase hexits
hash.hexdigest()
# to lowercase base64
base64.b64encode(hash.digest())
```
## Java
```java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.xml.bind.DatatypeConverter;
class Main {
public static void main(String[] args) {
try {
String secret = "the shared secret key here";
String message = "this is signature string";
Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes());
// to lowercase hexits
DatatypeConverter.printHexBinary(hash);
// to base64
DatatypeConverter.printBase64Binary(hash);
}
catch (NoSuchAlgorithmException e) {}
catch (InvalidKeyException e) {}
}
}
```
## Go
```go
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
)
func main() {
secret := []byte("the shared secret key here")
message := []byte("this is signature string")
hash := hmac.New(sha256.New, secret)
hash.Write(message)
// to lowercase hexits
hex.EncodeToString(hash.Sum(nil))
// to base64
base64.StdEncoding.EncodeToString(hash.Sum(nil))
}
```
## Ruby
```ruby
require 'base64'
require 'openssl'
secret = 'the shared secret key here'
message = 'this is signature string'
# to lowercase hexits
OpenSSL::HMAC.hexdigest('sha256', secret, message)
# to base64
Base64.encode64(OpenSSL::HMAC.digest('sha256', secret, message))
```
## NodeJs
```js
var crypto = require('crypto');
var secret = 'the shared secret key here';
var message = 'this is signature string';
var hash = crypto.createHmac('sha256', secret).update(message);
// to lowercase hexits
hash.digest('hex');
// to base64
hash.digest('base64');
```
## JavaScript ES6
```js
const secret = 'the shared secret key here';
const message = 'this is signature string';
const getUtf8Bytes = str =>
new Uint8Array(
[...unescape(encodeURIComponent(str))].map(c => c.charCodeAt(0))
);
const secretBytes = getUtf8Bytes(secret);
const messageBytes = getUtf8Bytes(message);
const cryptoKey = await crypto.subtle.importKey(
'raw', secretBytes, { name: 'HMAC', hash: 'SHA-256' },
true, ['sign']
);
const sig = await crypto.subtle.sign('HMAC', cryptoKey, messageBytes);
// to lowercase hexits
[...new Uint8Array(sig)].map(b => b.toString(16).padStart(2, '0')).join('');
// to base64
btoa(String.fromCharCode(...new Uint8Array(sig)));
```
## PHP
```php
<?php
$secret = 'the shared secret key here';
$message = 'this is signature string';
// to lowercase hexits
hash_hmac('sha256', $message, $secret);
// to base64
base64_encode(hash_hmac('sha256', $message, $secret, true));
```
## Lua
```lua
local hmac = require("resty.hmac")
local secret = 'the shared secret key here'
local message = 'this is signature string'
local digest = hmac:new(secret, hmac.ALGOS.SHA256):final(message)
--to lowercase hexits
ngx.say(digest)
--to base64
ngx.say(ngx.encode_base64(digest))
```
## Shell
```bash
SECRET="the shared secret key here"
MESSAGE="this is signature string"
# to lowercase hexits
echo -e $MESSAGE | openssl dgst -sha256 -hmac $SECRET
# to base64
echo -e $MESSAGE | openssl dgst -sha256 -hmac $SECRET -binary | base64
```

View File

@@ -0,0 +1,122 @@
---
title: External Plugin
---
<!--
#
# 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.
#
-->
## What are external plugin and plugin runner
APISIX supports writing plugins in Lua. This type of plugin will be executed
inside APISIX. Sometimes you want to develop plugins in other languages, so APISIX
provides sidecars that load your plugins and run them when the requests hit
APISIX. These sidecars are called plugin runners and your plugins are called
external plugins.
## How does it work
![external-plugin](../../assets/images/external-plugin.png)
When you configure a plugin runner in APISIX, APISIX will run the plugin runner
as a subprocess. The process will belong to the same user of the APISIX
process. When we restart or reload APISIX, the plugin runner will be restarted too.
Once you have configured `ext-plugin-*` plugins for a given route, the requests
which hit the route will trigger RPC call from APISIX to the plugin runner via
unix socket.
The plugin runner will handle the RPC call, create a fake request at its side,
run external plugins and return the result back to APISIX.
The target external plugins and the execution order are configured in the `ext-plugin-*`
plugins. Like other plugins, they can be enabled and reconfigured on the fly.
## How is it implemented
If you are interested in the implementation of Plugin Runner, please refer to [The Implementation of Plugin Runner](./internal/plugin-runner.md).
## Supported plugin runners
- Java: https://github.com/apache/apisix-java-plugin-runner
- Go: https://github.com/apache/apisix-go-plugin-runner
- Python: https://github.com/apache/apisix-python-plugin-runner
- JavaScript: https://github.com/zenozeng/apisix-javascript-plugin-runner
## Configuration for plugin runner in APISIX
To run the plugin runner in the prod, add the section below to `config.yaml`:
```yaml
ext-plugin:
cmd: ["blah"] # replace it to the real runner executable according to the runner you choice
```
Then APISIX will manage the runner as its subprocess.
Note: APISIX can't manage the runner on the Mac in `v2.6`.
During development, we want to run the runner separately so that we can restart it without
restarting APISIX first.
By specifying the environment variable `APISIX_LISTEN_ADDRESS`, we can force the runner to
listen to a fixed address.
For instance:
```bash
APISIX_LISTEN_ADDRESS=unix:/tmp/x.sock ./the_runner
```
will force the runner to listen to `/tmp/x.sock`.
Then you need to configure APISIX to send RPC to the fixed address:
```yaml
ext-plugin:
# cmd: ["blah"] # don't configure the executable!
path_for_test: "/tmp/x.sock" # without 'unix:' prefix
```
In the prod environment, `path_for_test` should not be used and the unix socket
path will be generated dynamically.
## FAQ
### When managing by APISIX, the runner can't access my environment variable
Since `v2.7`, APISIX can pass environment variables to the runner.
However, Nginx will hide all environment variables by default. So you need to
declare your variable first in the `conf/config.yaml`:
```yaml
nginx_config:
envs:
- MY_ENV_VAR
```
### APISIX terminates my runner with SIGKILL but not SIGTERM!
Since `v2.7`, APISIX will stop the runner with SIGTERM when it is running on
OpenResty 1.19+.
However, APISIX needs to wait for the runner to quit so that we can ensure the resource
for the process group is freed.
Therefore, we send SIGTERM first. And then after 1 second, if the runner is still
running, we will send SIGKILL.

View File

@@ -0,0 +1,71 @@
---
title: Get APISIX
description: This tutorial uses a script to quickly install Apache APISIX in your local environment and verify it through the Admin API.
---
<head>
<link rel="canonical" href="https://docs.api7.ai/apisix/getting-started/" />
</head>
> The Getting Started tutorials are contributed by [API7.ai](https://api7.ai/).
Developed and donated by API7.ai, Apache APISIX is an open source, dynamic, scalable, and high-performance cloud native API gateway for all your APIs and microservices. It is a [top-level project](https://projects.apache.org/project.html?apisix) of the Apache Software Foundation.
You can use APISIX API Gateway as a traffic entrance to process all business data. It offers features including dynamic routing, dynamic upstream, dynamic certificates, A/B testing, canary release, blue-green deployment, limit rate, defense against malicious attacks, metrics, monitoring alarms, service observability, service governance, and more.
This tutorial uses a script to quickly install [Apache APISIX](https://api7.ai/apisix) in your local environment and verifies the installation through the Admin API.
## Prerequisite(s)
The quickstart script relies on several components:
* [Docker](https://docs.docker.com/get-docker/) is used to install the containerized **etcd** and **APISIX**.
* [curl](https://curl.se/) is used to send requests to APISIX for validation.
## Get APISIX
:::caution
To provide a better experience in this tutorial, the authorization of Admin API is switched off by default. Please turn on the authorization of Admin API in the production environment.
:::
APISIX can be easily installed and started with the quickstart script:
```shell
curl -sL https://run.api7.ai/apisix/quickstart | sh
```
The script should start two Docker containers, _apisix-quickstart_ and _etcd_. APISIX uses etcd to save and synchronize configurations. Both the etcd and the APISIX use [**host**](https://docs.docker.com/network/host/) Docker network mode. That is, the APISIX can be accessed from local.
You will see the following message once APISIX is ready:
```text
✔ APISIX is ready!
```
## Validate
Once APISIX is running, you can use curl to interact with it. Send a simple HTTP request to validate if APISIX is working properly:
```shell
curl "http://127.0.0.1:9080" --head | grep Server
```
If everything is ok, you will get the following response:
```text
Server: APISIX/Version
```
`Version` refers to the version of APISIX that you have installed. For example, `APISIX/3.3.0`.
You now have APISIX installed and running successfully!
## Next Steps
The following tutorial is based on the working APISIX, please keep everything running and move on to the next step.
* [Configure Routes](configure-routes.md)
* [Load Balancing](load-balancing.md)
* [Rate Limiting](rate-limiting.md)
* [Key Authentication](key-authentication.md)

View File

@@ -0,0 +1,73 @@
---
title: Configure Routes
slug: /getting-started/configure-routes
---
<head>
<link rel="canonical" href="https://docs.api7.ai/apisix/getting-started/configure-routes" />
</head>
> The Getting Started tutorials are contributed by [API7.ai](https://api7.ai/).
Apache APISIX provides flexible gateway management capabilities based on _routes_, where routing paths and targets are defined for requests.
This tutorial guides you on how to create a route and validate it. You will complete the following steps:
1. Create a route with a sample _upstream_ that points to [httpbin.org](http://httpbin.org).
2. Use _cURL_ to send a test request to see how APISIX proxies and forwards the request.
## What is a Route
A route is a routing path to upstream targets. In [Apache APISIX](https://api7.ai/apisix), routes are responsible for matching client's requests based on defined rules, loading and executing the corresponding plugins, as well as forwarding requests to the specified upstream services.
In APISIX, a simple route can be set up with a path-matching URI and a corresponding upstream address.
## What is an Upstream
An upstream is a set of target nodes with the same work. It defines a virtual host abstraction that performs load balancing on a given set of service nodes according to the configured rules.
## Prerequisite(s)
1. Complete [Get APISIX](./README.md) to install APISIX.
## Create a Route
In this section, you will create a route that forwards client requests to [httpbin.org](http://httpbin.org), a public HTTP request and response service.
The following command creates a route, which should forward all requests sent to `http://127.0.0.1:9080/ip` to [httpbin.org/ip](http://httpbin.org/ip):
[//]: <TODO: Add the link to the authorization of Admin API>
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "getting-started-ip",
"uri": "/ip",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
You will receive an `HTTP/1.1 201 Created` response if the route was created successfully.
## Validate
```shell
curl "http://127.0.0.1:9080/ip"
```
The expected response is similar to the following:
```text
{
"origin": "183.94.122.205"
}
```
## What's Next
This tutorial creates a route with only one target node. In the next tutorial, you will learn how to configure load balancing with multiple target nodes.

View File

@@ -0,0 +1,184 @@
---
title: Key Authentication
slug: /getting-started/key-authentication
---
<head>
<link rel="canonical" href="https://docs.api7.ai/apisix/getting-started/key-authentication" />
</head>
> The Getting Started tutorials are contributed by [API7.ai](https://api7.ai/).
An API gateway's primary role is to connect API consumers and providers. For security reasons, it should authenticate and authorize consumers before access to internal resources.
![Key Authentication](https://static.apiseven.com/uploads/2023/02/08/8mRaK3v1_consumer.png)
APISIX has a flexible plugin extension system and a number of existing plugins for user authentication and authorization. For example:
- [Key Authentication](https://apisix.apache.org/docs/apisix/plugins/key-auth/)
- [Basic Authentication](https://apisix.apache.org/docs/apisix/plugins/basic-auth/)
- [JSON Web Token (JWT) Authentication](https://apisix.apache.org/docs/apisix/plugins/jwt-auth/)
- [Keycloak](https://apisix.apache.org/docs/apisix/plugins/authz-keycloak/)
- [Casdoor](https://apisix.apache.org/docs/apisix/plugins/authz-casdoor/)
- [Wolf RBAC](https://apisix.apache.org/docs/apisix/plugins/wolf-rbac/)
- [OpenID Connect](https://apisix.apache.org/docs/apisix/plugins/openid-connect/)
- [Central Authentication Service (CAS)](https://apisix.apache.org/docs/apisix/plugins/cas-auth/)
- [HMAC](https://apisix.apache.org/docs/apisix/plugins/hmac-auth/)
- [Casbin](https://apisix.apache.org/docs/apisix/plugins/authz-casbin/)
- [LDAP](https://apisix.apache.org/docs/apisix/plugins/ldap-auth/)
- [Open Policy Agent (OPA)](https://apisix.apache.org/docs/apisix/plugins/opa/)
- [Forward Authentication](https://apisix.apache.org/docs/apisix/plugins/forward-auth/)
- [Multiple Authentications](https://apisix.apache.org/docs/apisix/plugins/multi-auth/)
In this tutorial, you will create a _consumer_ with _key authentication_, and learn how to enable and disable key authentication.
## What is a Consumer
A Consumer is an application or a developer who consumes the API.
In APISIX, a Consumer requires a unique _username_ and an authentication _plugin_ from the list above to be created.
## What is Key Authentication
Key authentication is a relatively simple but widely used authentication approach. The idea is as follows:
1. Administrator adds an authentication key (API key) to the Route.
2. API consumers add the key to the query string or headers for authentication when sending requests.
## Enable Key Authentication
### Prerequisite(s)
1. Complete [Get APISIX](./README.md) to install APISIX.
2. Complete [Configure Routes](./configure-routes.md#what-is-a-route).
### Create a Consumer
Let's create a consumer named `tom` and enable the `key-auth` plugin with an API key `secret-key`. All requests sent with the key `secret-key` should be authenticated as `tom`.
:::caution
Please use a complex key in the Production environment.
:::
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT -d '
{
"username": "tom",
"plugins": {
"key-auth": {
"key": "secret-key"
}
}
}'
```
You will receive an `HTTP/1.1 201 Created` response if the consumer was created successfully.
### Enable Authentication
Inheriting the route `getting-started-ip` from [Configure Routes](./configure-routes.md), we only need to use the `PATCH` method to add the `key-auth` plugin to the route:
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/routes/getting-started-ip" -X PATCH -d '
{
"plugins": {
"key-auth": {}
}
}'
```
You will receive an `HTTP/1.1 201 Created` response if the plugin was added successfully.
### Validate
Let's validate the authentication in the following scenarios:
#### 1. Send a request without any key
Send a request without the `apikey` header.
```shell
curl -i "http://127.0.0.1:9080/ip"
```
Since you enabled the key authentication, you will receive an unauthorized response with `HTTP/1.1 401 Unauthorized`.
```text
HTTP/1.1 401 Unauthorized
Date: Wed, 08 Feb 2023 09:38:36 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.1.0
```
#### 2. Send a request with a wrong key
Send a request with a wrong key (e.g. `wrong-key`) in the `apikey` header.
```shell
curl -i "http://127.0.0.1:9080/ip" -H 'apikey: wrong-key'
```
Since the key is incorrect, you will receive an unauthorized response with `HTTP/1.1 401 Unauthorized`.
```text
HTTP/1.1 401 Unauthorized
Date: Wed, 08 Feb 2023 09:38:27 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.1.0
```
#### 3. Send a request with the correct key
Send a request with the correct key (`secret-key`) in the `apikey` header.
```shell
curl -i "http://127.0.0.1:9080/ip" -H 'apikey: secret-key'
```
You will receive an `HTTP/1.1 200 OK` response.
```text
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 44
Connection: keep-alive
Date: Thu, 09 Feb 2023 03:27:57 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.1.0
```
### Disable Authentication
Disable the key authentication plugin by setting the `_meta.disable` parameter to `true`.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/getting-started-ip" -X PATCH -d '
{
"plugins": {
"key-auth": {
"_meta": {
"disable": true
}
}
}
}'
```
You can send a request without any key to validate:
```shell
curl -i "http://127.0.0.1:9080/ip"
```
Because you have disabled the key authentication plugin, you will receive an `HTTP/1.1 200 OK` response.
## What's Next
You have learned how to configure key authentication for a route. In the next tutorial, you will learn how to configure rate limiting.

View File

@@ -0,0 +1,99 @@
---
title: Load Balancing
slug: /getting-started/load-balancing
---
<head>
<link rel="canonical" href="https://docs.api7.ai/apisix/getting-started/load-balancing" />
</head>
> The Getting Started tutorials are contributed by [API7.ai](https://api7.ai/).
Load balancing manages traffic between clients and servers. It is a mechanism used to decide which server handles a specific request, allowing for improved performance, scalability, and reliability. Load balancing is a key consideration in designing systems that need to handle a large volume of traffic.
Apache APISIX supports weighted round-robin load balancing, in which incoming traffic are distributed across a set of servers in a cyclical pattern, with each server taking a turn in a predefined order.
In this tutorial, you will create a route with two upstream services and enable round-robin load balancing to distribute traffic between the two services.
## Prerequisite(s)
1. Complete [Get APISIX](./README.md) to install APISIX.
2. Understand APISIX [Route and Upstream](./configure-routes.md#what-is-a-route).
## Enable Load Balancing
Let's create a route with two upstream services. All requests sent to the `/headers` endpoint will be forwarded to [httpbin.org](https://httpbin.org/headers) and [mock.api7.ai](https://mock.api7.ai/headers), which should echo back the requester's headers.
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "getting-started-headers",
"uri": "/headers",
"upstream" : {
"type": "roundrobin",
"nodes": {
"httpbin.org:443": 1,
"mock.api7.ai:443": 1
},
"pass_host": "node",
"scheme": "https"
}
}'
```
You will receive an `HTTP/1.1 201 Created` response if the route was created successfully.
:::info
1. The `pass_host` field is set to `node` to pass the host header to the upstream.
2. The `scheme` field is set to `https` to enable TLS when sending requests to the upstream.
:::
## Validate
The two services respond with different data.
From `httpbin.org`:
```json
{
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.58.0",
"X-Amzn-Trace-Id": "Root=1-63e34b15-19f666602f22591b525e1e80",
"X-Forwarded-Host": "localhost"
}
}
```
From `mock.api7.ai`:
```json
{
"headers": {
"accept": "*/*",
"host": "mock.api7.ai",
"user-agent": "curl/7.58.0",
"content-type": "application/json",
"x-application-owner": "API7.ai"
}
}
```
Let's generate 100 requests to test the load-balancing effect:
```shell
hc=$(seq 100 | xargs -I {} curl "http://127.0.0.1:9080/headers" -sL | grep "httpbin" | wc -l); echo httpbin.org: $hc, mock.api7.ai: $((100 - $hc))
```
The result shows the requests were distributed over the two services almost equally:
```text
httpbin.org: 51, mock.api7.ai: 49
```
## What's Next
You have learned how to configure load balancing. In the next tutorial, you will learn how to configure key authentication.

View File

@@ -0,0 +1,104 @@
---
title: Rate Limiting
slug: /getting-started/rate-limiting
---
<head>
<link rel="canonical" href="https://docs.api7.ai/apisix/getting-started/rate-limiting" />
</head>
> The Getting Started tutorials are contributed by [API7.ai](https://api7.ai/).
APISIX is a unified control point, managing the ingress and egress of APIs and microservices traffic. In addition to the legitimate client requests, these requests may also include unwanted traffic generated by web crawlers as well as cyber attacks, such as DDoS.
APISIX offers rate limiting capabilities to protect APIs and microservices by limiting the number of requests sent to upstream services in a given period of time. The count of requests is done efficiently in memory with low latency and high performance.
<br />
<div style={{textAlign: 'center'}}>
<img src="https://static.apiseven.com/uploads/2023/02/20/l9G9Kq41_rate-limiting.png" alt="Routes Diagram" />
</div>
<br />
In this tutorial, you will enable the `limit-count` plugin to set a rate limiting constraint on the incoming traffic.
## Prerequisite(s)
1. Complete the [Get APISIX](./README.md) step to install APISIX first.
2. Complete the [Configure Routes](./configure-routes.md#what-is-a-route) step.
## Enable Rate Limiting
The following route `getting-started-ip` is inherited from [Configure Routes](./configure-routes.md). You only need to use the `PATCH` method to add the `limit-count` plugin to the route:
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/routes/getting-started-ip" -X PATCH -d '
{
"plugins": {
"limit-count": {
"count": 2,
"time_window": 10,
"rejected_code": 503
}
}
}'
```
You will receive an `HTTP/1.1 201 Created` response if the plugin was added successfully. The above configuration limits the incoming requests to a maximum of 2 requests within 10 seconds.
### Validate
Let's generate 100 simultaneous requests to see the rate limiting plugin in effect.
```shell
count=$(seq 100 | xargs -I {} curl "http://127.0.0.1:9080/ip" -I -sL | grep "503" | wc -l); echo \"200\": $((100 - $count)), \"503\": $count
```
The results are as expected: out of the 100 requests, 2 requests were sent successfully (status code `200`) while the others were rejected (status code `503`).
```text
"200": 2, "503": 98
```
## Disable Rate Limiting
Disable rate limiting by setting the `_meta.disable` parameter to `true`:
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/routes/getting-started-ip" -X PATCH -d '
{
"plugins": {
"limit-count": {
"_meta": {
"disable": true
}
}
}
}'
```
### Validate
Let's generate 100 requests again to validate if it is disabled:
```shell
count=$(seq 100 | xargs -i curl "http://127.0.0.1:9080/ip" -I -sL | grep "503" | wc -l); echo \"200\": $((100 - $count)), \"503\": $count
```
The results below show that all of the requests were sent successfully:
```text
"200": 100, "503": 0
```
## More
[//]: <TODO: Add the link to matching rules configuration>
[//]: <TODO: Add the link to cluster-level rate limiting>
[//]: <TODO: Add the link to APISIX variables>
You can use the APISIX variables to configure fined matching rules of rate limiting, such as `$host` and `$uri`. In addition, APISIX also supports rate limiting at the cluster level using Redis.
## What's Next
Congratulations! You have learned how to configure rate limiting and completed the Getting Started tutorials.
You can continue to explore other documentations to customize APISIX and meet your production needs.

View File

@@ -0,0 +1,122 @@
---
title: gRPC Proxy
---
<!--
#
# 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.
#
-->
proxying gRPC traffic:
gRPC client -> APISIX -> gRPC/gRPCS server
## Parameters
* `scheme`: the `scheme` of the route's upstream must be `grpc` or `grpcs`.
* `uri`: format likes /service/method, Example/helloworld.Greeter/SayHello
### Example
#### create proxying gRPC route
Here's an example, to proxying gRPC service by specified route:
* attention: the `scheme` of the route's upstream must be `grpc` or `grpcs`.
* attention: APISIX use TLSencrypted HTTP/2 to expose gRPC service, so need to [config SSL certificate](certificate.md)
* attention: APISIX also support to expose gRPC service with plaintext HTTP/2, which does not rely on TLS, usually used to proxy gRPC service in intranet environment
* the grpc server example[grpc_server_example](https://github.com/api7/grpc_server_example)
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["POST", "GET"],
"uri": "/helloworld.Greeter/SayHello",
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
}
}
}'
```
#### testing HTTP/2 with TLSencrypted
Invoking the route created before
```shell
$ grpcurl -insecure -import-path /pathtoprotos -proto helloworld.proto -d '{"name":"apisix"}' 127.0.0.1:9443 helloworld.Greeter.SayHello
{
"message": "Hello apisix"
}
```
> grpcurl is a CLI tool, similar to curl, that acts as a gRPC client and lets you interact with a gRPC server. For installation, please check out the official [documentation](https://github.com/fullstorydev/grpcurl#installation).
This means that the proxying is working.
#### testing HTTP/2 with plaintext
By default, the APISIX only listens to `9443` for TLSencrypted HTTP/2. You can support HTTP/2 with plaintext via the `node_listen` section under `apisix` in `conf/config.yaml`:
```yaml
apisix:
node_listen:
- port: 9080
- port: 9081
enable_http2: true
```
Invoking the route created before
```shell
$ grpcurl -plaintext -import-path /pathtoprotos -proto helloworld.proto -d '{"name":"apisix"}' 127.0.0.1:9081 helloworld.Greeter.SayHello
{
"message": "Hello apisix"
}
```
This means that the proxying is working.
### gRPCS
If your gRPC service encrypts with TLS by itself (so called `gPRCS`, gPRC + TLS), you need to change the `scheme` to `grpcs`. The example above runs gRPCS service on port 50052, to proxy gRPC request, we need to use the configuration below:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["POST", "GET"],
"uri": "/helloworld.Greeter/SayHello",
"upstream": {
"scheme": "grpcs",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50052": 1
}
}
}'
```

View File

@@ -0,0 +1,186 @@
---
title: HTTP/3 Protocol
---
<!--
#
# 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.
#
-->
[HTTP/3](https://en.wikipedia.org/wiki/HTTP/3) is the third major version of the Hypertext Transfer Protocol (HTTP). Unlike its predecessors which rely on TCP, HTTP/3 is based on [QUIC (Quick UDP Internet Connections) protocol](https://en.wikipedia.org/wiki/QUIC). It brings several benefits that collectively result in reduced latency and improved performance:
* enabling seamless transition between different network connections, such as switching from Wi-Fi to mobile data.
* eliminating head-of-line blocking, so that a lost packet does not block all streams.
* negotiating TLS versions at the same time as the TLS handshakes, allowing for faster connections.
* providing encryption by default, ensuring that all data transmitted over an HTTP/3 connection is protected and confidential.
* providing zero round-trip time (0-RTT) when communicating with servers that clients already established connections to.
APISIX currently supports HTTP/3 connections between downstream clients and APISIX. HTTP/3 connections with upstream services are not yet supported, and contributions are welcomed.
:::caution
This feature is currently experimental and not recommended for production use.
:::
This document will show you how to configure APISIX to enable HTTP/3 connections between client and APISIX and document a few known issues.
## Usage
### Enable HTTP/3 in APISIX
Enable HTTP/3 on port `9443` (or a different port) by adding the following configurations to APISIX's `config.yaml` configuration file:
```yaml title="config.yaml"
apisix:
ssl:
listen:
- port: 9443
enable_http3: true
ssl_protocols: TLSv1.3
```
:::info
If you are deploying APISIX using Docker, make sure to allow UDP in the HTTP3 port, such as `-p 9443:9443/udp`.
:::
Then reload APISIX for configuration changes to take effect:
```shell
apisix reload
```
### Generate Certificates and Keys
HTTP/3 requires TLS. You can leverage the purchased certificates or self-generate them, whichever applicable.
To self-generate, first generate the certificate authority (CA) key and certificate:
```shell
openssl genrsa -out ca.key 2048 && \
openssl req -new -sha256 -key ca.key -out ca.csr -subj "/CN=ROOTCA" && \
openssl x509 -req -days 36500 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.crt
```
Next, generate the key and certificate with a common name for APISIX, and sign with the CA certificate:
```shell
openssl genrsa -out server.key 2048 && \
openssl req -new -sha256 -key server.key -out server.csr -subj "/CN=test.com" && \
openssl x509 -req -days 36500 -sha256 -extensions v3_req \
-CA ca.crt -CAkey ca.key -CAserial ca.srl -CAcreateserial \
-in server.csr -out server.crt
```
### Configure HTTPS
Optionally load the content stored in `server.crt` and `server.key` into shell variables:
```shell
server_cert=$(cat server.crt)
server_key=$(cat server.key)
```
Create an SSL certificate object to save the server certificate and its key:
```shell
curl -i "http://127.0.0.1:9180/apisix/admin/ssls" -X PUT -d '
{
"id": "quickstart-tls-client-ssl",
"sni": "test.com",
"cert": "'"${server_cert}"'",
"key": "'"${server_key}"'"
}'
```
### Create a Route
Create a sample route to `httpbin.org`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id":"httpbin-route",
"uri":"/get",
"upstream": {
"type":"roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
### Verify HTTP/3 Connections
Install [static-curl](https://github.com/stunnel/static-curl) or any other curl executable that has HTTP/3 support.
Send a request to the route:
```shell
curl -kv --http3-only \
-H "Host: test.com" \
--resolve "test.com:9443:127.0.0.1" "https://test.com:9443/get"
```
You should receive an `HTTP/3 200` response similar to the following:
```text
* Added test.com:9443:127.0.0.1 to DNS cache
* Hostname test.com was found in DNS cache
* Trying 127.0.0.1:9443...
* QUIC cipher selection: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_CCM_SHA256
* Skipped certificate verification
* Connected to test.com (127.0.0.1) port 9443
* using HTTP/3
* [HTTP/3] [0] OPENED stream for https://test.com:9443/get
* [HTTP/3] [0] [:method: GET]
* [HTTP/3] [0] [:scheme: https]
* [HTTP/3] [0] [:authority: test.com]
* [HTTP/3] [0] [:path: /get]
* [HTTP/3] [0] [user-agent: curl/8.7.1]
* [HTTP/3] [0] [accept: */*]
> GET /get HTTP/3
> Host: test.com
> User-Agent: curl/8.7.1
> Accept: */*
>
* Request completely sent off
< HTTP/3 200
...
{
"args": {},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "test.com",
"User-Agent": "curl/8.7.1",
"X-Amzn-Trace-Id": "Root=1-6656013a-27da6b6a34d98e3e79baaf5b",
"X-Forwarded-Host": "test.com"
},
"origin": "172.19.0.1, 123.40.79.456",
"url": "http://test.com/get"
}
* Connection #0 to host test.com left intact
```
## Known Issues
- For APISIX-3.9, test cases of Tongsuo will fail because the Tongsuo does not support QUIC TLS.
- APISIX-3.9 is based on NGINX-1.25.3 with vulnerabilities in HTTP/3 (CVE-2024-24989, CVE-2024-24990).

View File

@@ -0,0 +1,52 @@
---
title: Install Dependencies
---
<!--
#
# 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.
#
-->
## Note
- 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.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.
- On some platforms, installing LuaRocks via the package manager will cause Lua to be upgraded to Lua 5.3, so we recommend installing LuaRocks via source code. if you install OpenResty and its OpenSSL develop library (openresty-openssl111-devel for rpm and openresty-openssl111-dev for deb) via the official repository, then [we provide a script for automatic installation](https://github.com/apache/apisix/blob/master/utils/linux-install-luarocks.sh). If you compile OpenResty yourself, you can refer to the above script and change the path in it. If you don't specify the OpenSSL library path when you compile, you don't need to configure the OpenSSL variables in LuaRocks, because the system's OpenSSL is used by default. If the OpenSSL library is specified at compile time, then you need to ensure that LuaRocks' OpenSSL configuration is consistent with OpenResty's.
- OpenResty is a dependency of APISIX. If it is your first time to deploy APISIX and you don't need to use OpenResty to deploy other services, you can stop and disable OpenResty after installation since it will not affect the normal work of APISIX. Please operate carefully according to your service. For example in Ubuntu: `systemctl stop openresty && systemctl disable openresty`.
## Install
Run the following command to install Apache APISIX's dependencies on a supported operating system.
Supported OS versions: CentOS7, Fedora31 & 32, Ubuntu 16.04 & 18.04, Debian 9 & 10, Arch Linux.
Note that in the case of Arch Linux, we use `openresty` from the AUR, thus requiring a AUR helper. For now `yay` and `pacaur` are supported.
```
curl https://raw.githubusercontent.com/apache/apisix/master/utils/install-dependencies.sh -sL | bash -
```
If you have cloned the Apache APISIX project, execute in the Apache APISIX root directory:
```
bash utils/install-dependencies.sh
```

View File

@@ -0,0 +1,340 @@
---
title: Installation
keywords:
- APISIX
- Installation
description: This document walks you through the different Apache APISIX installation methods.
---
<!--
#
# 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.
#
-->
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
This guide walks you through how you can install and run Apache APISIX in your environment.
Refer to the [Getting Started](./getting-started/README.md) guide for a quick walk-through on running Apache APISIX.
## Installing APISIX
APISIX can be installed by the different methods listed below:
<Tabs
groupId="install-method"
defaultValue="docker"
values={[
{label: 'Docker', value: 'docker'},
{label: 'Helm', value: 'helm'},
{label: 'RPM', value: 'rpm'},
{label: 'DEB', value: 'deb'},
{label: 'Source Code', value: 'source code'},
]}>
<TabItem value="docker">
First clone the [apisix-docker](https://github.com/apache/apisix-docker) repository:
```shell
git clone https://github.com/apache/apisix-docker.git
cd apisix-docker/example
```
Now, you can use `docker-compose` to start APISIX.
<Tabs
groupId="cpu-arch"
defaultValue="x86"
values={[
{label: 'x86', value: 'x86'},
{label: 'ARM/M1', value: 'arm'},
]}>
<TabItem value="x86">
```shell
docker-compose -p docker-apisix up -d
```
</TabItem>
<TabItem value="arm">
```shell
docker-compose -p docker-apisix -f docker-compose-arm64.yml up -d
```
</TabItem>
</Tabs>
</TabItem>
<TabItem value="helm">
To install APISIX via Helm, run:
```shell
helm repo add apisix https://charts.apiseven.com
helm repo update
helm install apisix apisix/apisix --create-namespace --namespace apisix
```
You can find other Helm charts on the [apisix-helm-chart](https://github.com/apache/apisix-helm-chart) repository.
</TabItem>
<TabItem value="rpm">
This installation method is suitable for CentOS 7 and Centos 8. If you choose this method to install APISIX, you need to install etcd first. For the specific installation method, please refer to [Installing etcd](#installing-etcd).
### Installation via RPM repository
If OpenResty is **not** installed, you can run the command below to install both OpenResty and APISIX repositories:
```shell
sudo yum install -y https://repos.apiseven.com/packages/centos/apache-apisix-repo-1.0-1.noarch.rpm
```
If OpenResty is installed, the command below will install the APISIX repositories:
```shell
sudo yum-config-manager --add-repo https://repos.apiseven.com/packages/centos/apache-apisix.repo
```
Then, to install APISIX, run:
```shell
sudo yum install apisix
```
:::tip
You can also install a specific version of APISIX by specifying it:
```shell
sudo yum install apisix-3.8.0
```
:::
### Installation via RPM offline package
First, download APISIX RPM offline package to an `apisix` folder:
```shell
sudo mkdir -p apisix
sudo yum install -y https://repos.apiseven.com/packages/centos/apache-apisix-repo-1.0-1.noarch.rpm
sudo yum clean all && yum makecache
sudo yum install -y --downloadonly --downloaddir=./apisix apisix
```
Then copy the `apisix` folder to the target host and run:
```shell
sudo yum install ./apisix/*.rpm
```
### Managing APISIX server
Once APISIX is installed, you can initialize the configuration file and etcd by running:
```shell
apisix init
```
To start APISIX server, run:
```shell
apisix start
```
:::tip
Run `apisix help` to get a list of all available operations.
:::
</TabItem>
<TabItem value="deb">
### Installation via DEB repository
Currently the only DEB repository supported by APISIX is Debian 11 (Bullseye) and supports both amd64 and arm64 architectures.
```shell
# amd64
wget -O - http://repos.apiseven.com/pubkey.gpg | sudo apt-key add -
echo "deb http://repos.apiseven.com/packages/debian bullseye main" | sudo tee /etc/apt/sources.list.d/apisix.list
# arm64
wget -O - http://repos.apiseven.com/pubkey.gpg | sudo apt-key add -
echo "deb http://repos.apiseven.com/packages/arm64/debian bullseye main" | sudo tee /etc/apt/sources.list.d/apisix.list
```
Then, to install APISIX, run:
```shell
sudo apt update
sudo apt install -y apisix=3.8.0-0
```
### Managing APISIX server
Once APISIX is installed, you can initialize the configuration file and etcd by running:
```shell
sudo apisix init
```
To start APISIX server, run:
```shell
sudo apisix start
```
:::tip
Run `apisix help` to get a list of all available operations.
:::
</TabItem>
<TabItem value="source code">
If you want to build APISIX from source, please refer to [Building APISIX from source](./building-apisix.md).
</TabItem>
</Tabs>
## Installing etcd
APISIX uses [etcd](https://github.com/etcd-io/etcd) to save and synchronize configuration. Before installing APISIX, you need to install etcd on your machine.
It would be installed automatically if you choose the Docker or Helm install method while installing APISIX. If you choose a different method or you need to install it manually, follow the steps shown below:
<Tabs
groupId="os"
defaultValue="linux"
values={[
{label: 'Linux', value: 'linux'},
{label: 'macOS', value: 'mac'},
]}>
<TabItem value="linux">
```shell
ETCD_VERSION='3.5.4'
wget https://github.com/etcd-io/etcd/releases/download/v${ETCD_VERSION}/etcd-v${ETCD_VERSION}-linux-amd64.tar.gz
tar -xvf etcd-v${ETCD_VERSION}-linux-amd64.tar.gz && \
cd etcd-v${ETCD_VERSION}-linux-amd64 && \
sudo cp -a etcd etcdctl /usr/bin/
nohup etcd >/tmp/etcd.log 2>&1 &
```
</TabItem>
<TabItem value="mac">
```shell
brew install etcd
brew services start etcd
```
</TabItem>
</Tabs>
## Next steps
### Configuring APISIX
You can configure your APISIX deployment in two ways:
1. By directly changing your configuration file (`conf/config.yaml`).
2. By using the `--config` or the `-c` flag to pass the path to your configuration file while starting APISIX.
```shell
apisix start -c <path to config file>
```
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:
```yaml title="conf/config.yaml"
apisix:
node_listen: 8000
```
Now, if you decide you want to change the etcd address to `http://foo:2379`, you can add it to your configuration file. This will not change other configurations.
```yaml title="conf/config.yaml"
apisix:
node_listen: 8000
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
host:
- "http://foo:2379"
```
:::warning
The `conf/nginx.conf` file is automatically generated and should not be modified.
:::
### APISIX deployment modes
APISIX has three different deployment modes for different use cases. To learn more and configure deployment modes, see the [documentation](./deployment-modes.md).
### Updating Admin API key
It is recommended to modify the Admin API key to ensure security.
You can update your configuration file as shown below:
```yaml title="conf/config.yaml"
deployment:
admin:
admin_key:
- name: "admin"
key: newsupersecurekey
role: admin
```
Now, to access the Admin API, you can use the new key:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes?api_key=newsupersecurekey -i
```
### Adding APISIX systemd unit file
If you installed APISIX via RPM, the APISIX unit file will already be configured and you can start APISIX by:
```shell
systemctl start apisix
systemctl stop apisix
```
If you installed APISIX through other methods, you can create `/usr/lib/systemd/system/apisix.service` and add the [configuration from the template](https://github.com/api7/apisix-build-tools/blob/master/usr/lib/systemd/system/apisix.service).
See the [Getting Started](./getting-started/README.md) guide for a quick walk-through of using APISIX.

View File

@@ -0,0 +1,78 @@
---
title: The Implementation of Plugin Runner
---
<!--
#
# 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.
#
-->
## Prerequirement
Each request which runs the extern plugin will trigger an RPC to Plugin Runner over a connection on Unix socket. The data of RPC are serialized with [Flatbuffers](https://github.com/google/flatbuffers).
Therefore, the Plugin Runner needs to:
1. handle a connection on Unix socket
2. support Flatbuffers
3. use the proto & generated code in https://github.com/api7/ext-plugin-proto/
## Listening to the Path
APISIX will pass the path of Unix socket as an environment variable `APISIX_LISTEN_ADDRESS` to the Plugin Runner. So the runner needs to read the value and listen to that address during starting.
## Register Plugins
The Plugin Runner should be able to load plugins written in the particular language.
## Handle RPC
There are two kinds of RPC: PrepareConf & HTTPReqCall
### Handle PrepareConf
As people can configure the extern plugin on the side of APISIX, we need a way to sync the plugin configuration to the Plugin Runner.
When there is a configuration that needs to sync to the Plugin Runner, we will send it via the PrepareConf RPC call. The Plugin Runner should be able to handle the call and store the configuration in a cache, then returns a unique conf token that represents the configuration.
In the previous design, an idempotent key is sent with the configuration. This field is deprecated and the Plugin Runner can safely ignore it.
Requests run plugins with particular configuration will bear a particular conf token in the RPC call, and the Plugin Runner is expected to look up actual configuration via the token.
When the configuration is modified, APISIX will send a new PrepareConf to the Plugin Runner. Currently, there is no way to notify the Plugin Runner that a configuration is removed. Therefore, we introduce another environment variable `APISIX_CONF_EXPIRE_TIME` as the conf cache expire time. The Plugin Runner should be able to cache the conf slightly longer than `APISIX_CONF_EXPIRE_TIME`, and APISIX will send another PrepareConf to refresh the cache if the configuration is still existing after `APISIX_CONF_EXPIRE_TIME` seconds.
### Handle HTTPReqCall
Each request which runs the extern plugin will trigger the HTTPReqCall. The HTTPReqCall is almost a serialized version of HTTP request, plus a conf token. The Plugin Runner is expected to tell APISIX what to update by the response of HTTPReqCall RPC call.
Sometimes the plugin in the Plugin Runner needs to know some information that is not part of the HTTPReqCall request, such as the request start time and the route ID in APISIX. Hence the Plugin Runner needs to reply to an `ExtraInfo` message as the response on the connection which sends the HTTPReqCall request. APISIX will read the `ExtraInfo` message and return the asked information.
Currently, the information below is passed by `ExtraInfo`:
* variable value
* request body
The flow of HTTPReqCall procession is:
```
APISIX sends HTTPReqCall
Plugin Runner looks up the plugin configuration by the token in HTTPReqCall
(optional) loop:
    Plugin Runner asks for ExtraInfo
    APISIX replies the ExtraInfo
Plugin Runner replies HTTPReqCall
```

View File

@@ -0,0 +1,376 @@
---
title: Introducing APISIX's testing framework
---
<!--
#
# 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.
#
-->
APISIX uses a testing framework based on test-nginx: https://github.com/openresty/test-nginx.
For details, you can check the [documentation](https://metacpan.org/pod/Test::Nginx) of this project.
If you want to test the CLI behavior of APISIX (`./bin/apisix`),
you need to write a shell script in the t/cli directory to test it. You can refer to the existing test scripts for more details.
If you want to test the others, you need to write test code based on the framework.
Here, we briefly describe how to do simple testing based on this framework.
## Test file
you need to write test cases in the t/ directory, in a corresponding `.t` file. Note that a single test file should not exceed `800` lines, and if it is too long, it needs to be divided by a suffix. For example:
```
t/
├── admin
│ ├── consumers.t
│ ├── consumers2.t
```
Both `consumers.t` and `consumers2.t` contain tests for consumers in the Admin API.
Some of the test files start with this paragraph:
```
add_block_preprocessor(sub {
my ($block) = @_;
if (! $block->request) {
$block->set_value("request", "GET /t");
}
if (! $block->no_error_log && ! $block->error_log) {
$block->set_value("no_error_log", "[error]\n[alert]");
}
});
```
It means that all tests in this test file that do not define `request` are set to `GET /t`. The same is true for error_log.
## Preparing the configuration
When testing a behavior, we need to prepare the configuration.
If the configuration is from etcd:
We can set up specific configurations through the Admin API.
```
=== TEST 7: refer to empty nodes upstream
--- config
location /t {
content_by_lua_block {
local core = require("apisix.core")
local t = require("lib.test_admin").test
local code, message = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"methods": ["GET"],
"upstream_id": "1",
"uri": "/index.html"
}]]
)
if code >= 300 then
ngx.status = code
ngx.print(message)
return
end
ngx.say(message)
}
}
--- request
GET /t
--- response_body
passed
```
Then trigger it in a later test:
```
=== TEST 8: hit empty nodes upstream
--- request
GET /index.html
--- error_code: 503
--- error_log
no valid upstream node
```
## Preparing the upstream
To test the code, we need to provide a mock upstream.
For HTTP request, the upstream code is put in `t/lib/server.lua`. HTTP request with
a given `path` will trigger the method in the same name. For example, a call to `/server_port`
will call the `_M.server_port`.
For TCP request, a dummy upstream is used:
```
local sock = ngx.req.socket()
local data = sock:receive("1")
ngx.say("hello world")
```
If you want to custom the TCP upstream logic, you can use:
```
--- stream_upstream_code
local sock = ngx.req.socket()
local data = sock:receive("1")
ngx.sleep(0.2)
ngx.say("hello world")
```
## Send request
We can initiate a request with `request` and set the request headers with `more_headers`.
For example.
```
--- request
PUT /hello?xx=y&xx=z&&y=&&z
body part of the request
--- more_headers
X-Req: foo
X-Req: bar
X-Resp: cat
```
Lua code can be used to send multiple requests.
One request after another:
```
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/server_port"
local ports_count = {}
for i = 1, 12 do
local httpc = http.new()
local res, err = httpc:request_uri(uri, {method = "GET"})
if not res then
ngx.say(err)
return
end
ports_count[res.body] = (ports_count[res.body] or 0) + 1
end
}
}
```
Sending multiple requests concurrently:
```
--- config
location /t {
content_by_lua_block {
local http = require "resty.http"
local uri = "http://127.0.0.1:" .. ngx.var.server_port
.. "/server_port?var=2&var2="
local t = {}
local ports_count = {}
for i = 1, 180 do
local th = assert(ngx.thread.spawn(function(i)
local httpc = http.new()
local res, err = httpc:request_uri(uri..i, {method = "GET"})
if not res then
ngx.log(ngx.ERR, err)
return
end
ports_count[res.body] = (ports_count[res.body] or 0) + 1
end, i))
table.insert(t, th)
end
for i, th in ipairs(t) do
ngx.thread.wait(th)
end
}
}
```
## Send TCP request
We can use `stream_request` to send a TCP request, for example:
```
--- stream_request
hello
```
To send a TLS over TCP request, we can combine `stream_tls_request` with `stream_sni`:
```
--- stream_tls_request
mmm
--- stream_sni: xx.com
```
## Assertions
The following assertions are commonly used.
Check status (if not set, the framework will check if the request has 200 status code).
```
--- error_code: 405
```
Check response headers.
```
--- response_headers
X-Resp: foo
X-Req: foo, bar
```
Check response body.
```
--- response_body
[{"count":12, "port": "1982"}]
```
Check the TCP response.
When the request is sent via `stream_request`:
```
--- stream_response
receive stream response error: connection reset by peer
```
When the request is sent via `stream_tls_request`:
```
--- response_body
receive stream response error: connection reset by peer
```
Checking the error log (via grep error log with regular expression).
```
--- grep_error_log eval
qr/hash_on: header|chash_key: "custom-one"/
--- grep_error_log_out
hash_on: header
chash_key: "custom-one"
hash_on: header
chash_key: "custom-one"
hash_on: header
chash_key: "custom-one"
hash_on: header
chash_key: "custom-one"
```
The default log level is `info`, but you can get the debug level log with `--- log_level: debug`.
## Upstream
The test framework listens to multiple ports when it is started.
* 1980/1981/1982/5044: HTTP upstream port. We provide a mock upstream server for testing. See below for more information.
* 1983: HTTPS upstream port
* 1984: APISIX HTTP port. Can be used to verify HTTP related gateway logic, such as concurrent access to an API.
* 1985: APISIX TCP port. Can be used to verify TCP related gateway logic, such as concurrent access to an API.
* 1994: APISIX HTTPS port. Can be used to verify HTTPS related gateway logic, such as testing certificate matching logic.
* 1995: TCP upstream port
* 2005: APISIX TLS over TCP port. Can be used to verify TLS over TCP related gateway logic, such as concurrent access to an API.
The methods in `t/lib/server.lua` are executed when accessing the upstream port. `_M.go` is the entry point for this file.
When the request accesses the upstream `/xxx`, the `_M.xxx` method is executed. For example, a request for `/hello` will execute `_M.hello`.
This allows us to write methods inside `t/lib/server.lua` to emulate specific upstream logic, such as sending special responses.
Note that before adding new methods to `t/lib/server.lua`, make sure that you can reuse existing methods.
## Run the test
Assume your current work directory is the root of the apisix source code.
1. Git clone the latest [test-nginx](https://github.com/openresty/test-nginx) to `../test-nginx`.
2. Run the test: `prove -I. -I../test-nginx/inc -I../test-nginx/lib -r t/path/to/file.t`.
## Tips
### Debugging test cases
The Nginx configuration and logs generated by the test cases are located in the t/servroot directory. The Nginx configuration template for testing is located in t/APISIX.pm.
### Running only some test cases
Three notes can be used to control which parts of the tests are executed.
FIRST & LAST:
```
=== TEST 1: vars rule with ! (set)
--- FIRST
--- config
...
--- response_body
passed
=== TEST 2: vars rule with ! (hit)
--- request
GET /hello?name=jack&age=17
--- LAST
--- error_code: 403
--- response_body
Fault Injection!
```
ONLY:
```
=== TEST 1: list empty resources
--- ONLY
--- config
...
--- response_body
{"count":0,"node":{"dir":true,"key":"/apisix/upstreams","nodes":[]}}
```
### Executing Shell Commands
It is possible to execute shell commands while writing tests in test-nginx for APISIX. We expose this feature via `exec` code block. The `stdout` of the executed process can be captured via `response_body` code block and `stderr` (if any) can be captured by filtering error.log through `grep_error_log`. Here is an example:
```
=== TEST 1: check exec stdout
--- exec
echo hello world
--- response_body
hello world
=== TEST 2: when exec returns an error
--- exec
echxo hello world
--- grep_error_log eval
qr/failed to execute the script [ -~]*/
--- grep_error_log_out
failed to execute the script with status: 127, reason: exit, stderr: /bin/sh: 1: echxo: not found
```

View File

@@ -0,0 +1,210 @@
---
title: Mutual TLS Authentication
keywords:
- Apache APISIX
- Mutual TLS
- mTLS
description: This document describes how you can secure communication to and within APISIX with mTLS.
---
<!--
#
# 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.
#
-->
## Protect Admin API
### Why use it
Mutual TLS authentication provides a better way to prevent unauthorized access to APISIX.
The clients will provide their certificates to the server and the server will check whether the cert is signed by the supplied CA and decide whether to serve the request.
### How to configure
1. Generate self-signed key pairs, including ca, server, client key pairs.
2. Modify configuration items in `conf/config.yaml`:
```yaml title="conf/config.yaml"
admin_listen:
ip: 127.0.0.1
port: 9180
https_admin: true
admin_api_mtls:
admin_ssl_ca_cert: "/data/certs/mtls_ca.crt" # Path of your self-signed ca cert.
admin_ssl_cert: "/data/certs/mtls_server.crt" # Path of your self-signed server side cert.
admin_ssl_cert_key: "/data/certs/mtls_server.key" # Path of your self-signed server side key.
```
3. Run command:
```shell
apisix init
apisix reload
```
### How client calls
Please replace the following certificate paths and domain name with your real ones.
* Note: The same CA certificate as the server needs to be used *
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl --cacert /data/certs/mtls_ca.crt --key /data/certs/mtls_client.key --cert /data/certs/mtls_client.crt https://admin.apisix.dev:9180/apisix/admin/routes -H "X-API-KEY: $admin_key"
```
## etcd with mTLS
### How to configure
You need to configure `etcd.tls` for APISIX to work on an etcd cluster with mTLS enabled as shown below:
```yaml title="conf/config.yaml"
deployment:
role: traditional
role_traditional:
config_provider: etcd
etcd:
tls:
cert: /data/certs/etcd_client.pem # path of certificate used by the etcd client
key: /data/certs/etcd_client.key # path of key used by the etcd client
```
If APISIX does not trust the CA certificate that used by etcd server, we need to set up the CA certificate.
```yaml title="conf/config.yaml"
apisix:
ssl:
ssl_trusted_certificate: /path/to/certs/ca-certificates.crt # path of CA certificate used by the etcd server
```
## Protect Route
### Why use it
Using mTLS is a way to verify clients cryptographically. It is useful and important in cases where you want to have encrypted and secure traffic in both directions.
* Note: the mTLS protection only happens in HTTPS. If your route can also be accessed via HTTP, you should add additional protection in HTTP or disable the access via HTTP.*
### How to configure
We provide a [tutorial](./tutorials/client-to-apisix-mtls.md) that explains in detail how to configure mTLS between the client and APISIX.
When configuring `ssl`, use parameter `client.ca` and `client.depth` to configure the root CA that signing client certificates and the max length of certificate chain. Please refer to [Admin API](./admin-api.md#ssl) for details.
Here is an example shell script to create SSL with mTLS (id is `1`, changes admin API url if needed):
```shell
curl http://127.0.0.1:9180/apisix/admin/ssls/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"cert": "'"$(cat t/certs/mtls_server.crt)"'",
"key": "'"$(cat t/certs/mtls_server.key)"'",
"snis": [
"admin.apisix.dev"
],
"client": {
"ca": "'"$(cat t/certs/mtls_ca.crt)"'",
"depth": 10
}
}'
```
Send a request to verify:
```bash
curl --resolve 'mtls.test.com:<APISIX_HTTPS_PORT>:<APISIX_URL>' "https://<APISIX_URL>:<APISIX_HTTPS_PORT>/hello" -k --cert ./client.pem --key ./client.key
* Added admin.apisix.dev:9443:127.0.0.1 to DNS cache
* Hostname admin.apisix.dev was found in DNS cache
* Trying 127.0.0.1:9443...
* Connected to admin.apisix.dev (127.0.0.1) port 9443 (#0)
* ALPN: offers h2
* ALPN: offers http/1.1
* CAfile: t/certs/mtls_ca.crt
* CApath: none
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Client hello (1):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Server hello (2):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Unknown (8):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Request CERT (13):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] (304) (IN), TLS handshake, Finished (20):
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Certificate (11):
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, CERT verify (15):
* [CONN-0-0][CF-SSL] (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN: server accepted h2
* Server certificate:
* subject: C=cn; ST=GuangDong; L=ZhuHai; CN=admin.apisix.dev; OU=ops
* start date: Dec 1 10:17:24 2022 GMT
* expire date: Aug 18 10:17:24 2042 GMT
* subjectAltName: host "admin.apisix.dev" matched cert's "admin.apisix.dev"
* issuer: C=cn; ST=GuangDong; L=ZhuHai; CN=ca.apisix.dev; OU=ops
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* h2h3 [:method: GET]
* h2h3 [:path: /hello]
* h2h3 [:scheme: https]
* h2h3 [:authority: admin.apisix.dev:9443]
* h2h3 [user-agent: curl/7.87.0]
* h2h3 [accept: */*]
* Using Stream ID: 1 (easy handle 0x13000bc00)
> GET /hello HTTP/2
> Host: admin.apisix.dev:9443
> user-agent: curl/7.87.0
> accept: */*
```
Please make sure that the SNI fits the certificate domain.
## mTLS Between APISIX and Upstream
### Why use it
Sometimes the upstream requires mTLS. In this situation, the APISIX acts as the client, it needs to provide client certificate to communicate with upstream.
### How to configure
When configuring `upstreams`, we could use parameter `tls.client_cert` and `tls.client_key` to configure the client certificate APISIX used to communicate with upstreams. Please refer to [Admin API](./admin-api.md#upstream) for details.
This feature requires APISIX to run on [APISIX-Runtime](./FAQ.md#how-do-i-build-the-apisix-runtime-environment).
Here is a similar shell script to patch a existed upstream with mTLS (changes admin API url if needed):
```shell
curl http://127.0.0.1:9180/apisix/admin/upstreams/1 \
-H "X-API-KEY: $admin_key" -X PATCH -d '
{
"tls": {
"client_cert": "'"$(cat t/certs/mtls_client.crt)"'",
"client_key": "'"$(cat t/certs/mtls_client.key)"'"
}
}'
```

View File

@@ -0,0 +1,503 @@
---
title: Plugin Develop
---
<!--
#
# 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.
#
-->
This documentation is about developing plugin in Lua. For other languages,
see [external plugin](./external-plugin.md).
## Where to put your plugins
Use the `extra_lua_path` parameter in `conf/config.yaml` file to load your custom plugin code (or use `extra_lua_cpath` for compiled `.so` or `.dll` file).
For example, you can create a directory `/path/to/example`:
```yaml
apisix:
...
extra_lua_path: "/path/to/example/?.lua"
```
The structure of the `example` directory should look like this:
```
├── example
│   └── apisix
│   ├── plugins
│   │   └── 3rd-party.lua
│   └── stream
│   └── plugins
│   └── 3rd-party.lua
```
:::note
The directory (`/path/to/example`) must contain the `/apisix/plugins` subdirectory.
:::
## Enable the plugin
To enable your custom plugin, add the plugin list to `conf/config.yaml` and append your plugin name. For instance:
```yaml
plugins: # See `conf/config.yaml.example` for an example
- ... # Add existing plugins
- your-plugin # Add your custom plugin name (name is the plugin name defined in the code)
```
:::warning
In particular, most APISIX plugins are enabled by default when the plugins field configuration is not defined (The default enabled plugins can be found in [apisix/cli/config.lua](https://github.com/apache/apisix/blob/master/apisix/cli/config.lua)).
Once the plugins configuration is defined in `conf/config.yaml`, the new plugins list will replace the default configuration instead of merging. Therefore, when defining the `plugins` field, make sure to include the built-in plugins that are being used. To maintain consistency with the default behavior, you can include all the default enabled plugins defined in `apisix/cli/config.lua`.
:::
## Writing plugins
The [`example-plugin`](https://github.com/apache/apisix/blob/master/apisix/plugins/example-plugin.lua) plugin in this repo provides an example.
### Naming and priority
Specify the plugin name (the name is the unique identifier of the plugin and cannot be duplicate) and priority in the code.
```lua
local plugin_name = "example-plugin"
local _M = {
version = 0.1,
priority = 0,
name = plugin_name,
schema = schema,
metadata_schema = metadata_schema,
}
```
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.
Note: the order of the plugins is not related to the order of execution.
### Schema and check
Write [JSON Schema](https://json-schema.org) descriptions and check functions. Similarly, take the example-plugin plugin as an example to see its
configuration data:
```json
{
"example-plugin": {
"i": 1,
"s": "s",
"t": [1]
}
}
```
Let's look at its schema description :
```lua
local schema = {
type = "object",
properties = {
i = {type = "number", minimum = 0},
s = {type = "string"},
t = {type = "array", minItems = 1},
ip = {type = "string"},
port = {type = "integer"},
},
required = {"i"},
}
```
The schema defines a non-negative number `i`, a string `s`, a non-empty array of `t`, and `ip` / `port`. Only `i` is required.
At the same time, we need to implement the __check_schema(conf, schema_type)__ method to complete the specification verification.
```lua
function _M.check_schema(conf)
return core.schema.check(schema, conf)
end
```
:::note
Note: the project has provided the public method "__core.schema.check__", which can be used directly to complete JSON
verification.
:::
The input parameter **schema_type** is used to distinguish between different schemas types. For example, many plugins need to use some [metadata](./terminology/plugin-metadata.md), so they define the plugin's `metadata_schema`.
```lua title="example-plugin.lua"
-- schema definition for metadata
local metadata_schema = {
type = "object",
properties = {
ikey = {type = "number", minimum = 0},
skey = {type = "string"},
},
required = {"ikey", "skey"},
}
function _M.check_schema(conf, schema_type)
--- check schema for metadata
if schema_type == core.schema.TYPE_METADATA then
return core.schema.check(metadata_schema, conf)
end
return core.schema.check(schema, conf)
end
```
Another example, the [key-auth](https://github.com/apache/apisix/blob/master/apisix/plugins/key-auth.lua) plugin needs to provide a `consumer_schema` to check the configuration of the `plugins` attribute of the `consumer` resource in order to be used with the [Consumer](./admin-api.md#consumer) resource.
```lua title="key-auth.lua"
local consumer_schema = {
type = "object",
properties = {
key = {type = "string"},
},
required = {"key"},
}
function _M.check_schema(conf, schema_type)
if schema_type == core.schema.TYPE_CONSUMER then
return core.schema.check(consumer_schema, conf)
else
return core.schema.check(schema, conf)
end
end
```
### Choose phase to run
Determine which [phase](./terminology/plugin.md#plugins-execution-lifecycle) to run, generally access or rewrite. If you don't know the [OpenResty lifecycle](https://github.com/openresty/lua-nginx-module/blob/master/README.markdown#directives), it's
recommended to learn about it in advance. For example `key-auth` is an authentication plugin, thus the authentication should be completed
before forwarding the request to any upstream service. Therefore, the plugin must be executed in the rewrite phases.
Similarly, if you want to modify or process the response body or headers you can do that in the `body_filter` or in the `header_filter` phases respectively.
The following code snippet shows how to implement any logic relevant to the plugin in the OpenResty log phase.
```lua
function _M.log(conf, ctx)
-- Implement logic here
end
```
**Note : we can't invoke `ngx.exit`, `ngx.redirect` or `core.respond.exit` in rewrite phase and access phase. if need to exit, just return the status and body, the plugin engine will make the exit happen with the returned status and body. [example](https://github.com/apache/apisix/blob/35269581e21473e1a27b11cceca6f773cad0192a/apisix/plugins/limit-count.lua#L177)**
### extra phase
Besides OpenResty's phases, we also provide extra phases to satisfy specific purpose:
* `delayed_body_filter`
```lua
function _M.delayed_body_filter(conf, ctx)
-- delayed_body_filter is called after body_filter
-- it is used by the tracing plugins to end the span right after body_filter
end
```
### Implement the logic
Write the logic of the plugin in the corresponding phase. There are two parameters `conf` and `ctx` in the phase method, take the `limit-conn` plugin configuration as an example.
#### conf parameter
The `conf` parameter is the relevant configuration information of the plugin, you can use `core.log.warn(core.json.encode(conf))` to output it to `error.log` for viewing, as shown below:
```lua
function _M.access(conf, ctx)
core.log.warn(core.json.encode(conf))
......
end
```
conf:
```json
{
"rejected_code": 503,
"burst": 0,
"default_conn_delay": 0.1,
"conn": 1,
"key": "remote_addr"
}
```
#### ctx parameter
The `ctx` parameter caches data information related to the request. You can use `core.log.warn(core.json.encode(ctx, true))` to output it to `error.log` for viewing, as shown below :
```lua
function _M.access(conf, ctx)
core.log.warn(core.json.encode(ctx, true))
......
end
```
### Others
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:
```
$(INSTALL) -d $(INST_LUADIR)/apisix/plugins/skywalking
$(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalking/
```
There are other fields in the `_M` which affect the plugin's behavior.
```lua
local _M = {
...
type = 'auth',
run_policy = 'prefer_route',
}
```
`run_policy` field can be used to control the behavior of the plugin execution.
When this field set to `prefer_route`, and the plugin has been configured both
in the global and at the route level, only the route level one will take effect.
`type` field is required to be set to `auth` if your plugin needs to work with consumer.
## Load plugin and replace plugin
Using `require "apisix.plugins.3rd-party"` will load your plugin, just like `require "apisix.plugins.jwt-auth"` will load the `jwt-auth` plugin.
Sometimes you may want to override a method instead of a whole file. In this case, you can configure `lua_module_hook` in `conf/config.yaml`
to introduce your hook.
Assume that your configuration is as follows:
```yaml
apisix:
...
extra_lua_path: "/path/to/example/?.lua"
lua_module_hook: "my_hook"
```
The `example/my_hook.lua` will be loaded when APISIX starts, and you can use this hook to replace a method in APISIX.
The example of [my_hook.lua](https://github.com/apache/apisix/blob/master/example/my_hook.lua) can be found under the `example` directory of this project.
## Check external dependencies
If you have dependencies on external libraries, check the dependent items. If your plugin needs to use shared memory, it
needs to declare via [customizing Nginx configuration](./customize-nginx-configuration.md), for example :
```yaml
# put this in config.yaml:
nginx_config:
http_configuration_snippet: |
# for openid-connect plugin
lua_shared_dict discovery 1m; # cache for discovery metadata documents
lua_shared_dict jwks 1m; # cache for JWKs
lua_shared_dict introspection 10m; # cache for JWT verification results
```
The plugin itself provides the init method. It is convenient for plugins to perform some initialization after
the plugin is loaded. If you need to clean up the initialization, you can put it in the corresponding destroy method.
Note : if the dependency of some plugin needs to be initialized when Nginx start, you may need to add logic to the initialization
method "http_init" in the file `apisix/init.lua`, and you may need to add some processing on generated part of Nginx
configuration file in `apisix/cli/ngx_tpl.lua` file. But it is easy to have an impact on the overall situation according to the
existing plugin mechanism, **we do not recommend this unless you have a complete grasp of the code**.
## Encrypted storage fields
Some plugins require parameters to be stored encrypted, such as the `password` parameter of the `basic-auth` plugin. This plugin needs to specify in the `schema` which parameters need to be stored encrypted.
```lua
encrypt_fields = {"password"}
```
If it is a nested parameter, such as the `clickhouse.password` parameter of the `error-log-logger` plugin, it needs to be separated by `.`:
```lua
encrypt_fields = {"clickhouse.password"}
```
Currently not supported yet:
1. more than two levels of nesting
2. fields in arrays
Parameters can be stored encrypted by specifying `encrypt_fields = {"password"}` in the `schema`. APISIX will provide the following functionality.
- When adding and updating resources, APISIX automatically encrypts the parameters declared in `encrypt_fields` and stores them in etcd
- When fetching resources and when running the plugin, APISIX automatically decrypts the parameters declared in `encrypt_fields`
By default, APISIX has `data_encryption` enabled with [two default keys](https://github.com/apache/apisix/blob/85563f016c35834763376894e45908b2fb582d87/apisix/cli/config.lua#L75), you can modify them in `config.yaml`.
```yaml
apisix:
data_encryption:
enable: true
keyring:
- ...
```
APISIX will try to decrypt the data with keys in the order of the keys in the keyring (only for parameters declared in `encrypt_fields`). If the decryption fails, the next key will be tried until the decryption succeeds.
If none of the keys in `keyring` can decrypt the data, the original data is used.
## Register public API
A plugin can register API which exposes to the public. Take batch-requests plugin as an example, this plugin registers `POST /apisix/batch-requests` to allow developers to group multiple API requests into a single HTTP request/response cycle:
```lua
function batch_requests()
-- ...
end
function _M.api()
-- ...
return {
{
methods = {"POST"},
uri = "/apisix/batch-requests",
handler = batch_requests,
}
}
end
```
Note that the public API will not be exposed by default, you will need to use the [public-api plugin](plugins/public-api.md) to expose it.
## Register control API
If you only want to expose the API to the localhost or intranet, you can expose it via [Control API](./control-api.md).
Take a look at example-plugin plugin:
```lua
local function hello()
local args = ngx.req.get_uri_args()
if args["json"] then
return 200, {msg = "world"}
else
return 200, "world\n"
end
end
function _M.control_api()
return {
{
methods = {"GET"},
uris = {"/v1/plugin/example-plugin/hello"},
handler = hello,
}
}
end
```
If you don't change the default control API configuration, the plugin will be expose `GET /v1/plugin/example-plugin/hello` which can only be accessed via `127.0.0.1`. Test with the following command:
```shell
curl -i -X GET "http://127.0.0.1:9090/v1/plugin/example-plugin/hello"
```
[Read more about control API introduction](./control-api.md)
## Register custom variables
We can use variables in many places of APISIX. For example, customizing log format in http-logger, using it as the key of `limit-*` plugins. In some situations, the builtin variables are not enough. Therefore, APISIX allows developers to register their variables globally, and use them as normal builtin variables.
For instance, let's register a variable called `a6_labels_zone` to fetch the value of the `zone` label in a route:
```
local core = require "apisix.core"
core.ctx.register_var("a6_labels_zone", function(ctx)
local route = ctx.matched_route and ctx.matched_route.value
if route and route.labels then
return route.labels.zone
end
return nil
end)
```
After that, any get operation to `$a6_labels_zone` will call the registered getter to fetch the value.
Note that the custom variables can't be used in features that depend on the Nginx directive, like `access_log_format`.
## Write test cases
For functions, write and improve the test cases of various dimensions, do a comprehensive test for your plugin! The
test cases of plugins are all in the "__t/plugin__" directory. You can go ahead to find out. APISIX uses
[****test-nginx****](https://github.com/openresty/test-nginx) as the test framework. A test case (.t file) is usually
divided into prologue and data parts by \__data\__. Here we will briefly introduce the data part, that is, the part
of the real test case. For example, the key-auth plugin:
```perl
=== TEST 1: sanity
--- config
location /t {
content_by_lua_block {
local plugin = require("apisix.plugins.key-auth")
local ok, err = plugin.check_schema({key = 'test-key'}, core.schema.TYPE_CONSUMER)
if not ok then
ngx.say(err)
end
ngx.say("done")
}
}
--- request
GET /t
--- response_body
done
--- no_error_log
[error]
```
A test case consists of three parts :
- __Program code__ : configuration content of Nginx location
- __Input__ : http request information
- __Output check__ : status, header, body, error log check
When we request __/t__, which config in the configuration file, the Nginx will call "__content_by_lua_block__" instruction to
complete the Lua script, and finally return. The assertion of the use case is response_body return "done",
"__no_error_log__" means to check the "__error.log__" of Nginx. There must be no ERROR level record. The log files for the unit test
are located in the following folder: 't/servroot/logs'.
The above test case represents a simple scenario. Most scenarios will require multiple steps to validate. To do this, create multiple tests `=== TEST 1`, `=== TEST 2`, and so on. These tests will be executed sequentially, allowing you to break down scenarios into a sequence of atomic steps.
Additionally, there are some convenience testing endpoints which can be found [here](https://github.com/apache/apisix/blob/master/t/lib/server.lua#L36). For example, see [proxy-rewrite](https://github.com/apache/apisix/blob/master/t/plugin/proxy-rewrite.t). In test 42, the upstream `uri` is made to redirect `/test?new_uri=hello` to `/hello` (which always returns `hello world`). In test 43, the response body is confirmed to equal `hello world`, meaning the proxy-rewrite configuration added with test 42 worked correctly.
Refer the following [document](building-apisix.md) to setup the testing framework.
### Attach the test-nginx execution process:
According to the path we configured in the makefile and some configuration items at the front of each __.t__ file, the
framework will assemble into a complete nginx.conf file. "__t/servroot__" is the working directory of Nginx and start the
Nginx instance. according to the information provided by the test case, initiate the http request and check that the
return items of HTTP include HTTP status, HTTP response header, HTTP response body and so on.
## Additional Resource(s)
- Key Concepts - [Plugins](https://apisix.apache.org/docs/apisix/terminology/plugin/)
- [Apache APISIX Extensions Guide](https://apisix.apache.org/blog/2021/10/29/extension-guide/)
- [Create a Custom Plugin in Lua](https://docs.api7.ai/apisix/how-to-guide/custom-plugins/create-plugin-in-lua)
- [example-plugin code](https://github.com/apache/apisix/blob/master/apisix/plugins/example-plugin.lua)

View File

@@ -0,0 +1,247 @@
---
title: ai-aws-content-moderation
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ai-aws-content-moderation
description: This document contains information about the Apache APISIX ai-aws-content-moderation Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `ai-aws-content-moderation` plugin processes the request body to check for toxicity and rejects the request if it exceeds the configured threshold.
**_This plugin must be used in routes that proxy requests to LLMs only._**
**_As of now, the plugin only supports the integration with [AWS Comprehend](https://aws.amazon.com/comprehend/) for content moderation. PRs for introducing support for other service providers are welcomed._**
## Plugin Attributes
| **Field** | **Required** | **Type** | **Description** |
| ---------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| comprehend.access_key_id | Yes | String | AWS access key ID |
| comprehend.secret_access_key | Yes | String | AWS secret access key |
| comprehend.region | Yes | String | AWS region |
| comprehend.endpoint | No | String | AWS Comprehend service endpoint. Must match the pattern `^https?://` |
| comprehend.ssl_verify | No | String | Enables SSL certificate verification. |
| moderation_categories | No | Object | Key-value pairs of moderation category and their score. In each pair, the key should be one of the `PROFANITY`, `HATE_SPEECH`, `INSULT`, `HARASSMENT_OR_ABUSE`, `SEXUAL`, or `VIOLENCE_OR_THREAT`; and the value should be between 0 and 1 (inclusive). |
| moderation_threshold | No | Number | The degree to which content is harmful, offensive, or inappropriate. A higher value indicates more toxic content allowed. Range: 0 - 1. Default: 0.5 |
## Example usage
First initialise these shell variables:
```shell
ADMIN_API_KEY=edd1c9f034335f136f87ad84b625c8f1
ACCESS_KEY_ID=aws-comprehend-access-key-id-here
SECRET_ACCESS_KEY=aws-comprehend-secret-access-key-here
OPENAI_KEY=open-ai-key-here
```
Create a route with the `ai-aws-content-moderation` and `ai-proxy` 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": "/post",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "'"$ACCESS_KEY_ID"'",
"secret_access_key": "'"$SECRET_ACCESS_KEY"'",
"region": "us-east-1"
},
"moderation_categories": {
"PROFANITY": 0.5
}
},
"ai-proxy": {
"auth": {
"header": {
"api-key": "'"$OPENAI_KEY"'"
}
},
"model": {
"provider": "openai",
"name": "gpt-4",
"options": {
"max_tokens": 512,
"temperature": 1.0
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
The `ai-proxy` plugin is used here as it simplifies access to LLMs. However, you may configure the LLM in the upstream configuration as well.
Now send a request:
```shell
curl http://127.0.0.1:9080/post -i -XPOST -H 'Content-Type: application/json' -d '{
"messages": [
{
"role": "user",
"content": "<very profane message here>"
}
]
}'
```
Then the request will be blocked with error like this:
```text
HTTP/1.1 400 Bad Request
Date: Thu, 03 Oct 2024 11:53:15 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.10.0
request body exceeds PROFANITY threshold
```
Send a request with compliant content in the request body:
```shell
curl http://127.0.0.1:9080/post -i -XPOST -H 'Content-Type: application/json' -d '{
"messages": [
{
"role": "system",
"content": "You are a mathematician"
},
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
This request will be proxied normally to the configured LLM.
```text
HTTP/1.1 200 OK
Date: Thu, 03 Oct 2024 11:53:00 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.10.0
{"choices":[{"finish_reason":"stop","index":0,"message":{"content":"1+1 equals 2.","role":"assistant"}}],"created":1727956380,"id":"chatcmpl-AEEg8Pe5BAW5Sw3C1gdwXnuyulIkY","model":"gpt-4o-2024-05-13","object":"chat.completion","system_fingerprint":"fp_67802d9a6d","usage":{"completion_tokens":7,"prompt_tokens":23,"total_tokens":30}}
```
You can also configure filters on other moderation categories 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": "/post",
"plugins": {
"ai-aws-content-moderation": {
"comprehend": {
"access_key_id": "'"$ACCESS_KEY_ID"'",
"secret_access_key": "'"$SECRET_ACCESS_KEY"'",
"region": "us-east-1"
},
"moderation_categories": {
"PROFANITY": 0.5,
"HARASSMENT_OR_ABUSE": 0.7,
"SEXUAL": 0.2
}
},
"ai-proxy": {
"auth": {
"header": {
"api-key": "'"$OPENAI_KEY"'"
}
},
"model": {
"provider": "openai",
"name": "gpt-4",
"options": {
"max_tokens": 512,
"temperature": 1.0
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
If none of the `moderation_categories` are configured, request bodies will be moderated on the basis of overall toxicity.
The default `moderation_threshold` is 0.5, it can be configured 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": "/post",
"plugins": {
"ai-aws-content-moderation": {
"provider": {
"comprehend": {
"access_key_id": "'"$ACCESS_KEY_ID"'",
"secret_access_key": "'"$SECRET_ACCESS_KEY"'",
"region": "us-east-1"
}
},
"moderation_threshold": 0.7,
"llm_provider": "openai"
},
"ai-proxy": {
"auth": {
"header": {
"api-key": "'"$OPENAI_KEY"'"
}
},
"model": {
"provider": "openai",
"name": "gpt-4",
"options": {
"max_tokens": 512,
"temperature": 1.0
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -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.
---
<!--
#
# 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.
#
-->
## 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 <your token here>"
```
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."
}
]
}
```

View File

@@ -0,0 +1,89 @@
---
title: ai-prompt-guard
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ai-prompt-guard
description: This document contains information about the Apache APISIX ai-prompt-guard Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `ai-prompt-guard` plugin safeguards your AI endpoints by inspecting and validating incoming prompt messages. It checks the content of requests against user-defined allowed and denied patterns to ensure that only approved inputs are processed. Based on its configuration, the plugin can either examine just the latest message or the entire conversation history, and it can be set to check prompts from all roles or only from end users.
When both **allow** and **deny** patterns are configured, the plugin first ensures that at least one allowed pattern is matched. If none match, the request is rejected with a _"Request doesn't match allow patterns"_ error. If an allowed pattern is found, it then checks for any occurrences of denied patterns—rejecting the request with a _"Request contains prohibited content"_ error if any are detected.
## Plugin Attributes
| **Field** | **Required** | **Type** | **Description** |
| ------------------------------ | ------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| match_all_roles | No | boolean | If set to `true`, the plugin will check prompt messages from all roles. Otherwise, it only validates when its role is `"user"`. Default is `false`. |
| match_all_conversation_history | No | boolean | When enabled, all messages in the conversation history are concatenated and checked. If `false`, only the content of the last message is examined. Default is `false`. |
| allow_patterns | No | array | A list of regex patterns. When provided, the prompt must match **at least one** pattern to be considered valid. |
| deny_patterns | No | array | A list of regex patterns. If any of these patterns match the prompt content, the request is rejected. |
## Example usage
Create a route with the `ai-prompt-guard` 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-guard": {
"match_all_roles": true,
"allow_patterns": [
"goodword"
],
"deny_patterns": [
"badword"
]
}
},
"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": "badword request" }]
}' -H "Authorization: Bearer <your token here>"
```
The request will fail with 400 error and following response.
```bash
{"message":"Request doesn't match allow patterns"}
```

View File

@@ -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.
---
<!--
#
# 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.
#
-->
## 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 <your token here>"
```
Then the request body will be modified to something like this:
```json
{
"model": "some model",
"messages": [
{ "role": "user", "content": "Explain about psychology in brief." }
]
}
```

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,453 @@
---
title: ai-proxy
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ai-proxy
- AI
- LLM
description: The ai-proxy Plugin simplifies access to LLM and embedding models providers by converting Plugin configurations into the required request format for OpenAI, DeepSeek, AIMLAPI, and other OpenAI-compatible APIs.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/ai-proxy" />
</head>
## Description
The `ai-proxy` Plugin simplifies access to LLM and embedding models by transforming Plugin configurations into the designated request format. It supports the integration with OpenAI, DeepSeek, AIMLAPI, and other OpenAI-compatible APIs.
In addition, the Plugin also supports logging LLM request information in the access log, such as token usage, model, time to the first response, and more.
## Request Format
| Name | Type | Required | Description |
| ------------------ | ------ | -------- | --------------------------------------------------- |
| `messages` | Array | True | An array of message objects. |
| `messages.role` | String | True | Role of the message (`system`, `user`, `assistant`).|
| `messages.content` | String | True | Content of the message. |
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|--------------------|--------|----------|---------|------------------------------------------|-------------|
| provider | string | True | | [openai, deepseek, aimlapi, openai-compatible] | LLM service provider. When set to `openai`, the Plugin will proxy the request to `https://api.openai.com/chat/completions`. When set to `deepseek`, the Plugin will proxy the request to `https://api.deepseek.com/chat/completions`. When set to `aimlapi`, the Plugin uses the OpenAI-compatible driver and proxies the request to `https://api.aimlapi.com/v1/chat/completions` by default. When set to `openai-compatible`, the Plugin will proxy the request to the custom endpoint configured in `override`. |
| auth | object | True | | | Authentication configurations. |
| auth.header | object | False | | | Authentication headers. At least one of `header` or `query` must be configured. |
| auth.query | object | False | | | Authentication query parameters. At least one of `header` or `query` must be configured. |
| options | object | False | | | Model configurations. In addition to `model`, you can configure additional parameters and they will be forwarded to the upstream LLM service in the request body. For instance, if you are working with OpenAI, you can configure additional parameters such as `temperature`, `top_p`, and `stream`. See your LLM provider's API documentation for more available options. |
| options.model | string | False | | | Name of the LLM model, such as `gpt-4` or `gpt-3.5`. Refer to the LLM provider's API documentation for available models. |
| override | object | False | | | Override setting. |
| override.endpoint | string | False | | | Custom LLM provider endpoint, required when `provider` is `openai-compatible`. |
| logging | object | False | | | Logging configurations. |
| logging.summaries | boolean | False | false | | If true, logs request LLM model, duration, request, and response tokens. |
| logging.payloads | boolean | False | false | | If true, logs request and response payload. |
| timeout | integer | False | 30000 | ≥ 1 | Request timeout in milliseconds when requesting the LLM service. |
| keepalive | boolean | False | true | | If true, keeps the connection alive when requesting the LLM service. |
| keepalive_timeout | integer | False | 60000 | ≥ 1000 | Keepalive timeout in milliseconds when connecting to the LLM service. |
| keepalive_pool | integer | False | 30 | | Keepalive pool size for the LLM service connection. |
| ssl_verify | boolean | False | true | | If true, verifies the LLM service's certificate. |
## Examples
The examples below demonstrate how you can configure `ai-proxy` for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Proxy to OpenAI
The following example demonstrates how you can configure the API key, model, and other parameters in the `ai-proxy` Plugin and configure the Plugin on a Route to proxy user prompts to OpenAI.
Obtain the OpenAI [API key](https://openai.com/blog/openai-api) and save it to an environment variable:
```shell
export OPENAI_API_KEY=<your-api-key>
```
Create a Route and configure the `ai-proxy` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-proxy-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options":{
"model": "gpt-4"
}
}
}
}'
```
Send a POST request to the Route with a system prompt and a sample user question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-H "Host: api.openai.com" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1+1 equals 2.",
"refusal": null
},
"logprobs": null,
"finish_reason": "stop"
}
],
...
}
```
### Proxy to DeepSeek
The following example demonstrates how you can configure the `ai-proxy` Plugin to proxy requests to DeekSeek.
Obtain the DeekSeek API key and save it to an environment variable:
```shell
export DEEPSEEK_API_KEY=<your-api-key>
```
Create a Route and configure the `ai-proxy` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-proxy-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-proxy": {
"provider": "deepseek",
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
}
}
}'
```
Send a POST request to the Route with a sample question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "system",
"content": "You are an AI assistant that helps people find information."
},
{
"role": "user",
"content": "Write me a 50-word introduction for Apache APISIX."
}
]
}'
```
You should receive a response similar to the following:
```json
{
...
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Apache APISIX is a dynamic, real-time, high-performance API gateway and cloud-native platform. It provides rich traffic management features like load balancing, dynamic upstream, canary release, circuit breaking, authentication, observability, and more. Designed for microservices and serverless architectures, APISIX ensures scalability, security, and seamless integration with modern DevOps workflows."
},
"logprobs": null,
"finish_reason": "stop"
}
],
...
}
```
### Proxy to Azure OpenAI
The following example demonstrates how you can configure the `ai-proxy` Plugin to proxy requests to other LLM services, such as Azure OpenAI.
Obtain the Azure OpenAI API key and save it to an environment variable:
```shell
export AZ_OPENAI_API_KEY=<your-api-key>
```
Create a Route and configure the `ai-proxy` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-proxy-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-proxy": {
"provider": "openai-compatible",
"auth": {
"header": {
"api-key": "'"$AZ_OPENAI_API_KEY"'"
}
},
"options":{
"model": "gpt-4"
},
"override": {
"endpoint": "https://api7-auzre-openai.openai.azure.com/openai/deployments/gpt-4/chat/completions?api-version=2024-02-15-preview"
}
}
}
}'
```
Send a POST request to the Route with a sample question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{
"role": "system",
"content": "You are an AI assistant that helps people find information."
},
{
"role": "user",
"content": "Write me a 50-word introduction for Apache APISIX."
}
],
"max_tokens": 800,
"temperature": 0.7,
"frequency_penalty": 0,
"presence_penalty": 0,
"top_p": 0.95,
"stop": null
}'
```
You should receive a response similar to the following:
```json
{
"choices": [
{
...,
"message": {
"content": "Apache APISIX is a modern, cloud-native API gateway built to handle high-performance and low-latency use cases. It offers a wide range of features, including load balancing, rate limiting, authentication, and dynamic routing, making it an ideal choice for microservices and cloud-native architectures.",
"role": "assistant"
}
}
],
...
}
```
### Proxy to Embedding Models
The following example demonstrates how you can configure the `ai-proxy` Plugin to proxy requests to embedding models. This example will use the OpenAI embedding model endpoint.
Obtain the OpenAI [API key](https://openai.com/blog/openai-api) and save it to an environment variable:
```shell
export OPENAI_API_KEY=<your-api-key>
```
Create a Route and configure the `ai-proxy` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-proxy-route",
"uri": "/embeddings",
"methods": ["POST"],
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options":{
"model": "text-embedding-3-small",
"encoding_format": "float"
},
"override": {
"endpoint": "https://api.openai.com/v1/embeddings"
}
}
}
}'
```
Send a POST request to the Route with an input string:
```shell
curl "http://127.0.0.1:9080/embeddings" -X POST \
-H "Content-Type: application/json" \
-d '{
"input": "hello world"
}'
```
You should receive a response similar to the following:
```json
{
"object": "list",
"data": [
{
"object": "embedding",
"index": 0,
"embedding": [
-0.0067144386,
-0.039197803,
0.034177095,
0.028763203,
-0.024785956,
-0.04201061,
...
],
}
],
"model": "text-embedding-3-small",
"usage": {
"prompt_tokens": 2,
"total_tokens": 2
}
}
```
### Include LLM Information in Access Log
The following example demonstrates how you can log LLM request related information in the gateway's access log to improve analytics and audit. The following variables are available:
* `request_type`: Type of request, where the value could be `traditional_http`, `ai_chat`, or `ai_stream`.
* `llm_time_to_first_token`: Duration from request sending to the first token received from the LLM service, in milliseconds.
* `llm_model`: LLM model.
* `llm_prompt_tokens`: Number of tokens in the prompt.
* `llm_completion_tokens`: Number of chat completion tokens in the prompt.
:::note
The usage will become available in APISIX 3.13.0.
:::
Update the access log format in your configuration file to include additional LLM related variables:
```yaml title="conf/config.yaml"
nginx_config:
http:
access_log_format: "$remote_addr $remote_user [$time_local] $http_host \"$request_line\" $status $body_bytes_sent $request_time \"$http_referer\" \"$http_user_agent\" $upstream_addr $upstream_status $upstream_response_time \"$upstream_scheme://$upstream_host$upstream_uri\" \"$apisix_request_id\" \"$request_type\" \"$llm_time_to_first_token\" \"$llm_model\" \"$llm_prompt_tokens\" \"$llm_completion_tokens\""
```
Reload APISIX for configuration changes to take effect.
Now if you create a Route and send a request following the [Proxy to OpenAI example](#proxy-to-openai), you should receive a response similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1+1 equals 2.",
"refusal": null,
"annotations": []
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 8,
"total_tokens": 31,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
...
},
"service_tier": "default",
"system_fingerprint": null
}
```
In the gateway's access log, you should see a log entry similar to the following:
```text
192.168.215.1 - [21/Mar/2025:04:28:03 +0000] api.openai.com "POST /anything HTTP/1.1" 200 804 2.858 "-" "curl/8.6.0" - "http://api.openai.com" "5c5e0b95f8d303cb81e4dc456a4b12d9" "ai_chat" "2858" "gpt-4" "23" "8"
```
The access log entry shows the request type is `ai_chat`, time to first token is `2858` milliseconds, LLM model is `gpt-4`, prompt token usage is `23`, and completion token usage is `8`.

View File

@@ -0,0 +1,235 @@
---
title: ai-rag
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ai-rag
- AI
- LLM
description: The ai-rag Plugin enhances LLM outputs with Retrieval-Augmented Generation (RAG), efficiently retrieving relevant documents to improve accuracy and contextual relevance in responses.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/ai-rag" />
</head>
## Description
The `ai-rag` Plugin provides Retrieval-Augmented Generation (RAG) capabilities with LLMs. It facilitates the efficient retrieval of relevant documents or information from external data sources, which are used to enhance the LLM responses, thereby improving the accuracy and contextual relevance of the generated outputs.
The Plugin supports using [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search) services for generating embeddings and performing vector search.
**_As of now only [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) and [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search) services are supported for generating embeddings and performing vector search respectively. PRs for introducing support for other service providers are welcomed._**
## Attributes
| Name | Required | Type | Description |
| ----------------------------------------------- | ------------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| embeddings_provider | True | object | Configurations of the embedding models provider. |
| embeddings_provider.azure_openai | True | object | Configurations of [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service) as the embedding models provider. |
| embeddings_provider.azure_openai.endpoint | True | string | Azure OpenAI embedding model endpoint. |
| embeddings_provider.azure_openai.api_key | True | string | Azure OpenAI API key. |
| vector_search_provider | True | object | Configuration for the vector search provider. |
| vector_search_provider.azure_ai_search | True | object | Configuration for Azure AI Search. |
| vector_search_provider.azure_ai_search.endpoint | True | string | Azure AI Search endpoint. |
| vector_search_provider.azure_ai_search.api_key | True | string | Azure AI Search API key. |
## Request Body Format
The following fields must be present in the request body.
| Field | Type | Description |
| -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| ai_rag | object | Request body RAG specifications. |
| ai_rag.embeddings | object | Request parameters required to generate embeddings. Contents will depend on the API specification of the configured provider. |
| ai_rag.vector_search | object | Request parameters required to perform vector search. Contents will depend on the API specification of the configured provider. |
- Parameters of `ai_rag.embeddings`
- Azure OpenAI
| Name | Required | Type | Description |
| --------------- | ------------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| input | True | string | Input text used to compute embeddings, encoded as a string. |
| user | False | string | A unique identifier representing your end-user, which can help in monitoring and detecting abuse. |
| encoding_format | False | string | The format to return the embeddings in. Can be either `float` or `base64`. Defaults to `float`. |
| dimensions | False | integer | The number of dimensions the resulting output embeddings should have. Only supported in text-embedding-3 and later models. |
For other parameters please refer to the [Azure OpenAI embeddings documentation](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#embeddings).
- Parameters of `ai_rag.vector_search`
- Azure AI Search
| Field | Required | Type | Description |
| --------- | ------------ | -------- | ---------------------------- |
| fields | True | String | Fields for the vector search. |
For other parameters please refer the [Azure AI Search documentation](https://learn.microsoft.com/en-us/rest/api/searchservice/documents/search-post).
Example request body:
```json
{
"ai_rag": {
"vector_search": { "fields": "contentVector" },
"embeddings": {
"input": "which service is good for devops",
"dimensions": 1024
}
}
}
```
## Example
To follow along the example, create an [Azure account](https://portal.azure.com) and complete the following steps:
* In [Azure AI Foundry](https://oai.azure.com/portal), deploy a generative chat model, such as `gpt-4o`, and an embedding model, such as `text-embedding-3-large`. Obtain the API key and model endpoints.
* Follow [Azure's example](https://github.com/Azure/azure-search-vector-samples/blob/main/demo-python/code/basic-vector-workflow/azure-search-vector-python-sample.ipynb) to prepare for a vector search in [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search) using Python. The example will create a search index called `vectest` with the desired schema and upload the [sample data](https://github.com/Azure/azure-search-vector-samples/blob/main/data/text-sample.json) which contains 108 descriptions of various Azure services, for embeddings `titleVector` and `contentVector` to be generated based on `title` and `content`. Complete all the setups before performing vector searches in Python.
* In [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search), [obtain the Azure vector search API key and the search service endpoint](https://learn.microsoft.com/en-us/azure/search/search-get-started-vector?tabs=api-key#retrieve-resource-information).
Save the API keys and endpoints to environment variables:
```shell
# replace with your values
AZ_OPENAI_DOMAIN=https://ai-plugin-developer.openai.azure.com
AZ_OPENAI_API_KEY=9m7VYroxITMDEqKKEnpOknn1rV7QNQT7DrIBApcwMLYJQQJ99ALACYeBjFXJ3w3AAABACOGXGcd
AZ_CHAT_ENDPOINT=${AZ_OPENAI_DOMAIN}/openai/deployments/gpt-4o/chat/completions?api-version=2024-02-15-preview
AZ_EMBEDDING_MODEL=text-embedding-3-large
AZ_EMBEDDINGS_ENDPOINT=${AZ_OPENAI_DOMAIN}/openai/deployments/${AZ_EMBEDDING_MODEL}/embeddings?api-version=2023-05-15
AZ_AI_SEARCH_SVC_DOMAIN=https://ai-plugin-developer.search.windows.net
AZ_AI_SEARCH_KEY=IFZBp3fKVdq7loEVe9LdwMvVdZrad9A4lPH90AzSeC06SlR
AZ_AI_SEARCH_INDEX=vectest
AZ_AI_SEARCH_ENDPOINT=${AZ_AI_SEARCH_SVC_DOMAIN}/indexes/${AZ_AI_SEARCH_INDEX}/docs/search?api-version=2024-07-01
```
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Integrate with Azure for RAG-Enhaned Responses
The following example demonstrates how you can use the [`ai-proxy`](./ai-proxy.md) Plugin to proxy requests to Azure OpenAI LLM and use the `ai-rag` Plugin to generate embeddings and perform vector search to enhance LLM responses.
Create a Route as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "ai-rag-route",
"uri": "/rag",
"plugins": {
"ai-rag": {
"embeddings_provider": {
"azure_openai": {
"endpoint": "'"$AZ_EMBEDDINGS_ENDPOINT"'",
"api_key": "'"$AZ_OPENAI_API_KEY"'"
}
},
"vector_search_provider": {
"azure_ai_search": {
"endpoint": "'"$AZ_AI_SEARCH_ENDPOINT"'",
"api_key": "'"$AZ_AI_SEARCH_KEY"'"
}
}
},
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"api-key": "'"$AZ_OPENAI_API_KEY"'"
}
},
"model": "gpt-4o",
"override": {
"endpoint": "'"$AZ_CHAT_ENDPOINT"'"
}
}
}
}'
```
Send a POST request to the Route with the vector fields name, embedding model dimensions, and an input prompt in the request body:
```shell
curl "http://127.0.0.1:9080/rag" -X POST \
-H "Content-Type: application/json" \
-d '{
"ai_rag":{
"vector_search":{
"fields":"contentVector"
},
"embeddings":{
"input":"Which Azure services are good for DevOps?",
"dimensions":1024
}
}
}'
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"choices": [
{
"content_filter_results": {
...
},
"finish_reason": "length",
"index": 0,
"logprobs": null,
"message": {
"content": "Here is a list of Azure services categorized along with a brief description of each based on the provided JSON data:\n\n### Developer Tools\n- **Azure DevOps**: A suite of services that help you plan, build, and deploy applications, including Azure Boards, Azure Repos, Azure Pipelines, Azure Test Plans, and Azure Artifacts.\n- **Azure DevTest Labs**: A fully managed service to create, manage, and share development and test environments in Azure, supporting custom templates, cost management, and integration with Azure DevOps.\n\n### Containers\n- **Azure Kubernetes Service (AKS)**: A managed container orchestration service based on Kubernetes, simplifying deployment and management of containerized applications with features like automatic upgrades and scaling.\n- **Azure Container Instances**: A serverless container runtime to run and scale containerized applications without managing the underlying infrastructure.\n- **Azure Container Registry**: A fully managed Docker registry service to store and manage container images and artifacts.\n\n### Web\n- **Azure App Service**: A fully managed platform for building, deploying, and scaling web apps, mobile app backends, and RESTful APIs with support for multiple programming languages.\n- **Azure SignalR Service**: A fully managed real-time messaging service to build and scale real-time web applications.\n- **Azure Static Web Apps**: A serverless hosting service for modern web applications using static front-end technologies and serverless APIs.\n\n### Compute\n- **Azure Virtual Machines**: Infrastructure-as-a-Service (IaaS) offering for deploying and managing virtual machines in the cloud.\n- **Azure Functions**: A serverless compute service to run event-driven code without managing infrastructure.\n- **Azure Batch**: A job scheduling service to run large-scale parallel and high-performance computing (HPC) applications.\n- **Azure Service Fabric**: A platform to build, deploy, and manage scalable and reliable microservices and container-based applications.\n- **Azure Quantum**: A quantum computing service to build and run quantum applications.\n- **Azure Stack Edge**: A managed edge computing appliance to run Azure services and AI workloads on-premises or at the edge.\n\n### Security\n- **Azure Bastion**: A fully managed service providing secure and scalable remote access to virtual machines.\n- **Azure Security Center**: A unified security management service to protect workloads across Azure and on-premises infrastructure.\n- **Azure DDoS Protection**: A cloud-based service to protect applications and resources from distributed denial-of-service (DDoS) attacks.\n\n### Databases\n",
"role": "assistant"
}
}
],
"created": 1740625850,
"id": "chatcmpl-B54gQdumpfioMPIybFnirr6rq9ZZS",
"model": "gpt-4o-2024-05-13",
"object": "chat.completion",
"prompt_filter_results": [
{
"prompt_index": 0,
"content_filter_results": {
...
}
}
],
"system_fingerprint": "fp_65792305e4",
"usage": {
...
}
}
```

View File

@@ -0,0 +1,873 @@
---
title: ai-rate-limiting
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ai-rate-limiting
- AI
- LLM
description: The ai-rate-limiting Plugin enforces token-based rate limiting for LLM service requests, preventing overuse, optimizing API consumption, and ensuring efficient resource allocation.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/ai-rate-limiting" />
</head>
## Description
The `ai-rate-limiting` Plugin enforces token-based rate limiting for requests sent to LLM services. It helps manage API usage by controlling the number of tokens consumed within a specified time frame, ensuring fair resource allocation and preventing excessive load on the service. It is often used with [`ai-proxy`](./ai-proxy.md) or [`ai-proxy-multi`](./ai-proxy-multi.md) plugin.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|------------------------------|----------------|----------|----------|---------------------------------------------------------|-------------|
| limit | integer | False | | >0 | The maximum number of tokens allowed within a given time interval. At least one of `limit` and `instances.limit` should be configured. |
| time_window | integer | False | | >0 | The time interval corresponding to the rate limiting `limit` in seconds. At least one of `time_window` and `instances.time_window` should be configured. |
| show_limit_quota_header | boolean | False | true | | If true, includes `X-AI-RateLimit-Limit-*`, `X-AI-RateLimit-Remaining-*`, and `X-AI-RateLimit-Reset-*` headers in the response, where `*` is the instance name. |
| limit_strategy | string | False | total_tokens | [total_tokens, prompt_tokens, completion_tokens] | Type of token to apply rate limiting. `total_tokens` is the sum of `prompt_tokens` and `completion_tokens`. |
| instances | array[object] | False | | | LLM instance rate limiting configurations. |
| instances.name | string | True | | | Name of the LLM service instance. |
| instances.limit | integer | True | | >0 | The maximum number of tokens allowed within a given time interval for an instance. |
| instances.time_window | integer | True | | >0 | The time interval corresponding to the rate limiting `limit` in seconds for an instance. |
| rejected_code | integer | False | 503 | [200, 599] | The HTTP status code returned when a request exceeding the quota is rejected. |
| rejected_msg | string | False | | | The response body returned when a request exceeding the quota is rejected. |
## Examples
The examples below demonstrate how you can configure `ai-rate-limiting` for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Apply Rate Limiting with `ai-proxy`
The following example demonstrates how you can use `ai-proxy` to proxy LLM traffic and use `ai-rate-limiting` to configure token-based rate limiting on the instance.
Create a Route as such and update with your LLM providers, models, API keys, and endpoints, if applicable:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-rate-limiting-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-proxy": {
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options": {
"model": "gpt-35-turbo-instruct",
"max_tokens": 512,
"temperature": 1.0
}
},
"ai-rate-limiting": {
"limit": 300,
"time_window": 30,
"limit_strategy": "prompt_tokens"
}
}
}'
```
Send a POST request to the Route with a system prompt and a sample user question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1 + 1 equals 2. This is a fundamental arithmetic operation where adding one unit to another results in a total of two units."
},
"logprobs": null,
"finish_reason": "stop"
}
],
...
}
```
If the rate limiting quota of 300 prompt tokens has been consumed in a 30-second window, all additional requests will be rejected.
### Rate Limit One Instance Among Multiple
The following example demonstrates how you can use `ai-proxy-multi` to configure two models for load balancing, forwarding 80% of the traffic to one instance and 20% to the other. Additionally, use `ai-rate-limiting` to configure token-based rate limiting on the instance that receives 80% of the traffic, such that when the configured quota is fully consumed, the additional traffic will be forwarded to the other instance.
Create a Route which applies rate limiting quota of 100 total tokens in a 30-second window on the `deepseek-instance-1` instance, and update with your LLM providers, models, API keys, and endpoints, if applicable:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-rate-limiting-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-rate-limiting": {
"instances": [
{
"name": "deepseek-instance-1",
"provider": "deepseek",
"weight": 8,
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
},
{
"name": "deepseek-instance-2",
"provider": "deepseek",
"weight": 2,
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
}
]
},
"ai-rate-limiting": {
"instances": [
{
"name": "deepseek-instance-1",
"limit_strategy": "total_tokens",
"limit": 100,
"time_window": 30
}
]
}
}
}'
```
Send a POST request to the Route with a system prompt and a sample user question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1 + 1 equals 2. This is a fundamental arithmetic operation where adding one unit to another results in a total of two units."
},
"logprobs": null,
"finish_reason": "stop"
}
],
...
}
```
If `deepseek-instance-1` instance rate limiting quota of 100 tokens has been consumed in a 30-second window, the additional requests will all be forwarded to `deepseek-instance-2`, which is not rate limited.
### Apply the Same Quota to All Instances
The following example demonstrates how you can apply the same rate limiting quota to all LLM upstream instances in `ai-rate-limiting`.
For demonstration and easier differentiation, you will be configuring one OpenAI instance and one DeepSeek instance as the upstream LLM services.
Create a Route which applies a rate limiting quota of 100 total tokens for all instances within a 60-second window, and update with your LLM providers, models, API keys, and endpoints, if applicable:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-rate-limiting-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-rate-limiting": {
"instances": [
{
"name": "openai-instance",
"provider": "openai",
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options": {
"model": "gpt-4"
}
},
{
"name": "deepseek-instance",
"provider": "deepseek",
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
}
]
},
"ai-rate-limiting": {
"limit": 100,
"time_window": 60,
"rejected_code": 429,
"limit_strategy": "total_tokens"
}
}
}'
```
Send a POST request to the Route with a system prompt and a sample user question in the request body:
```shell
curl -i "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newtons laws" }
]
}'
```
You should receive a response from either LLM instance, similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Sure! Sir Isaac Newton formulated three laws of motion that describe the motion of objects. These laws are widely used in physics and engineering for studying and understanding how things move. Here they are:\n\n1. Newton's First Law - Law of Inertia: An object at rest tends to stay at rest and an object in motion tends to stay in motion with the same speed and in the same direction unless acted upon by an unbalanced force. This is also known as the principle of inertia.\n\n2. Newton's Second Law of Motion - Force and Acceleration: The acceleration of an object is directly proportional to the net force acting on it and inversely proportional to its mass. This is usually formulated as F=ma where F is the force applied, m is the mass of the object and a is the acceleration produced.\n\n3. Newton's Third Law - Action and Reaction: For every action, there is an equal and opposite reaction. This means that any force exerted on a body will create a force of equal magnitude but in the opposite direction on the object that exerted the first force.\n\nIn simple terms: \n1. If you slide a book on a table and let go, it will stop because of the friction (or force) between it and the table.\n2.",
"refusal": null
},
"logprobs": null,
"finish_reason": "length"
}
],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 256,
"total_tokens": 279,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": null
}
```
Since the `total_tokens` value exceeds the configured quota of `100`, the next request within the 60-second window is expected to be forwarded to the other instance.
Within the same 60-second window, send another POST request to the Route:
```shell
curl -i "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newtons laws" }
]
}'
```
You should receive a response from the other LLM instance, similar to the following:
```json
{
...
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Sure! Newton's laws of motion are three fundamental principles that describe the relationship between the motion of an object and the forces acting on it. They were formulated by Sir Isaac Newton in the late 17th century and are foundational to classical mechanics. Here's an explanation of each law:\n\n---\n\n### **1. Newton's First Law (Law of Inertia)**\n- **Statement**: An object will remain at rest or in uniform motion in a straight line unless acted upon by an external force.\n- **What it means**: This law introduces the concept of **inertia**, which is the tendency of an object to resist changes in its state of motion. If no net force acts on an object, its velocity (speed and direction) will not change.\n- **Example**: A book lying on a table will stay at rest unless you push it. Similarly, a hockey puck sliding on ice will keep moving at a constant speed unless friction or another force slows it down.\n\n---\n\n### **2. Newton's Second Law (Law of Acceleration)**\n- **Statement**: The acceleration of an object is directly proportional to the net force acting on it and inversely proportional to its mass. Mathematically, this is expressed as:\n \\[\n F = ma\n \\]\n"
},
"logprobs": null,
"finish_reason": "length"
}
],
"usage": {
"prompt_tokens": 13,
"completion_tokens": 256,
"total_tokens": 269,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 13
},
"system_fingerprint": "fp_3a5770e1b4_prod0225"
}
```
Since the `total_tokens` value exceeds the configured quota of `100`, the next request within the 60-second window is expected to be rejected.
Within the same 60-second window, send a third POST request to the Route:
```shell
curl -i "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newtons laws" }
]
}'
```
You should receive an `HTTP 429 Too Many Requests` response and observe the following headers:
```text
X-AI-RateLimit-Limit-openai-instance: 100
X-AI-RateLimit-Remaining-openai-instance: 0
X-AI-RateLimit-Reset-openai-instance: 0
X-AI-RateLimit-Limit-deepseek-instance: 100
X-AI-RateLimit-Remaining-deepseek-instance: 0
X-AI-RateLimit-Reset-deepseek-instance: 0
```
### Configure Instance Priority and Rate Limiting
The following example demonstrates how you can configure two models with different priorities and apply rate limiting on the instance with a higher priority. In the case where `fallback_strategy` is set to `instance_health_and_rate_limiting`, the Plugin should continue to forward requests to the low priority instance once the high priority instance's rate limiting quota is fully consumed.
Create a Route as such to set rate limiting and a higher priority on `openai-instance` instance and set the `fallback_strategy` to `instance_health_and_rate_limiting`. Update with your LLM providers, models, API keys, and endpoints, if applicable:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-rate-limiting-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"ai-proxy-multi": {
"fallback_strategy: "instance_health_and_rate_limiting",
"instances": [
{
"name": "openai-instance",
"provider": "openai",
"priority": 1,
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options": {
"model": "gpt-4"
}
},
{
"name": "deepseek-instance",
"provider": "deepseek",
"priority": 0,
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
}
]
},
"ai-rate-limiting": {
"instances": [
{
"name": "openai-instance",
"limit": 10,
"time_window": 60
}
],
"limit_strategy": "total_tokens"
}
}
}'
```
Send a POST request to the Route with a system prompt and a sample user question in the request body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1+1 equals 2.",
"refusal": null
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 8,
"total_tokens": 31,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": null
}
```
Since the `total_tokens` value exceeds the configured quota of `10`, the next request within the 60-second window is expected to be forwarded to the other instance.
Within the same 60-second window, send another POST request to the Route:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newton law" }
]
}'
```
You should see a response similar to the following:
```json
{
...,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Certainly! Newton's laws of motion are three fundamental principles that describe the relationship between the motion of an object and the forces acting on it. They were formulated by Sir Isaac Newton in the late 17th century and are foundational to classical mechanics.\n\n---\n\n### **1. Newton's First Law (Law of Inertia):**\n- **Statement:** An object at rest will remain at rest, and an object in motion will continue moving at a constant velocity (in a straight line at a constant speed), unless acted upon by an external force.\n- **Key Idea:** This law introduces the concept of **inertia**, which is the tendency of an object to resist changes in its state of motion.\n- **Example:** If you slide a book across a table, it eventually stops because of the force of friction acting on it. Without friction, the book would keep moving indefinitely.\n\n---\n\n### **2. Newton's Second Law (Law of Acceleration):**\n- **Statement:** The acceleration of an object is directly proportional to the net force acting on it and inversely proportional to its mass. Mathematically, this is expressed as:\n \\[\n F = ma\n \\]\n where:\n - \\( F \\) = net force applied (in Newtons),\n -"
},
...
}
],
...
}
```
### Load Balance and Rate Limit by Consumers
The following example demonstrates how you can configure two models for load balancing and apply rate limiting by Consumer.
Create a Consumer `johndoe` and a rate limiting quota of 10 tokens in a 60-second window on `openai-instance` instance:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe",
"plugins": {
"ai-rate-limiting": {
"instances": [
{
"name": "openai-instance",
"limit": 10,
"time_window": 60
}
],
"rejected_code": 429,
"limit_strategy": "total_tokens"
}
}
}'
```
Configure `key-auth` credential for `johndoe`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create another Consumer `janedoe` and a rate limiting quota of 10 tokens in a 60-second window on `deepseek-instance` instance:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe",
"plugins": {
"ai-rate-limiting": {
"instances": [
{
"name": "deepseek-instance",
"limit": 10,
"time_window": 60
}
],
"rejected_code": 429,
"limit_strategy": "total_tokens"
}
}
}'
```
Configure `key-auth` credential for `janedoe`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/janedoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
```
Create a Route as such and update with your LLM providers, models, API keys, and endpoints, if applicable:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ai-rate-limiting-route",
"uri": "/anything",
"methods": ["POST"],
"plugins": {
"key-auth": {},
"ai-proxy-multi": {
"fallback_strategy: "instance_health_and_rate_limiting",
"instances": [
{
"name": "openai-instance",
"provider": "openai",
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$OPENAI_API_KEY"'"
}
},
"options": {
"model": "gpt-4"
}
},
{
"name": "deepseek-instance",
"provider": "deepseek",
"weight": 0,
"auth": {
"header": {
"Authorization": "Bearer '"$DEEPSEEK_API_KEY"'"
}
},
"options": {
"model": "deepseek-chat"
}
}
]
}
}
}'
```
Send a POST request to the Route without any Consumer key:
```shell
curl -i "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive an `HTTP/1.1 401 Unauthorized` response.
Send a POST request to the Route with `johndoe`'s key:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-H 'apikey: john-key' \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "1+1 equals 2.",
"refusal": null
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 23,
"completion_tokens": 8,
"total_tokens": 31,
"prompt_tokens_details": {
"cached_tokens": 0,
"audio_tokens": 0
},
"completion_tokens_details": {
"reasoning_tokens": 0,
"audio_tokens": 0,
"accepted_prediction_tokens": 0,
"rejected_prediction_tokens": 0
}
},
"service_tier": "default",
"system_fingerprint": null
}
```
Since the `total_tokens` value exceeds the configured quota of the `openai` instance for `johndoe`, the next request within the 60-second window from `johndoe` is expected to be forwarded to the `deepseek` instance.
Within the same 60-second window, send another POST request to the Route with `johndoe`'s key:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-H 'apikey: john-key' \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newtons laws to me" }
]
}'
```
You should see a response similar to the following:
```json
{
...,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Certainly! Newton's laws of motion are three fundamental principles that describe the relationship between the motion of an object and the forces acting on it. They were formulated by Sir Isaac Newton in the late 17th century and are foundational to classical mechanics.\n\n---\n\n### **1. Newton's First Law (Law of Inertia):**\n- **Statement:** An object at rest will remain at rest, and an object in motion will continue moving at a constant velocity (in a straight line at a constant speed), unless acted upon by an external force.\n- **Key Idea:** This law introduces the concept of **inertia**, which is the tendency of an object to resist changes in its state of motion.\n- **Example:** If you slide a book across a table, it eventually stops because of the force of friction acting on it. Without friction, the book would keep moving indefinitely.\n\n---\n\n### **2. Newton's Second Law (Law of Acceleration):**\n- **Statement:** The acceleration of an object is directly proportional to the net force acting on it and inversely proportional to its mass. Mathematically, this is expressed as:\n \\[\n F = ma\n \\]\n where:\n - \\( F \\) = net force applied (in Newtons),\n -"
},
...
}
],
...
}
```
Send a POST request to the Route with `janedoe`'s key:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-H 'apikey: jane-key' \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "What is 1+1?" }
]
}'
```
You should receive a response similar to the following:
```json
{
...,
"model": "deepseek-chat",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "The sum of 1 and 1 is 2. This is a basic arithmetic operation where you combine two units to get a total of two units."
},
"logprobs": null,
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 14,
"completion_tokens": 31,
"total_tokens": 45,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 14
},
"system_fingerprint": "fp_3a5770e1b4_prod0225"
}
```
Since the `total_tokens` value exceeds the configured quota of the `deepseek` instance for `janedoe`, the next request within the 60-second window from `janedoe` is expected to be forwarded to the `openai` instance.
Within the same 60-second window, send another POST request to the Route with `janedoe`'s key:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-H 'apikey: jane-key' \
-d '{
"messages": [
{ "role": "system", "content": "You are a mathematician" },
{ "role": "user", "content": "Explain Newtons laws to me" }
]
}'
```
You should see a response similar to the following:
```json
{
...,
"model": "gpt-4-0613",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Sure, here are Newton's three laws of motion:\n\n1) Newton's First Law, also known as the Law of Inertia, states that an object at rest will stay at rest, and an object in motion will stay in motion, unless acted on by an external force. In simple words, this law suggests that an object will keep doing whatever it is doing until something causes it to do otherwise. \n\n2) Newton's Second Law states that the force acting on an object is equal to the mass of that object times its acceleration (F=ma). This means that force is directly proportional to mass and acceleration. The heavier the object and the faster it accelerates, the greater the force.\n\n3) Newton's Third Law, also known as the law of action and reaction, states that for every action, there is an equal and opposite reaction. Essentially, any force exerted onto a body will create a force of equal magnitude but in the opposite direction on the object that exerted the first force.\n\nRemember, these laws become less accurate when considering speeds near the speed of light (where Einstein's theory of relativity becomes more appropriate) or objects very small or very large. However, for everyday situations, they provide a good model of how things move.",
"refusal": null
},
"logprobs": null,
"finish_reason": "stop"
}
],
...
}
```
This shows `ai-proxy-multi` load balance the traffic with respect to the rate limiting rules in `ai-rate-limiting` by Consumers.

View File

@@ -0,0 +1,177 @@
---
title: ai-request-rewrite
keywords:
- Apache APISIX
- AI Gateway
- Plugin
- ai-request-rewrite
description: The ai-request-rewrite plugin intercepts client requests before they are forwarded to the upstream service. It sends a predefined prompt, along with the original request body, to a specified LLM service. The LLM processes the input and returns a modified request body, which is then used for the upstream request. This allows dynamic transformation of API requests based on AI-generated content.
---
<!--
#
# 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.
#
-->
## Description
The `ai-request-rewrite` plugin intercepts client requests before they are forwarded to the upstream service. It sends a predefined prompt, along with the original request body, to a specified LLM service. The LLM processes the input and returns a modified request body, which is then used for the upstream request. This allows dynamic transformation of API requests based on AI-generated content.
## Plugin Attributes
| **Field** | **Required** | **Type** | **Description** |
| ------------------------- | ------------ | -------- | ------------------------------------------------------------------------------------ |
| prompt | Yes | String | The prompt send to LLM service. |
| provider | Yes | String | Name of the LLM service. Available options: openai, deekseek, aimlapi and openai-compatible. When `aimlapi` is selected, the plugin uses the OpenAI-compatible driver with a default endpoint of `https://api.aimlapi.com/v1/chat/completions`. |
| auth | Yes | Object | Authentication configuration |
| auth.header | No | Object | Authentication headers. Key must match pattern `^[a-zA-Z0-9._-]+$`. |
| auth.query | No | Object | Authentication query parameters. Key must match pattern `^[a-zA-Z0-9._-]+$`. |
| options | No | Object | Key/value settings for the model |
| options.model | No | String | Model to execute. Examples: "gpt-3.5-turbo" for openai, "deepseek-chat" for deekseek, or "qwen-turbo" for openai-compatible or aimlapi services |
| override.endpoint | No | String | Override the default endpoint when using OpenAI-compatible services (e.g., self-hosted models or third-party LLM services). When the provider is 'openai-compatible', the endpoint field is required. |
| timeout | No | Integer | Total timeout in milliseconds for requests to LLM service, including connect, send, and read timeouts. Range: 1 - 60000. Default: 30000|
| keepalive | No | Boolean | Enable keepalive for requests to LLM service. Default: true |
| keepalive_timeout | No | Integer | Keepalive timeout in milliseconds for requests to LLM service. Minimum: 1000. Default: 60000 |
| keepalive_pool | No | Integer | Keepalive pool size for requests to LLM service. Minimum: 1. Default: 30 |
| ssl_verify | No | Boolean | SSL verification for requests to LLM service. Default: true |
## How it works
![image](https://github.com/user-attachments/assets/c7288e4f-00fc-46ca-b69e-d3d74d7085ca)
## Examples
The examples below demonstrate how you can configure `ai-request-rewrite` for different scenarios.
:::note
You can fetch the admin_key from config.yaml and save to an environment variable with the following command:
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
:::
### Redact sensitive information
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "Given a JSON request body, identify and mask any sensitive information such as credit card numbers, social security numbers, and personal identification numbers (e.g., passport or driver'\''s license numbers). Replace detected sensitive values with a masked format (e.g., \"*** **** **** 1234\") for credit card numbers. Ensure the JSON structure remains unchanged.",
"provider": "openai",
"auth": {
"header": {
"Authorization": "Bearer <some-token>"
}
},
"options": {
"model": "gpt-4"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Now send a request:
```shell
curl "http://127.0.0.1:9080/anything" \
-H "Content-Type: application/json" \
-d '{
"name": "John Doe",
"email": "john.doe@example.com",
"credit_card": "4111 1111 1111 1111",
"ssn": "123-45-6789",
"address": "123 Main St"
}'
```
The request body send to the LLM Service is as follows:
```json
{
"messages": [
{
"role": "system",
"content": "Given a JSON request body, identify and mask any sensitive information such as credit card numbers, social security numbers, and personal identification numbers (e.g., passport or driver's license numbers). Replace detected sensitive values with a masked format (e.g., '*** **** **** 1234') for credit card numbers). Ensure the JSON structure remains unchanged."
},
{
"role": "user",
"content": "{\n\"name\":\"John Doe\",\n\"email\":\"john.doe@example.com\",\n\"credit_card\":\"4111 1111 1111 1111\",\n\"ssn\":\"123-45-6789\",\n\"address\":\"123 Main St\"\n}"
}
]
}
```
The LLM processes the input and returns a modified request body, which replace detected sensitive values with a masked format then used for the upstream request:
```json
{
"name": "John Doe",
"email": "john.doe@example.com",
"credit_card": "**** **** **** 1111",
"ssn": "***-**-6789",
"address": "123 Main St"
}
```
### Send request to an OpenAI compatible LLM
Create a route with the `ai-request-rewrite` plugin with `provider` set to `openai-compatible` and the endpoint of the model set to `override.endpoint` like so:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"uri": "/anything",
"plugins": {
"ai-request-rewrite": {
"prompt": "Given a JSON request body, identify and mask any sensitive information such as credit card numbers, social security numbers, and personal identification numbers (e.g., passport or driver'\''s license numbers). Replace detected sensitive values with a masked format (e.g., '*** **** **** 1234') for credit card numbers). Ensure the JSON structure remains unchanged.",
"provider": "openai-compatible",
"auth": {
"header": {
"Authorization": "Bearer <some-token>"
}
},
"options": {
"model": "qwen-plus",
"max_tokens": 1024,
"temperature": 1
},
"override": {
"endpoint": "https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,136 @@
---
title: api-breaker
keywords:
- Apache APISIX
- API Gateway
- API Breaker
description: This document describes the information about the Apache APISIX api-breaker Plugin, you can use it to protect Upstream services.
---
<!--
#
# 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.
#
-->
## Description
The `api-breaker` Plugin implements circuit breaker functionality to protect Upstream services.
:::note
Whenever the Upstream service responds with a status code from the configured `unhealthy.http_statuses` list for the configured `unhealthy.failures` number of times, the Upstream service will be considered unhealthy.
The request is then retried in 2, 4, 8, 16 ... seconds until the `max_breaker_sec`.
In an unhealthy state, if the Upstream service responds with a status code from the configured list `healthy.http_statuses` for `healthy.successes` times, the service is considered healthy again.
:::
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|-------------------------|----------------|----------|---------|-----------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| break_response_code | integer | True | | [200, ..., 599] | HTTP error code to return when Upstream is unhealthy. |
| break_response_body | string | False | | | Body of the response message to return when Upstream is unhealthy. |
| break_response_headers | array[object] | False | | [{"key":"header_name","value":"can contain Nginx $var"}] | Headers of the response message to return when Upstream is unhealthy. Can only be configured when the `break_response_body` attribute is configured. The values can contain APISIX variables. For example, we can use `{"key":"X-Client-Addr","value":"$remote_addr:$remote_port"}`. |
| max_breaker_sec | integer | False | 300 | >=3 | Maximum time in seconds for circuit breaking. |
| unhealthy.http_statuses | array[integer] | False | [500] | [500, ..., 599] | Status codes of Upstream to be considered unhealthy. |
| unhealthy.failures | integer | False | 3 | >=1 | Number of failures within a certain period of time for the Upstream service to be considered unhealthy. |
| healthy.http_statuses | array[integer] | False | [200] | [200, ..., 499] | Status codes of Upstream to be considered healthy. |
| healthy.successes | integer | False | 3 | >=1 | Number of consecutive healthy requests for the Upstream service to be considered healthy. |
## Enable Plugin
The example below shows how you can configure the Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"api-breaker": {
"break_response_code": 502,
"unhealthy": {
"http_statuses": [500, 503],
"failures": 3
},
"healthy": {
"http_statuses": [200],
"successes": 1
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
In this configuration, a response code of `500` or `503` three times within a certain period of time triggers the unhealthy status of the Upstream service. A response code of `200` restores its healthy status.
## Example usage
Once you have configured the Plugin as shown above, you can test it out by sending a request.
```shell
curl -i -X POST "http://127.0.0.1:9080/hello"
```
If the Upstream service responds with an unhealthy response code, you will receive the configured response code (`break_response_code`).
```shell
HTTP/1.1 502 Bad Gateway
...
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
## Delete Plugin
To remove the `api-breaker` 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 '
{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,180 @@
---
title: attach-consumer-label
keywords:
- Apache APISIX
- API Gateway
- API Consumer
description: This article describes the Apache APISIX attach-consumer-label plugin, which you can use to pass custom consumer labels to upstream services.
---
<!--
#
# 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.
#
-->
## Description
The `attach-consumer-label` plugin attaches custom consumer-related labels, in addition to `X-Consumer-Username` and `X-Credential-Indentifier`, to authenticated requests, for upstream services to differentiate between consumers and implement additional logics.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------|--------|----------|---------|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| headers | object | True | | | Key-value pairs of consumer labels to be attached to request headers, where key is the request header name, such as `X-Consumer-Role`, and the value is a reference to the custom label key, such as `$role`. Note that the value should always start with a dollar sign (`$`). If a referenced consumer value is not configured on the consumer, the corresponding header will not be attached to the request. |
## Enable Plugin
The following example demonstrates how you can attach custom labels to request headers before authenticated requests are forwarded to upstream services. If the request is rejected, you should not see any consumer labels attached to request headers. If a certain label value is not configured on the consumer but referenced in the `attach-consumer-label` plugin, the corresponding header will also not be attached.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
Create a consumer `john` with custom labels:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"username": "john",
# highlight-start
"labels": {
// Annotate 1
"department": "devops",
// Annotate 2
"company": "api7"
}
# highlight-end
}'
```
❶ Label the `department` information for the consumer.
❷ Label the `company` information for the consumer.
Configure the `key-auth` credential for the consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create a route enabling the `key-auth` and `attach-consumer-label` plugins:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"id": "attach-consumer-label-route",
"uri": "/get",
"plugins": {
"key-auth": {},
# highlight-start
"attach-consumer-label": {
"headers": {
// Annotate 1
"X-Consumer-Department": "$department",
// Annotate 2
"X-Consumer-Company": "$company",
// Annotate 3
"X-Consumer-Role": "$role"
}
}
# highlight-end
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
❶ Attach the `department` consumer label value in the `X-Consumer-Department` request header.
❷ Attach the `company` consumer label value in the `X-Consumer-Company` request header.
❸ Attach the `role` consumer label value in the `X-Consumer-Role` request header. As the `role` label is not configured on the consumer, it is expected that the header will not appear in the request forwarded to the upstream service.
:::tip
The consumer label references must be prefixed by a dollar sign (`$`).
:::
To verify, send a request to the route with the valid credential:
```shell
curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {},
"headers": {
"Accept": "*/*",
"Apikey": "john-key",
"Host": "127.0.0.1",
# highlight-start
"X-Consumer-Username": "john",
"X-Credential-Indentifier": "cred-john-key-auth",
"X-Consumer-Company": "api7",
"X-Consumer-Department": "devops",
# highlight-end
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66e5107c-5bb3e24f2de5baf733aec1cc",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 205.198.122.37",
"url": "http://127.0.0.1/get"
}
```
## Delete plugin
To remove the 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/attach-consumer-label-route" -X PUT \
-H "X-API-KEY: ${ADMIN_API_KEY}" \
-d '{
"uri": "/get",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,263 @@
---
title: authz-casbin
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Authz Casbin
- authz-casbin
description: This document contains information about the Apache APISIX authz-casbin Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `authz-casbin` Plugin is an authorization Plugin based on [Lua Casbin](https://github.com/casbin/lua-casbin/). This Plugin supports powerful authorization scenarios based on various [access control models](https://casbin.org/docs/en/supported-models).
## Attributes
| Name | Type | Required | Description |
|-------------|--------|----------|----------------------------------------------------------------------------------------|
| model_path | string | True | Path of the Casbin model configuration file. |
| policy_path | string | True | Path of the Casbin policy file. |
| model | string | True | Casbin model configuration in text format. |
| policy | string | True | Casbin policy in text format. |
| username | string | True | Header in the request that will be used in the request to pass the username (subject). |
:::note
You must either specify the `model_path`, `policy_path`, and the `username` attributes or specify the `model`, `policy` and the `username` attributes in the Plugin configuration for it to be valid.
If you wish to use a global Casbin configuration, you can first specify `model` and `policy` attributes in the Plugin metadata and only the `username` attribute in the Plugin configuration. All Routes will use the Plugin configuration this way.
:::
## Metadata
| Name | Type | Required | Description |
|--------|--------|----------|--------------------------------------------|
| model | string | True | Casbin model configuration in text format. |
| policy | string | True | Casbin policy in text format. |
## Enable Plugin
You can enable the Plugin on a Route by either using the model/policy file paths or using the model/policy text in Plugin configuration/metadata.
### By using model/policy file paths
The example below shows setting up Casbin authentication from your model/policy configuration file:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"authz-casbin": {
"model_path": "/path/to/model.conf",
"policy_path": "/path/to/policy.csv",
"username": "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}'
```
### By using model/policy text in Plugin configuration
The example below shows setting up Casbin authentication from your model/policy text in your Plugin configuration:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"authz-casbin": {
"model": "[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
"policy": "p, *, /, GET
p, admin, *, *
g, alice, admin",
"username": "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}'
```
### By using model/policy text in Plugin metadata
First, you need to send a `PUT` request to the Admin API to add the `model` and `policy` text to the Plugin metadata.
All Routes configured this way will use a single Casbin enforcer with the configured Plugin metadata. You can also update the model/policy in this way and the Plugin will automatically update to the new configuration.
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/authz-casbin -H "X-API-KEY: $admin_key" -i -X PUT -d '
{
"model": "[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)",
"policy": "p, *, /, GET
p, admin, *, *
g, alice, admin"
}'
```
Once you have updated the Plugin metadata, you can add the Plugin to a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"authz-casbin": {
"username": "user"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/*"
}'
```
:::note
The Plugin Route configuration has a higher precedence than the Plugin metadata configuration. If the model/policy configuration is present in the Plugin Route configuration, it is used instead of the metadata configuration.
:::
## Example usage
We define the example model as:
```conf
[request_definition]
r = sub, obj, act
[policy_definition]
p = sub, obj, act
[role_definition]
g = _, _
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = (g(r.sub, p.sub) || keyMatch(r.sub, p.sub)) && keyMatch(r.obj, p.obj) && keyMatch(r.act, p.act)
```
And the example policy as:
```conf
p, *, /, GET
p, admin, *, *
g, alice, admin
```
See [examples](https://github.com/casbin/lua-casbin/tree/master/examples) for more policy and model configurations.
The above configuration will let anyone access the homepage (`/`) using a `GET` request while only users with admin permissions can access other pages and use other request methods.
So if we make a get request to the homepage:
```shell
curl -i http://127.0.0.1:9080/ -X GET
```
But if an unauthorized user tries to access any other page, they will get a 403 error:
```shell
curl -i http://127.0.0.1:9080/res -H 'user: bob' -X GET
HTTP/1.1 403 Forbidden
```
And only users with admin privileges can access the endpoints:
```shell
curl -i http://127.0.0.1:9080/res -H 'user: alice' -X GET
```
## Delete Plugin
To remove the `authz-casbin` 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 '
{
"methods": ["GET"],
"uri": "/*",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,118 @@
---
title: authz-casdoor
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Authz Casdoor
- authz-casdoor
description: This document contains information about the Apache APISIX authz-casdoor Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `authz-casdoor` Plugin can be used to add centralized authentication with [Casdoor](https://casdoor.org/).
## Attributes
| Name | Type | Required | Description |
|---------------|--------|----------|----------------------------------------------|
| endpoint_addr | string | True | URL of Casdoor. |
| client_id | string | True | Client ID in Casdoor. |
| client_secret | string | True | Client secret in Casdoor. |
| callback_url | string | True | Callback URL used to receive state and code. |
NOTE: `encrypt_fields = {"client_secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
:::info IMPORTANT
`endpoint_addr` and `callback_url` should not end with '/'.
:::
:::info IMPORTANT
The `callback_url` must belong to the URI of your Route. See the code snippet below for an example configuration.
:::
## Enable Plugin
You can enable the Plugin on a specific Route as shown below:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/1" -H "X-API-KEY: edd1c9f034335f136f87ad84b625c8f1" -X PUT -d '
{
"methods": ["GET"],
"uri": "/anything/*",
"plugins": {
"authz-casdoor": {
"endpoint_addr":"http://localhost:8000",
"callback_url":"http://localhost:9080/anything/callback",
"client_id":"7ceb9b7fda4a9061ec1c",
"client_secret":"3416238e1edf915eac08b8fe345b2b95cdba7e04"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
## Example usage
Once you have enabled the Plugin, a new user visiting this Route would first be processed by the `authz-casdoor` Plugin. They would be redirected to the login page of Casdoor.
After successfully logging in, Casdoor will redirect this user to the `callback_url` with GET parameters `code` and `state` specified. The Plugin will also request for an access token and confirm whether the user is really logged in. This process is only done once and subsequent requests are left uninterrupted.
Once this is done, the user is redirected to the original URL they wanted to visit.
## Delete Plugin
To remove the `authz-casdoor` 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.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/anything/*",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,241 @@
---
title: authz-keycloak
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Authz Keycloak
- authz-keycloak
description: This document contains information about the Apache APISIX authz-keycloak Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `authz-keycloak` Plugin can be used to add authentication with [Keycloak Identity Server](https://www.keycloak.org/).
:::tip
Although this Plugin was developed to work with Keycloak, it should work with any OAuth/OIDC and UMA compliant identity providers as well.
:::
Refer to [Authorization Services Guide](https://www.keycloak.org/docs/latest/authorization_services/) for more information on Keycloak.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------------------------------------------|---------------|----------|-----------------------------------------------|--------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| discovery | string | False | | https://host.domain/realms/foo/.well-known/uma2-configuration | URL to [discovery document](https://www.keycloak.org/docs/latest/authorization_services/index.html) of Keycloak Authorization Services. |
| token_endpoint | string | False | | https://host.domain/realms/foo/protocol/openid-connect/token | An OAuth2-compliant token endpoint that supports the `urn:ietf:params:oauth:grant-type:uma-ticket` grant type. If provided, overrides the value from discovery. |
| resource_registration_endpoint | string | False | | https://host.domain/realms/foo/authz/protection/resource_set | A UMA-compliant resource registration endpoint. If provided, overrides the value from discovery. |
| client_id | string | True | | | The identifier of the resource server to which the client is seeking access. |
| client_secret | string | False | | | The client secret, if required. You can use APISIX secret to store and reference this value. APISIX currently supports storing secrets in two ways. [Environment Variables and HashiCorp Vault](../terminology/secret.md) |
| grant_type | string | False | "urn:ietf:params:oauth:grant-type:uma-ticket" | ["urn:ietf:params:oauth:grant-type:uma-ticket"] | |
| policy_enforcement_mode | string | False | "ENFORCING" | ["ENFORCING", "PERMISSIVE"] | |
| permissions | array[string] | False | | | An array of strings, each representing a set of one or more resources and scopes the client is seeking access. |
| lazy_load_paths | boolean | False | false | | When set to true, dynamically resolves the request URI to resource(s) using the resource registration endpoint instead of the static permission. |
| http_method_as_scope | boolean | False | false | | When set to true, maps the HTTP request type to scope of the same name and adds to all requested permissions. |
| timeout | integer | False | 3000 | [1000, ...] | Timeout in ms for the HTTP connection with the Identity Server. |
| access_token_expires_in | integer | False | 300 | [1, ...] | Expiration time(s) of the access token. |
| access_token_expires_leeway | integer | False | 0 | [0, ...] | Expiration leeway(s) for access_token renewal. When set, the token will be renewed access_token_expires_leeway seconds before expiration. This avoids errors in cases where the access_token just expires when reaching the OAuth Resource Server. |
| refresh_token_expires_in | integer | False | 3600 | [1, ...] | The expiration time(s) of the refresh token. |
| refresh_token_expires_leeway | integer | False | 0 | [0, ...] | Expiration leeway(s) for refresh_token renewal. When set, the token will be renewed refresh_token_expires_leeway seconds before expiration. This avoids errors in cases where the refresh_token just expires when reaching the OAuth Resource Server. |
| ssl_verify | boolean | False | true | | When set to true, verifies if TLS certificate matches hostname. |
| cache_ttl_seconds | integer | False | 86400 (equivalent to 24h) | positive integer >= 1 | Maximum time in seconds up to which the Plugin caches discovery documents and tokens used by the Plugin to authenticate to Keycloak. |
| keepalive | boolean | False | true | | When set to true, enables HTTP keep-alive to keep connections open after use. Set to `true` if you are expecting a lot of requests to Keycloak. |
| keepalive_timeout | integer | False | 60000 | positive integer >= 1000 | Idle time after which the established HTTP connections will be closed. |
| keepalive_pool | integer | False | 5 | positive integer >= 1 | Maximum number of connections in the connection pool. |
| access_denied_redirect_uri | string | False | | [1, 2048] | URI to redirect the user to instead of returning an error message like `"error_description":"not_authorized"`. |
| password_grant_token_generation_incoming_uri | string | False | | /api/token | Set this to generate token using the password grant type. The Plugin will compare incoming request URI to this value. |
NOTE: `encrypt_fields = {"client_secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
### Discovery and endpoints
It is recommended to use the `discovery` attribute as the `authz-keycloak` Plugin can discover the Keycloak API endpoints from it.
If set, the `token_endpoint` and `resource_registration_endpoint` will override the values obtained from the discovery document.
### Client ID and secret
The Plugin needs the `client_id` attribute for identification and to specify the context in which to evaluate permissions when interacting with Keycloak.
If the `lazy_load_paths` attribute is set to true, then the Plugin additionally needs to obtain an access token for itself from Keycloak. In such cases, if the client access to Keycloak is confidential, you need to configure the `client_secret` attribute.
### Policy enforcement mode
The `policy_enforcement_mode` attribute specifies how policies are enforced when processing authorization requests sent to the server.
#### `ENFORCING` mode
Requests are denied by default even when there is no policy associated with a resource.
The `policy_enforcement_mode` is set to `ENFORCING` by default.
#### `PERMISSIVE` mode
Requests are allowed when there is no policy associated with a given resource.
### Permissions
When handling incoming requests, the Plugin can determine the permissions to check with Keycloak statically or dynamically from the properties of the request.
If the `lazy_load_paths` attribute is set to `false`, the permissions are taken from the `permissions` attribute. Each entry in `permissions` needs to be formatted as expected by the token endpoint's `permission` parameter. See [Obtaining Permissions](https://www.keycloak.org/docs/latest/authorization_services/index.html#_service_obtaining_permissions).
:::note
A valid permission can be a single resource or a resource paired with on or more scopes.
:::
If the `lazy_load_paths` attribute is set to `true`, the request URI is resolved to one or more resources configured in Keycloak using the resource registration endpoint. The resolved resources are used as the permissions to check.
:::note
This requires the Plugin to obtain a separate access token for itself from the token endpoint. So, make sure to set the `Service Accounts Enabled` option in the client settings in Keycloak.
Also make sure that the issued access token contains the `resource_access` claim with the `uma_protection` role to ensure that the Plugin is able to query resources through the Protection API.
:::
### Automatically mapping HTTP method to scope
The `http_method_as_scope` is often used together with `lazy_load_paths` but can also be used with a static permission list.
If the `http_method_as_scope` attribute is set to `true`, the Plugin maps the request's HTTP method to the scope with the same name. The scope is then added to every permission to check.
If the `lazy_load_paths` attribute is set to false, the Plugin adds the mapped scope to any of the static permissions configured in the `permissions` attribute—even if they contain on or more scopes already.
### Generating a token using `password` grant
To generate a token using `password` grant, you can set the value of the `password_grant_token_generation_incoming_uri` attribute.
If the incoming URI matches the configured attribute and the request method is POST, a token is generated using the `token_endpoint`.
You also need to add `application/x-www-form-urlencoded` as `Content-Type` header and `username` and `password` as parameters.
The example below shows a request if the `password_grant_token_generation_incoming_uri` is `/api/token`:
```shell
curl --location --request POST 'http://127.0.0.1:9080/api/token' \
--header 'Accept: application/json, text/plain, */*' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=<User_Name>' \
--data-urlencode 'password=<Password>'
```
## Enable Plugin
The example below shows how you can enable the `authz-keycloak` Plugin on a specific Route. `${realm}` represents the realm name in Keycloak.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/5 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/get",
"plugins": {
"authz-keycloak": {
"token_endpoint": "http://127.0.0.1:8090/realms/${realm}/protocol/openid-connect/token",
"permissions": ["resource name#scope name"],
"client_id": "Client ID"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```
## Example usage
Once you have enabled the Plugin on a Route you can use it.
First, you have to get the JWT token from Keycloak:
```shell
curl "http://<YOUR_KEYCLOAK_HOST>/realms/<YOUR_REALM>/protocol/openid-connect/token" \
-d "client_id=<YOUR_CLIENT_ID>" \
-d "client_secret=<YOUR_CLIENT_SECRET>" \
-d "username=<YOUR_USERNAME>" \
-d "password=<YOUR_PASSWORD>" \
-d "grant_type=password"
```
You should see a response similar to the following:
```text
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJoT3ludlBPY2d6Y3VWWnYtTU42bXZKMUczb0dOX2d6MFo3WFl6S2FSa1NBIn0.eyJleHAiOjE3MDMyOTAyNjAsImlhdCI6MTcwMzI4OTk2MCwianRpIjoiMjJhOGFmMzItNDM5Mi00Yzg3LThkM2UtZDkyNDVmZmNiYTNmIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjAyZWZlY2VlLTBmYTgtNDg1OS1iYmIwLTgyMGZmZDdjMWRmYSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI1YzIzZjVkZC1hN2ZhLTRlMmItOWQxNC02MmI1YzYyNmU1NDYiLCJhY3IiOiIxIiwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImRlZmF1bHQtcm9sZXMtcXVpY2tzdGFydC1yZWFsbSIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpbIm1hbmFnZS1hY2NvdW50IiwibWFuYWdlLWFjY291bnQtbGlua3MiLCJ2aWV3LXByb2ZpbGUiXX19LCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI1YzIzZjVkZC1hN2ZhLTRlMmItOWQxNC02MmI1YzYyNmU1NDYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInByZWZlcnJlZF91c2VybmFtZSI6InF1aWNrc3RhcnQtdXNlciJ9.WNZQiLRleqCxw-JS-MHkqXnX_BPA9i6fyVHqF8l-L-2QxcqTAwbIp7AYKX-z90CG6EdRXOizAEkQytB32eVWXaRkLeTYCI7wIrT8XSVTJle4F88ohuBOjDfRR61yFh5k8FXXdAyRzcR7tIeE2YUFkRqw1gCT_VEsUuXPqm2wTKOmZ8fRBf4T-rP4-ZJwPkHAWc_nG21TmLOBCSulzYqoC6Lc-OvX5AHde9cfRuXx-r2HhSYs4cXtvX-ijA715MY634CQdedheoGca5yzPsJWrAlBbCruN2rdb4u5bDxKU62pJoJpmAsR7d5qYpYVA6AsANDxHLk2-W5F7I_IxqR0YQ","expires_in":300,"refresh_expires_in":1800,"refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJjN2IwYmY4NC1kYjk0LTQ5YzctYWIyZC01NmU3ZDc1MmRkNDkifQ.eyJleHAiOjE3MDMyOTE3NjAsImlhdCI6MTcwMzI4OTk2MCwianRpIjoiYzcyZjAzMzctYmZhNS00MWEzLTlhYjEtZmJlNGY0NmZjMDgxIiwiaXNzIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwiYXVkIjoiaHR0cDovLzE5Mi4xNjguMS44Mzo4MDgwL3JlYWxtcy9xdWlja3N0YXJ0LXJlYWxtIiwic3ViIjoiMDJlZmVjZWUtMGZhOC00ODU5LWJiYjAtODIwZmZkN2MxZGZhIiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImFwaXNpeC1xdWlja3N0YXJ0LWNsaWVudCIsInNlc3Npb25fc3RhdGUiOiI1YzIzZjVkZC1hN2ZhLTRlMmItOWQxNC02MmI1YzYyNmU1NDYiLCJzY29wZSI6ImVtYWlsIHByb2ZpbGUiLCJzaWQiOiI1YzIzZjVkZC1hN2ZhLTRlMmItOWQxNC02MmI1YzYyNmU1NDYifQ.7AH7ppbVOlkYc9CoJ7kLSlDUkmFuNga28Amugn2t724","token_type":"Bearer","not-before-policy":0,"session_state":"5c23f5dd-a7fa-4e2b-9d14-62b5c626e546","scope":"email profile"}
```
Now you can make requests with the access token:
```shell
curl http://127.0.0.1:9080/get -H 'Authorization: Bearer ${ACCESS_TOKEN}'
```
To learn more about how you can integrate authorization policies into your API workflows you can checkout the unit test [authz-keycloak.t](https://github.com/apache/apisix/blob/master/t/plugin/authz-keycloak.t).
Run the following Docker image and go to `http://localhost:8090` to view the associated policies for the unit tests.
```bash
docker run -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=123456 -p 8090:8080 sshniro/keycloak-apisix
```
The image below shows how the policies are configured in the Keycloak server:
![Keycloak policy design](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/authz-keycloak.png)
## Delete Plugin
To remove the `authz-keycloak` 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/5 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/get",
"plugins": {
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```
## Plugin roadmap
- Currently, the `authz-keycloak` Plugin requires you to define the resource name and the required scopes to enforce policies for a Route. Keycloak's official adapted (Java, Javascript) provides path matching by querying Keycloak paths dynamically and lazy loading the paths to identity resources. Upcoming releases of the Plugin will support this function.
- To support reading scope and configurations from the Keycloak JSON file.

View File

@@ -0,0 +1,217 @@
---
title: aws-lambda
keywords:
- Apache APISIX
- Plugin
- AWS Lambda
- aws-lambda
description: This document contains information about the Apache APISIX aws-lambda Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `aws-lambda` Plugin is used for integrating APISIX with [AWS Lambda](https://aws.amazon.com/lambda/) and [Amazon API Gateway](https://aws.amazon.com/api-gateway/) as a dynamic upstream to proxy all requests for a particular URI to the AWS Cloud.
When enabled, the Plugin terminates the ongoing request to the configured URI and initiates a new request to the AWS Lambda Gateway URI on behalf of the client with configured authorization details, request headers, body and parameters (all three passed from the original request). It returns the response with headers, status code and the body to the client that initiated the request with APISIX.
This Plugin supports authorization via AWS API key and AWS IAM secrets. The Plugin implements [AWS Signature Version 4 signing](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_aws-signing.html) for IAM secrets.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------------------|---------|----------|---------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| function_uri | string | True | | | AWS API Gateway endpoint which triggers the lambda serverless function. |
| authorization | object | False | | | Authorization credentials to access the cloud function. |
| authorization.apikey | string | False | | | Generated API Key to authorize requests to the AWS Gateway endpoint. |
| authorization.iam | object | False | | | Used for AWS IAM role based authorization performed via AWS v4 request signing. See [IAM authorization schema](#iam-authorization-schema). |
| authorization.iam.accesskey | string | True | | Generated access key ID from AWS IAM console. |
| authorization.iam.secretkey | string | True | | Generated access key secret from AWS IAM console. |
| authorization.iam.aws_region | string | False | "us-east-1" | AWS region where the request is being sent. |
| authorization.iam.service | string | False | "execute-api" | The service that is receiving the request. For Amazon API gateway APIs, it should be set to `execute-api`. For Lambda function, it should be set to `lambda`. |
| timeout | integer | False | 3000 | [100,...] | Proxy request timeout in milliseconds. |
| ssl_verify | boolean | False | true | true/false | When set to `true` performs SSL verification. |
| keepalive | boolean | False | true | true/false | When set to `true` keeps the connection alive for reuse. |
| keepalive_pool | integer | False | 5 | [1,...] | Maximum number of requests that can be sent on this connection before closing it. |
| keepalive_timeout | integer | False | 60000 | [1000,...] | Time is ms for connection to remain idle without closing. |
## Enable Plugin
The example below shows how you can configure the Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"aws-lambda": {
"function_uri": "https://x9w6z07gb9.execute-api.us-east-1.amazonaws.com/default/test-apisix",
"authorization": {
"apikey": "<Generated API Key from aws console>"
},
"ssl_verify":false
}
},
"uri": "/aws"
}'
```
Now, any requests (HTTP/1.1, HTTPS, HTTP2) to the endpoint `/aws` will invoke the configured AWS Functions URI and the response will be proxied back to the client.
In the example below, AWS Lambda takes in name from the query and returns a message "Hello $name":
```shell
curl -i -XGET localhost:9080/aws\?name=APISIX
```
```shell
HTTP/1.1 200 OK
Content-Type: application/json
Connection: keep-alive
Date: Sat, 27 Nov 2021 13:08:27 GMT
x-amz-apigw-id: JdwXuEVxIAMFtKw=
x-amzn-RequestId: 471289ab-d3b7-4819-9e1a-cb59cac611e0
Content-Length: 16
X-Amzn-Trace-Id: Root=1-61a22dca-600c552d1c05fec747fd6db0;Sampled=0
Server: APISIX/2.10.2
"Hello, APISIX!"
```
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
```
```shell
HTTP/2 200
content-type: application/json
content-length: 16
x-amz-apigw-id: JdwulHHrIAMFoFg=
date: Sat, 27 Nov 2021 13:10:53 GMT
x-amzn-trace-id: Root=1-61a22e5d-342eb64077dc9877644860dd;Sampled=0
x-amzn-requestid: a2c2b799-ecc6-44ec-b586-38c0e3b11fe4
server: APISIX/2.10.2
"Hello, APISIX!"
```
Similarly, the function can be triggered via AWS API Gateway by using AWS IAM permissions for authorization. The Plugin includes authentication signatures in HTTP calls via AWS v4 request signing. The example below shows this method:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"aws-lambda": {
"function_uri": "https://ajycz5e0v9.execute-api.us-east-1.amazonaws.com/default/test-apisix",
"authorization": {
"iam": {
"accesskey": "<access key>",
"secretkey": "<access key secret>"
}
},
"ssl_verify": false
}
},
"uri": "/aws"
}'
```
:::note
This approach assumes that you have already an IAM user with programmatic access enabled with the required permissions (`AmazonAPIGatewayInvokeFullAccess`) to access the endpoint.
:::
### Configuring path forwarding
The `aws-lambda` Plugin also supports URL path forwarding while proxying requests to the AWS upstream. Extensions to the base request path gets appended to the `function_uri` specified in the Plugin configuration.
:::info IMPORTANT
The `uri` configured on a Route must end with `*` for this feature to work properly. APISIX Routes are matched strictly and the `*` implies that any subpath to this URI would be matched to the same Route.
:::
The example below configures this feature:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"aws-lambda": {
"function_uri": "https://x9w6z07gb9.execute-api.us-east-1.amazonaws.com",
"authorization": {
"apikey": "<Generate API key>"
},
"ssl_verify":false
}
},
"uri": "/aws/*"
}'
```
Now, any requests to the path `aws/default/test-apisix` will invoke the AWS Lambda Function and the added path is forwarded:
```shell
curl -i -XGET http://127.0.0.1:9080/aws/default/test-apisix\?name\=APISIX
```
```shell
HTTP/1.1 200 OK
Content-Type: application/json
Connection: keep-alive
Date: Wed, 01 Dec 2021 14:23:27 GMT
X-Amzn-Trace-Id: Root=1-61a7855f-0addc03e0cf54ddc683de505;Sampled=0
x-amzn-RequestId: f5f4e197-9cdd-49f9-9b41-48f0d269885b
Content-Length: 16
x-amz-apigw-id: JrHG8GC4IAMFaGA=
Server: APISIX/2.11.0
"Hello, APISIX!"
```
## Delete Plugin
To remove the `aws-lambda` 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 '
{
"uri": "/aws",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,199 @@
---
title: azure-functions
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Azure Functions
- azure-functions
description: This document contains information about the Apache APISIX azure-functions Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `azure-functions` Plugin is used to integrate APISIX with [Azure Serverless Function](https://azure.microsoft.com/en-in/services/functions/) as a dynamic upstream to proxy all requests for a particular URI to the Microsoft Azure Cloud.
When enabled, the Plugin terminates the ongoing request to the configured URI and initiates a new request to Azure Functions on behalf of the client with configured authorization details, request headers, body and parameters (all three passed from the original request). It returns back the response with headers, status code and the body to the client that initiated the request with APISIX.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|------------------------|---------|----------|---------|--------------|---------------------------------------------------------------------------------------------------------------------------------------|
| function_uri | string | True | | | Azure FunctionS endpoint which triggers the serverless function. For example, `http://test-apisix.azurewebsites.net/api/HttpTrigger`. |
| authorization | object | False | | | Authorization credentials to access Azure Functions. |
| authorization.apikey | string | False | | | Generated API key to authorize requests. |
| authorization.clientid | string | False | | | Azure AD client ID to authorize requests. |
| timeout | integer | False | 3000 | [100,...] | Proxy request timeout in milliseconds. |
| ssl_verify | boolean | False | true | true/false | When set to `true` performs SSL verification. |
| keepalive | boolean | False | true | true/false | When set to `true` keeps the connection alive for reuse. |
| keepalive_pool | integer | False | 5 | [1,...] | Maximum number of requests that can be sent on this connection before closing it. |
| keepalive_timeout | integer | False | 60000 | [1000,...] | Time is ms for connection to remain idle without closing. |
## Metadata
| Name | Type | Required | Default | Description |
|-----------------|--------|----------|---------|----------------------------------------------------------------------|
| master_apikey | string | False | "" | API Key secret that could be used to access the Azure Functions URI. |
| master_clientid | string | False | "" | Azure AD client ID that could be used to authorize the function URI. |
Metadata can be used in the `azure-functions` Plugin for an authorization fallback. If there are no authorization details in the Plugin's attributes, the `master_apikey` and `master_clientid` configured in the metadata is used.
The relative order priority is as follows:
1. Plugin looks for `x-functions-key` or `x-functions-clientid` key inside the header from the request to APISIX.
2. If not found, the Plugin checks the configured attributes for authorization details. If present, it adds the respective header to the request sent to the Azure Functions.
3. If authorization details are not configured in the Plugin's attributes, APISIX fetches the metadata and uses the master keys.
To add a new master API key, you can make a request to `/apisix/admin/plugin_metadata` with the required metadata as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/azure-functions -H "X-API-KEY: $admin_key" -X PUT -d '
{
"master_apikey" : "<Your Azure master access key>"
}'
```
## Enable Plugin
You can configure the Plugin on a specific Route as shown below assuming that you already have your Azure Functions up and running:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"azure-functions": {
"function_uri": "http://test-apisix.azurewebsites.net/api/HttpTrigger",
"authorization": {
"apikey": "<Generated API key to access the Azure-Function>"
}
}
},
"uri": "/azure"
}'
```
Now, any requests (HTTP/1.1, HTTPS, HTTP2) to the endpoint `/azure` will invoke the configured Azure Functions URI and the response will be proxied back to the client.
In the example below, the Azure Function takes in name from the query and returns a message "Hello $name":
```shell
curl -i -XGET http://localhost:9080/azure\?name=APISIX
```
```shell
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Request-Context: appId=cid-v1:38aae829-293b-43c2-82c6-fa94aec0a071
Date: Wed, 17 Nov 2021 14:46:55 GMT
Server: APISIX/2.10.2
Hello, APISIX
```
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
```
```shell
HTTP/2 200
content-type: text/plain; charset=utf-8
request-context: appId=cid-v1:38aae829-293b-43c2-82c6-fa94aec0a071
date: Wed, 17 Nov 2021 14:54:07 GMT
server: APISIX/2.10.2
Hello, APISIX
```
### Configuring path forwarding
The `azure-functions` Plugins also supports URL path forwarding while proxying requests to the Azure Functions upstream. Extensions to the base request path gets appended to the `function_uri` specified in the Plugin configuration.
:::info IMPORTANT
The `uri` configured on a Route must end with `*` for this feature to work properly. APISIX Routes are matched strictly and the `*` implies that any subpath to this URI would be matched to the same Route.
:::
The example below configures this feature:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"azure-functions": {
"function_uri": "http://app-bisakh.azurewebsites.net/api",
"authorization": {
"apikey": "<Generated API key to access the Azure-Function>"
}
}
},
"uri": "/azure/*"
}'
```
Now, any requests to the path `azure/HttpTrigger1` will invoke the Azure Function and the added path is forwarded:
```shell
curl -i -XGET http://127.0.0.1:9080/azure/HttpTrigger1\?name\=APISIX\
```
```shell
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Wed, 01 Dec 2021 14:19:53 GMT
Request-Context: appId=cid-v1:4d4b6221-07f1-4e1a-9ea0-b86a5d533a94
Server: APISIX/2.11.0
Hello, APISIX
```
## Delete Plugin
To remove the `azure-functions` 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 '
{
"uri": "/azure",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,514 @@
---
title: basic-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Basic Auth
- basic-auth
description: The basic-auth Plugin adds basic access authentication for Consumers to authenticate themselves before being able to access Upstream resources.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/basic-auth" />
</head>
## Description
The `basic-auth` Plugin adds [basic access authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) for [Consumers](../terminology/consumer.md) to authenticate themselves before being able to access Upstream resources.
When a Consumer is successfully authenticated, APISIX adds additional headers, such as `X-Consumer-Username`, `X-Credential-Indentifier`, and other Consumer custom headers if configured, to the request, before proxying it to the Upstream service. The Upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.
## Attributes
For Consumer/Credentials:
| Name | Type | Required | Description |
|----------|--------|----------|------------------------------------------------------------------------------------------------------------------------|
| username | string | True | Unique basic auth username for a consumer. |
| password | string | True | Basic auth password for the consumer. |
NOTE: `encrypt_fields = {"password"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
For Route:
| Name | Type | Required | Default | Description |
|------------------|---------|----------|---------|------------------------------------------------------------------------|
| hide_credentials | boolean | False | false | If true, do not pass the authorization request header to Upstream services. |
| anonymous_consumer | boolean | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
## Examples
The examples below demonstrate how you can work with the `basic-auth` Plugin for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Implement Basic Authentication on Route
The following example demonstrates how to implement basic authentication on a Route.
Create a Consumer `johndoe`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe"
}'
```
Create `basic-auth` Credential for the consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-basic-auth",
"plugins": {
"basic-auth": {
"username": "johndoe",
"password": "john-key"
}
}
}'
```
Create a Route with `basic-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "basic-auth-route",
"uri": "/anything",
"plugins": {
"basic-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
#### Verify with a Valid Key
Send a request to with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -u johndoe:john-key
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"args": {},
"headers": {
"Accept": "*/*",
"Apikey": "john-key",
"Authorization": "Basic am9obmRvZTpqb2huLWtleQ==",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66e5107c-5bb3e24f2de5baf733aec1cc",
"X-Consumer-Username": "john",
"X-Credential-Indentifier": "cred-john-basic-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 205.198.122.37",
"url": "http://127.0.0.1/get"
}
```
#### Verify with an Invalid Key
Send a request with an invalid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -u johndoe:invalid-key
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following:
```text
{"message":"Invalid user authorization"}
```
#### Verify without a Key
Send a request to without a key:
```shell
curl -i "http://127.0.0.1:9080/anything"
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following:
```text
{"message":"Missing authorization in request"}
```
### Hide Authentication Information From Upstream
The following example demonstrates how to prevent the key from being sent to the Upstream services by configuring `hide_credentials`. In APISIX, the authentication key is forwarded to the Upstream services by default, which might lead to security risks in some circumstances and you should consider updating `hide_credentials`.
Create a Consumer `johndoe`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe"
}'
```
Create `basic-auth` Credential for the consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-basic-auth",
"plugins": {
"basic-auth": {
"username": "johndoe",
"password": "john-key"
}
}
}'
```
#### Without Hiding Credentials
Create a Route with `basic-auth` and configure `hide_credentials` to `false`, which is the default configuration:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "basic-auth-route",
"uri": "/anything",
"plugins": {
"basic-auth": {
"hide_credentials": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -u johndoe:john-key
```
You should see an `HTTP/1.1 200 OK` response with the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "Basic am9obmRvZTpqb2huLWtleQ==",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66cc2195-22bd5f401b13480e63c498c6",
"X-Consumer-Username": "john",
"X-Credential-Indentifier": "cred-john-basic-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "192.168.65.1, 43.228.226.23",
"url": "http://127.0.0.1/anything"
}
```
Note that the credentials are visible to the Upstream service in base64-encoded format.
:::tip
You can also pass the base64-encoded credentials in the request using the `Authorization` header as such:
```shell
curl -i "http://127.0.0.1:9080/anything" -H "Authorization: Basic am9obmRvZTpqb2huLWtleQ=="
```
:::
#### Hide Credentials
Update the plugin's `hide_credentials` to `true`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/basic-auth-route" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"basic-auth": {
"hide_credentials": true
}
}
}'
```
Send a request with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -u johndoe:john-key
```
You should see an `HTTP/1.1 200 OK` response with the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66cc21a7-4f6ac87946e25f325167d53a",
"X-Consumer-Username": "john",
"X-Credential-Indentifier": "cred-john-basic-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "192.168.65.1, 43.228.226.23",
"url": "http://127.0.0.1/anything"
}
```
Note that the credentials are no longer visible to the Upstream service.
### Add Consumer Custom ID to Header
The following example demonstrates how you can attach a Consumer custom ID to authenticated request in the `Consumer-Custom-Id` header, which can be used to implement additional logics as needed.
Create a Consumer `johndoe` with a custom ID label:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe",
"labels": {
"custom_id": "495aec6a"
}
}'
```
Create `basic-auth` Credential for the consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-basic-auth",
"plugins": {
"basic-auth": {
"username": "johndoe",
"password": "john-key"
}
}
}'
```
Create a Route with `basic-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "basic-auth-route",
"uri": "/anything",
"plugins": {
"basic-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send a request to the Route with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -u johndoe:john-key
```
You should see an `HTTP/1.1 200 OK` response with the `X-Consumer-Custom-Id` similar to the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "Basic am9obmRvZTpqb2huLWtleQ==",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea8d64-33df89052ae198a706e18c2a",
"X-Consumer-Username": "johndoe",
"X-Credential-Identifier": "cred-john-basic-auth",
"X-Consumer-Custom-Id": "495aec6a",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "192.168.65.1, 205.198.122.37",
"url": "http://127.0.0.1/anything"
}
```
### Rate Limit with Anonymous Consumer
The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous Consumer does not need to authenticate and has less quotas.
Create a regular Consumer `johndoe` and configure the `limit-count` Plugin to allow for a quota of 3 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "johndoe",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create the `basic-auth` Credential for the Consumer `johndoe`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/johndoe/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-basic-auth",
"plugins": {
"basic-auth": {
"username": "johndoe",
"password": "john-key"
}
}
}'
```
Create an anonymous user `anonymous` and configure the `limit-count` Plugin to allow for a quota of 1 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create a Route and configure the `basic-auth` Plugin to accept anonymous Consumer `anonymous` from bypassing the authentication:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "basic-auth-route",
"uri": "/anything",
"plugins": {
"basic-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send five consecutive requests with `john`'s key:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -u johndoe:john-key -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
```text
200: 3, 429: 2
```
Send five anonymous requests:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that only one request was successful:
```text
200: 1, 429: 4
```

View File

@@ -0,0 +1,225 @@
---
title: batch-requests
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Batch Requests
description: This document contains information about the Apache APISIX batch-request Plugin.
---
<!--
#
# 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.
#
-->
## Description
After enabling the batch-requests plugin, users can assemble multiple requests into one request and send them to the gateway. The gateway will parse the corresponding requests from the request body and then individually encapsulate them into separate requests. Instead of the user initiating multiple HTTP requests to the gateway, the gateway will use the HTTP pipeline method, go through several stages such as route matching, forwarding to the corresponding upstream, and then return the combined results to the client after merging.
![batch-request](https://static.apiseven.com/uploads/2023/06/27/ATzEuOn4_batch-request.png)
In cases where the client needs to access multiple APIs, this will significantly improve performance.
:::note
The request headers in the users original request (except for headers starting with “Content-”, such as “Content-Type”) will be assigned to each request in the HTTP pipeline. Therefore, to the gateway, these HTTP pipeline requests sent to itself are no different from external requests initiated directly by users. They can only access pre-configured routes and will undergo a complete authentication process, so there are no security issues.
If the request headers of the original request conflict with those configured in the plugin, the request headers configured in the plugin will take precedence (except for the real_ip_header specified in the configuration file).
:::
## Attributes
None.
## API
This plugin adds `/apisix/batch-requests` as an endpoint.
:::note
You may need to use the [public-api](public-api.md) plugin to expose this endpoint.
:::
## Enable Plugin
You can enable the `batch-requests` Plugin by adding it to your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
- ...
- batch-requests
```
## Configuration
By default, the maximum body size that can be sent to `/apisix/batch-requests` can't be larger than 1 MiB. You can change this configuration of the Plugin through the endpoint `apisix/admin/plugin_metadata/batch-requests`:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/batch-requests -H "X-API-KEY: $admin_key" -X PUT -d '
{
"max_body_size": 4194304
}'
```
## Metadata
| Name | Type | Required | Default | Valid values | Description |
| ------------- | ------- | -------- | ------- | ------------ | ------------------------------------------ |
| max_body_size | integer | True | 1048576 | [1, ...] | Maximum size of the request body in bytes. |
## Request and response format
This plugin will create an API endpoint in APISIX to handle batch requests.
### Request
| Name | Type | Required | Default | Description |
| -------- |------------------------------------| -------- | ------- | ----------------------------- |
| query | object | False | | Query string for the request. |
| headers | object | False | | Headers for all the requests. |
| timeout | integer | False | 30000 | Timeout in ms. |
| pipeline | array[[HttpRequest](#httprequest)] | True | | Details of the request. |
#### HttpRequest
| Name | Type | Required | Default | Valid | Description |
| ---------- | ------- | -------- | ------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| version | string | False | 1.1 | [1.0, 1.1] | HTTP version. |
| method | string | False | GET | ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE"] | HTTP method. |
| query | object | False | | | Query string for the request. If set, overrides the value of the global query string. |
| headers | object | False | | | Headers for the request. If set, overrides the value of the global query string. |
| path | string | True | | | Path of the HTTP request. |
| body | string | False | | | Body of the HTTP request. |
| ssl_verify | boolean | False | false | | Set to verify if the SSL certs matches the hostname. |
### Response
The response is an array of [HttpResponses](#httpresponse).
#### HttpResponse
| Name | Type | Description |
| ------- | ------- | ---------------------- |
| status | integer | HTTP status code. |
| reason | string | HTTP reason-phrase. |
| body | string | HTTP response body. |
| headers | object | HTTP response headers. |
## Specifying a custom URI
You can specify a custom URI with the [public-api](public-api.md) Plugin.
You can set the URI you want when creating the Route and change the configuration of the public-api Plugin:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/br -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/batch-requests",
"plugins": {
"public-api": {
"uri": "/apisix/batch-requests"
}
}
}'
```
## Example usage
First, you need to setup a Route to the batch request API. We will use the [public-api](public-api.md) Plugin for this:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/br -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/apisix/batch-requests",
"plugins": {
"public-api": {}
}
}'
```
Now you can make a request to the batch request API (`/apisix/batch-requests`):
```shell
curl --location --request POST 'http://127.0.0.1:9080/apisix/batch-requests' \
--header 'Content-Type: application/json' \
--data '{
"headers": {
"Content-Type": "application/json",
"admin-jwt":"xxxx"
},
"timeout": 500,
"pipeline": [
{
"method": "POST",
"path": "/community.GiftSrv/GetGifts",
"body": "test"
},
{
"method": "POST",
"path": "/community.GiftSrv/GetGifts",
"body": "test2"
}
]
}'
```
This will give a response:
```json
[
{
"status": 200,
"reason": "OK",
"body": "{\"ret\":500,\"msg\":\"error\",\"game_info\":null,\"gift\":[],\"to_gets\":0,\"get_all_msg\":\"\"}",
"headers": {
"Connection": "keep-alive",
"Date": "Sat, 11 Apr 2020 17:53:20 GMT",
"Content-Type": "application/json",
"Content-Length": "81",
"Server": "APISIX web server"
}
},
{
"status": 200,
"reason": "OK",
"body": "{\"ret\":500,\"msg\":\"error\",\"game_info\":null,\"gift\":[],\"to_gets\":0,\"get_all_msg\":\"\"}",
"headers": {
"Connection": "keep-alive",
"Date": "Sat, 11 Apr 2020 17:53:20 GMT",
"Content-Type": "application/json",
"Content-Length": "81",
"Server": "APISIX web server"
}
}
]
```
## Delete Plugin
You can remove `batch-requests` from your list of Plugins in your configuration file (`conf/config.yaml`).

View File

@@ -0,0 +1,609 @@
---
title: body-transformer
keywords:
- Apache APISIX
- API Gateway
- Plugin
- BODY TRANSFORMER
- body-transformer
description: The body-transformer Plugin performs template-based transformations to transform the request and/or response bodies from one format to another, for example, from JSON to JSON, JSON to HTML, or XML to YAML.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/body-transformer" />
</head>
## Description
The `body-transformer` Plugin performs template-based transformations to transform the request and/or response bodies from one format to another, for example, from JSON to JSON, JSON to HTML, or XML to YAML.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ------------- | ------- | -------- | ------- | ------------ | ------------------------------------------ |
| `request` | object | False | | | Request body transformation configuration. |
| `request.input_format` | string | False | | [`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] | Request body original media type. If unspecified, the value would be determined by the `Content-Type` header to apply the corresponding decoder. The `xml` option corresponds to `text/xml` media type. The `json` option corresponds to `application/json` media type. The `encoded` option corresponds to `application/x-www-form-urlencoded` media type. The `args` option corresponds to GET requests. The `plain` option corresponds to `text/plain` media type. The `multipart` option corresponds to `multipart/related` media type. If the media type is neither type, the value would be left unset and the transformation template will be directly applied. |
| `request.template` | string | True | | | Request body transformation template. The template uses [lua-resty-template](https://github.com/bungle/lua-resty-template) syntax. See the [template syntax](https://github.com/bungle/lua-resty-template#template-syntax) for more details. You can also use auxiliary functions `_escape_json()` and `_escape_xml()` to escape special characters such as double quotes, `_body` to access request body, and `_ctx` to access context variables. |
| `request.template_is_base64` | boolean | False | false | | Set to true if the template is base64 encoded. |
| `response` | object | False | | | Response body transformation configuration. |
| `response.input_format` | string | False | | [`xml`,`json`] | Response body original media type. If unspecified, the value would be determined by the `Content-Type` header to apply the corresponding decoder. If the media type is neither `xml` nor `json`, the value would be left unset and the transformation template will be directly applied. |
| `response.template` | string | True | | | Response body transformation template. |
| `response.template_is_base64` | boolean | False | false | | Set to true if the template is base64 encoded. |
## Examples
The examples below demonstrate how you can configure `body-transformer` for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
The transformation template uses [lua-resty-template](https://github.com/bungle/lua-resty-template) syntax. See the [template syntax](https://github.com/bungle/lua-resty-template#template-syntax) to learn more.
You can also use auxiliary functions `_escape_json()` and `_escape_xml()` to escape special characters such as double quotes, `_body` to access request body, and `_ctx` to access context variables.
In all cases, you should ensure that the transformation template is a valid JSON string.
### Transform between JSON and XML SOAP
The following example demonstrates how to transform the request body from JSON to XML and the response body from XML to JSON when working with a SOAP Upstream service.
Start the sample SOAP service:
```shell
cd /tmp
git clone https://github.com/spring-guides/gs-soap-service.git
cd gs-soap-service/complete
./mvnw spring-boot:run
```
Create the request and response transformation templates:
```shell
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
<?xml version="1.0"?>
<soap-env:Envelope xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
<soap-env:Body>
<ns0:getCountryRequest xmlns:ns0="http://spring.io/guides/gs-producing-web-service">
<ns0:name>{{_escape_xml(name)}}</ns0:name>
</ns0:getCountryRequest>
</soap-env:Body>
</soap-env:Envelope>
EOF
)
rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr -d '\r\n'
{% if Envelope.Body.Fault == nil then %}
{
"status":"{{_ctx.var.status}}",
"currency":"{{Envelope.Body.getCountryResponse.country.currency}}",
"population":{{Envelope.Body.getCountryResponse.country.population}},
"capital":"{{Envelope.Body.getCountryResponse.country.capital}}",
"name":"{{Envelope.Body.getCountryResponse.country.name}}"
}
{% else %}
{
"message":{*_escape_json(Envelope.Body.Fault.faultstring[1])*},
"code":"{{Envelope.Body.Fault.faultcode}}"
{% if Envelope.Body.Fault.faultactor ~= nil then %}
, "actor":"{{Envelope.Body.Fault.faultactor}}"
{% end %}
}
{% end %}
EOF
)
```
`awk` and `tr` are used above to manipulate the template such that the template would be a valid JSON string.
Create a Route with `body-transformer` using the templates created previously. In the Plugin, set the request input format as JSON, the response input format as XML, and the `Content-Type` header to `text/xml` for the Upstream service to respond properly:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"methods": ["POST"],
"uri": "/ws",
"plugins": {
"body-transformer": {
"request": {
"template": "'"$req_template"'",
"input_format": "json"
},
"response": {
"template": "'"$rsp_template"'",
"input_format": "xml"
}
},
"proxy-rewrite": {
"headers": {
"set": {
"Content-Type": "text/xml"
}
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"localhost:8080": 1
}
}
}'
```
:::tip
If it is cumbersome to adjust complex text files to be valid transformation templates, you can use the base64 utility to encode the files, such as the following:
```json
"body-transformer": {
"request": {
"template": "'"$(base64 -w0 /path/to/request_template_file)"'"
},
"response": {
"template": "'"$(base64 -w0 /path/to/response_template_file)"'"
}
}
```
:::
Send a request with a valid JSON body:
```shell
curl "http://127.0.0.1:9080/ws" -X POST -d '{"name": "Spain"}'
```
The JSON body sent in the request will be transformed into XML before being forwarded to the Upstream SOAP service, and the response body will be transformed back from XML to JSON.
You should see a response similar to the following:
```json
{
"status": "200",
"currency": "EUR",
"population": 46704314,
"capital": "Madrid",
"name": "Spain"
}
```
### Modify Request Body
The following example demonstrates how to dynamically modify the request body.
Create a Route with `body-transformer`, in which the template appends the word `world` to the `name` and adds `10` to the `age` to set them as values to `foo` and `bar` respectively:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to the Route:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/json" \
-d '{"name":"hello","age":20}' \
-i
```
You should see a response of the following:
```json
{
"args": {},
"data": "{\"foo\":\"hello world\",\"bar\":30}",
...
"json": {
"bar": 30,
"foo": "hello world"
},
"method": "POST",
...
}
```
### Generate Request Body Using Variables
The following example demonstrates how to generate request body dynamically using the `ctx` context variables.
Create a Route with `body-transformer`, in which the template accesses the request argument using the [Nginx variable](https://nginx.org/en/docs/http/ngx_http_core_module.html) `arg_name`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "{\"foo\":\"{{_ctx.var.arg_name .. \" world\"}}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to the Route with `name` argument:
```shell
curl -i "http://127.0.0.1:9080/anything?name=hello"
```
You should see a response like this:
```json
{
"args": {
"name": "hello"
},
...,
"json": {
"foo": "hello world"
},
...
}
```
### Transform Body from YAML to JSON
The following example demonstrates how to transform request body from YAML to JSON.
Create the request transformation template:
```shell
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1'
{%
local yaml = require("tinyyaml")
local body = yaml.parse(_body)
%}
{"foobar":"{{body.foobar.foo .. " " .. body.foobar.bar}}"}
EOF
)
```
Create a Route with `body-transformer` that uses the template:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"template": "'"$req_template"'"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to the Route with a YAML body:
```shell
body='
foobar:
foo: hello
bar: world'
curl "http://127.0.0.1:9080/anything" -X POST \
-d "$body" \
-H "Content-Type: text/yaml" \
-i
```
You should see a response similar to the following, which verifies that the YAML body was appropriately transformed to JSON:
```json
{
"args": {},
"data": "{\"foobar\":\"hello world\"}",
...
"json": {
"foobar": "hello world"
},
...
}
```
### Transform Form URL Encoded Body to JSON
The following example demonstrates how to transform `form-urlencoded` body to JSON.
Create a Route with `body-transformer` which sets the `input_format` to `encoded` and configures a template that appends string `world` to the `name` input, add `10` to the `age` input:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "encoded",
"template": "{\"foo\":\"{{name .. \" world\"}}\",\"bar\":{{age+10}}}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a POST request to the Route with an encoded body:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
-d 'name=hello&age=20'
```
You should see a response similar to the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"foo\":\"hello world\",\"bar\":30}": ""
},
"headers": {
...
},
...
}
```
### Transform GET Request Query Parameter to Body
The following example demonstrates how to transform a GET request query parameter to request body. Note that this does not transform the HTTP method. To transform the method, see [`proxy-rewrite`](./proxy-rewrite.md).
Create a Route with `body-transformer`, which sets the `input_format` to `args` and configures a template that adds a message to the request:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "args",
"template": "{\"message\": \"hello {{name}}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a GET request to the Route:
```shell
curl "http://127.0.0.1:9080/anything?name=john"
```
You should see a response similar to the following:
```json
{
"args": {},
"data": "{\"message\": \"hello john\"}",
"files": {},
"form": {},
"headers": {
...
},
"json": {
"message": "hello john"
},
"method": "GET",
...
}
```
### Transform Plain Media Type
The following example demonstrates how to transform requests with `plain` media type.
Create a Route with `body-transformer`, which sets the `input_format` to `plain` and configures a template to remove `not` and a subsequent space from the body string:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "plain",
"template": "{\"message\": \"{* string.gsub(_body, \"not \", \"\") *}\"}"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a POST request to the Route:
```shell
curl "http://127.0.0.1:9080/anything" -X POST \
-d 'not actually json' \
-i
```
You should see a response similar to the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"message\": \"actually json\"}": ""
},
"headers": {
...
},
...
}
```
### Transform Multipart Media Type
The following example demonstrates how to transform requests with `multipart` media type.
Create a request transformation template which adds a `status` to the body based on the `age` provided in the request body:
```shell
req_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1'
{%
local core = require 'apisix.core'
local cjson = require 'cjson'
if tonumber(context.age) > 18 then
context._multipart:set_simple("status", "adult")
else
context._multipart:set_simple("status", "minor")
end
local body = context._multipart:tostring()
%}{* body *}
EOF
)
```
Create a Route with `body-transformer`, which sets the `input_format` to `multipart` and uses the previously created request template for transformation:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "body-transformer-route",
"uri": "/anything",
"plugins": {
"body-transformer": {
"request": {
"input_format": "multipart",
"template": "'"$req_template"'"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a multipart POST request to the Route:
```shell
curl -X POST \
-F "name=john" \
-F "age=10" \
"http://127.0.0.1:9080/anything"
```
You should see a response similar to the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {
"age": "10",
"name": "john",
"status": "minor"
},
"headers": {
"Accept": "*/*",
"Content-Length": "361",
"Content-Type": "multipart/form-data; boundary=------------------------qtPjk4c8ZjmGOXNKzhqnOP",
...
},
...
}
```

View File

@@ -0,0 +1,138 @@
---
title: brotli
keywords:
- Apache APISIX
- API Gateway
- Plugin
- brotli
description: This document contains information about the Apache APISIX brotli Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `brotli` Plugin dynamically sets the behavior of [brotli in Nginx](https://github.com/google/ngx_brotli).
## Prerequisites
This Plugin requires brotli shared libraries.
The example commands to build and install brotli shared libraries:
``` shell
wget https://github.com/google/brotli/archive/refs/tags/v1.1.0.zip
unzip v1.1.0.zip
cd brotli-1.1.0 && mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local/brotli ..
sudo cmake --build . --config Release --target install
sudo sh -c "echo /usr/local/brotli/lib >> /etc/ld.so.conf.d/brotli.conf"
sudo ldconfig
```
:::caution
If the upstream is returning a compressed response, then the Brotli plugin won't be able to compress it.
:::
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------------|----------------------|----------|---------------|--------------|-----------------------------------------------------------------------------------------|
| types | array[string] or "*" | False | ["text/html"] | | Dynamically sets the `brotli_types` directive. Special value `"*"` matches any MIME type. |
| min_length | integer | False | 20 | >= 1 | Dynamically sets the `brotli_min_length` directive. |
| comp_level | integer | False | 6 | [0, 11] | Dynamically sets the `brotli_comp_level` directive. |
| mode | integer | False | 0 | [0, 2] | Dynamically sets the `brotli decompress mode`, more info in [RFC 7932](https://tools.ietf.org/html/rfc7932). |
| lgwin | integer | False | 19 | [0, 10-24] | Dynamically sets the `brotli sliding window size`, `lgwin` is Base 2 logarithm of the sliding window size, set to `0` lets compressor decide over the optimal value, more info in [RFC 7932](https://tools.ietf.org/html/rfc7932). |
| lgblock | integer | False | 0 | [0, 16-24] | Dynamically sets the `brotli input block size`, `lgblock` is Base 2 logarithm of the maximum input block size, set to `0` lets compressor decide over the optimal value, more info in [RFC 7932](https://tools.ietf.org/html/rfc7932). |
| http_version | number | False | 1.1 | 1.1, 1.0 | Like the `gzip_http_version` directive, sets the minimum HTTP version of a request required to compress a response. |
| vary | boolean | False | false | | Like the `gzip_vary` directive, enables or disables inserting the “Vary: Accept-Encoding” response header field. |
## Enable Plugin
The example below enables the `brotli` Plugin on the specified Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/",
"plugins": {
"brotli": {
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
```
## Example usage
Once you have configured the Plugin as shown above, you can make a request as shown below:
```shell
curl http://127.0.0.1:9080/ -i -H "Accept-Encoding: br"
```
```
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Tue, 05 Dec 2023 03:06:49 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.6.0
Content-Encoding: br
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
```
## Delete Plugin
To remove the `brotli` 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 '
{
"uri": "/",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
```

View File

@@ -0,0 +1,117 @@
---
title: cas-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- CAS AUTH
- cas-auth
description: This document contains information about the Apache APISIX cas-auth Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `cas-auth` Plugin can be used to access CAS (Central Authentication Service 2.0) IdP (Identity Provider)
to do authentication, from the SP (service provider) perspective.
## Attributes
| Name | Type | Required | Description |
| ----------- | ----------- | ----------- | ----------- |
| `idp_uri` | string | True | URI of IdP. |
| `cas_callback_uri` | string | True | redirect uri used to callback the SP from IdP after login or logout. |
| `logout_uri` | string | True | logout uri to trigger logout. |
## Enable Plugin
You can enable the Plugin on a specific Route as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/cas1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET", "POST"],
"host" : "127.0.0.1",
"uri": "/anything/*",
"plugins": {
"cas-auth": {
"idp_uri": "http://127.0.0.1:8080/realms/test/protocol/cas",
"cas_callback_uri": "/anything/cas_callback",
"logout_uri": "/anything/logout"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
```
## Configuration description
Once you have enabled the Plugin, a new user visiting this Route would first be processed by the `cas-auth` Plugin.
If no login session exists, the user would be redirected to the login page of `idp_uri`.
After successfully logging in from IdP, IdP will redirect this user to the `cas_callback_uri` with
GET parameters CAS ticket specified. If the ticket gets verified, the login session would be created.
This process is only done once and subsequent requests are left uninterrupted.
Once this is done, the user is redirected to the original URL they wanted to visit.
Later, the user could visit `logout_uri` to start logout process. The user would be redirected to `idp_uri` to do logout.
Note that, `cas_callback_uri` and `logout_uri` should be
either full qualified address (e.g. `http://127.0.0.1:9080/anything/logout`),
or path only (e.g. `/anything/logout`), but it is recommended to be path only to keep consistent.
These uris need to be captured by the route where the current APISIX is located.
For example, if the `uri` of the current route is `/api/v1/*`, `cas_callback_uri` can be filled in as `/api/v1/cas_callback`.
## Delete Plugin
To remove the `cas-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/cas1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET", "POST"],
"uri": "/anything/*",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,284 @@
---
title: chaitin-waf
keywords:
- Apache APISIX
- API Gateway
- Plugin
- WAF
description: This document contains basic information about the Apache APISIX `chaitin-waf` plugin.
---
<!--
#
# 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.
#
-->
## Description
After enabling the chaitin-waf plugin, the traffic will be forwarded to the Chaitin WAF service for detection and
prevention of various web application attacks, ensuring the security of the application and user data.
## Response Headers
Depending on the plugin configuration, it is optional to add additional response headers.
The response headers are listed below:
- **X-APISIX-CHAITIN-WAF**: Whether APISIX forwards the request to the WAF server.
- yes: forwarded
- no: not forwarded
- unhealthy: matches the match variables, but no WAF server is available.
- err: an error occurred during the execution of the plugin. Also includes the **X-APISIX-CHAITIN-WAF-ERROR** header.
- waf-err: error while interacting with the WAF server. Also includes the **X-APISIX-CHAITIN-WAF-ERROR** header.
- timeout: request to the WAF server timed out.
- **X-APISIX-CHAITIN-WAF-ERROR**: Debug header. Contains WAF error message.
- **X-APISIX-CHAITIN-WAF-TIME**: The time in milliseconds that APISIX spent interacting with WAF.
- **X-APISIX-CHAITIN-WAF-STATUS**: The status code returned to APISIX by the WAF server.
- **X-APISIX-CHAITIN-WAF-ACTION**: The action returned to APISIX by the WAF server.
- pass: request valid and passed.
- reject: request rejected by WAF service.
- **X-APISIX-CHAITIN-WAF-SERVER**: Debug header. Indicates which WAF server was selected.
## Plugin Metadata
| Name | Type | Required | Default value | Description |
|--------------------------|---------------|----------|---------------|------------------------------------------------------------------------------------------------------------------------------|
| nodes | array(object) | true | | A list of addresses for the Chaitin SafeLine WAF service. |
| nodes[0].host | string | true | | The address of Chaitin SafeLine WAF service. Supports IPv4, IPv6, Unix Socket, etc. |
| nodes[0].port | integer | false | 80 | The port of the Chaitin SafeLine WAF service. |
| mode | string | false | block | The global default mode if a Route doesn't specify its own: `off`, `monitor`, or `block`. |
| config | object | false | | WAF configuration defaults if none are specified on the Route. |
| config.connect_timeout | integer | false | 1000 | Connect timeout, in milliseconds. |
| config.send_timeout | integer | false | 1000 | Send timeout, in milliseconds. |
| config.read_timeout | integer | false | 1000 | Read timeout, in milliseconds. |
| config.req_body_size | integer | false | 1024 | Request body size, in KB. |
| config.keepalive_size | integer | false | 256 | Maximum concurrent idle connections to the SafeLine WAF detection service. |
| config.keepalive_timeout | integer | false | 60000 | Idle connection timeout, in milliseconds. |
| config.real_client_ip | boolean | false | true | Specifies whether to use the `X-Forwarded-For` as the client IP (if present). If `false`, uses the direct client IP from the connection. |
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```bash
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/chaitin-waf -H "X-API-KEY: $admin_key" -X PUT -d '
{
"nodes": [
{
"host": "unix:/path/to/safeline/resources/detector/snserver.sock",
"port": 8000
}
],
"mode": "block",
"config": {
"real_client_ip": true
}
}'
```
## Attributes
| Name | Type | Required | Default value | Description |
|--------------------------|---------------|----------|---------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| mode | string | false | block | Determines how the plugin behaves for matched requests. Valid values are `off`, `monitor`, or `block`. When set to `off`, the plugin skips WAF checks. In `monitor` mode, the plugin logs potential blocks without actually blocking the request. In `block` mode, the plugin enforces blocks as determined by the WAF service. |
| match | array[object] | false | | A list of matching rules. The plugin evaluates these rules to decide whether to perform the WAF check on a request. If empty, all requests are processed. |
| match.vars | array[array] | false | | List of variables used for matching requests. Each rule is specified as `[variable, operator, value]` (for example, `["http_waf", "==", "true"]`). These variables refer to NGINX internal variables. For supported operators, see [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list). |
| append_waf_resp_header | bool | false | true | Determines whether the plugin adds WAF-related response headers (such as `X-APISIX-CHAITIN-WAF`, `X-APISIX-CHAITIN-WAF-ACTION`, etc.) to the response. |
| append_waf_debug_header | bool | false | false | Determines whether debugging headers (such as `X-APISIX-CHAITIN-WAF-ERROR` and `X-APISIX-CHAITIN-WAF-SERVER`) are added. Effective only when `append_waf_resp_header` is enabled. |
| config | object | false | | Provides route-specific configuration for the Chaitin SafeLine WAF service. Settings here override the corresponding metadata defaults when specified. |
| config.connect_timeout | integer | false | 1000 | The connect timeout for the WAF server, in milliseconds. |
| config.send_timeout | integer | false | 1000 | The send timeout for transmitting data to the WAF server, in milliseconds. |
| config.read_timeout | integer | false | 1000 | The read timeout for receiving data from the WAF server, in milliseconds. |
| config.req_body_size | integer | false | 1024 | The maximum allowed request body size, in KB. |
| config.keepalive_size | integer | false | 256 | The maximum number of idle connections to the WAF detection service that can be maintained concurrently. |
| config.keepalive_timeout | integer | false | 60000 | The idle connection timeout for the WAF service, in milliseconds. |
| config.real_client_ip | boolean | false | true | Specifies whether to determine the client IP from the `X-Forwarded-For` header. If set to `false`, the plugin uses the direct client IP from the connection. |
Below is a sample Route configuration that uses:
- httpbun.org as the upstream backend.
- mode set to monitor, so the plugin only logs potential blocks.
- A matching rule that triggers the plugin when the custom header waf: true is set.
- An override to disable the `real client IP` logic by setting config.real_client_ip to false.
```bash
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" \
-X PUT -d '
{
"uri": "/*",
"plugins": {
"chaitin-waf": {
"mode": "monitor",
"match": [
{
"vars": [
["http_waf", "==", "true"]
]
}
],
"config": {
"real_client_ip": false
},
"append_waf_resp_header": true,
"append_waf_debug_header": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbun.org:80": 1
}
}
}'
```
## Test Plugin
With the sample configuration described above (including your chosen `mode` and `real_client_ip` settings), the plugin behaves as follows:
- **If the `match` condition is not satisfied** (for example, `waf: true` is missing), the request proceeds normally without contacting the WAF. You can observe:
```bash
curl -H "Host: httpbun.org" http://127.0.0.1:9080/get -i
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 408
Connection: keep-alive
X-APISIX-CHAITIN-WAF: no
Date: Wed, 19 Jul 2023 09:30:42 GMT
X-Powered-By: httpbun/3c0dc05883dd9212ac38b04705037d50b02f2596
Server: APISIX/3.3.0
{
"args": {},
"headers": {
"Accept": "*/*",
"Connection": "close",
"Host": "httpbun.org",
"User-Agent": "curl/8.1.2",
"X-Forwarded-For": "127.0.0.1",
"X-Forwarded-Host": "httpbun.org",
"X-Forwarded-Port": "9080",
"X-Forwarded-Proto": "http",
"X-Real-Ip": "127.0.0.1"
},
"method": "GET",
"origin": "127.0.0.1, 122.231.76.178",
"url": "http://httpbun.org/get"
}
```
- **Potential injection requests** (e.g., containing SQL snippets) are forwarded unmodified if they do not meet the plugins match rules, and might result in a `404 Not Found` or other response from the upstream:
```bash
curl -H "Host: httpbun.org" http://127.0.0.1:9080/getid=1%20AND%201=1 -i
HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 19
Connection: keep-alive
X-APISIX-CHAITIN-WAF: no
Date: Wed, 19 Jul 2023 09:30:28 GMT
X-Content-Type-Options: nosniff
X-Powered-By: httpbun/3c0dc05883dd9212ac38b04705037d50b02f2596
Server: APISIX/3.3.0
404 page not found
```
- **Matching safe requests** (those that satisfy `match.vars`, such as `-H "waf: true"`) are checked by the WAF. If deemed harmless, you see:
```bash
curl -H "Host: httpbun.org" -H "waf: true" http://127.0.0.1:9080/get -i
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 427
Connection: keep-alive
X-APISIX-CHAITIN-WAF-TIME: 2
X-APISIX-CHAITIN-WAF-STATUS: 200
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-ACTION: pass
Date: Wed, 19 Jul 2023 09:29:58 GMT
X-Powered-By: httpbun/3c0dc05883dd9212ac38b04705037d50b02f2596
Server: APISIX/3.3.0
{
"args": {},
"headers": {
"Accept": "*/*",
"Connection": "close",
"Host": "httpbun.org",
"User-Agent": "curl/8.1.2",
"Waf": "true",
"X-Forwarded-For": "127.0.0.1",
"X-Forwarded-Host": "httpbun.org",
"X-Forwarded-Port": "9080",
"X-Forwarded-Proto": "http",
"X-Real-Ip": "127.0.0.1"
},
"method": "GET",
"origin": "127.0.0.1, 122.231.76.178",
"url": "http://httpbun.org/get"
}
```
- **Suspicious requests** that meet the plugins match rules and are flagged by the WAF are typically rejected with a 403 status, along with headers that include `X-APISIX-CHAITIN-WAF-ACTION: reject`. For example:
```bash
curl -H "Host: httpbun.org" -H "waf: true" http://127.0.0.1:9080/getid=1%20AND%201=1 -i
HTTP/1.1 403 Forbidden
Date: Wed, 19 Jul 2023 09:29:06 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-APISIX-CHAITIN-WAF: yes
X-APISIX-CHAITIN-WAF-TIME: 2
X-APISIX-CHAITIN-WAF-ACTION: reject
X-APISIX-CHAITIN-WAF-STATUS: 403
Server: APISIX/3.3.0
Set-Cookie: sl-session=UdywdGL+uGS7q8xMfnJlbQ==; Domain=; Path=/; Max-Age=86400
{"code": 403, "success":false, "message": "blocked by Chaitin SafeLine Web Application Firewall", "event_id": "51a268653f2c4189bfa3ec66afbcb26d"}
```
## Delete Plugin
To remove the `chaitin-waf` 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:
```bash
$ curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/*",
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbun.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,207 @@
---
title: clickhouse-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ClickHouse Logger
description: This document contains information about the Apache APISIX clickhouse-logger Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `clickhouse-logger` Plugin is used to push logs to [ClickHouse](https://clickhouse.com/) database.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|---------------|---------|----------|---------------------|--------------|----------------------------------------------------------------|
| endpoint_addr | Deprecated | True | | | Use `endpoint_addrs` instead. ClickHouse endpoints. |
| endpoint_addrs | array | True | | | ClickHouse endpoints. |
| database | string | True | | | Name of the database to store the logs. |
| logtable | string | True | | | Table name to store the logs. |
| user | string | True | | | ClickHouse username. |
| password | string | True | | | ClickHouse password. |
| timeout | integer | False | 3 | [1,...] | Time to keep the connection alive for after sending a request. |
| name | string | False | "clickhouse logger" | | Unique identifier for the logger. If you use Prometheus to monitor APISIX metrics, the name is exported in `apisix_batch_process_entries`. |
| ssl_verify | boolean | False | true | [true,false] | When set to `true`, verifies SSL. |
| 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. |
| 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. |
NOTE: `encrypt_fields = {"password"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
### Example of default log format
```json
{
"response": {
"status": 200,
"size": 118,
"headers": {
"content-type": "text/plain",
"connection": "close",
"server": "APISIX/3.7.0",
"content-length": "12"
}
},
"client_ip": "127.0.0.1",
"upstream_latency": 3,
"apisix_latency": 98.999998092651,
"upstream": "127.0.0.1:1982",
"latency": 101.99999809265,
"server": {
"version": "3.7.0",
"hostname": "localhost"
},
"route_id": "1",
"start_time": 1704507612177,
"service_id": "",
"request": {
"method": "POST",
"querystring": {
"foo": "unknown"
},
"headers": {
"host": "localhost",
"connection": "close",
"content-length": "18"
},
"size": 110,
"uri": "/hello?foo=unknown",
"url": "http://localhost:1984/hello?foo=unknown"
}
}
```
## Metadata
You can also set the format of the logs by configuring the Plugin metadata. The following configurations are available:
| Name | Type | Required | Default | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 `$`. |
:::info IMPORTANT
Configuring the Plugin metadata is global in scope. This means that it will take effect on all Routes and Services which use the `clickhouse-logger` Plugin.
:::
The example below shows how you can configure through the Admin API:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/clickhouse-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}'
```
You can use the clickhouse docker image to create a container like so:
```shell
docker run -d -p 8123:8123 -p 9000:9000 -p 9009:9009 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server
```
Then create a table in your ClickHouse database to store the logs.
```shell
curl -X POST 'http://localhost:8123/' \
--data-binary 'CREATE TABLE default.test (host String, client_ip String, route_id String, service_id String, `@timestamp` String, PRIMARY KEY(`@timestamp`)) ENGINE = MergeTree()' --user default:
```
## Enable Plugin
If multiple endpoints are configured, they will be written randomly.
The example below shows how you can enable the Plugin on a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"clickhouse-logger": {
"user": "default",
"password": "",
"database": "default",
"logtable": "test",
"endpoint_addrs": ["http://127.0.0.1:8123"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
## Example usage
Now, if you make a request to APISIX, it will be logged in your ClickHouse database:
```shell
curl -i http://127.0.0.1:9080/hello
```
Now, if you check for the rows in the table, you will get the following output:
```shell
curl 'http://localhost:8123/?query=select%20*%20from%20default.test'
127.0.0.1 127.0.0.1 1 2023-05-08T19:15:53+05:30
```
## Delete Plugin
To remove the `clickhouse-logger` 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 '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,113 @@
---
title: client-control
keywords:
- Apache APISIX
- API Gateway
- Client Control
description: This document describes the Apache APISIX client-control Plugin, you can use it to control NGINX behavior to handle a client request dynamically.
---
<!--
#
# 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.
#
-->
## Description
The `client-control` Plugin can be used to dynamically control the behavior of NGINX to handle a client request, by setting the max size of the request body.
:::info IMPORTANT
This Plugin requires APISIX to run on APISIX-Runtime. See [apisix-build-tools](https://github.com/api7/apisix-build-tools) for more info.
:::
## Attributes
| Name | Type | Required | Valid values | Description |
| ------------- | ------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------------------------------------ |
| max_body_size | integer | False | [0,...] | Set the maximum limit for the client request body and dynamically adjust the size of [`client_max_body_size`](https://nginx.org/en/docs/http/ngx_http_core_module.html#client_max_body_size), measured in bytes. If you set the `max_body_size` to 0, then the size of the client's request body will not be checked. |
## Enable Plugin
The example below enables the Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"plugins": {
"client-control": {
"max_body_size" : 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
## Example usage
Now since you have configured the `max_body_size` to `1` above, you will get the following message when you make a request:
```shell
curl -i http://127.0.0.1:9080/index.html -d '123'
```
```shell
HTTP/1.1 413 Request Entity Too Large
...
<html>
<head><title>413 Request Entity Too Large</title></head>
<body>
<center><h1>413 Request Entity Too Large</h1></center>
<hr><center>openresty</center>
</body>
</html>
```
## Delete Plugin
To remove the `client-control` 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 '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,347 @@
---
title: consumer-restriction
keywords:
- Apache APISIX
- API Gateway
- Consumer restriction
description: The Consumer Restriction Plugin allows users to configure access restrictions on Consumer, Route, Service, or Consumer Group.
---
<!--
#
# 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.
#
-->
## Description
The `consumer-restriction` Plugin allows users to configure access restrictions on Consumer, Route, Service, or Consumer Group.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| -------------------------- | ------------- | -------- | ------------- | ------------------------------------------------------------ | ------------------------------------------------------------ |
| type | string | False | consumer_name | ["consumer_name", "consumer_group_id", "service_id", "route_id"] | Type of object to base the restriction on. |
| whitelist | array[string] | True | | | List of objects to whitelist. Has a higher priority than `allowed_by_methods`. |
| blacklist | array[string] | True | | | List of objects to blacklist. Has a higher priority than `whitelist`. |
| rejected_code | integer | False | 403 | [200,...] | HTTP status code returned when the request is rejected. |
| rejected_msg | string | False | | | Message returned when the request is rejected. |
| allowed_by_methods | array[object] | False | | | List of allowed configurations for Consumer settings, including a username of the Consumer and a list of allowed HTTP methods. |
| allowed_by_methods.user | string | False | | | A username for a Consumer. |
| allowed_by_methods.methods | array[string] | False | | ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS", "CONNECT", "TRACE", "PURGE"] | List of allowed HTTP methods for a Consumer. |
:::note
The different values in the `type` attribute have these meanings:
- `consumer_name`: Username of the Consumer to restrict access to a Route or a Service.
- `consumer_group_id`: ID of the Consumer Group to restrict access to a Route or a Service.
- `service_id`: ID of the Service to restrict access from a Consumer. Need to be used with an Authentication Plugin.
- `route_id`: ID of the Route to restrict access from a Consumer.
:::
## Example usage
### Restricting by `consumer_name`
The example below shows how you can use the `consumer-restriction` Plugin on a Route to restrict specific consumers.
You can first create two consumers `jack1` and `jack2`:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"username": "jack1",
"plugins": {
"basic-auth": {
"username":"jack2019",
"password": "123456"
}
}
}'
curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X PUT -i -d '
{
"username": "jack2",
"plugins": {
"basic-auth": {
"username":"jack2020",
"password": "123456"
}
}
}'
```
Next, you can configure the Plugin to the Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {},
"consumer-restriction": {
"whitelist": [
"jack1"
]
}
}
}'
```
Now, this configuration will only allow `jack1` to access your Route:
```shell
curl -u jack2019:123456 http://127.0.0.1:9080/index.html
```
```shell
HTTP/1.1 200 OK
```
And requests from `jack2` are blocked:
```shell
curl -u jack2020:123456 http://127.0.0.1:9080/index.html -i
```
```shell
HTTP/1.1 403 Forbidden
...
{"message":"The consumer_name is forbidden."}
```
### Restricting by `allowed_by_methods`
The example below configures the Plugin to a Route to restrict `jack1` to only make `POST` requests:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {},
"consumer-restriction": {
"allowed_by_methods":[{
"user": "jack1",
"methods": ["POST"]
}]
}
}
}'
```
Now if `jack1` makes a `GET` request, the access is restricted:
```shell
curl -u jack2019:123456 http://127.0.0.1:9080/index.html
```
```shell
HTTP/1.1 403 Forbidden
...
{"message":"The consumer_name is forbidden."}
```
To also allow `GET` requests, you can update the Plugin configuration and it would be reloaded automatically:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {},
"consumer-restriction": {
"allowed_by_methods":[{
"user": "jack1",
"methods": ["POST","GET"]
}]
}
}
}'
```
Now, if a `GET` request is made:
```shell
curl -u jack2019:123456 http://127.0.0.1:9080/index.html
```
```shell
HTTP/1.1 200 OK
```
### Restricting by `service_id`
To restrict a Consumer from accessing a Service, you also need to use an Authentication Plugin. The example below uses the [key-auth](./key-auth.md) Plugin.
First, you can create two services:
```shell
curl http://127.0.0.1:9180/apisix/admin/services/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "new service 001"
}'
curl http://127.0.0.1:9180/apisix/admin/services/2 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"desc": "new service 002"
}'
```
Then configure the `consumer-restriction` Plugin on the Consumer with the `key-auth` Plugin and the `service_id` to whitelist.
```shell
curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X PUT -d '
{
"username": "new_consumer",
"plugins": {
"key-auth": {
"key": "auth-jack"
},
"consumer-restriction": {
"type": "service_id",
"whitelist": [
"1"
],
"rejected_code": 403
}
}
}'
```
Finally, you can configure the `key-auth` Plugin and bind the service to the Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"service_id": 1,
"plugins": {
"key-auth": {
}
}
}'
```
Now, if you test the Route, you should be able to access the Service:
```shell
curl http://127.0.0.1:9080/index.html -H 'apikey: auth-jack' -i
```
```shell
HTTP/1.1 200 OK
...
```
Now, if the Route is configured to the Service with `service_id` `2`:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"service_id": 2,
"plugins": {
"key-auth": {
}
}
}'
```
Since the Service is not in the whitelist, it cannot be accessed:
```shell
curl http://127.0.0.1:9080/index.html -H 'apikey: auth-jack' -i
```
```shell
HTTP/1.1 403 Forbidden
...
{"message":"The service_id is forbidden."}
```
## Delete Plugin
To remove the `consumer-restriction` 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 '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"plugins": {
"basic-auth": {}
}
}'
```

View File

@@ -0,0 +1,142 @@
---
title: cors
keywords:
- Apache APISIX
- API Gateway
- CORS
description: This document contains information about the Apache APISIX cors Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `cors` Plugins lets you enable [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) easily.
## Attributes
### CORS attributes
| Name | Type | Required | Default | Description |
|---------------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allow_origins | string | False | "*" | Origins to allow CORS. Use the `scheme://host:port` format. For example, `https://somedomain.com:8081`. If you have multiple origins, use a `,` to list them. If `allow_credential` is set to `false`, you can enable CORS for all origins by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all origins by using `**` but it will pose some security issues. |
| allow_methods | string | False | "*" | Request methods to enable CORS on. For example `GET`, `POST`. Use `,` to add multiple methods. If `allow_credential` is set to `false`, you can enable CORS for all methods by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all methods by using `**` but it will pose some security issues. |
| allow_headers | string | False | "*" | Headers in the request allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all request headers by using `*`. If `allow_credential` is set to `true`, you can forcefully allow CORS on all request headers by using `**` but it will pose some security issues. |
| expose_headers | string | False | | Headers in the response allowed when accessing a cross-origin resource. Use `,` to add multiple headers. If `allow_credential` is set to `false`, you can enable CORS for all response headers by using `*`. If not specified, the plugin will not modify the `Access-Control-Expose-Headers header`. See [Access-Control-Expose-Headers - MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers) for more details. |
| max_age | integer | False | 5 | Maximum time in seconds the result is cached. If the time is within this limit, the browser will check the cached result. Set to `-1` to disable caching. Note that the maximum value is browser dependent. See [Access-Control-Max-Age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age#Directives) for more details. |
| allow_credential | boolean | False | false | When set to `true`, allows requests to include credentials like cookies. According to CORS specification, if you set this to `true`, you cannot use '*' to allow all for the other attributes. |
| allow_origins_by_regex | array | False | nil | Regex to match origins that allow CORS. For example, `[".*\.test.com$"]` can match all subdomains of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `allow_origins` is. |
| allow_origins_by_metadata | array | False | nil | Origins to enable CORS referenced from `allow_origins` set in the Plugin metadata. For example, if `"allow_origins": {"EXAMPLE": "https://example.com"}` is set in the Plugin metadata, then `["EXAMPLE"]` can be used to allow CORS on the origin `https://example.com`. |
:::info IMPORTANT
1. The `allow_credential` attribute is sensitive and must be used carefully. If set to `true` the default value `*` of the other attributes will be invalid and they should be specified explicitly.
2. When using `**` you are vulnerable to security risks like CSRF. Make sure that this meets your security levels before using it.
:::
### Resource Timing attributes
| Name | Type | Required | Default | Description |
|---------------------------|---------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| timing_allow_origins | string | False | nil | Origin to allow to access the resource timing information. See [Timing-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin). Use the `scheme://host:port` format. For example, `https://somedomain.com:8081`. If you have multiple origins, use a `,` to list them. |
| timing_allow_origins_by_regex | array | False | nil | Regex to match with origin for enabling access to the resource timing information. For example, `[".*\.test.com"]` can match all subdomain of `test.com`. When set to specified range, only domains in this range will be allowed, no matter what `timing_allow_origins` is. |
:::note
The Timing-Allow-Origin header is defined in the Resource Timing API, but it is related to the CORS concept.
Suppose you have 2 domains, `domain-A.com` and `domain-B.com`.
You are on a page on `domain-A.com`, you have an XHR call to a resource on `domain-B.com` and you need its timing information.
You can allow the browser to show this timing information only if you have cross-origin permissions on `domain-B.com`.
So, you have to set the CORS headers first, then access the `domain-B.com` URL, and if you set [Timing-Allow-Origin](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Timing-Allow-Origin), the browser will show the requested timing information.
:::
## Metadata
| Name | Type | Required | Description |
|---------------|--------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allow_origins | object | False | A map with origin reference and allowed origins. The keys in the map are used in the attribute `allow_origins_by_metadata` and the value are equivalent to the `allow_origins` attribute of the Plugin. |
## Enable Plugin
You can enable the Plugin on a specific Route or Service:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/hello",
"plugins": {
"cors": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```
## Example usage
After enabling the Plugin, you can make a request to the server and see the CORS headers returned:
```shell
curl http://127.0.0.1:9080/hello -v
```
```shell
...
< Server: APISIX web server
< Access-Control-Allow-Origin: *
< Access-Control-Allow-Methods: *
< Access-Control-Allow-Headers: *
< Access-Control-Max-Age: 5
...
```
## Delete Plugin
To remove the `cors` 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 '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```

View File

@@ -0,0 +1,154 @@
---
title: csrf
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Cross-site request forgery
- csrf
description: The CSRF Plugin can be used to protect your API against CSRF attacks using the Double Submit Cookie method.
---
<!--
#
# 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.
#
-->
## Description
The `csrf` Plugin can be used to protect your API against [CSRF attacks](https://en.wikipedia.org/wiki/Cross-site_request_forgery) using the [Double Submit Cookie](https://en.wikipedia.org/wiki/Cross-site_request_forgery#Double_Submit_Cookie) method.
This Plugin considers the `GET`, `HEAD` and `OPTIONS` methods to be safe operations (`safe-methods`) and such requests are not checked for interception by an attacker. Other methods are termed as `unsafe-methods`.
## Attributes
| Name | Type | Required | Default | Description |
|---------|--------|----------|---------------------|---------------------------------------------------------------------------------------------|
| name | string | False | `apisix-csrf-token` | Name of the token in the generated cookie. |
| expires | number | False | `7200` | Expiration time in seconds of the CSRF cookie. Set to `0` to skip checking expiration time. |
| key | string | True | | Secret key used to encrypt the cookie. |
NOTE: `encrypt_fields = {"key"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
## Enable Plugin
The example below shows how you can enable the Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT-d '
{
"uri": "/hello",
"plugins": {
"csrf": {
"key": "edd1c9f034335f136f87ad84b625c8f1"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:9001": 1
}
}
}'
```
The Route is now protected and trying to access it with methods other than `GET` will be blocked with a 401 status code.
Sending a `GET` request to the `/hello` endpoint will send back a cookie with an encrypted token. The name of the token can be set through the `name` attribute of the Plugin configuration and if unset, it defaults to `apisix-csrf-token`.
:::note
A new cookie is returned for each request.
:::
For subsequent requests with `unsafe-methods`, you need to read the encrypted token from the cookie and append the token to the request header by setting the field name to the `name` attribute in the Plugin configuration.
## Example usage
After you have configured the Plugin as shown above, trying to directly make a `POST` request to the `/hello` Route will result in an error:
```shell
curl -i http://127.0.0.1:9080/hello -X POST
```
```shell
HTTP/1.1 401 Unauthorized
...
{"error_msg":"no csrf token in headers"}
```
To get the cookie with the encrypted token, you can make a `GET` request:
```shell
curl -i http://127.0.0.1:9080/hello
```
```shell
HTTP/1.1 200 OK
Set-Cookie: apisix-csrf-token=eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ==;path=/;Expires=Mon, 13-Dec-21 09:33:55 GMT
```
This token must then be read from the cookie and added to the request header for subsequent `unsafe-methods` requests.
For example, you can use [js-cookie](https://github.com/js-cookie/js-cookie) to read the cookie and [axios](https://github.com/axios/axios) to send requests:
```js
const token = Cookie.get('apisix-csrf-token');
const instance = axios.create({
headers: {'apisix-csrf-token': token}
});
```
Also make sure that you carry the cookie.
You can also use curl to send the request:
```shell
curl -i http://127.0.0.1:9080/hello -X POST -H 'apisix-csrf-token: eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ==' -b 'apisix-csrf-token=eyJyYW5kb20iOjAuNjg4OTcyMzA4ODM1NDMsImV4cGlyZXMiOjcyMDAsInNpZ24iOiJcL09uZEF4WUZDZGYwSnBiNDlKREtnbzVoYkJjbzhkS0JRZXVDQm44MG9ldz0ifQ=='
```
```shell
HTTP/1.1 200 OK
```
## Delete Plugin
To remove the `csrf` 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 '
{
"uri": "/hello",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,164 @@
---
title: datadog
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Datadog
description: This document contains information about the Apache APISIX datadog Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `datadog` monitoring Plugin is for seamless integration of APISIX with [Datadog](https://www.datadoghq.com/), one of the most used monitoring and observability platform for cloud applications.
When enabled, the Plugin supports multiple metric capture types for request and response cycles.
This Plugin, pushes its custom metrics to the [DogStatsD](https://docs.datadoghq.com/developers/dogstatsd/?tab=hostagent) server over UDP protocol and comes bundled with [Datadog agent](https://docs.datadoghq.com/agent/).
DogStatsD implements the StatsD protocol which collects the custom metrics for the Apache APISIX agent, aggregates them into a single data point, and sends it to the configured Datadog server.
This Plugin provides the ability to push metrics as a batch to the external Datadog agent, reusing the same datagram socket. It might take some time to receive the log data. It will be automatically sent after the timer function in the [batch processor](../batch-processor.md) expires.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ----------- | ------- | -------- | ------- | ------------ | -------------------------------------------------------------------------------------- |
| prefer_name | boolean | False | true | [true,false] | When set to `false`, uses Route/Service ID instead of name (default) with metric tags. |
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
## Metadata
You can configure the Plugin through the Plugin metadata.
| Name | Type | Required | Default | Description |
| ------------- | ------- | -------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| host | string | False | "127.0.0.1" | DogStatsD server host address. |
| port | integer | False | 8125 | DogStatsD server host port. |
| namespace | string | False | "apisix" | Prefix for all custom metrics sent by APISIX agent. Useful for finding entities for metrics graph. For example, `apisix.request.counter`. |
| constant_tags | array | False | [ "source:apisix" ] | Static tags to embed into generated metrics. Useful for grouping metrics over certain signals. |
:::tip
See [defining tags](https://docs.datadoghq.com/getting_started/tagging/#defining-tags) to know more about how to effectively use tags.
:::
By default, the Plugin expects the DogStatsD service to be available at `127.0.0.1:8125`. If you want to change this, you can update the Plugin metadata as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/datadog -H "X-API-KEY: $admin_key" -X PUT -d '
{
"host": "172.168.45.29",
"port": 8126,
"constant_tags": [
"source:apisix",
"service:custom"
],
"namespace": "apisix"
}'
```
To reset to default configuration, make a PUT request with empty body:
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/datadog -H "X-API-KEY: $admin_key" -X PUT -d '{}'
```
## Exported metrics
When the `datadog` Plugin is enabled, the APISIX agent exports the following metrics to the DogStatsD server for each request/response cycle:
| Metric name | StatsD type | Description |
| ---------------- | ----------- | ----------------------------------------------------------------------------------------------------- |
| Request Counter | Counter | Number of requests received. |
| Request Latency | Histogram | Time taken to process the request (in milliseconds). |
| Upstream latency | Histogram | Time taken to proxy the request to the upstream server till a response is received (in milliseconds). |
| APISIX Latency | Histogram | Time taken by APISIX agent to process the request (in milliseconds). |
| Ingress Size | Timer | Request body size in bytes. |
| Egress Size | Timer | Response body size in bytes. |
The metrics will be sent to the DogStatsD agent with the following tags:
- `route_name`: Name specified in the Route schema definition. If not present or if the attribute `prefer_name` is set to false, falls back to the Route ID.
- `service_name`: If a Route has been created with an abstracted Service, the Service name/ID based on the attribute `prefer_name`.
- `consumer`: If the Route is linked to a Consumer, the username will be added as a tag.
- `balancer_ip`: IP address of the Upstream balancer that processed the current request.
- `response_status`: HTTP response status code.
- `scheme`: Request scheme such as HTTP, gRPC, and gRPCs.
:::note
If there are no suitable values for any particular tag, the tag will be omitted.
:::
## Enable Plugin
Once you have your Datadog agent running, you can enable the Plugin as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"datadog": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
Now, requests to the endpoint `/hello` will generate metrics and push it to the DogStatsD server.
## Delete Plugin
To remove the `datadog` 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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,337 @@
---
title: degraphql
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Degraphql
description: This document contains information about the Apache APISIX degraphql Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `degraphql` Plugin is used to support decoding RESTful API to GraphQL.
## Attributes
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------------------------------------------------------------------------- |
| query | string | True | The GraphQL query sent to the upstream |
| operation_name | string | False | The name of the operation, is only required if multiple operations are present in the query. |
| variables | array | False | The variables used in the GraphQL query |
## Example usage
### Start GraphQL server
We use docker to deploy a [GraphQL server demo](https://github.com/npalm/graphql-java-demo) as the backend.
```bash
docker run -d --name grapql-demo -p 8080:8080 npalm/graphql-java-demo
```
After starting the server, the following endpoints are now available:
- http://localhost:8080/graphiql - GraphQL IDE - GrahphiQL
- http://localhost:8080/playground - GraphQL IDE - Prisma GraphQL Client
- http://localhost:8080/altair - GraphQL IDE - Altair GraphQL Client
- http://localhost:8080/ - A simple reacter
- ws://localhost:8080/subscriptions
### Enable Plugin
#### Query list
If we have a GraphQL query like this:
```graphql
query {
persons {
id
name
}
}
```
We can execute it on `http://localhost:8080/playground`, and get the data as below:
```json
{
"data": {
"persons": [
{
"id": "7",
"name": "Niek"
},
{
"id": "8",
"name": "Josh"
},
......
]
}
}
```
Now we can use RESTful API to query the same data that is proxy by APISIX.
First, we need to create a route in APISIX, and enable the degreaph plugin on the route, we need to define the GraphQL query in the plugin's config.
```bash
curl --location --request PUT 'http://localhost:9180/apisix/admin/routes/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' \
--data-raw '{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
},
"plugins": {
"degraphql": {
"query": "{\n persons {\n id\n name\n }\n}\n"
}
}
}'
```
We convert the GraphQL query
```graphql
{
persons {
id
name
}
}
```
to JSON string `"{\n persons {\n id\n name\n }\n}\n"`, and put it in the plugin's configuration.
Then we can query the data by RESTful API:
```bash
curl --location --request POST 'http://localhost:9080/graphql'
```
and get the result:
```json
{
"data": {
"persons": [
{
"id": "7",
"name": "Niek"
},
{
"id": "8",
"name": "Josh"
},
......
]
}
}
```
#### Query with variables
If we have a GraphQL query like this:
```graphql
query($name: String!, $githubAccount: String!) {
persons(filter: { name: $name, githubAccount: $githubAccount }) {
id
name
blog
githubAccount
talks {
id
title
}
}
}
variables:
{
"name": "Niek",
"githubAccount": "npalm"
}
```
we can execute it on `http://localhost:8080/playground`, and get the data as below:
```json
{
"data": {
"persons": [
{
"id": "7",
"name": "Niek",
"blog": "https://040code.github.io",
"githubAccount": "npalm",
"talks": [
{
"id": "19",
"title": "GraphQL - The Next API Language"
},
{
"id": "20",
"title": "Immutable Infrastructure"
}
]
}
]
}
}
```
We convert the GraphQL query to JSON string like `"query($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}"`, so we create a route like this:
```bash
curl --location --request PUT 'http://localhost:9180/apisix/admin/routes/1' \
--header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
--header 'Content-Type: application/json' \
--data-raw '{
"uri": "/graphql",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
},
"plugins": {
"degraphql": {
"query": "query($name: String!, $githubAccount: String!) {\n persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n name\n blog\n githubAccount\n talks {\n id\n title\n }\n }\n}",
"variables": [
"name",
"githubAccount"
]
}
}
}'
```
We define the `variables` in the plugin's config, and the `variables` is an array, which contains the variables' name in the GraphQL query, so that we can pass the query variables by RESTful API.
Query the data by RESTful API that proxy by APISIX:
```bash
curl --location --request POST 'http://localhost:9080/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "Niek",
"githubAccount": "npalm"
}'
```
and get the result:
```json
{
"data": {
"persons": [
{
"id": "7",
"name": "Niek",
"blog": "https://040code.github.io",
"githubAccount": "npalm",
"talks": [
{
"id": "19",
"title": "GraphQL - The Next API Language"
},
{
"id": "20",
"title": "Immutable Infrastructure"
}
]
}
]
}
}
```
which is the same as the result of the GraphQL query.
It's also possible to get the same result via GET request:
```bash
curl 'http://localhost:9080/graphql?name=Niek&githubAccount=npalm'
```
```json
{
"data": {
"persons": [
{
"id": "7",
"name": "Niek",
"blog": "https://040code.github.io",
"githubAccount": "npalm",
"talks": [
{
"id": "19",
"title": "GraphQL - The Next API Language"
},
{
"id": "20",
"title": "Immutable Infrastructure"
}
]
}
]
}
}
```
In the GET request, the variables are passed in the query string.
## Delete Plugin
To remove the `degraphql` 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.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/graphql",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:8080": 1
}
}
}'
```

View File

@@ -0,0 +1,191 @@
---
title: dubbo-proxy
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Apache Dubbo
- dubbo-proxy
description: This document contains information about the Apache APISIX dubbo-proxy Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `dubbo-proxy` Plugin allows you to proxy HTTP requests to [Apache Dubbo](https://dubbo.apache.org/en/index.html).
:::info IMPORTANT
If you are using OpenResty, you need to build it with Dubbo support. See [How do I build the APISIX runtime environment](./../FAQ.md#how-do-i-build-the-apisix-runtime-environment) for details.
:::
## Runtime Attributes
| Name | Type | Required | Default | Description |
| --------------- | ------ | -------- | -------------------- | ------------------------------- |
| service_name | string | True | | Dubbo provider service name. |
| service_version | string | True | | Dubbo provider service version. |
| method | string | False | The path of the URI. | Dubbo provider service method. |
## Static Attributes
| Name | Type | Required | Default | Valid values | Description |
| ------------------------ | ------ | -------- | ------- | ------------ | --------------------------------------------------------------- |
| upstream_multiplex_count | number | True | 32 | >= 1 | Maximum number of multiplex requests in an upstream connection. |
## Enable Plugin
To enable the `dubbo-proxy` Plugin, you have to add it in your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
- ...
- dubbo-proxy
```
Now, when APISIX is reloaded, you can add it to a specific Route as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/upstreams/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"nodes": {
"127.0.0.1:20880": 1
},
"type": "roundrobin"
}'
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uris": [
"/hello"
],
"plugins": {
"dubbo-proxy": {
"service_name": "org.apache.dubbo.sample.tengine.DemoService",
"service_version": "0.0.0",
"method": "tengineDubbo"
}
},
"upstream_id": 1
}'
```
## Example usage
You can follow the [Quick Start](https://github.com/alibaba/tengine/tree/master/modules/mod_dubbo#quick-start) guide in Tengine with the configuration above for testing.
APISIX dubbo plugin uses `hessian2` as the serialization protocol. It supports only `Map<String, Object>` as the request and response data type.
### Application
Your dubbo config should be configured to use `hessian2` as the serialization protocol.
```yml
dubbo:
...
protocol:
...
serialization: hessian2
```
Your application should implement the interface with the request and response data type as `Map<String, Object>`.
```java
public interface DemoService {
Map<String, Object> sayHello(Map<String, Object> context);
}
```
### Request and Response
If you need to pass request data, you can add the data to the HTTP request header. The plugin will convert the HTTP request header to the request data of the Dubbo service. Here is a sample HTTP request that passes `user` information:
```bash
curl -i -X POST 'http://localhost:9080/hello' \
--header 'user: apisix'
HTTP/1.1 200 OK
Date: Mon, 15 Jan 2024 10:15:57 GMT
Content-Type: text/plain; charset=utf-8
...
hello: apisix
...
Server: APISIX/3.8.0
```
If the returned data is:
```json
{
"status": "200",
"header1": "value1",
"header2": "value2",
"body": "body of the message"
}
```
The converted HTTP response will be:
```
HTTP/1.1 200 OK
...
header1: value1
header2: value2
...
body of the message
```
## Delete Plugin
To remove the `dubbo-proxy` 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 '
{
"methods": ["GET"],
"uris": [
"/hello"
],
"plugins": {
},
"upstream_id": 1
}
}'
```
To completely disable the `dubbo-proxy` Plugin, you can remove it from your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
# - dubbo-proxy
```

View File

@@ -0,0 +1,116 @@
---
title: echo
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Echo
description: This document contains information about the Apache APISIX echo Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `echo` Plugin is to help users understand how they can develop an APISIX Plugin.
This Plugin addresses common functionalities in phases like init, rewrite, access, balancer, header filter, body filter and log.
:::caution WARNING
The `echo` Plugin is built as an example. It has missing cases and should **not** be used in production environments.
:::
## Attributes
| Name | Type | Requirement | Default | Valid | Description |
| ----------- | ------ | ----------- | ------- | ----- | ----------------------------------------- |
| before_body | string | optional | | | Body to use before the filter phase. |
| body | string | optional | | | Body that replaces the Upstream response. |
| after_body | string | optional | | | Body to use after the modification phase. |
| headers | object | optional | | | New headers to use for the response. |
At least one of `before_body`, `body`, and `after_body` must be specified.
## Enable Plugin
The example below shows how you can enable the `echo` Plugin for a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"echo": {
"before_body": "before the body modification "
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
## Example usage
First, we configure the Plugin as mentioned above. We can then make a request as shown below:
```shell
curl -i http://127.0.0.1:9080/hello
```
```
HTTP/1.1 200 OK
...
before the body modification hello world
```
## Delete Plugin
To remove the `echo` 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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,445 @@
---
title: elasticsearch-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Elasticsearch-logger
description: The elasticsearch-logger Plugin pushes request and response logs in batches to Elasticsearch and supports the customization of log formats.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/elasticsearch-logger" />
</head>
## Description
The `elasticsearch-logger` Plugin pushes request and response logs in batches to [Elasticsearch](https://www.elastic.co) and supports the customization of log formats. When enabled, the Plugin will serialize the request context information to [Elasticsearch Bulk format](https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html#docs-bulk) and add them to the queue, before they are pushed to Elasticsearch. See [batch processor](../batch-processor.md) for more details.
## Attributes
| Name | Type | Required | Default | Description |
| ------------- | ------- | -------- | --------------------------- | ------------------------------------------------------------ |
| endpoint_addrs | array[string] | True | | Elasticsearch API endpoint addresses. If multiple endpoints are configured, they will be written randomly. |
| field | object | True | | Elasticsearch `field` configuration. |
| field.index | string | True | | Elasticsearch [_index field](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-index-field.html#mapping-index-field). |
| log_format | object | False | | Custom log format in key-value pairs in JSON format. Support [APISIX](../apisix-variable.md) or [NGINX variables](http://nginx.org/en/docs/varindex.html) in values. |
| auth | array | False | | Elasticsearch [authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html) configuration. |
| auth.username | string | True | | Elasticsearch [authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html) username. |
| auth.password | string | True | | Elasticsearch [authentication](https://www.elastic.co/guide/en/elasticsearch/reference/current/setting-up-authentication.html) password. |
| ssl_verify | boolean | False | true | If true, perform SSL verification. |
| timeout | integer | False | 10 | Elasticsearch send data timeout in seconds. |
| include_req_body | boolean | False | false | If true, include the request body in the log. Note that if the request body is too big to be kept in the memory, it can not be logged due to NGINX's limitations. |
| include_req_body_expr | array[array] | False | | An array of one or more conditions in the form of [lua-resty-expr](https://github.com/api7/lua-resty-expr). Used when the `include_req_body` is true. Request body would only be logged when the expressions configured here evaluate to true. |
| include_resp_body | boolean | False | false | If true, include the response body in the log. |
| include_resp_body_expr | array[array] | False | | An array of one or more conditions in the form of [lua-resty-expr](https://github.com/api7/lua-resty-expr). Used when the `include_resp_body` is true. Response body would only be logged when the expressions configured here evaluate to true. |
NOTE: `encrypt_fields = {"auth.password"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
## Plugin Metadata
| Name | Type | Required | Default | Description |
|------|------|----------|---------|-------------|
| log_format | object | False | | Custom log format in key-value pairs in JSON format. Support [APISIX variables](../apisix-variable.md) and [NGINX variables](http://nginx.org/en/docs/varindex.html) in values. |
## Examples
The examples below demonstrate how you can configure `elasticsearch-logger` Plugin for different scenarios.
To follow along the examples, start an Elasticsearch instance in Docker:
```shell
docker run -d \
--name elasticsearch \
--network apisix-quickstart-net \
-v elasticsearch_vol:/usr/share/elasticsearch/data/ \
-p 9200:9200 \
-p 9300:9300 \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-e discovery.type=single-node \
-e xpack.security.enabled=false \
docker.elastic.co/elasticsearch/elasticsearch:7.17.1
```
Start a Kibana instance in Docker to visualize the indexed data in Elasticsearch:
```shell
docker run -d \
--name kibana \
--network apisix-quickstart-net \
-p 5601:5601 \
-e ELASTICSEARCH_HOSTS="http://elasticsearch:9200" \
docker.elastic.co/kibana/kibana:7.17.1
```
If successful, you should see the Kibana dashboard on [localhost:5601](http://localhost:5601).
:::note
You can fetch the APISIX `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Log in the Default Log Format
The following example demonstrates how you can enable the `elasticsearch-logger` Plugin on a route, which logs client requests and responses to the Route and pushes logs to Elasticsearch.
Create a Route with `elasticsearch-logger` to configure the `index` field as `gateway`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "elasticsearch-logger-route",
"uri": "/anything",
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
}
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```
Send a request to the Route to generate a log entry:
```shell
curl -i "http://127.0.0.1:9080/anything"
```
You should receive an `HTTP/1.1 200 OK` response.
Navigate to the Kibana dashboard on [localhost:5601](http://localhost:5601) and under __Discover__ tab, create a new index pattern `gateway` to fetch the data from Elasticsearch. Once configured, navigate back to the __Discover__ tab and you should see a log generated, similar to the following:
```json
{
"_index": "gateway",
"_id": "CE-JL5QBOkdYRG7kEjTJ",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"host": "127.0.0.1:9080",
"accept": "*/*",
"user-agent": "curl/8.6.0"
},
"size": 85,
"querystring": {},
"method": "GET",
"url": "http://127.0.0.1:9080/anything",
"uri": "/anything"
},
"response": {
"headers": {
"content-type": "application/json",
"access-control-allow-credentials": "true",
"server": "APISIX/3.11.0",
"content-length": "390",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 10:18:14 GMT"
},
"status": 200,
"size": 618
},
"route_id": "elasticsearch-logger-route",
"latency": 585.00003814697,
"apisix_latency": 18.000038146973,
"upstream_latency": 567,
"upstream": "50.19.58.113:80",
"server": {
"hostname": "0b9a772e68f8",
"version": "3.11.0"
},
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}
```
### Log Request and Response Headers With Plugin Metadata
The following example demonstrates how you can customize log format using [Plugin Metadata](../terminology/plugin-metadata.md) and [NGINX variables](http://nginx.org/en/docs/varindex.html) to log specific headers from request and response.
In APISIX, [Plugin Metadata](../terminology/plugin-metadata.md) is used to configure the common metadata fields of all Plugin instances of the same plugin. It is useful when a Plugin is enabled across multiple resources and requires a universal update to their metadata fields.
First, create a Route with `elasticsearch-logger` as follows:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "elasticsearch-logger-route",
"uri": "/anything",
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```
Next, configure the Plugin metadata for `elasticsearch-logger`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/plugin_metadata/elasticsearch-logger" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr",
"env": "$http_env",
"resp_content_type": "$sent_http_Content_Type"
}
}'
```
Send a request to the Route with the `env` header:
```shell
curl -i "http://127.0.0.1:9080/anything" -H "env: dev"
```
You should receive an `HTTP/1.1 200 OK` response.
Navigate to the Kibana dashboard on [localhost:5601](http://localhost:5601) and under __Discover__ tab, create a new index pattern `gateway` to fetch the data from Elasticsearch, if you have not done so already. Once configured, navigate back to the __Discover__ tab and you should see a log generated, similar to the following:
```json
{
"_index": "gateway",
"_id": "Ck-WL5QBOkdYRG7kODS0",
"_version": 1,
"_score": 1,
"_source": {
"client_ip": "192.168.65.1",
"route_id": "elasticsearch-logger-route",
"@timestamp": "2025-01-06T10:32:36+00:00",
"host": "127.0.0.1",
"resp_content_type": "application/json"
},
"fields": {
...
}
}
```
### Log Request Bodies Conditionally
The following example demonstrates how you can conditionally log request body.
Create a Route with `elasticsearch-logger` to only log request body if the URL query string `log_body` is `true`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"elasticsearch-logger": {
"endpoint_addrs": ["http://elasticsearch:9200"],
"field": {
"index": "gateway"
},
"include_req_body": true,
"include_req_body_expr": [["arg_log_body", "==", "yes"]]
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
},
"uri": "/anything",
"id": "elasticsearch-logger-route"
}'
```
Send a request to the Route with an URL query string satisfying the condition:
```shell
curl -i "http://127.0.0.1:9080/anything?log_body=yes" -X POST -d '{"env": "dev"}'
```
You should receive an `HTTP/1.1 200 OK` response.
Navigate to the Kibana dashboard on [localhost:5601](http://localhost:5601) and under __Discover__ tab, create a new index pattern `gateway` to fetch the data from Elasticsearch, if you have not done so already. Once configured, navigate back to the __Discover__ tab and you should see a log generated, similar to the following:
```json
{
"_index": "gateway",
"_id": "Dk-cL5QBOkdYRG7k7DSW",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"user-agent": "curl/8.6.0",
"accept": "*/*",
"content-length": "14",
"host": "127.0.0.1:9080",
"content-type": "application/x-www-form-urlencoded"
},
"size": 182,
"querystring": {
"log_body": "yes"
},
"body": "{\"env\": \"dev\"}",
"method": "POST",
"url": "http://127.0.0.1:9080/anything?log_body=yes",
"uri": "/anything?log_body=yes"
},
"start_time": 1735965595203,
"response": {
"headers": {
"content-type": "application/json",
"server": "APISIX/3.11.0",
"access-control-allow-credentials": "true",
"content-length": "548",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 11:02:32 GMT"
},
"status": 200,
"size": 776
},
"route_id": "elasticsearch-logger-route",
"latency": 703.9999961853,
"apisix_latency": 34.999996185303,
"upstream_latency": 669,
"upstream": "34.197.122.172:80",
"server": {
"hostname": "0b9a772e68f8",
"version": "3.11.0"
},
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}
```
Send a request to the Route without any URL query string:
```shell
curl -i "http://127.0.0.1:9080/anything" -X POST -d '{"env": "dev"}'
```
Navigate to the Kibana dashboard __Discover__ tab and you should see a log generated, but without the request body:
```json
{
"_index": "gateway",
"_id": "EU-eL5QBOkdYRG7kUDST",
"_version": 1,
"_score": 1,
"_source": {
"request": {
"headers": {
"content-type": "application/x-www-form-urlencoded",
"accept": "*/*",
"content-length": "14",
"host": "127.0.0.1:9080",
"user-agent": "curl/8.6.0"
},
"size": 169,
"querystring": {},
"method": "POST",
"url": "http://127.0.0.1:9080/anything",
"uri": "/anything"
},
"start_time": 1735965686363,
"response": {
"headers": {
"content-type": "application/json",
"access-control-allow-credentials": "true",
"server": "APISIX/3.11.0",
"content-length": "510",
"access-control-allow-origin": "*",
"connection": "close",
"date": "Mon, 13 Jan 2025 11:15:54 GMT"
},
"status": 200,
"size": 738
},
"route_id": "elasticsearch-logger-route",
"latency": 680.99999427795,
"apisix_latency": 4.9999942779541,
"upstream_latency": 676,
"upstream": "34.197.122.172:80",
"server": {
"hostname": "0b9a772e68f8",
"version": "3.11.0"
},
"service_id": "",
"client_ip": "192.168.65.1"
},
"fields": {
...
}
}
```
:::info
If you have customized the `log_format` in addition to setting `include_req_body` or `include_resp_body` to `true`, the Plugin would not include the bodies in the logs.
As a workaround, you may be able to use the NGINX variable `$request_body` in the log format, such as:
```json
{
"elasticsearch-logger": {
...,
"log_format": {"body": "$request_body"}
}
}
```
:::

View File

@@ -0,0 +1,181 @@
---
title: error-log-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Error log logger
description: This document contains information about the Apache APISIX error-log-logger Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `error-log-logger` Plugin is used to push APISIX's error logs (`error.log`) to TCP, [Apache SkyWalking](https://skywalking.apache.org/), Apache Kafka or ClickHouse servers. You can also set the error log level to send the logs to server.
It might take some time to receive the log data. It will be automatically sent after the timer function in the [batch processor](../batch-processor.md) expires.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------------------------------|---------|----------|--------------------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| tcp.host | string | True | | | IP address or the hostname of the TCP server. |
| tcp.port | integer | True | | [0,...] | Target upstream port. |
| tcp.tls | boolean | False | false | | When set to `true` performs SSL verification. |
| tcp.tls_server_name | string | False | | | Server name for the new TLS extension SNI. |
| skywalking.endpoint_addr | string | False | http://127.0.0.1:12900/v3/logs | | Apache SkyWalking HTTP endpoint. |
| skywalking.service_name | string | False | APISIX | | Service name for the SkyWalking reporter. |
| skywalking.service_instance_name | String | False | APISIX Instance Name | | Service instance name for the SkyWalking reporter. Set it to `$hostname` to directly get the local hostname. |
| clickhouse.endpoint_addr | String | False | http://127.0.0.1:8213 | | ClickHouse endpoint. |
| clickhouse.user | String | False | default | | ClickHouse username. |
| clickhouse.password | String | False | | | ClickHouse password. |
| clickhouse.database | String | False | | | Name of the database to store the logs. |
| clickhouse.logtable | String | False | | | Table name to store the logs. |
| kafka.brokers | array | True | | | List of Kafka brokers (nodes). |
| kafka.brokers.host | string | True | | | The host of Kafka broker, e.g, `192.168.1.1`. |
| kafka.brokers.port | integer | True | | [0, 65535] | The port of Kafka broker |
| kafka.brokers.sasl_config | object | False | | | The sasl config of Kafka broker |
| kafka.brokers.sasl_config.mechanism | string | False | "PLAIN" | ["PLAIN"] | The mechaism of sasl config |
| kafka.brokers.sasl_config.user | string | True | | | The user of sasl_config. If sasl_config exists, it's required. |
| kafka.brokers.sasl_config.password | string | True | | | The password of sasl_config. If sasl_config exists, it's required. |
| kafka.kafka_topic | string | True | | | Target topic to push the logs for organisation. |
| kafka.producer_type | string | False | async | ["async", "sync"] | Message sending mode of the producer. |
| kafka.required_acks | integer | False | 1 | [0, 1, -1] | Number of acknowledgements the leader needs to receive for the producer to consider the request complete. This controls the durability of the sent records. The attribute follows the same configuration as the Kafka `acks` attribute. See [Apache Kafka documentation](https://kafka.apache.org/documentation/#producerconfigs_acks) for more. |
| kafka.key | string | False | | | Key used for allocating partitions for messages. |
| 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`. |
| kafka.meta_refresh_interval | integer | False | 30 | [1,...] | `refresh_interval` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) specifies the time to auto refresh the metadata, in seconds.|
| timeout | integer | False | 3 | [1,...] | Timeout (in seconds) for the upstream to connect and send data. |
| keepalive | integer | False | 30 | [1,...] | Time in seconds to keep the connection alive after sending data. |
| level | string | False | WARN | ["STDERR", "EMERG", "ALERT", "CRIT", "ERR", "ERROR", "WARN", "NOTICE", "INFO", "DEBUG"] | Log level to filter the error logs. `ERR` is same as `ERROR`. |
NOTE: `encrypt_fields = {"clickhouse.password"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
### Example of default log format
```text
["2024/01/06 16:04:30 [warn] 11786#9692271: *1 [lua] plugin.lua:205: load(): new plugins: {"error-log-logger":true}, context: init_worker_by_lua*","\n","2024/01/06 16:04:30 [warn] 11786#9692271: *1 [lua] plugin.lua:255: load_stream(): new plugins: {"limit-conn":true,"ip-restriction":true,"syslog":true,"mqtt-proxy":true}, context: init_worker_by_lua*","\n"]
```
## Enable Plugin
To enable the Plugin, you can add it in your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
- request-id
- hmac-auth
- api-breaker
- error-log-logger
```
Once you have enabled the Plugin, you can configure it through the Plugin metadata.
### Configuring TCP server address
You can set the TCP server address by configuring the Plugin metadata as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/error-log-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"tcp": {
"host": "127.0.0.1",
"port": 1999
},
"inactive_timeout": 1
}'
```
### Configuring SkyWalking OAP server address
You can configure the SkyWalking OAP server address as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/error-log-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"skywalking": {
"endpoint_addr":"http://127.0.0.1:12800/v3/logs"
},
"inactive_timeout": 1
}'
```
### Configuring ClickHouse server details
The Plugin sends the error log as a string to the `data` field of a table in your ClickHouse server.
You can configure it as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/error-log-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"clickhouse": {
"user": "default",
"password": "a",
"database": "error_log",
"logtable": "t",
"endpoint_addr": "http://127.0.0.1:8123"
}
}'
```
### Configuring Kafka server
The Plugin sends the error log to Kafka, you can configure it as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/error-log-logger \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"kafka":{
"brokers":[
{
"host":"127.0.0.1",
"port":9092
}
],
"kafka_topic":"test2"
},
"level":"ERROR",
"inactive_timeout":1
}'
```
## Delete Plugin
To remove the Plugin, you can remove it from your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
- request-id
- hmac-auth
- api-breaker
# - error-log-logger
```

View File

@@ -0,0 +1,33 @@
---
title: ext-plugin-post-req
keywords:
- Apache APISIX
- Plugin
- ext-plugin-post-req
description: This document contains information about the Apache APISIX ext-plugin-post-req Plugin.
---
<!--
#
# 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.
#
-->
## Description
`ext-plugin-post-req` differs from the [ext-plugin-pre-req](./ext-plugin-pre-req.md) Plugin in that it runs after executing the built-in Lua Plugins and before proxying to the Upstream.
You can learn more about the configuration from the [ext-plugin-pre-req](./ext-plugin-pre-req.md) Plugin document.

View File

@@ -0,0 +1,111 @@
---
title: ext-plugin-post-resp
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ext-plugin-post-resp
description: This document contains information about the Apache APISIX ext-plugin-post-resp Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `ext-plugin-post-resp` Plugin is for running specific external Plugins in the Plugin Runner before executing the built-in Lua Plugins.
The `ext-plugin-post-resp` plugin will be executed after the request gets a response from the upstream.
This plugin uses [lua-resty-http](https://github.com/api7/lua-resty-http) library under the hood to send requests to the upstream, due to which the [proxy-control](./proxy-control.md), [proxy-mirror](./proxy-mirror.md), and [proxy-cache](./proxy-cache.md) plugins are not available to be used alongside this plugin. Also, [mTLS Between APISIX and Upstream](../mtls.md#mtls-between-apisix-and-upstream) is not yet supported.
See [External Plugin](../external-plugin.md) to learn more.
:::note
Execution of External Plugins will affect the response of the current request.
:::
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|-------------------|---------|----------|---------|-----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| conf | array | False | | [{"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"}] | List of Plugins and their configurations to be executed on the Plugin Runner. |
| allow_degradation | boolean | False | false | | Sets Plugin degradation when the Plugin Runner is not available. When set to `true`, requests are allowed to continue. |
## Enable Plugin
The example below enables the `ext-plugin-post-resp` Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"plugins": {
"ext-plugin-post-resp": {
"conf" : [
{"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
## Example usage
Once you have configured the External Plugin as shown above, you can make a request to execute the Plugin:
```shell
curl -i http://127.0.0.1:9080/index.html
```
This will reach the configured Plugin Runner and the `ext-plugin-A` will be executed.
## Delete Plugin
To remove the `ext-plugin-post-resp` 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 '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,107 @@
---
title: ext-plugin-pre-req
keywords:
- Apache APISIX
- API Gateway
- Plugin
- ext-plugin-pre-req
description: This document contains information about the Apache APISIX ext-plugin-pre-req Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `ext-plugin-pre-req` Plugin is for running specific external Plugins in the Plugin Runner before executing the built-in Lua Plugins.
See [External Plugin](../external-plugin.md) to learn more.
:::note
Execution of External Plugins will affect the behavior of the current request.
:::
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|-------------------|---------|----------|---------|-----------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------|
| conf | array | False | | [{"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"}] | List of Plugins and their configurations to be executed on the Plugin Runner. |
| allow_degradation | boolean | False | false | | Sets Plugin degradation when the Plugin Runner is not available. When set to `true`, requests are allowed to continue. |
## Enable Plugin
The example below enables the `ext-plugin-pre-req` Plugin on a specific Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"plugins": {
"ext-plugin-pre-req": {
"conf" : [
{"name": "ext-plugin-A", "value": "{\"enable\":\"feature\"}"}
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
## Example usage
Once you have configured the External Plugin as shown above, you can make a request to execute the Plugin:
```shell
curl -i http://127.0.0.1:9080/index.html
```
This will reach the configured Plugin Runner and the `ext-plugin-A` will be executed.
## Delete Plugin
To remove the `ext-plugin-pre-req` 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 '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,293 @@
---
title: fault-injection
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Fault Injection
- fault-injection
description: This document contains information about the Apache APISIX fault-injection Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `fault-injection` Plugin can be used to test the resiliency of your application. This Plugin will be executed before the other configured Plugins.
The `abort` attribute will directly return the specified HTTP code to the client and skips executing the subsequent Plugins.
The `delay` attribute delays a request and executes the subsequent Plugins.
## Attributes
| Name | Type | Requirement | Default | Valid | Description |
|-------------------|---------|-------------|---------|------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------|
| abort.http_status | integer | required | | [200, ...] | HTTP status code of the response to return to the client. |
| abort.body | string | optional | | | Body of the response returned to the client. Nginx variables like `client addr: $remote_addr\n` can be used in the body. |
| abort.headers | object | optional | | | Headers of the response returned to the client. The values in the header can contain Nginx variables like `$remote_addr`. |
| abort.percentage | integer | optional | | [0, 100] | Percentage of requests to be aborted. |
| abort.vars | array[] | optional | | | Rules which are matched before executing fault injection. See [lua-resty-expr](https://github.com/api7/lua-resty-expr) for a list of available expressions. |
| delay.duration | number | required | | | Duration of the delay. Can be decimal. |
| delay.percentage | integer | optional | | [0, 100] | Percentage of requests to be delayed. |
| delay.vars | array[] | optional | | | Rules which are matched before executing fault injection. See [lua-resty-expr](https://github.com/api7/lua-resty-expr) for a list of available expressions. |
:::info IMPORTANT
To use the `fault-injection` Plugin one of `abort` or `delay` must be specified.
:::
:::tip
`vars` can have expressions from [lua-resty-expr](https://github.com/api7/lua-resty-expr) and can flexibly implement AND/OR relationship between rules. For example:
```json
[
[
[ "arg_name","==","jack" ],
[ "arg_age","==",18 ]
],
[
[ "arg_name2","==","allen" ]
]
]
```
This means that the relationship between the first two expressions is AND, and the relationship between them and the third expression is OR.
:::
## Enable Plugin
You can enable the `fault-injection` Plugin on a specific Route as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 200,
"body": "Fault Injection!"
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
Similarly, to enable a `delay` fault:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"fault-injection": {
"delay": {
"duration": 3
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
You can also enable the Plugin with both `abort` and `delay` which can have `vars` for matching:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 403,
"body": "Fault Injection!\n",
"vars": [
[
[ "arg_name","==","jack" ]
]
]
},
"delay": {
"duration": 2,
"vars": [
[
[ "http_age","==","18" ]
]
]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
## Example usage
Once you have enabled the Plugin as shown above, you can make a request to the configured Route:
```shell
curl http://127.0.0.1:9080/hello -i
```
```
HTTP/1.1 200 OK
Date: Mon, 13 Jan 2020 13:50:04 GMT
Content-Type: text/plain
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX web server
Fault Injection!
```
And if we configure the `delay` fault:
```shell
time curl http://127.0.0.1:9080/hello -i
```
```
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Content-Length: 6
Connection: keep-alive
Server: APISIX web server
Date: Tue, 14 Jan 2020 14:30:54 GMT
Last-Modified: Sat, 11 Jan 2020 12:46:21 GMT
hello
real 0m3.034s
user 0m0.007s
sys 0m0.010s
```
### Fault injection with criteria matching
You can enable the `fault-injection` Plugin with the `vars` attribute to set specific rules:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"fault-injection": {
"abort": {
"http_status": 403,
"body": "Fault Injection!\n",
"vars": [
[
[ "arg_name","==","jack" ]
]
]
}
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
Now, we can test the Route. First, we test with a different `name` argument:
```shell
curl "http://127.0.0.1:9080/hello?name=allen" -i
```
You will get the expected response without the fault injected:
```
HTTP/1.1 200 OK
Content-Type: application/octet-stream
Transfer-Encoding: chunked
Connection: keep-alive
Date: Wed, 20 Jan 2021 07:21:57 GMT
Server: APISIX/2.2
hello
```
Now if we set the `name` to match our configuration, the `fault-injection` Plugin is executed:
```shell
curl "http://127.0.0.1:9080/hello?name=jack" -i
```
```
HTTP/1.1 403 Forbidden
Date: Wed, 20 Jan 2021 07:23:37 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/2.2
Fault Injection!
```
## Delete Plugin
To remove the `fault-injection` 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 '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,226 @@
---
title: file-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- File Logger
description: This document contains information about the Apache APISIX file-logger Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `file-logger` Plugin is used to push log streams to a specific location.
:::tip
- `file-logger` plugin can count request and response data for individual routes locally, which is useful for [debugging](../debug-mode.md).
- `file-logger` plugin can get [APISIX variables](../apisix-variable.md) and [NGINX variables](http://nginx.org/en/docs/varindex.html), while `access.log` can only use NGINX variables.
- `file-logger` plugin support hot-loaded so that we can change its configuration at any time with immediate effect.
- `file-logger` plugin saves every data in JSON format.
- The user can modify the functions executed by the `file-logger` during the `log phase` to collect the information they want.
:::
## Attributes
| Name | Type | Required | Description |
| ---- | ------ | -------- | ------------- |
| path | string | True | Log file path. |
| 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 | 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. |
| include_resp_body | boolean | False | When set to `true` includes the response body in the log file. |
| include_resp_body_expr | array | False | When the `include_resp_body` attribute is set to `true`, use this to filter based on [lua-resty-expr](https://github.com/api7/lua-resty-expr). If present, only logs the response into file if the expression evaluates to `true`. |
| match | array[array] | False | Logs will be recorded when the rule matching is successful if the option is set. See [lua-resty-expr](https://github.com/api7/lua-resty-expr#operator-list) for a list of available expressions. |
### Example of default log format
```json
{
"service_id": "",
"apisix_latency": 100.99999809265,
"start_time": 1703907485819,
"latency": 101.99999809265,
"upstream_latency": 1,
"client_ip": "127.0.0.1",
"route_id": "1",
"server": {
"version": "3.7.0",
"hostname": "localhost"
},
"request": {
"headers": {
"host": "127.0.0.1:1984",
"content-type": "application/x-www-form-urlencoded",
"user-agent": "lua-resty-http/0.16.1 (Lua) ngx_lua/10025",
"content-length": "12"
},
"method": "POST",
"size": 194,
"url": "http://127.0.0.1:1984/hello?log_body=no",
"uri": "/hello?log_body=no",
"querystring": {
"log_body": "no"
}
},
"response": {
"headers": {
"content-type": "text/plain",
"connection": "close",
"content-length": "12",
"server": "APISIX/3.7.0"
},
"status": 200,
"size": 123
},
"upstream": "127.0.0.1:1982"
}
```
## Metadata
You can also set the format of the logs by configuring the Plugin metadata. The following configurations are available:
| Name | Type | Required | Default | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 `$`. |
The example below shows how you can configure through the Admin API:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/file-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}'
```
With this configuration, your logs would be formatted as shown below:
```shell
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
```
## Enable Plugin
The example below shows how you can enable the Plugin on a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"file-logger": {
"path": "logs/file.log"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:9001": 1
}
},
"uri": "/hello"
}'
```
## Example usage
Now, if you make a request, it will be logged in the path you specified:
```shell
curl -i http://127.0.0.1:9080/hello
```
You will be able to find the `file.log` file in the configured `logs` directory.
## Filter logs
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"file-logger": {
"path": "logs/file.log",
"match": [
[
[ "arg_name","==","jack" ]
]
]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:9001": 1
}
},
"uri": "/hello"
}'
```
Test:
```shell
curl -i http://127.0.0.1:9080/hello?name=jack
```
Log records can be seen in `logs/file.log`.
```shell
curl -i http://127.0.0.1:9080/hello?name=rose
```
Log records cannot be seen in `logs/file.log`.
## Delete Plugin
To remove the `file-logger` 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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:9001": 1
}
}
}'
```

View File

@@ -0,0 +1,186 @@
---
title: forward-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Forward Authentication
- forward-auth
description: This document contains information about the Apache APISIX forward-auth Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `forward-auth` Plugin implements a classic external authentication model. When authentication fails, you can have a custom error message or redirect the user to an authentication page.
This Plugin moves the authentication and authorization logic to a dedicated external service. APISIX forwards the user's requests to the external service, blocks the original request, and replaces the result when the external service responds with a non 2xx status code.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ----------------- | ------------- | -------- | ------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
| uri | string | True | | | URI of the authorization service. |
| ssl_verify | boolean | False | true | | When set to `true`, verifies the SSL certificate. |
| request_method | string | False | GET | ["GET","POST"] | HTTP method for a client to send requests to the authorization service. When set to `POST` the request body is send to the authorization service. |
| request_headers | array[string] | False | | | Client request headers to be sent to the authorization service. If not set, only the headers provided by APISIX are sent (for example, `X-Forwarded-XXX`). |
| upstream_headers | array[string] | False | | | Authorization service response headers to be forwarded to the Upstream. If not set, no headers are forwarded to the Upstream service. |
| client_headers | array[string] | False | | | Authorization service response headers to be sent to the client when authorization fails. If not set, no headers will be sent to the client. |
| timeout | integer | False | 3000ms | [1, 60000]ms | Timeout for the authorization service HTTP call. |
| keepalive | boolean | False | true | | When set to `true`, keeps the connection alive for multiple requests. |
| keepalive_timeout | integer | False | 60000ms | [1000, ...]ms | Idle time after which the connection is closed. |
| keepalive_pool | integer | False | 5 | [1, ...]ms | Connection pool limit. |
| allow_degradation | boolean | False | false | | When set to `true`, allows authentication to be skipped when authentication server is unavailable. |
| status_on_error | integer | False | 403 | [200,...,599] | Sets the HTTP status that is returned to the client when there is a network error to the authorization service. The default status is “403” (HTTP Forbidden). |
## Data definition
APISIX will generate and send the request headers listed below to the authorization service:
| Scheme | HTTP Method | Host | URI | Source IP |
| ----------------- | ------------------ | ---------------- | --------------- | --------------- |
| X-Forwarded-Proto | X-Forwarded-Method | X-Forwarded-Host | X-Forwarded-Uri | X-Forwarded-For |
## Example usage
First, you need to setup your external authorization service. The example below uses Apache APISIX's [serverless](./serverless.md) Plugin to mock the service:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/auth' \
-H "X-API-KEY: $admin_key" \
-H 'Content-Type: application/json' \
-d '{
"uri": "/auth",
"plugins": {
"serverless-pre-function": {
"phase": "rewrite",
"functions": [
"return function (conf, ctx)
local core = require(\"apisix.core\");
local authorization = core.request.header(ctx, \"Authorization\");
if authorization == \"123\" then
core.response.exit(200);
elseif authorization == \"321\" then
core.response.set_header(\"X-User-ID\", \"i-am-user\");
core.response.exit(200);
else core.response.set_header(\"Location\", \"http://example.com/auth\");
core.response.exit(403);
end
end"
]
}
}
}'
```
Now you can configure the `forward-auth` Plugin to a specific Route:
```shell
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/1' \
-H "X-API-KEY: $admin_key" \
-d '{
"uri": "/headers",
"plugins": {
"forward-auth": {
"uri": "http://127.0.0.1:9080/auth",
"request_headers": ["Authorization"],
"upstream_headers": ["X-User-ID"],
"client_headers": ["Location"]
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```
Now if we send the authorization details in the request header:
```shell
curl http://127.0.0.1:9080/headers -H 'Authorization: 123'
```
```
{
"headers": {
"Authorization": "123",
"Next": "More-headers"
}
}
```
The authorization service response can also be forwarded to the Upstream:
```shell
curl http://127.0.0.1:9080/headers -H 'Authorization: 321'
```
```
{
"headers": {
"Authorization": "321",
"X-User-ID": "i-am-user",
"Next": "More-headers"
}
}
```
When authorization fails, the authorization service can send custom response back to the user:
```shell
curl -i http://127.0.0.1:9080/headers
```
```
HTTP/1.1 403 Forbidden
Location: http://example.com/auth
```
## Delete Plugin
To remove the `forward-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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,31 @@
---
title: GM
keywords:
- Apache APISIX
- Plugin
- GM
description: This article introduces the basic information and usage of the Apache APISIX `gm` plugin.
---
<!--
#
# 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.
#
-->
:::info
The function usage scenarios introduced in this article are mainly in China, so this article only has a Chinese version temporarily. You can click [here](https://apisix.apache.org/zh/docs/apisix/plugins/gm/) for more details. If you are interested in this feature, welcome to translate this document.
:::

View File

@@ -0,0 +1,223 @@
---
title: google-cloud-logging
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Google Cloud logging
description: This document contains information about the Apache APISIX google-cloud-logging Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `google-cloud-logging` Plugin is used to send APISIX access logs to [Google Cloud Logging Service](https://cloud.google.com/logging/).
This plugin also allows to push logs as a batch to your Google Cloud Logging Service. It might take some time to receive the log data. It will be automatically sent after the timer function in the [batch processor](../batch-processor.md) expires.
## Attributes
| Name | Required | Default | Description |
|-------------------------|----------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| auth_config | True | | Either `auth_config` or `auth_file` must be provided. |
| auth_config.client_email | True | | Email address of the Google Cloud service account. |
| auth_config.private_key | True | | Private key of the Google Cloud service account. |
| auth_config.project_id | True | | Project ID in the Google Cloud service account. |
| auth_config.token_uri | True | https://oauth2.googleapis.com/token | Token URI of the Google Cloud service account. |
| auth_config.entries_uri | False | https://logging.googleapis.com/v2/entries:write | Google Cloud Logging Service API. |
| auth_config.scope | False | ["https://www.googleapis.com/auth/logging.read", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/logging.admin", "https://www.googleapis.com/auth/cloud-platform"] | Access scopes of the Google Cloud service account. See [OAuth 2.0 Scopes for Google APIs](https://developers.google.com/identity/protocols/oauth2/scopes#logging). |
| auth_config.scopes | Deprecated | ["https://www.googleapis.com/auth/logging.read", "https://www.googleapis.com/auth/logging.write", "https://www.googleapis.com/auth/logging.admin", "https://www.googleapis.com/auth/cloud-platform"] | Access scopes of the Google Cloud service account. Use `auth_config.scope` instead. |
| auth_file | True | | Path to the Google Cloud service account authentication JSON file. Either `auth_config` or `auth_file` must be provided. |
| ssl_verify | False | true | When set to `true`, enables SSL verification as mentioned in [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake). |
| resource | False | {"type": "global"} | Google monitor resource. See [MonitoredResource](https://cloud.google.com/logging/docs/reference/v2/rest/v2/MonitoredResource) for more details. |
| log_id | False | apisix.apache.org%2Flogs | Google Cloud logging ID. See [LogEntry](https://cloud.google.com/logging/docs/reference/v2/rest/v2/LogEntry) for details. |
| log_format | 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 `$`. |
NOTE: `encrypt_fields = {"auth_config.private_key"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
### Example of default log format
```json
{
"insertId": "0013a6afc9c281ce2e7f413c01892bdc",
"labels": {
"source": "apache-apisix-google-cloud-logging"
},
"logName": "projects/apisix/logs/apisix.apache.org%2Flogs",
"httpRequest": {
"requestMethod": "GET",
"requestUrl": "http://localhost:1984/hello",
"requestSize": 59,
"responseSize": 118,
"status": 200,
"remoteIp": "127.0.0.1",
"serverIp": "127.0.0.1:1980",
"latency": "0.103s"
},
"resource": {
"type": "global"
},
"jsonPayload": {
"service_id": "",
"route_id": "1"
},
"timestamp": "2024-01-06T03:34:45.065Z"
}
```
## Metadata
You can also set the format of the logs by configuring the Plugin metadata. The following configurations are available:
| Name | Type | Required | Default | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 `$`. |
:::info IMPORTANT
Configuring the Plugin metadata is global in scope. This means that it will take effect on all Routes and Services which use the `google-cloud-logging` Plugin.
:::
The example below shows how you can configure through the Admin API:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/google-cloud-logging -H "X-API-KEY: $admin_key" -X PUT -d '
{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}'
```
With this configuration, your logs would be formatted as shown below:
```json
{"partialSuccess":false,"entries":[{"jsonPayload":{"client_ip":"127.0.0.1","host":"localhost","@timestamp":"2023-01-09T14:47:25+08:00","route_id":"1"},"resource":{"type":"global"},"insertId":"942e81f60b9157f0d46bc9f5a8f0cc40","logName":"projects/apisix/logs/apisix.apache.org%2Flogs","timestamp":"2023-01-09T14:47:25+08:00","labels":{"source":"apache-apisix-google-cloud-logging"}}]}
```
## Enable Plugin
### Full configuration
The example below shows a complete configuration of the Plugin on a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"google-cloud-logging": {
"auth_config":{
"project_id":"apisix",
"client_email":"your service account email@apisix.iam.gserviceaccount.com",
"private_key":"-----BEGIN RSA PRIVATE KEY-----your private key-----END RSA PRIVATE KEY-----",
"token_uri":"https://oauth2.googleapis.com/token",
"scope":[
"https://www.googleapis.com/auth/logging.admin"
],
"entries_uri":"https://logging.googleapis.com/v2/entries:write"
},
"resource":{
"type":"global"
},
"log_id":"apisix.apache.org%2Flogs",
"inactive_timeout":10,
"max_retry_count":0,
"buffer_duration":60,
"retry_delay":1,
"batch_max_size":1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
### Minimal configuration
The example below shows a bare minimum configuration of the Plugin on a Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"google-cloud-logging": {
"auth_config":{
"project_id":"apisix",
"client_email":"your service account email@apisix.iam.gserviceaccount.com",
"private_key":"-----BEGIN RSA PRIVATE KEY-----your private key-----END RSA PRIVATE KEY-----"
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
## Example usage
Now, if you make a request to APISIX, it will be logged in your Google Cloud Logging Service.
```shell
curl -i http://127.0.0.1:9080/hello
```
You can then login and view the logs in [Google Cloud Logging Service](https://console.cloud.google.com/logs/viewer).
## Delete Plugin
To remove the `google-cloud-logging` 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 '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,391 @@
---
title: grpc-transcode
keywords:
- Apache APISIX
- API Gateway
- Plugin
- gRPC Transcode
- grpc-transcode
description: This document contains information about the Apache APISIX grpc-transcode Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `grpc-transcode` Plugin converts between HTTP and gRPC requests.
APISIX takes in an HTTP request, transcodes it and forwards it to a gRPC service, gets the response and returns it back to the client in HTTP format.
<!-- TODO: use an image here to explain the concept better -->
## Attributes
| Name | Type | Required | Default | Description |
| --------- | ------------------------------------------------------ | -------- | ------- | ------------------------------------ |
| proto_id | string/integer | True | | id of the the proto content. |
| service | string | True | | Name of the gRPC service. |
| method | string | True | | Method name of the gRPC service. |
| deadline | number | False | 0 | Deadline for the gRPC service in ms. |
| pb_option | array[string([pb_option_def](#options-for-pb_option))] | False | | protobuf options. |
| show_status_in_body | boolean | False | false | Whether to display the parsed `grpc-status-details-bin` in the response body |
| status_detail_type | string | False | | The message type corresponding to the [details](https://github.com/googleapis/googleapis/blob/b7cb84f5d42e6dba0fdcc2d8689313f6a8c9d7b9/google/rpc/status.proto#L46) part of `grpc-status-details-bin`, if not specified, this part will not be decoded |
### Options for pb_option
| Type | Valid values |
|-----------------|-------------------------------------------------------------------------------------------|
| enum as result | `enum_as_name`, `enum_as_value` |
| int64 as result | `int64_as_number`, `int64_as_string`, `int64_as_hexstring` |
| default values | `auto_default_values`, `no_default_values`, `use_default_values`, `use_default_metatable` |
| hooks | `enable_hooks`, `disable_hooks` |
## Enable Plugin
Before enabling the Plugin, you have to add the content of your `.proto` or `.pb` files to APISIX.
You can use the `/admin/protos/id` endpoint and add the contents of the file to the `content` field:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/protos/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}"
}'
```
If your proto file contains imports, or if you want to combine multiple proto files, you can generate a `.pb` file and use it in APISIX.
For example, if we have a file called `proto/helloworld.proto` which imports another proto file:
```proto
syntax = "proto3";
package helloworld;
import "proto/import.proto";
...
```
We first generate a `.pb` file from the proto files:
```shell
protoc --include_imports --descriptor_set_out=proto.pb proto/helloworld.proto
```
The output binary file, `proto.pb` will contain both `helloworld.proto` and `import.proto`.
We can now use the content of `proto.pb` in the `content` field of the API request.
As the content of the proto is binary, we encode it in `base64` and configure the content in APISIX:
```shell
curl http://127.0.0.1:9180/apisix/admin/protos/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"content" : "'"$(base64 -w0 /path/to/proto.pb)"'"
}'
```
You should see an `HTTP/1.1 201 Created` response with the following:
```
{"node":{"value":{"create_time":1643879753,"update_time":1643883085,"content":"CmgKEnByb3RvL2ltcG9ydC5wcm90bxIDcGtnIhoKBFVzZXISEgoEbmFtZRgBIAEoCVIEbmFtZSIeCghSZXNwb25zZRISCgRib2R5GAEgASgJUgRib2R5QglaBy4vcHJvdG9iBnByb3RvMwq9AQoPcHJvdG8vc3JjLnByb3RvEgpoZWxsb3dvcmxkGhJwcm90by9pbXBvcnQucHJvdG8iPAoHUmVxdWVzdBIdCgR1c2VyGAEgASgLMgkucGtnLlVzZXJSBHVzZXISEgoEYm9keRgCIAEoCVIEYm9keTI5CgpUZXN0SW1wb3J0EisKA1J1bhITLmhlbGxvd29ybGQuUmVxdWVzdBoNLnBrZy5SZXNwb25zZSIAQglaBy4vcHJvdG9iBnByb3RvMw=="},"key":"\/apisix\/proto\/1"}}
```
Now, we can enable the `grpc-transcode` Plugin to a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/111 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "SayHello"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
}
}
}'
```
:::note
The Upstream service used here should be a gRPC service. Note that the `scheme` is set to `grpc`.
You can use the [grpc_server_example](https://github.com/api7/grpc_server_example) for testing.
:::
## Example usage
Once you configured the Plugin as mentioned above, you can make a request to APISIX to get a response back from the gRPC service (through APISIX):
```shell
curl -i http://127.0.0.1:9080/grpctest?name=world
```
Response:
```shell
HTTP/1.1 200 OK
Date: Fri, 16 Aug 2019 11:55:36 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX web server
Proxy-Connection: keep-alive
{"message":"Hello world"}
```
You can also configure the `pb_option` as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/23 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/zeebe/WorkflowInstanceCreate",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "gateway_protocol.Gateway",
"method": "CreateWorkflowInstance",
"pb_option":["int64_as_string"]
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:26500": 1
}
}
}'
```
Now if you check the configured Route:
```shell
curl -i "http://127.0.0.1:9080/zeebe/WorkflowInstanceCreate?bpmnProcessId=order-process&version=1&variables=\{\"orderId\":\"7\",\"ordervalue\":99\}"
```
```
HTTP/1.1 200 OK
Date: Wed, 13 Nov 2019 03:38:27 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
grpc-encoding: identity
grpc-accept-encoding: gzip
Server: APISIX web server
Trailer: grpc-status
Trailer: grpc-message
{"workflowKey":"#2251799813685260","workflowInstanceKey":"#2251799813688013","bpmnProcessId":"order-process","version":1}
```
## Show `grpc-status-details-bin` in response body
If the gRPC service returns an error, there may be a `grpc-status-details-bin` field in the response header describing the error, which you can decode and display in the response body.
Upload the proto file
```shell
curl http://127.0.0.1:9180/apisix/admin/protos/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc GetErrResp (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}"
}'
```
Enable the `grpc-transcode` pluginand set the option `show_status_in_body` to `true`
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
}
}
}'
```
Access the route configured above
```shell
curl -i http://127.0.0.1:9080/grpctest?name=world
```
Response:
```Shell
HTTP/1.1 503 Service Temporarily Unavailable
Date: Wed, 10 Aug 2022 08:59:46 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
Server: APISIX web server
{"error":{"details":[{"type_url":"type.googleapis.com\/helloworld.ErrorDetail","value":"\b\u0001\u0012\u001cThe server is out of service\u001a\u0007service"}],"message":"Out of service","code":14}}
```
Note that there is an undecoded field in the return body. If you need to decode the field, you need to add the `message type` of the field in the uploaded proto file.
```shell
curl http://127.0.0.1:9180/apisix/admin/protos/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"content" : "syntax = \"proto3\";
package helloworld;
service Greeter {
rpc GetErrResp (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
repeated string items = 2;
}
message HelloReply {
string message = 1;
repeated string items = 2;
}
message ErrorDetail {
int64 code = 1;
string message = 2;
string type = 3;
}"
}'
```
Also configure the option `status_detail_type` to `helloworld.ErrorDetail`.
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/grpctest",
"plugins": {
"grpc-transcode": {
"proto_id": "1",
"service": "helloworld.Greeter",
"method": "GetErrResp",
"show_status_in_body": true,
"status_detail_type": "helloworld.ErrorDetail"
}
},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
}
}
}'
```
The fully decoded result is returned.
```Shell
HTTP/1.1 503 Service Temporarily Unavailable
Date: Wed, 10 Aug 2022 09:02:46 GMT
Content-Type: application/json
Transfer-Encoding: chunked
Connection: keep-alive
grpc-status: 14
grpc-message: Out of service
grpc-status-details-bin: CA4SDk91dCBvZiBzZXJ2aWNlGlcKKnR5cGUuZ29vZ2xlYXBpcy5jb20vaGVsbG93b3JsZC5FcnJvckRldGFpbBIpCAESHFRoZSBzZXJ2ZXIgaXMgb3V0IG9mIHNlcnZpY2UaB3NlcnZpY2U
Server: APISIX web server
{"error":{"details":[{"type":"service","message":"The server is out of service","code":1}],"message":"Out of service","code":14}}
```
## Delete Plugin
To remove the `grpc-transcode` 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/111 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/grpctest",
"plugins": {},
"upstream": {
"scheme": "grpc",
"type": "roundrobin",
"nodes": {
"127.0.0.1:50051": 1
}
}
}'
```

View File

@@ -0,0 +1,110 @@
---
title: grpc-web
keywords:
- Apache APISIX
- API Gateway
- Plugin
- gRPC Web
- grpc-web
description: This document contains information about the Apache APISIX grpc-web Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `grpc-web` Plugin is a proxy Plugin that can process [gRPC Web](https://github.com/grpc/grpc-web) requests from JavaScript clients to a gRPC service.
## Attributes
| Name | Type | Required | Default | Description |
|-------------------------|---------|----------|-----------------------------------------|----------------------------------------------------------------------------------------------------------|
| cors_allow_headers | string | False | "content-type,x-grpc-web,x-user-agent" | Headers in the request allowed when accessing a cross-origin resource. Use `,` to add multiple headers. |
## Enable Plugin
You can enable the `grpc-web` Plugin on a specific Route as shown below:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri":"/grpc/web/*",
"plugins":{
"grpc-web":{}
},
"upstream":{
"scheme":"grpc",
"type":"roundrobin",
"nodes":{
"127.0.0.1:1980":1
}
}
}'
```
:::info IMPORTANT
While using the `grpc-web` Plugin, always use a prefix matching pattern (`/*`, `/grpc/example/*`) for matching Routes. This is because the gRPC Web client passes the package name, the service interface name, the method name and other information in the proto in the URI. For example, `/path/a6.RouteService/Insert`.
So, when absolute matching is used, the Plugin would not be hit and the information from the proto would not be extracted.
:::
## Example usage
Refer to [gRPC-Web Client Runtime Library](https://www.npmjs.com/package/grpc-web) or [Apache APISIX gRPC Web Test Framework](https://github.com/apache/apisix/tree/master/t/plugin/grpc-web) to learn how to setup your web client.
Once you have your gRPC Web client running, you can make a request to APISIX from the browser or through Node.js.
:::note
The supported request methods are `POST` and `OPTIONS`. See [CORS support](https://github.com/grpc/grpc-web/blob/master/doc/browser-features.md#cors-support).
The supported `Content-Type` includes `application/grpc-web`, `application/grpc-web-text`, `application/grpc-web+proto`, and `application/grpc-web-text+proto`. See [Protocol differences vs gRPC over HTTP2](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-WEB.md#protocol-differences-vs-grpc-over-http2).
:::
## Delete Plugin
To remove the `grpc-web` 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 '
{
"uri":"/grpc/web/*",
"plugins":{},
"upstream":{
"scheme":"grpc",
"type":"roundrobin",
"nodes":{
"127.0.0.1:1980":1
}
}
}'
```

View File

@@ -0,0 +1,123 @@
---
title: gzip
keywords:
- Apache APISIX
- API Gateway
- Plugin
- gzip
description: This document contains information about the Apache APISIX gzip Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `gzip` Plugin dynamically sets the behavior of [gzip in Nginx](https://docs.nginx.com/nginx/admin-guide/web-server/compression/).
When the `gzip` plugin is enabled, the client needs to include `Accept-Encoding: gzip` in the request header to indicate support for gzip compression. Upon receiving the request, APISIX dynamically determines whether to compress the response content based on the client's support and server configuration. If the conditions are met, `APISIX` adds the `Content-Encoding: gzip` header to the response, indicating that the response content has been compressed using gzip. Upon receiving the response, the client uses the corresponding decompression algorithm based on the `Content-Encoding` header to decompress the response content and obtain the original response content.
:::info IMPORTANT
This Plugin requires APISIX to run on [APISIX-Runtime](../FAQ.md#how-do-i-build-the-apisix-runtime-environment).
:::
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|----------------|----------------------|----------|---------------|--------------|-----------------------------------------------------------------------------------------|
| types | array[string] or "*" | False | ["text/html"] | | Dynamically sets the `gzip_types` directive. Special value `"*"` matches any MIME type. |
| 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 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
The example below enables the `gzip` Plugin on the specified Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/index.html",
"plugins": {
"gzip": {
"buffers": {
"number": 8
}
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
## Example usage
Once you have configured the Plugin as shown above, you can make a request as shown below:
```shell
curl http://127.0.0.1:9080/index.html -i -H "Accept-Encoding: gzip"
```
```
HTTP/1.1 404 Not Found
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Date: Wed, 21 Jul 2021 03:52:55 GMT
Server: APISIX/2.7
Content-Encoding: gzip
Warning: Binary output can mess up your terminal. Use "--output -" to tell
Warning: curl to output it to your terminal anyway, or consider "--output
Warning: <FILE>" to save to a file.
```
## Delete Plugin
To remove the `gzip` 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 '
{
"uri": "/index.html",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,760 @@
---
title: hmac-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- HMAC Authentication
- hmac-auth
description: The hmac-auth Plugin supports HMAC authentication to ensure request integrity, preventing modifications during transmission and enhancing API security.
---
<!--
#
# 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.
#
-->
## Description
The `hmac-auth` Plugin supports HMAC (Hash-based Message Authentication Code) authentication as a mechanism to ensure the integrity of requests, preventing them from being modified during transmissions. To use the Plugin, you would configure HMAC secret keys on [Consumers](../terminology/consumer.md) and enable the Plugin on Routes or Services.
When a Consumer is successfully authenticated, APISIX adds additional headers, such as `X-Consumer-Username`, `X-Credential-Indentifier`, and other Consumer custom headers if configured, to the request, before proxying it to the Upstream service. The Upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.
Once enabled, the Plugin verifies the HMAC signature in the request's `Authorization` header and check that incoming requests are from trusted sources. Specifically, when APISIX receives an HMAC-signed request, the key ID is extracted from the `Authorization` header. APISIX then retrieves the corresponding Consumer configuration, including the secret key. If the key ID is valid and exists, APISIX generates an HMAC signature using the request's `Date` header and the secret key. If this generated signature matches the signature provided in the `Authorization` header, the request is authenticated and forwarded to Upstream services.
The Plugin implementation is based on [draft-cavage-http-signatures](https://www.ietf.org/archive/id/draft-cavage-http-signatures-12.txt).
## Attributes
The following attributes are available for configurations on Consumers or Credentials.
| Name | Type | Required | Default | Valid values | Description |
|-----------------------|---------------|----------|---------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| key_id | string | True | | | Unique identifier for the Consumer, which identifies the associated configurations such as the secret key. |
| secret_key | string | True | | | Secret key used to generate an HMAC. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. |
The following attributes are available for configurations on Routes or Services.
| Name | Type | Required | Default | Valid values | Description |
|-----------------------|---------------|----------|---------------|---------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| allowed_algorithms | array[string] | False | ["hmac-sha1","hmac-sha256","hmac-sha512"] | combination of "hmac-sha1","hmac-sha256",and "hmac-sha512" | The list of HMAC algorithms allowed. |
| clock_skew | integer | False | 300 | >=1 | Maximum allowable time difference in seconds between the client request's timestamp and APISIX server's current time. This helps account for discrepancies in time synchronization between the clients and servers clocks and protect against replay attacks. The timestamp in the Date header (must be in GMT format) will be used for the calculation. |
| signed_headers | array[string] | False | | | The list of HMAC-signed headers that should be included in the client request's HMAC signature. |
| validate_request_body | boolean | False | false | | If true, validate the integrity of the request body to ensure it has not been tampered with during transmission. Specifically, the Plugin creates a SHA-256 base64-encoded digest and compare it to the `Digest` header. If the Digest` header is missing or if the digests do not match, the validation fails. |
| hide_credentials | boolean | False | false | | If true, do not pass the authorization request header to Upstream services. |
| anonymous_consumer | string | False | | | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
NOTE: `encrypt_fields = {"secret_key"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
## Examples
The examples below demonstrate how you can work with the `hmac-auth` Plugin for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Implement HMAC Authentication on a Route
The following example demonstrates how to implement HMAC authentications on a route. You will also attach a Consumer custom ID to authenticated request in the `Consumer-Custom-Id` header, which can be used to implement additional logics as needed.
Create a Consumer `john` with a custom ID label:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john",
"labels": {
"custom_id": "495aec6a"
}
}'
```
Create `hmac-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
```
Create a Route with the `hmac-auth` Plugin using its default configurations:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Generate a signature. You can use the below Python snippet or other stack of your choice:
```python title="hmac-sig-header-gen.py"
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # Route URI
algorithm= "hmac-sha256" # can use other algorithms in allowed_algorithms
# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# construct the request headers
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="{algorithm}",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# print headers
print(headers)
```
Run the script:
```shell
python3 hmac-sig-header-gen.py
```
You should see the request headers printed:
```text
{'Date': 'Fri, 06 Sep 2024 06:41:29 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'}
```
Using the headers generated, send a request to the route:
```shell
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 06:41:29 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date\",signature=\"wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM=\"",
"Date": "Fri, 06 Sep 2024 06:41:29 GMT",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Consumer-Custom-Id": "495aec6a",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/get"
}
```
### Hide Authorization Information From Upstream
As seen the in the [last example](#implement-hmac-authentication-on-a-route), the `Authorization` header passed to the Upstream includes the signature and all other details. This could potentially introduce security risks.
The following example demonstrates how to prevent these information from being sent to the Upstream service.
Update the Plugin configuration to set `hide_credentials` to `true`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/hmac-auth-route" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"hmac-auth": {
"hide_credentials": true
}
}
}'
```
Send a request to the route:
```shell
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 06:41:29 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="wWfKQvPDr0wHQ4IHdluB4IzeNZcj0bGJs2wvoCOT5rM="'
```
You should see an `HTTP/1.1 200 OK` response and notice the `Authorization` header is entirely removed:
```json
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d96513-2e52d4f35c9b6a2772d667ea",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/get"
}
```
### Enable Body Validation
The following example demonstrates how to enable body validation to ensure the integrity of the request body.
Create a Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john"
}'
```
Create `hmac-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
```
Create a Route with the `hmac-auth` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "hmac-auth-route",
"uri": "/post",
"methods": ["POST"],
"plugins": {
"hmac-auth": {
"validate_request_body": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Generate a signature. You can use the below Python snippet or other stack of your choice:
```python title="hmac-sig-digest-header-gen.py"
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "POST" # HTTP method
request_path = "/post" # Route URI
algorithm= "hmac-sha256" # can use other algorithms in allowed_algorithms
body = '{"name": "world"}' # example request body
# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s).
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# create the SHA-256 digest of the request body and base64 encode it
body_digest = hashlib.sha256(body.encode('utf-8')).digest()
body_digest_base64 = base64.b64encode(body_digest).decode('utf-8')
# construct the request headers
headers = {
"Date": gmt_time,
"Digest": f"SHA-256={body_digest_base64}",
"Authorization": (
f'Signature keyId="{key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# print headers
print(headers)
```
Run the script:
```shell
python3 hmac-sig-digest-header-gen.py
```
You should see the request headers printed:
```text
{'Date': 'Fri, 06 Sep 2024 09:16:16 GMT', 'Digest': 'SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="'}
```
Using the headers generated, send a request to the route:
```shell
curl "http://127.0.0.1:9080/post" -X POST \
-H "Date: Fri, 06 Sep 2024 09:16:16 GMT" \
-H "Digest: SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="' \
-d '{"name": "world"}'
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"name\": \"world\"}": ""
},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date\",signature=\"rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE=\"",
"Content-Length": "17",
"Content-Type": "application/x-www-form-urlencoded",
"Date": "Fri, 06 Sep 2024 09:16:16 GMT",
"Digest": "SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d978c3-49f929ad5237da5340bbbeb4",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"origin": "192.168.65.1, 34.0.34.160",
"url": "http://127.0.0.1/post"
}
```
If you send a request without the digest or with an invalid digest:
```shell
curl "http://127.0.0.1:9080/post" -X POST \
-H "Date: Fri, 06 Sep 2024 09:16:16 GMT" \
-H "Digest: SHA-256=78qzJuLwSpZ8HacsTdFCQJWxzPMOf8bYctRk2ySLpS8=" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="rjS6NxOBKmzS8CZL05uLiAfE16hXdIpMD/L/HukOTYE="' \
-d '{"name": "world"}'
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following message:
```text
{"message":"client request can't be validated"}
```
### Mandate Signed Headers
The following example demonstrates how you can mandate certain headers to be signed in the request's HMAC signature.
Create a Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john"
}'
```
Create `hmac-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
```
Create a Route with the `hmac-auth` Plugin which requires three headers to be present in the HMAC signature:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {
"signed_headers": ["date","x-custom-header-a","x-custom-header-b"]
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Generate a signature. You can use the below Python snippet or other stack of your choice:
```python title="hmac-sig-req-header-gen.py"
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # Route URI
algorithm= "hmac-sha256" # can use other algorithms in allowed_algorithms
custom_header_a = "hello123" # required custom header
custom_header_b = "world456" # required custom header
# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
f"x-custom-header-a: {custom_header_a}\n"
f"x-custom-header-b: {custom_header_b}\n"
)
# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# construct the request headers
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="hmac-sha256",'
f'headers="@request-target date x-custom-header-a x-custom-header-b",'
f'signature="{signature_base64}"'
),
"x-custom-header-a": custom_header_a,
"x-custom-header-b": custom_header_b
}
# print headers
print(headers)
```
Run the script:
```shell
python3 hmac-sig-req-header-gen.py
```
You should see the request headers printed:
```text
{'Date': 'Fri, 06 Sep 2024 09:58:49 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE="', 'x-custom-header-a': 'hello123', 'x-custom-header-b': 'world456'}
```
Using the headers generated, send a request to the route:
```shell
curl -X GET "http://127.0.0.1:9080/get" \
-H "Date: Fri, 06 Sep 2024 09:58:49 GMT" \
-H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date x-custom-header-a x-custom-header-b",signature="MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE="' \
-H "x-custom-header-a: hello123" \
-H "x-custom-header-b: world456"
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "Signature keyId=\"john-key\",algorithm=\"hmac-sha256\",headers=\"@request-target date x-custom-header-a x-custom-header-b\",signature=\"MwJR8JOhhRLIyaHlJ3Snbrf5hv0XwdeeRiijvX3A3yE=\"",
"Date": "Fri, 06 Sep 2024 09:58:49 GMT",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66d98196-64a58db25ece71c077999ecd",
"X-Consumer-Username": "john",
"X-Credential-Identifier": "cred-john-hmac-auth",
"X-Custom-Header-A": "hello123",
"X-Custom-Header-B": "world456",
"X-Forwarded-Host": "127.0.0.1"
},
"origin": "192.168.65.1, 103.97.2.206",
"url": "http://127.0.0.1/get"
}
```
### Rate Limit with Anonymous Consumer
The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous Consumer does not need to authenticate and has less quotas.
Create a regular Consumer `john` and configure the `limit-count` Plugin to allow for a quota of 3 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create the `hmac-auth` Credential for the Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-hmac-auth",
"plugins": {
"hmac-auth": {
"key_id": "john-key",
"secret_key": "john-secret-key"
}
}
}'
```
Create an anonymous user `anonymous` and configure the `limit-count` Plugin to allow for a quota of 1 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create a Route and configure the `hmac-auth` Plugin to accept anonymous Consumer `anonymous` from bypassing the authentication:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "hmac-auth-route",
"uri": "/get",
"methods": ["GET"],
"plugins": {
"hmac-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Generate a signature. You can use the below Python snippet or other stack of your choice:
```python title="hmac-sig-header-gen.py"
import hmac
import hashlib
import base64
from datetime import datetime, timezone
key_id = "john-key" # key id
secret_key = b"john-secret-key" # secret key
request_method = "GET" # HTTP method
request_path = "/get" # Route URI
algorithm= "hmac-sha256" # can use other algorithms in allowed_algorithms
# get current datetime in GMT
# note: the signature will become invalid after the clock skew (default 300s)
# you can regenerate the signature after it becomes invalid, or increase the clock
# skew to prolong the validity within the advised security boundary
gmt_time = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
# construct the signing string (ordered)
# the date and any subsequent custom headers should be lowercased and separated by a
# single space character, i.e. `<key>:<space><value>`
# https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures-12#section-2.1.6
signing_string = (
f"{key_id}\n"
f"{request_method} {request_path}\n"
f"date: {gmt_time}\n"
)
# create signature
signature = hmac.new(secret_key, signing_string.encode('utf-8'), hashlib.sha256).digest()
signature_base64 = base64.b64encode(signature).decode('utf-8')
# construct the request headers
headers = {
"Date": gmt_time,
"Authorization": (
f'Signature keyId="{key_id}",algorithm="{algorithm}",'
f'headers="@request-target date",'
f'signature="{signature_base64}"'
)
}
# print headers
print(headers)
```
Run the script:
```shell
python3 hmac-sig-header-gen.py
```
You should see the request headers printed:
```text
{'Date': 'Mon, 21 Oct 2024 17:31:18 GMT', 'Authorization': 'Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="ztFfl9w7LmCrIuPjRC/DWSF4gN6Bt8dBBz4y+u1pzt8="'}
```
To verify, send five consecutive requests with the generated headers:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H "Date: Mon, 21 Oct 2024 17:31:18 GMT" -H 'Authorization: Signature keyId="john-key",algorithm="hmac-sha256",headers="@request-target date",signature="ztFfl9w7LmCrIuPjRC/DWSF4gN6Bt8dBBz4y+u1pzt8="' -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
```text
200: 3, 429: 2
```
Send five anonymous requests:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that only one request was successful:
```text
200: 1, 429: 4
```

View File

@@ -0,0 +1,128 @@
---
title: http-dubbo
keywords:
- Apache APISIX
- API Gateway
- Plugin
- http-dubbo
- http to dubbo
- transcode
description: This document contains information about the Apache APISIX http-dubbo Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `http-dubbo` plugin can transcode between http and Dubbo (Note: in
Dubbo 2.x, the serialization type of the upstream service must be fastjson).
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|--------------------------|---------|----------|---------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| service_name | string | True | | | Dubbo service name |
| service_version | string | False | 0.0.0 | | Dubbo service version |
| method | string | True | | | Dubbo service method name |
| params_type_desc | string | True | | | Description of the Dubbo service method signature |
| serialization_header_key | string | False | | | If `serialization_header_key` is set, the plugin will read this request header to determine if the body has already been serialized according to the Dubbo protocol. If the value of this request header is true, the plugin will not modify the body content and will directly consider it as Dubbo request parameters. If it is false, the developer is required to pass parameters in the format of Dubbo's generic invocation, and the plugin will handle serialization. Note: Due to differences in precision between Lua and Java, serialization by the plugin may lead to parameter precision discrepancies. |
| serialized | boolean | False | false | [true, false] | Same as `serialization_header_key`. Priority is lower than `serialization_header_key`. |
| connect_timeout | number | False | 6000 | | Upstream tcp connect timeout |
| read_timeout | number | False | 6000 | | Upstream tcp read_timeout |
| send_timeout | number | False | 6000 | | Upstream tcp send_timeout |
## Enable Plugin
The example below enables the `http-dubbo` Plugin on the specified Route:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/TestService/testMethod",
"plugins": {
"http-dubbo": {
"method": "testMethod",
"params_type_desc": "Ljava/lang/Long;Ljava/lang/Integer;",
"serialized": true,
"service_name": "com.xxx.xxx.TestService",
"service_version": "0.0.0"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:20880": 1
}
}
}'
```
## Example usage
Once you have configured the Plugin as shown above, you can make a request as shown below:
```shell
curl --location 'http://127.0.0.1:9080/TestService/testMethod' \
--data '1
2'
```
## How to Get `params_type_desc`
```java
Method[] declaredMethods = YourService.class.getDeclaredMethods();
String params_type_desc = ReflectUtils.getDesc(Arrays.stream(declaredMethods).filter(it -> it.getName().equals("yourmethod")).findAny().get().getParameterTypes());
// If there are method overloads, you need to find the method you want to expose.
// ReflectUtils is a Dubbo implementation.
```
## How to Serialize JSON According to Dubbo Protocol
To prevent loss of precision, we recommend using pre-serialized bodies for requests. The serialization rules for Dubbo's
fastjson are as follows:
- Convert each parameter to a JSON string using toJSONString.
- Separate each parameter with a newline character `\n`.
Some languages and libraries may produce unchanged results when calling toJSONString on strings or numbers. In such
cases, you may need to manually handle some special cases. For example:
- The string `abc"` needs to be encoded as `"abc\""`.
- The string `123` needs to be encoded as `"123"`.
Abstract class, parent class, or generic type as input parameter signature, when the input parameter requires a specific
type. Serialization requires writing specific type information.
Refer to [WriteClassName](https://github.com/alibaba/fastjson/wiki/SerializerFeature_cn) for more details.
## Delete Plugin
To remove the `http-dubbo` 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.

View File

@@ -0,0 +1,194 @@
---
title: http-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- HTTP Logger
description: This document contains information about the Apache APISIX http-logger Plugin. Using this Plugin, you can push APISIX log data to HTTP or HTTPS servers.
---
<!--
#
# 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.
#
-->
## Description
The `http-logger` Plugin is used to push log data requests to HTTP/HTTPS servers.
This will allow the ability to send log data requests as JSON objects to monitoring tools and other HTTP servers.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ---------------------- | ------- | -------- | ------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| uri | string | True | | | URI of the HTTP/HTTPS server. |
| auth_header | string | False | | | Authorization headers if required. |
| timeout | integer | False | 3 | [1,...] | Time to keep the connection alive for after sending a request. |
| 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. |
| 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 | | | When the `include_resp_body` attribute is set to `true`, use this to filter based on [lua-resty-expr](https://github.com/api7/lua-resty-expr). If present, only logs the response if the expression evaluates to `true`. |
| concat_method | string | False | "json" | ["json", "new_line"] | Sets how to concatenate logs. When set to `json`, uses `json.encode` for all pending logs and when set to `new_line`, also uses `json.encode` but uses the newline (`\n`) to concatenate lines. |
| ssl_verify | boolean | False | false | [false, true] | When set to `true` verifies the SSL certificate. |
:::note
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
:::
### Example of default log format
```json
{
"service_id": "",
"apisix_latency": 100.99999809265,
"start_time": 1703907485819,
"latency": 101.99999809265,
"upstream_latency": 1,
"client_ip": "127.0.0.1",
"route_id": "1",
"server": {
"version": "3.7.0",
"hostname": "localhost"
},
"request": {
"headers": {
"host": "127.0.0.1:1984",
"content-type": "application/x-www-form-urlencoded",
"user-agent": "lua-resty-http/0.16.1 (Lua) ngx_lua/10025",
"content-length": "12"
},
"method": "POST",
"size": 194,
"url": "http://127.0.0.1:1984/hello?log_body=no",
"uri": "/hello?log_body=no",
"querystring": {
"log_body": "no"
}
},
"response": {
"headers": {
"content-type": "text/plain",
"connection": "close",
"content-length": "12",
"server": "APISIX/3.7.0"
},
"status": 200,
"size": 123
},
"upstream": "127.0.0.1:1982"
}
```
## Metadata
You can also set the format of the logs by configuring the Plugin metadata. The following configurations are available:
| Name | Type | Required | Default | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 `$`. |
:::info IMPORTANT
Configuring the Plugin metadata is global in scope. This means that it will take effect on all Routes and Services which use the `http-logger` Plugin.
:::
The example below shows how you can configure through the Admin API:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/http-logger \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}'
```
With this configuration, your logs would be formatted as shown below:
```shell
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
```
## Enable Plugin
The example below shows how you can enable the Plugin on a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 \
-H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"http-logger": {
"uri": "http://mockbin.org/bin/:ID"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
},
"uri": "/hello"
}'
```
As an example the [mockbin](http://mockbin.org/bin/create) server is used for mocking an HTTP server to see the logs produced by APISIX.
## Example usage
Now, if you make a request to APISIX, it will be logged in your mockbin server:
```shell
curl -i http://127.0.0.1:9080/hello
```
## Delete Plugin
To disable this 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 '
{
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,188 @@
---
title: inspect
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Inspect
- Dynamic Lua Debugging
description: This document contains information about the Apache APISIX inspect Plugin.
---
<!--
#
# 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.
#
-->
## Description
It's useful to set arbitrary breakpoint in any Lua file to inspect the context information,
e.g. print local variables if some condition satisfied.
In this way, you don't need to modify the source code of your project, and just get diagnose information
on demand, i.e. dynamic logging.
This plugin supports setting breakpoints within both interpretd function and jit compiled function.
The breakpoint could be at any position within the function. The function could be global/local/module/ananymous.
## Features
* Set breakpoint at any position
* Dynamic breakpoint
* customized breakpoint handler
* You could define one-shot breakpoint
* Work for jit compiled function
* If function reference specified, then performance impact is only bound to that function (JIT compiled code will not trigger debug hook, so they would run fast even if hook is enabled)
* If all breakpoints deleted, jit could recover
## Operation Graph
![Operation Graph](https://raw.githubusercontent.com/apache/apisix/master/docs/assets/images/plugin/inspect.png)
## API to define hook in hooks file
### require("apisix.inspect.dbg").set_hook(file, line, func, filter_func)
The breakpoint is specified by `file` (full qualified or short file name) and the `line` number.
The `func` specified the scope (which function or global) of jit cache to flush:
* If the breakpoint is related to a module function or
global function, you should set it that function reference, then only the jit cache of that function would
be flushed, and it would not affect other caches to avoid slowing down other parts of the program.
* If the breakpointis related to local function or anonymous function,
then you have to set it to `nil` (because no way to get function reference), which would flush the whole jit cache of Lua vm.
You attach a `filter_func` function of the breakpoint, the function takes the `info` as argument and returns
true of false to determine whether the breakpoint would be removed. You could setup one-shot breakpoint
at ease.
The `info` is a hash table which contains below keys:
* `finfo`: `debug.getinfo(level, "nSlf")`
* `uv`: upvalues hash table
* `vals`: local variables hash table
## Attributes
| Name | Type | Required | Default | Description |
|--------------------|---------|----------|---------|------------------------------------------------------------------------------------------------|
| delay | integer | False | 3 | Time in seconds specifying how often to check the hooks file. |
| hooks_file | string | False | "/usr/local/apisix/plugin_inspect_hooks.lua" | Lua file to define hooks, which could be a link file. Ensure only administrator could write this file, otherwise it may be a security risk. |
## Enable Plugin
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
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```bash
# create test route
curl http://127.0.0.1:9180/apisix/admin/routes/test_limit_req -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/get",
"plugins": {
"limit-req": {
"rate": 100,
"burst": 0,
"rejected_code": 503,
"key_type": "var",
"key": "remote_addr"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org": 1
}
}
}'
# create a hooks file to set a test breakpoint
# Note that the breakpoint is associated with the line number,
# so if the Lua code changes, you need to adjust the line number in the hooks file
cat <<EOF >/usr/local/apisix/example_hooks.lua
local dbg = require "apisix.inspect.dbg"
dbg.set_hook("limit-req.lua", 88, require("apisix.plugins.limit-req").access, function(info)
ngx.log(ngx.INFO, debug.traceback("foo traceback", 3))
ngx.log(ngx.INFO, dbg.getname(info.finfo))
ngx.log(ngx.INFO, "conf_key=", info.vals.conf_key)
return true
end)
--- more breakpoints could be defined via dbg.set_hook()
--- ...
EOF
# enable the hooks file
ln -sf /usr/local/apisix/example_hooks.lua /usr/local/apisix/plugin_inspect_hooks.lua
# check errors.log to confirm the test breakpoint is enabled
2022/09/01 00:55:38 [info] 2754534#2754534: *3700 [lua] init.lua:29: setup_hooks(): set hooks: err=nil, hooks=["limit-req.lua#88"], context: ngx.timer
# access the test route
curl -i http://127.0.0.1:9080/get
# check errors.log to confirm the test breakpoint is triggered
2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:4: foo traceback
stack traceback:
/opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:50: in function </opt/lua-resty-inspect/lib/resty/inspect/dbg.lua:17>
/opt/apisix.fork/apisix/plugins/limit-req.lua:88: in function 'phase_func'
/opt/apisix.fork/apisix/plugin.lua:900: in function 'run_plugin'
/opt/apisix.fork/apisix/init.lua:456: in function 'http_access_phase'
access_by_lua(nginx.conf:303):2: in main chunk, client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080"
2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:5: /opt/apisix.fork/apisix/plugins/limit-req.lua:88 (phase_func), client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080"
2022/09/01 00:55:52 [info] 2754534#2754534: *4070 [lua] resty_inspect_hooks.lua:6: conf_key=remote_addr, client: 127.0.0.1, server: _, request: "GET /get HTTP/1.1", host: "127.0.0.1:9080"
```
## Delete Plugin
To remove the `inspect` Plugin, you can remove it from your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
# - inspect
```

View File

@@ -0,0 +1,154 @@
---
title: ip-restriction
keywords:
- Apache APISIX
- API Gateway
- Plugin
- IP restriction
- ip-restriction
description: The ip-restriction Plugin supports restricting access to upstream resources by IP addresses, through either configuring a whitelist or blacklist of IP addresses.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/ip-restriction" />
</head>
## Description
The `ip-restriction` Plugin supports restricting access to upstream resources by IP addresses, through either configuring a whitelist or blacklist of IP addresses. Restricting IP to resources helps prevent unauthorized access and harden API security.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|---------------|---------------|----------|----------------------------------|--------------|------------------------------------------------------------------------|
| whitelist | array[string] | False | | | List of IPs or CIDR ranges to whitelist. |
| blacklist | array[string] | False | | | List of IPs or CIDR ranges to blacklist. |
| message | string | False | "Your IP address is not allowed" | [1, 1024] | Message returned when the IP address is not allowed access. |
| response_code | integer | False | 403 | [403, 404] | HTTP response code returned when the IP address is not allowed access. |
:::note
At least one of the `whitelist` or `blacklist` should be configured, but they cannot be configured at the same time.
:::
## Examples
The examples below demonstrate how you can configure the `ip-restriction` Plugin for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Restrict Access by Whitelisting
The following example demonstrates how you can whitelist a list of IP addresses that should have access to the upstream resource and customize the error message for access denial.
Create a Route with the `ip-restriction` Plugin to whitelist a range of IPs and customize the error message when the access is denied:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ip-restriction-route",
"uri": "/anything",
"plugins": {
"ip-restriction": {
"whitelist": [
"192.168.0.1/24"
],
"message": "Access denied"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to the Route:
```shell
curl -i "http://127.0.0.1:9080/anything"
```
If your IP is allowed, you should receive an `HTTP/1.1 200 OK` response. If not, you should receive an `HTTP/1.1 403 Forbidden` response with the following error message:
```text
{"message":"Access denied"}
```
### Restrict Access Using Modified IP
The following example demonstrates how you can modify the IP used for IP restriction, using the `real-ip` Plugin. This is particularly useful if APISIX is behind a reverse proxy and the real client IP is not available to APISIX.
Create a Route with the `ip-restriction` Plugin to whitelist a specific IP address and obtain client IP address from the URL parameter `realip`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "ip-restriction-route",
"uri": "/anything",
"plugins": {
"ip-restriction": {
"whitelist": [
"192.168.1.241"
]
},
"real-ip": {
"source": "arg_realip"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to the Route:
```shell
curl -i "http://127.0.0.1:9080/anything?realip=192.168.1.241"
```
You should receive an `HTTP/1.1 200 OK` response.
Send another request with a different IP address:
```shell
curl -i "http://127.0.0.1:9080/anything?realip=192.168.10.24"
```
You should receive an `HTTP/1.1 403 Forbidden` response.

View File

@@ -0,0 +1,198 @@
---
title: jwe-decrypt
keywords:
- Apache APISIX
- API Gateway
- Plugin
- JWE Decrypt
- jwe-decrypt
description: This document contains information about the Apache APISIX jwe-decrypt Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `jwe-decrypt` Plugin is used to decrypt [JWE](https://datatracker.ietf.org/doc/html/rfc7516) authorization headers in requests to an APISIX [Service](../terminology/service.md) or [Route](../terminology/route.md).
This Plugin adds an endpoint `/apisix/plugin/jwe/encrypt` for JWE encryption. For decryption, the key should be configured in [Consumer](../terminology/consumer.md).
## Attributes
For Consumer:
| Name | Type | Required | Default | Valid values | Description |
|---------------|---------|-------------------------------------------------------|---------|-----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------|
| key | string | True | | | Unique key for a Consumer. |
| secret | string | True | | | The decryption key. Must be 32 characters. The key could be saved in a secret manager using the [Secret](../terminology/secret.md) resource. |
| is_base64_encoded | boolean | False | false | | Set to true if the secret is base64 encoded. |
:::note
After enabling `is_base64_encoded`, your `secret` length may exceed 32 chars. You only need to make sure that the length after decoding is still 32 chars.
:::
For Route:
| Name | Type | Required | Default | Description |
|--------|--------|----------|---------------|---------------------------------------------------------------------|
| header | string | True | Authorization | The header to get the token from. |
| forward_header | string | True | Authorization | Set the header name that passes the plaintext to the Upstream. |
| strict | boolean | False | true | If true, throw a 403 error if JWE token is missing from the request. If false, do not throw an error if JWE token cannot be found. |
## Example usage
First, create a Consumer with `jwe-decrypt` and configure the decryption key:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X PUT -d '
{
"username": "jack",
"plugins": {
"jwe-decrypt": {
"key": "user-key",
"secret": "-secret-length-must-be-32-chars-"
}
}
}'
```
Next, create a Route with `jwe-decrypt` enabled to decrypt the authorization header:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/anything*",
"plugins": {
"jwe-decrypt": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
### Encrypt Data with JWE
The Plugin creates an internal endpoint `/apisix/plugin/jwe/encrypt` to encrypt data with JWE. To expose it publicly, create a Route with the [public-api](public-api.md) Plugin:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/jwenew -H "X-API-KEY: $admin_key" -X PUT -d '
{
"uri": "/apisix/plugin/jwe/encrypt",
"plugins": {
"public-api": {}
}
}'
```
Send a request to the endpoint passing the key configured in Consumer to the URI parameter to encrypt some sample data in the payload:
```shell
curl -G --data-urlencode 'payload={"uid":10000,"uname":"test"}' 'http://127.0.0.1:9080/apisix/plugin/jwe/encrypt?key=user-key' -i
```
You should see a response similar to the following, with the JWE encrypted data in the response body:
```
HTTP/1.1 200 OK
Date: Mon, 25 Sep 2023 02:38:16 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
Server: APISIX/3.5.0
Apisix-Plugins: public-api
eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.hfzMJ0YfmbMcJ0ojgv4PYAHxPjlgMivmv35MiA.7nilnBt2dxLR_O6kf-HQUA
```
### Decrypt Data with JWE
Send a request to the route with the JWE encrypted data in the `Authorization` header:
```shell
curl http://127.0.0.1:9080/anything/hello -H 'Authorization: eyJhbGciOiJkaXIiLCJraWQiOiJ1c2VyLWtleSIsImVuYyI6IkEyNTZHQ00ifQ..MTIzNDU2Nzg5MDEy.hfzMJ0YfmbMcJ0ojgv4PYAHxPjlgMivmv35MiA.7nilnBt2dxLR_O6kf-HQUA' -i
```
You should see a response similar to the following, where the `Authorization` header shows the plaintext of the payload:
```
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 452
Connection: keep-alive
Date: Mon, 25 Sep 2023 02:38:59 GMT
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Server: APISIX/3.5.0
Apisix-Plugins: jwe-decrypt
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "{\"uid\":10000,\"uname\":\"test\"}",
"Host": "127.0.0.1",
"User-Agent": "curl/8.1.2",
"X-Amzn-Trace-Id": "Root=1-6510f2c3-1586ec011a22b5094dbe1896",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "127.0.0.1, 119.143.79.94",
"url": "http://127.0.0.1/anything/hello"
}
```
## Delete Plugin
To remove the `jwe-decrypt` 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 '
{
"methods": ["GET"],
"uri": "/anything*",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```

View File

@@ -0,0 +1,911 @@
---
title: jwt-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- JWT Auth
- jwt-auth
description: The jwt-auth Plugin supports the use of JSON Web Token (JWT) as a mechanism for clients to authenticate themselves before accessing Upstream resources.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/jwt-auth" />
</head>
## Description
The `jwt-auth` Plugin supports the use of [JSON Web Token (JWT)](https://jwt.io/) as a mechanism for clients to authenticate themselves before accessing Upstream resources.
Once enabled, the Plugin exposes an endpoint to create JWT credentials by [Consumers](../terminology/consumer.md). The process generates a token that client requests should carry to identify themselves to APISIX. The token can be included in the request URL query string, request header, or cookie. APISIX will then verify the token to determine if a request should be allowed or denied to access Upstream resources.
When a Consumer is successfully authenticated, APISIX adds additional headers, such as `X-Consumer-Username`, `X-Credential-Indentifier`, and other Consumer custom headers if configured, to the request, before proxying it to the Upstream service. The Upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.
## Attributes
For Consumer/Credential:
| Name | Type | Required | Default | Valid values | Description |
|---------------|---------|-------------------------------------------------------|---------|-----------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| key | string | True | | non-empty | Unique key for a Consumer. |
| secret | string | False | | non-empty | Shared key used to sign and verify the JWT when the algorithm is symmetric. Required when using `HS256` or `HS512` as the algorithm. If unspecified, the secret will be auto-generated. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. |
| public_key | string | True if `RS256` or `ES256` is set for the `algorithm` attribute. | | | RSA or ECDSA public key. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. |
| algorithm | string | False | HS256 | ["HS256", "HS512", "RS256", "ES256"] | Encryption algorithm. |
| exp | integer | False | 86400 | [1,...] | Expiry time of the token in seconds. |
| base64_secret | boolean | False | false | | Set to true if the secret is base64 encoded. |
| lifetime_grace_period | integer | False | 0 | [0,...] | Grace period in seconds. Used to account for clock skew between the server generating the JWT and the server validating the JWT. |
| key_claim_name | string | False | key | | The claim in the JWT payload that identifies the associated secret, such as `iss`. |
NOTE: `encrypt_fields = {"secret"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
For Routes or Services:
| Name | Type | Required | Default | Description |
|--------|--------|----------|---------------|---------------------------------------------------------------------|
| header | string | False | authorization | The header to get the token from. |
| query | string | False | jwt | The query string to get the token from. Lower priority than header. |
| cookie | string | False | jwt | The cookie to get the token from. Lower priority than query. |
| hide_credentials| boolean | False | false | If true, do not pass the header, query, or cookie with JWT to Upstream services. |
| key_claim_name | string | False | key | The name of the JWT claim that contains the user key (corresponds to Consumer's key attribute). |
| anonymous_consumer | string | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
| store_in_ctx | boolean | False | false | Set to true will store the JWT payload in the request context (`ctx.jwt_auth_payload`). This allows lower-priority plugins that run afterwards on the same request to retrieve and use the JWT token. |
You can implement `jwt-auth` with [HashiCorp Vault](https://www.vaultproject.io/) to store and fetch secrets and RSA keys pairs from its [encrypted KV engine](https://developer.hashicorp.com/vault/docs/secrets/kv) using the [APISIX Secret](../terminology/secret.md) resource.
## Examples
The examples below demonstrate how you can work with the `jwt-auth` Plugin for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Use JWT for Consumer Authentication
The following example demonstrates how to implement JWT for Consumer key authentication.
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `jwt-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret"
}
}
}'
```
Create a Route with `jwt-auth` plugin:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To issue a JWT for `jack`, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `jack-hs256-secret`.
* Update payload with Consumer key `jack-key`; and add `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jack-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```text
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.0VDKUzNkSaa_H5g_rGNbNtDcKJ9fBGgcGC56AsVsV-I
```
Send a request to the Route with the JWT in the `Authorization` header:
```shell
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"headers": {
"Accept": "*/*",
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjY2NDk2NDAsImtleSI6ImphY2sta2V5In0.kdhumNWrZFxjUvYzWLt4lFr546PNsr9TXuf0Az5opoM",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea951a-4d740d724bd2a44f174d4daf",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth",
"X-Forwarded-Host": "127.0.0.1"
}
}
```
In 30 seconds, the token should expire. Send a request with the same token to verify:
```shell
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
```
You should receive an `HTTP/1.1 401 Unauthorized` response similar to the following:
```text
{"message":"failed to verify jwt"}
```
### Carry JWT in Request Header, Query String, or Cookie
The following example demonstrates how to accept JWT in specified header, query string, and cookie.
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `jwt-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret"
}
}
}'
```
Create a Route with `jwt-auth` Plugin, and specify that the request can either carry the token in the header, query, or the cookie:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {
"header": "jwt-auth-header",
"query": "jwt-query",
"cookie": "jwt-cookie"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To issue a JWT for `jack`, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `jack-hs256-secret`.
* Update payload with Consumer key `jack-key`; and add `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jack-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```text
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.0VDKUzNkSaa_H5g_rGNbNtDcKJ9fBGgcGC56AsVsV-I
```
#### Verify With JWT in Header
Sending request with JWT in the header:
```shell
curl -i "http://127.0.0.1:9080/get" -H "jwt-auth-header: ${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"Jwt-Auth-Header": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ",
...
},
...
}
```
#### Verify With JWT in Query String
Sending request with JWT in the query string:
```shell
curl -i "http://127.0.0.1:9080/get?jwt-query=${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {
"jwt-query": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ"
},
"headers": {
"Accept": "*/*",
...
},
"origin": "127.0.0.1, 183.17.233.107",
"url": "http://127.0.0.1/get?jwt-query=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ"
}
```
#### Verify With JWT in Cookie
Sending request with JWT in the cookie:
```shell
curl -i "http://127.0.0.1:9080/get" --cookie jwt-cookie=${jwt_token}
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {},
"headers": {
"Accept": "*/*",
"Cookie": "jwt-cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJrZXkiOiJ1c2VyLWtleSIsImV4cCI6MTY5NTEyOTA0NH0.EiktFX7di_tBbspbjmqDKoWAD9JG39Wo_CAQ1LZ9voQ",
...
},
...
}
```
### Manage Secrets in Environment Variables
The following example demonstrates how to save `jwt-auth` Consumer key to an environment variable and reference it in configuration.
APISIX supports referencing system and user environment variables configured through the [NGINX `env` directive](https://nginx.org/en/docs/ngx_core_module.html#env).
Save the key to an environment variable:
```shell
JACK_JWT_AUTH_KEY=jack-key
```
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `jwt-auth` Credential for the Consumer and reference the environment variable in the key:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "$env://JACK_JWT_AUTH_KEY",
"secret": "jack-hs256-secret"
}
}
}'
```
Create a Route with `jwt-auth` enabled:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To issue a JWT for `jack`, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `jack-hs256-secret`.
* Update payload with Consumer key `jack-key`; and add `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jack-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```text
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.0VDKUzNkSaa_H5g_rGNbNtDcKJ9fBGgcGC56AsVsV-I
```
Sending request with JWT in the header:
```shell
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2OTUxMzMxNTUsImtleSI6Imp3dC1rZXkifQ.jiKuaAJqHNSSQCjXRomwnQXmdkC5Wp5VDPRsJlh1WAQ",
...
},
...
}
```
### Manage Secrets in Secret Manager
The following example demonstrates how to manage `jwt-auth` Consumer key in [HashiCorp Vault](https://www.vaultproject.io) and reference it in Plugin configuration.
Start a Vault development server in Docker:
```shell
docker run -d \
--name vault \
-p 8200:8200 \
--cap-add IPC_LOCK \
-e VAULT_DEV_ROOT_TOKEN_ID=root \
-e VAULT_DEV_LISTEN_ADDRESS=0.0.0.0:8200 \
vault:1.9.0 \
vault server -dev
```
APISIX currently supports [Vault KV engine version 1](https://developer.hashicorp.com/vault/docs/secrets/kv#kv-version-1). Enable it in Vault:
```shell
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"
```
You should see a response similar to the following:
```text
Success! Enabled the kv secrets engine at: kv/
```
Create a secret and configure the Vault address and other connection information:
```shell
curl "http://127.0.0.1:9180/apisix/admin/secrets/vault/jwt" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"uri": "https://127.0.0.1:8200"
"prefix": "kv/apisix",
"token": "root"
}'
```
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `jwt-auth` Credential for the Consumer and reference the secret in the key:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "$secret://vault/jwt/jack/jwt-key",
"secret": "vault-hs256-secret"
}
}
}'
```
Create a Route with `jwt-auth` enabled:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-route",
"uri": "/get",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Set `jwt-auth` key value to be `jwt-vault-key` in Vault:
```shell
docker exec -i vault sh -c "VAULT_TOKEN='root' VAULT_ADDR='http://0.0.0.0:8200' vault kv put kv/apisix/jack jwt-key=jwt-vault-key"
```
You should see a response similar to the following:
```text
Success! Data written to: kv/apisix/jack
```
To issue a JWT, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `vault-hs256-secret`.
* Update payload with Consumer key `jwt-vault-key`; and add `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jwt-vault-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```text
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqd3QtdmF1bHQta2V5IiwibmJmIjoxNzI5MTMyMjcxfQ.faiN93LNP1lGSXqAb4empNJKMRWop8-KgnU58VQn1EE
```
Sending request with the token as header:
```shell
curl -i "http://127.0.0.1:9080/get" -H "Authorization: ${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```text
{
"args": {},
"headers": {
"Accept": "*/*",
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqd3QtdmF1bHQta2V5IiwiZXhwIjoxNjk1MTM4NjM1fQ.Au2liSZ8eQXUJR3SJESwNlIfqZdNyRyxIJK03L4dk_g",
...
},
...
}
```
### Sign JWT with RS256 Algorithm
The following example demonstrates how you can use asymmetric algorithms, such as RS256, to sign and validate JWT when implementing JWT for Consumer authentication. You will be generating RSA key pairs using [openssl](https://openssl-library.org/source/) and generating JWT using [JWT.io](https://jwt.io/#debugger-io) to better understand the composition of JWT.
Generate a 2048-bit RSA private key and extract the corresponding public key in PEM format:
```shell
openssl genrsa -out jwt-rsa256-private.pem 2048
openssl rsa -in jwt-rsa256-private.pem -pubout -out jwt-rsa256-public.pem
```
You should see `jwt-rsa256-private.pem` and `jwt-rsa256-public.pem` generated in your current working directory.
Visit [JWT.io's debugger](https://jwt.io/#debugger-io) and do the following:
* Select __RS256__ in the __Algorithm__ dropdown.
* Copy and paste the key content into the __Verify Signature__ section.
* Update the payload with `key` matching the Consumer key you would like to use; and `exp` or `nbf` in UNIX timestamp.
The configuration should look similar to the following:
<br />
<div style={{textAlign: 'center'}}>
<img
src="https://static.apiseven.com/uploads/2024/12/12/SRe7AXMw_jwt_token.png"
alt="complete configuration of JWT generation on jwt.io"
width="70%"
/>
</div>
<br />
Copy the JWT on the left and save to an environment variable:
```shell
jwt_token=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsImV4cCI6MTczNDIzMDQwMH0.XjqM0oszmCggwZs-8PUIlJv8wPJON1la2ET5v70E6TCE32Yq5ibrl-1azaK7IreAer3HtnVHeEfII2rR02v8xfR1TPIjU_oHov4qC-A4tLTbgqGVXI7fCy2WFm3PFh6MEKuRe6M3dCQtCAdkRRQrBr1gWFQZhV3TNeMmmtyIfuJpB7cp4DW5pYFsCcoE1Nw6Tz7dt8k0tPBTPI2Mv9AYfMJ30LHDscOaPNtz8YIk_TOkV9b9mhQudUJ7J_suCZMRxD3iL655jTp2gKsstGKdZa0_W9Reu4-HY3LSc5DS1XtfjuftpuUqgg9FvPU0mK_b0wT_Rq3lbYhcHb9GZ72qiQ
```
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `jwt-auth` Credential for the Consumer and configure the RSA keys:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"algorithm": "RS256",
"public_key": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnE0h4k/GWfEbYO/yE2MPjHtNKDLNz4mv1KNIPLxY2ccjPYOtjuug+iZ4MujLV59YfrHriTs0H8jweQfff3pRSMjyEK+4qWTY3TeKBXIEa3pVDeoedSJrgjLBVio6xH7et8ir+QScScfLaJHGB4/l3DDGyEhO782a9teY8brn5hsWX5uLmDJvxtTGAHYi847XOcx2UneW4tZ8wQ6JGBSiSg5qAHan4dFZ7CpixCNNqEcSK6EQ7lKOLeFGG8ys/dHBIEasU4oMlCuJH77+XQQ/shchy+vm9oZfP+grLZkV+nKAd8MQZsid7ZJ/fiB/BmnhGrjtIfh98jwxSx4DgdLhdwIDAQAB\n-----END PUBLIC KEY-----",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCcTSHiT8ZZ8Rtg7/ITYw+Me00oMs3Pia/Uo0g8vFjZxyM9g62O66D6Jngy6MtXn1h+seuJOzQfyPB5B99/elFIyPIQr7ipZNjdN4oFcgRrelUN6h51ImuCMsFWKjrEft63yKv5BJxJx8tokcYHj+XcMMbISE7vzZr215jxuufmGxZfm4uYMm/G1MYAdiLzjtc5zHZSd5bi1nzBDokYFKJKDmoAdqfh0VnsKmLEI02oRxIroRDuUo4t4UYbzKz90cEgRqxTigyUK4kfvv5dBD+yFyHL6+b2hl8/6CstmRX6coB3wxBmyJ3tkn9+IH8GaeEauO0h+H3yPDFLHgOB0uF3AgMBAAECggEARpY68Daw0Funzq5uN70r/3iLztSqx8hZpQEclXlF8wwQ6S33iqz1JSOMcwlZE7g9wfHd+jrHfndDypT4pVx7KxC86TZCghWuLrFvXqgwQM2dbcxGdwXVYZZEZAJsSeM19+/jYnFnl5ZoUVBMC4w79aX9j+O/6mKDUmjphHmxUuRCFjN0w7BRoYwmS796rSf1eoOcSXh2G9Ycc34DUFDfGpOzabndbmMfOz7W0DyUBG23fgLhNChTUGq8vMaqKXkQ8JKeKdEugSmRGz42HxjWoNlIGBDyB8tPNPT6SXsu/JBskdf9Gb71OWiub381oXC259sz+1K1REb1KSkgyC+bkQKBgQDKCnwXaf8aOIoJPCG53EqQfKScCIYQrvp1Uk3bs5tfYN4HcI3yAUnOqQ3Ux3eY9PfS37urlJXCfCbCnZ6P6xALZnN+aL2zWvZArlHvD6vnXiyevwK5IY+o2EW02h3A548wrGznQSsfX0tum22bEVlRuFfBbpZpizXwrV4ODSNhTwKBgQDGC27QQxah3yq6EbOhJJlJegjawVXEaEp/j4fD3qe/unLbUIFvCz6j9BAbgocDKzqXxlpTtIbnsesdLo7KM3MtYL0XO/87HIsBj9XCVgMkFCcM6YZ6fHnkJl0bs3haU4N9uI/wpokvfvXJp7iC9LUCseBdBj+N6T230HWiSbPjWQKBgQC8zzGKO/8vRNkSqkQmSczQ2/qE6p5G5w6eJy0lfOJdLswvDatJFpUf8PJA/6svoPYb9gOO5AtUNeuPAfeVLSnQTYzu+/kTrJTme0GMdAvE60gtjfmAgvGa64mw6gjWJk+1P92B+2/OIKMAmXXDbWIYMXqpBKzBs1vUMF/uJ68BlwKBgQDEivQem3YKj3/HyWmLstatpP7EmrqTgSzuC3OhX4b7L/5sySirG22/KKgTpSZ4bp5noeJiz/ZSWrAK9fmfkg/sKOV/+XsDHwCVPDnX86SKWbWnitp7FK2jTq94nlQC0H7edhvjqGLdUBJ9XoYu8MvzMLSJnXnVTHSDx832kU6FgQKBgQCbw4Eiu2IcOduIAokmsZl8Smh9ZeyhP2B/UBa1hsiPKQ6bw86QJr2OMbRXLBxtx+HYIfwDo4vXEE862PfoQyu6SjJBNmHiid7XcV06Z104UQNjP7IDLMMF+SASMqYoQWg/5chPfxBgIXnfWqw6TMmND3THY4Oj4Nhf4xeUg3HsaA==\n-----END PRIVATE KEY-----"
}
}
}'
```
:::tip
You should add a newline character after the opening line and before the closing line, for example `-----BEGIN PRIVATE KEY-----\n......\n-----END PRIVATE KEY-----`.
The key content can be directly concatenated.
:::
Create a Route with the `jwt-auth` Plugin:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-route",
"uri": "/headers",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send a request to the Route with the JWT in the `Authorization` header:
```shell
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
```
You should receive an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"headers": {
"Accept": "*/*",
"Authorization": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsImV4cCI6MTczNDIzMDQwMH0.XjqM0oszmCggwZs-8PUIlJv8wPJON1la2ET5v70E6TCE32Yq5ibrl-1azaK7IreAer3HtnVHeEfII2rR02v8xfR1TPIjU_oHov4qC-A4tLTbgqGVXI7fCy2WFm3PFh6MEKuRe6M3dCQtCAdkRRQrBr1gWFQZhV3TNeMmmtyIfuJpB7cp4DW5pYFsCcoE1Nw6Tz7dt8k0tPBTPI2Mv9AYfMJ30LHDscOaPNtz8YIk_TOkV9b9mhQudUJ7J_suCZMRxD3iL655jTp2gKsstGKdZa0_W9Reu4-HY3LSc5DS1XtfjuftpuUqgg9FvPU0mK_b0wT_Rq3lbYhcHb9GZ72qiQ",
...
}
}
```
### Add Consumer Custom ID to Header
The following example demonstrates how you can attach a Consumer custom ID to authenticated request in the `Consumer-Custom-Id` header, which can be used to implement additional logics as needed.
Create a Consumer `jack` with a custom ID label:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack",
"labels": {
"custom_id": "495aec6a"
}
}'
```
Create `jwt-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret"
}
}
}'
```
Create a Route with `jwt-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To issue a JWT for `jack`, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `jack-hs256-secret`.
* Update payload with Consumer key `jack-key`; and add `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jack-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```text
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.0VDKUzNkSaa_H5g_rGNbNtDcKJ9fBGgcGC56AsVsV-I
```
To verify, send a request to the Route with the JWT in the `Authorization` header:
```shell
curl -i "http://127.0.0.1:9080/headers" -H "Authorization: ${jwt_token}"
```
You should see an `HTTP/1.1 200 OK` response similar to the following, where `X-Consumer-Custom-Id` is attached:
```json
{
"headers": {
"Accept": "*/*",
"Authorization": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjY2NDk2NDAsImtleSI6ImphY2sta2V5In0.kdhumNWrZFxjUvYzWLt4lFr546PNsr9TXuf0Az5opoM",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea951a-4d740d724bd2a44f174d4daf",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-jwt-auth",
"X-Consumer-Custom-Id": "495aec6a",
"X-Forwarded-Host": "127.0.0.1"
}
}
```
### Rate Limit with Anonymous Consumer
The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous Consumer does not need to authenticate and has less quotas.
Create a regular Consumer `jack` and configure the `limit-count` Plugin to allow for a quota of 3 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create the `jwt-auth` Credential for the Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-jwt-auth",
"plugins": {
"jwt-auth": {
"key": "jack-key",
"secret": "jack-hs256-secret"
}
}
}'
```
Create an anonymous user `anonymous` and configure the `limit-count` Plugin to allow for a quota of 1 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create a Route and configure the `jwt-auth` Plugin to accept anonymous Consumer `anonymous` from bypassing the authentication:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "jwt-auth-route",
"uri": "/anything",
"plugins": {
"jwt-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To issue a JWT for `jack`, you could use [JWT.io's debugger](https://jwt.io/#debugger-io) or other utilities. If you are using [JWT.io's debugger](https://jwt.io/#debugger-io), do the following:
* Select __HS256__ in the __Algorithm__ dropdown.
* Update the secret in the __Verify Signature__ section to be `jack-hs256-secret`.
* Update payload with role `user`, permission `read`, and Consumer key `jack-key`; as well as `exp` or `nbf` in UNIX timestamp.
Your payload should look similar to the following:
```json
{
"key": "jack-key",
"nbf": 1729132271
}
```
Copy the generated JWT under the __Encoded__ section and save to a variable:
```shell
jwt_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJrZXkiOiJqYWNrLWtleSIsIm5iZiI6MTcyOTEzMjI3MX0.hjtSsEILpko14zb8-ibyxrB2tA5biYY9JrFm3do69vs
```
To verify the rate limiting, send five consecutive requests with `jack`'s JWT:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H "Authorization: ${jwt_token}" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
```text
200: 3, 429: 2
```
Send five anonymous requests:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that only one request was successful:
```text
200: 1, 429: 4
```

View File

@@ -0,0 +1,249 @@
---
title: kafka-logger
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Kafka Logger
description: This document contains information about the Apache APISIX kafka-logger Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `kafka-logger` Plugin is used to push logs as JSON objects to Apache Kafka clusters. It works as a Kafka client driver for the ngx_lua Nginx module.
It might take some time to receive the log data. It will be automatically sent after the timer function in the [batch processor](../batch-processor.md) expires.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ---------------------- | ------- | -------- | -------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| broker_list | object | True | | | Deprecated, use `brokers` instead. List of Kafka brokers. (nodes). |
| brokers | array | True | | | List of Kafka brokers (nodes). |
| brokers.host | string | True | | | The host of Kafka broker, e.g, `192.168.1.1`. |
| brokers.port | integer | True | | [0, 65535] | The port of Kafka broker |
| brokers.sasl_config | object | False | | | The sasl config of Kafka broker |
| brokers.sasl_config.mechanism | string | False | "PLAIN" | ["PLAIN"] | The mechaism of sasl config |
| brokers.sasl_config.user | string | True | | | The user of sasl_config. If sasl_config exists, it's required. |
| brokers.sasl_config.password | string | True | | | The password of sasl_config. If sasl_config exists, it's required. |
| kafka_topic | string | True | | | Target topic to push the logs for organisation. |
| producer_type | string | False | async | ["async", "sync"] | Message sending mode of the producer. |
| required_acks | integer | False | 1 | [1, -1] | Number of acknowledgements the leader needs to receive for the producer to consider the request complete. This controls the durability of the sent records. The attribute follows the same configuration as the Kafka `acks` attribute. `required_acks` cannot be 0. See [Apache Kafka documentation](https://kafka.apache.org/documentation/#producerconfigs_acks) for more. |
| key | string | False | | | Key used for allocating partitions for messages. |
| timeout | integer | False | 3 | [1,...] | Timeout for the upstream to send data. |
| name | string | False | "kafka logger" | | Unique identifier for the batch processor. If you use Prometheus to monitor APISIX metrics, the name is exported in `apisix_batch_process_entries`. |
| meta_format | enum | False | "default" | ["default""origin"] | Format to collect the request information. Setting to `default` collects the information in JSON format and `origin` collects the information with the original HTTP request. See [examples](#meta_format-example) below. |
| 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 | 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 | 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. |
| producer_max_buffering | integer | optional | 50000 | [1,...] | `max_buffering` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) representing maximum buffer size. Unit is message count. |
| producer_time_linger | integer | optional | 1 | [1,...] | `flush_time` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) in seconds. |
| meta_refresh_interval | integer | optional | 30 | [1,...] | `refresh_interval` parameter in [lua-resty-kafka](https://github.com/doujiang24/lua-resty-kafka) specifies the time to auto refresh the metadata, in seconds. |
This Plugin supports using batch processors to aggregate and process entries (logs/data) in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
:::info IMPORTANT
The data is first written to a buffer. When the buffer exceeds the `batch_max_size` or `buffer_duration` attribute, the data is sent to the Kafka server and the buffer is flushed.
If the process is successful, it will return `true` and if it fails, returns `nil` with a string with the "buffer overflow" error.
:::
### meta_format example
- `default`:
```json
{
"upstream": "127.0.0.1:1980",
"start_time": 1619414294760,
"client_ip": "127.0.0.1",
"service_id": "",
"route_id": "1",
"request": {
"querystring": {
"ab": "cd"
},
"size": 90,
"uri": "/hello?ab=cd",
"url": "http://localhost:1984/hello?ab=cd",
"headers": {
"host": "localhost",
"content-length": "6",
"connection": "close"
},
"body": "abcdef",
"method": "GET"
},
"response": {
"headers": {
"connection": "close",
"content-type": "text/plain; charset=utf-8",
"date": "Mon, 26 Apr 2021 05:18:14 GMT",
"server": "APISIX/2.5",
"transfer-encoding": "chunked"
},
"size": 190,
"status": 200
},
"server": {
"hostname": "localhost",
"version": "2.5"
},
"latency": 0
}
```
- `origin`:
```http
GET /hello?ab=cd HTTP/1.1
host: localhost
content-length: 6
connection: close
abcdef
```
## Metadata
You can also set the format of the logs by configuring the Plugin metadata. The following configurations are available:
| Name | Type | Required | Default | Description |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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 `$`. |
:::info IMPORTANT
Configuring the Plugin metadata is global in scope. This means that it will take effect on all Routes and Services which use the `kafka-logger` Plugin.
:::
The example below shows how you can configure through the Admin API:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/plugin_metadata/kafka-logger -H "X-API-KEY: $admin_key" -X PUT -d '
{
"log_format": {
"host": "$host",
"@timestamp": "$time_iso8601",
"client_ip": "$remote_addr"
}
}'
```
With this configuration, your logs would be formatted as shown below:
```shell
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
{"host":"localhost","@timestamp":"2020-09-23T19:05:05-04:00","client_ip":"127.0.0.1","route_id":"1"}
```
## Enable Plugin
The example below shows how you can enable the `kafka-logger` Plugin on a specific Route:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/5 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"plugins": {
"kafka-logger": {
"brokers" : [
{
"host" :"127.0.0.1",
"port" : 9092
}
],
"kafka_topic" : "test2",
"key" : "key1",
"batch_max_size": 1,
"name": "kafka logger"
}
},
"upstream": {
"nodes": {
"127.0.0.1:1980": 1
},
"type": "roundrobin"
},
"uri": "/hello"
}'
```
This Plugin also supports pushing to more than one broker at a time. You can specify multiple brokers in the Plugin configuration as shown below:
```json
"brokers" : [
{
"host" :"127.0.0.1",
"port" : 9092
},
{
"host" :"127.0.0.1",
"port" : 9093
}
],
```
## Example usage
Now, if you make a request to APISIX, it will be logged in your Kafka server:
```shell
curl -i http://127.0.0.1:9080/hello
```
## Delete Plugin
To remove the `kafka-logger` 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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,83 @@
---
title: kafka-proxy
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Kafka proxy
description: This document contains information about the Apache APISIX kafka-proxy Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `kafka-proxy` plugin can be used to configure advanced parameters for the kafka upstream of Apache APISIX, such as SASL authentication.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|-------------------|---------|----------|---------|---------------|------------------------------------|
| sasl | object | optional | | {"username": "user", "password" :"pwd"} | SASL/PLAIN authentication configuration, when this configuration exists, turn on SASL authentication; this object will contain two parameters username and password, they must be configured. |
| sasl.username | string | required | | | SASL/PLAIN authentication username |
| sasl.password | string | required | | | SASL/PLAIN authentication password |
NOTE: `encrypt_fields = {"sasl.password"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
:::note
If SASL authentication is enabled, the `sasl.username` and `sasl.password` must be set.
The current SASL authentication only supports PLAIN mode, which is the username password login method.
:::
## Example usage
When we use scheme as the upstream of kafka, we can add kafka authentication configuration to it through this plugin.
```shell
curl -X PUT 'http://127.0.0.1:9180/apisix/admin/routes/r1' \
-H 'X-API-KEY: <api-key>' \
-H 'Content-Type: application/json' \
-d '{
"uri": "/kafka",
"plugins": {
"kafka-proxy": {
"sasl": {
"username": "user",
"password": "pwd"
}
}
},
"upstream": {
"nodes": {
"kafka-server1:9092": 1,
"kafka-server2:9092": 1,
"kafka-server3:9092": 1
},
"type": "none",
"scheme": "kafka"
}
}'
```
Now, we can test it by connecting to the `/kafka` endpoint via websocket.
## Delete Plugin
To remove the `kafka-proxy` 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.

View File

@@ -0,0 +1,571 @@
---
title: key-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Key Auth
- key-auth
description: The key-auth Plugin supports the use of an authentication key as a mechanism for clients to authenticate themselves before accessing Upstream resources.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/key-auth" />
</head>
## Description
The `key-auth` Plugin supports the use of an authentication key as a mechanism for clients to authenticate themselves before accessing Upstream resources.
To use the plugin, you would configure authentication keys on [Consumers](../terminology/consumer.md) and enable the Plugin on routes or services. The key can be included in the request URL query string or request header. APISIX will then verify the key to determine if a request should be allowed or denied to access Upstream resources.
When a Consumer is successfully authenticated, APISIX adds additional headers, such as `X-Consumer-Username`, `X-Credential-Indentifier`, and other Consumer custom headers if configured, to the request, before proxying it to the Upstream service. The Upstream service will be able to differentiate between consumers and implement additional logics as needed. If any of these values is not available, the corresponding header will not be added.
## Attributes
For Consumer/Credential:
| Name | Type | Required | Description |
|------|--------|-------------|----------------------------|
| key | string | True | Unique key for a Consumer. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. |
NOTE: `encrypt_fields = {"key"}` is also defined in the schema, which means that the field will be stored encrypted in etcd. See [encrypted storage fields](../plugin-develop.md#encrypted-storage-fields).
For Route:
| Name | Type | Required | Default | Description |
|--------|--------|-------------|-------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| header | string | False | apikey | The header to get the key from. |
| query | string | False | apikey | The query string to get the key from. Lower priority than header. |
| hide_credentials | boolean | False | false | If true, do not pass the header or query string with key to Upstream services. |
| anonymous_consumer | string | False | false | Anonymous Consumer name. If configured, allow anonymous users to bypass the authentication. |
## Examples
The examples below demonstrate how you can work with the `key-auth` Plugin for different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Implement Key Authentication on Route
The following example demonstrates how to implement key authentications on a Route and include the key in the request header.
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-key-auth",
"plugins": {
"key-auth": {
"key": "jack-key"
}
}
}'
```
Create a Route with `key-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
#### Verify with a Valid Key
Send a request to with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -H 'apikey: jack-key'
```
You should receive an `HTTP/1.1 200 OK` response.
#### Verify with an Invalid Key
Send a request with an invalid key:
```shell
curl -i "http://127.0.0.1:9080/anything" -H 'apikey: wrong-key'
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following:
```text
{"message":"Invalid API key in request"}
```
#### Verify without a Key
Send a request to without a key:
```shell
curl -i "http://127.0.0.1:9080/anything"
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following:
```text
{"message":"Missing API key found in request"}
```
### Hide Authentication Information From Upstream
The following example demonstrates how to prevent the key from being sent to the Upstream services by configuring `hide_credentials`. By default, the authentication key is forwarded to the Upstream services, which might lead to security risks in some circumstances.
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-key-auth",
"plugins": {
"key-auth": {
"key": "jack-key"
}
}
}'
```
#### Without Hiding Credentials
Create a Route with `key-auth` and configure `hide_credentials` to `false`, which is the default configuration:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {
"hide_credentials": false
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything?apikey=jack-key"
```
You should see an `HTTP/1.1 200 OK` response with the following:
```json
{
"args": {
"auth": "jack-key"
},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.2.1",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-key-auth",
"X-Amzn-Trace-Id": "Root=1-6502d8a5-2194962a67aa21dd33f94bb2",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "127.0.0.1, 103.248.35.179",
"url": "http://127.0.0.1/anything?apikey=jack-key"
}
```
Note that the Credential `jack-key` is visible to the Upstream service.
#### Hide Credentials
Update the plugin's `hide_credentials` to `true`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/key-auth-route" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"key-auth": {
"hide_credentials": true
}
}
}'
```
Send a request with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything?apikey=jack-key"
```
You should see an `HTTP/1.1 200 OK` response with the following:
```json
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.2.1",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-key-auth",
"X-Amzn-Trace-Id": "Root=1-6502d85c-16f34dbb5629a5960183e803",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "127.0.0.1, 103.248.35.179",
"url": "http://127.0.0.1/anything"
}
```
Note that the Credential `jack-key` is no longer visible to the Upstream service.
### Demonstrate Priority of Keys in Header and Query
The following example demonstrates how to implement key authentication by consumers on a Route and customize the URL parameter that should include the key. The example also shows that when the API key is configured in both the header and the query string, the request header has a higher priority.
Create a Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-key-auth",
"plugins": {
"key-auth": {
"key": "jack-key"
}
}
}'
```
Create a Route with `key-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {
"query": "auth"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
#### Verify with a Valid Key
Send a request to with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything?auth=jack-key"
```
You should receive an `HTTP/1.1 200 OK` response.
#### Verify with an Invalid Key
Send a request with an invalid key:
```shell
curl -i "http://127.0.0.1:9080/anything?auth=wrong-key"
```
You should see an `HTTP/1.1 401 Unauthorized` response with the following:
```text
{"message":"Invalid API key in request"}
```
#### Verify with a Valid Key in Query String
However, if you include the valid key in header with the invalid key still in the URL query string:
```shell
curl -i "http://127.0.0.1:9080/anything?auth=wrong-key" -H 'apikey: jack-key'
```
You should see an `HTTP/1.1 200 OK` response. This shows that the key included in the header always has a higher priority.
### Add Consumer Custom ID to Header
The following example demonstrates how you can attach a Consumer custom ID to authenticated request in the `Consumer-Custom-Id` header, which can be used to implement additional logics as needed.
Create a Consumer `jack` with a custom ID label:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack",
"labels": {
"custom_id": "495aec6a"
}
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-key-auth",
"plugins": {
"key-auth": {
"key": "jack-key"
}
}
}'
```
Create a Route with `key-auth`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send a request to the Route with the valid key:
```shell
curl -i "http://127.0.0.1:9080/anything?auth=jack-key"
```
You should see an `HTTP/1.1 200 OK` response similar to the following:
```json
{
"args": {
"auth": "jack-key"
},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "127.0.0.1",
"User-Agent": "curl/8.6.0",
"X-Amzn-Trace-Id": "Root=1-66ea8d64-33df89052ae198a706e18c2a",
"X-Consumer-Username": "jack",
"X-Credential-Identifier": "cred-jack-key-auth",
"X-Consumer-Custom-Id": "495aec6a",
"X-Forwarded-Host": "127.0.0.1"
},
"json": null,
"method": "GET",
"origin": "192.168.65.1, 205.198.122.37",
"url": "http://127.0.0.1/anything?apikey=jack-key"
}
```
### Rate Limit with Anonymous Consumer
The following example demonstrates how you can configure different rate limiting policies by regular and anonymous consumers, where the anonymous Consumer does not need to authenticate and has less quotas.
Create a regular Consumer `jack` and configure the `limit-count` Plugin to allow for a quota of 3 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jack",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create the `key-auth` Credential for the Consumer `jack`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jack/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jack-key-auth",
"plugins": {
"key-auth": {
"key": "jack-key"
}
}
}'
```
Create an anonymous user `anonymous` and configure the `limit-count` Plugin to allow for a quota of 1 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create a Route and configure the `key-auth` Plugin to accept anonymous Consumer `anonymous` from bypassing the authentication:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send five consecutive requests with `jack`'s key:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H 'apikey: jack-key' -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
```text
200: 3, 429: 2
```
Send five anonymous requests:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that only one request was successful:
```text
200: 1, 429: 4
```

View File

@@ -0,0 +1,255 @@
---
title: lago
keywords:
- Apache APISIX
- API Gateway
- Plugin
- lago
- monetization
- github.com/getlago/lago
description: The lago plugin reports usage to a Lago instance, which allows users to integrate Lago with APISIX for API monetization.
---
<!--
#
# 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.
#
-->
## Description
The `lago` plugin pushes requests and responses to [Lago Self-hosted](https://github.com/getlago/lago) and [Lago Cloud](https://getlago.com) via the Lago REST API. the plugin allows you to use it with a variety of APISIX built-in features, such as the APISIX consumer and the request-id plugin.
This allows for API monetization or let APISIX to be an AI gateway for AI tokens billing scenarios.
:::note disclaimer
Lago owns its trademarks and controls its commercial products and open source projects.
The [https://github.com/getlago/lago](https://github.com/getlago/lago) project uses the `AGPL-3.0` license instead of the `Apache-2.0` license that is the same as Apache APISIX. As a user, you will need to evaluate for yourself whether it is applicable to your business to use the project in a compliant way or to obtain another type of license from Lago. Apache APISIX community does not endorse it.
The plugin does not contain any proprietary code or SDKs from Lago, it is contributed by contributors to Apache APISIX and licensed under the `Apache-2.0` license, which is in line with any other part of APISIX and you don't need to worry about its compliance.
:::
When enabled, the plugin will collect information from the request context (e.g. event code, transaction ID, associated subscription ID) as configured and serialize them into [Event JSON objects](https://getlago.com/docs/api-reference/events/event-object) as required by Lago. They will be added to the buffer and sent to Lago in batches of up to 100. This batch size is a [requirement](https://getlago.com/docs/api-reference/events/batch) from Lago. If you want to modify it, see [batch processor](../batch-processor.md) for more details.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|---|---|---|---|---|---|
| endpoint_addrs | array[string] | True | | | Lago API address, such as `http://127.0.0.1:3000`. It supports both self-hosted Lago and Lago Cloud. If multiple endpoints are configured, the log will be pushed to a randomly selected endpoint from the list. |
| endpoint_uri | string | False | /api/v1/events/batch | | Lago API endpoint for [batch usage events](https://docs.getlago.com/api-reference/events/batch). |
| token | string | True | | | Lago API key created in the Lago dashboard. |
| event_transaction_id | string | True | | | Event's transaction ID, used to identify and de-duplicate the event. It supports string templates containing APISIX and NGINX variables, such as `req_${request_id}`, which allows you to use values returned by upstream services or the `request-id` plugin. |
| event_subscription_id | string | True | | | Event's subscription ID, which is automatically generated or configured when you assign the plan to the customer on Lago. This is used to associate API consumption to a customer subscription and supports string templates containing APISIX and NGINX variables, such as `cus_${consumer_name}`, which allows you to use values returned by upstream services or APISIX consumer. |
| event_code | string | True | | | Lago billable metric's code for associating an event to a specified billable item. |
| event_properties | object | False | | | Event's properties, used to attach information to an event. This allows you to send certain information on an event to Lago, such as the HTTP status to exclude failed requests from billing, or the AI token consumption in the response body for accurate billing. The keys are fixed strings, while the values can be string templates containing APISIX and NGINX variables, such as `${status}`. |
| ssl_verify | boolean | False | true | | If true, verify Lago's SSL certificates. |
| timeout | integer | False | 3000 | [1, 60000] | Timeout for the Lago service HTTP call in milliseconds. |
| keepalive | boolean | False | true | | If true, keep the connection alive for multiple requests. |
| keepalive_timeout | integer | False | 60000 | >=1000 | Keepalive timeout in milliseconds. |
| keepalive_pool | integer | False | 5 | >=1 | Maximum number of connections in the connection pool. |
This Plugin supports using batch processors to aggregate and process events in a batch. This avoids the need for frequently submitting the data. The batch processor submits data every `5` seconds or when the data in the queue reaches `1000`. See [Batch Processor](../batch-processor.md#configuration) for more information or setting your custom configuration.
## Examples
The examples below demonstrate how you can configure `lago` Plugin for typical scenario.
To follow along the examples, start a Lago instance. Refer to [https://github.com/getlago/lago](https://github.com/getlago/lago) or use Lago Cloud.
Follow these brief steps to configure Lago:
1. Get the Lago API Key (also known as `token`), from the __Developer__ page of the Lago dashboard.
2. Next, create a billable metric used by APISIX, assuming its code is `test`. Set the `Aggregation type` to `Count`; and add a filter with a key of `tier` whose value contains `expensive` to allow us to distinguish between API values, which will be demonstrated later.
3. Create a plan and add the created metric to it. Its code can be configured however you like. In the __Usage-based charges__ section, add the billable metric created previously as a `Metered charge` item. Specify the default price as `$1`. Add a filter, use `tier: expensive` to perform the filtering, and specify its price as `$10`.
4. Select an existing consumer or create a new one to assign the plan you just created. You need to specify a `Subscription external ID` (or you can have Lago generate it), which will be used as the APISIX consumer username.
Next we need to configure APISIX for demonstrations.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Report API call usage
The following example demonstrates how you can configure the `lago` Plugin on a Route to measuring API call usage.
Create a Route with the `lago`, `request-id`, `key-auth` Plugins as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "lago-route-1",
"uri": "/get",
"plugins": {
"request-id": {
"include_in_response": true
},
"key-auth": {},
"lago": {
"endpoint_addrs": ["http://12.0.0.1:3000"],
"token": "<Get token from Lago dashboard>",
"event_transaction_id": "${http_x_request_id}",
"event_subscription_id": "${http_x_consumer_username}",
"event_code": "test"
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```
Create a second route with the `lago`, `request-id`, `key-auth` Plugin as such:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "lago-route-2",
"uri": "/anything",
"plugins": {
"request-id": {
"include_in_response": true
},
"key-auth": {},
"lago": {
"endpoint_addrs": ["http://12.0.0.1:3000"],
"token": "<Get token from Lago dashboard>",
"event_transaction_id": "${http_x_request_id}",
"event_subscription_id": "${http_x_consumer_username}",
"event_code": "test",
"event_properties": {
"tier": "expensive"
}
}
},
"upstream": {
"nodes": {
"httpbin.org:80": 1
},
"type": "roundrobin"
}
}'
```
Create a Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "<Lago subscription external ID>",
"plugins": {
"key-auth": {
"key": "demo"
}
}
}'
```
Send three requests to the two routes respectively:
```shell
curl "http://127.0.0.1:9080/get"
curl "http://127.0.0.1:9080/get"
curl "http://127.0.0.1:9080/get"
curl "http://127.0.0.1:9080/anything"
curl "http://127.0.0.1:9080/anything"
curl "http://127.0.0.1:9080/anything"
```
You should receive `HTTP/1.1 200 OK` responses for all requests.
Wait a few seconds, then navigate to the __Developer__ page in the Lago dashboard. Under __Events__, you should see 6 event entries sent by APISIX.
If the self-hosted instance's event worker is configured correctly (or if you're using Lago Cloud), you can also see the total amount consumed in real time in the consumer's subscription usage, which should be `3 * $1 + 3 * $10 = $33` according to our demo use case.
## FAQ
### Purpose of the Plugin
When you make an effort to monetize your API, it's hard to find a ready-made, low-cost solution, so you may have to build your own billing stack, which is complicated.
This plugin allows you to use APISIX to handle API proxies and use Lago as a billing stack through direct integration with Lago, and both the APISIX open source project and Lago will be part of your portfolio, which is a huge time saver.
Every API call results in a Lago event, which allows you to bill users for real usage, i.e. pay-as-you-go, and thanks to our built-in transaction ID (request ID) support, you can simply implement API call logging and troubleshooting for your customers.
In addition to typical API monetization scenarios, APISIX can also do AI tokens-based billing when it is acting as an AI gateway, where each Lago event generated by an API request includes exactly how many tokens were consumed, to allow you to charge the user for a fine-grained per-tokens usage.
### Is it flexible?
Of course, the fact that we make transaction ID, subscription ID as a configuration item and allow you to use APISIX and NGINX variables in it means that it's simple to integrate the plugin with any existing or your own authentication and internal services.
- Use custom authentication: as long as the Lago subscription ID represented by the user ID is registered as an APISIX variable, it will be available from there, so custom authentication is completely possible!
- Integration with internal services: You might not need the APISIX built-in request-id plugin. That's OK. You can have your internal service (APISIX upstream) generate it and include it in the HTTP response header. Then you can access it via an NGINX variable in the transaction ID.
Event properties are supported, allowing you to set special values for specific APIs. For example, if your service has 100 APIs, you can enable general billing for all of them while customizing a few with different pricing—just as demonstrated above.
### Which Lago versions does it work with?
When we first developed the Lago plugin, it was released to `1.17.0`, which we used for integration, so it works at least with `1.17.0`.
Technically, we use the Lago batch event API to submit events in batches, and APISIX will only use this API, so as long as Lago doesn't make any disruptive changes to this API, APISIX will be able to integrate with it.
Here's an [archive page](https://web.archive.org/web/20250516073803/https://getlago.com/docs/api-reference/events/batch) of the API documentation, which allows you to check the differences between the API at the time of our integration and the latest API.
If the latest API changes, you can submit an issue to inform the APISIX maintainers that this may require some changes.
### Why Lago can't receive events?
Look at `error.log` for such a log.
```text
2023/04/30 13:45:46 [error] 19381#19381: *1075673 [lua] batch-processor.lua:95: Batch Processor[lago logger] failed to process entries: lago api returned status: 400, body: <error message>, context: ngx.timer, client: 127.0.0.1, server: 0.0.0.0:9080
```
The error can be diagnosed based on the error code in the `failed to process entries: lago api returned status: 400, body: <error message>` and the response body of the lago server.
### Reliability of reporting
The plugin may encounter a network problem that prevents the node where the gateway is located from communicating with the Lago API, in which case APISIX will discard the batch according to the [batch processor](../batch-processor.md) configuration, the batch will be discarded if the specified number of retries are made and the dosage still cannot be sent.
Discarded events are permanently lost, so it is recommended that you use this plugin in conjunction with other logging mechanisms and perform event replay after Lago is unavailable causing data to be discarded to ensure that all logs are correctly sent to Lago.
### Will the event duplicate?
While APISIX performs retries based on the [batch processor](../batch-processor.md) configuration, you don't need to worry about duplicate events being reported to Lago.
The `event_transcation_id` and `timestamp` are generated and logged after the request is processed on the APISIX side, and Lago de-duplicates the event based on them.
So even if a retry is triggered because the network causes Lago to send a `success` response that is not received by APISIX, the event is still not duplicated on Lago.
### Performance Impacts
The plugin is logically simple and reliable; it simply builds a Lago event object for each request, buffers and sends them in bulk. The logic is not coupled to the request proxy path, so this does not cause latency to rise for requests going through the gateway.
Technically, the logic is executed in the NGINX log phase and [batch processor](../batch-processor.md) timer, so this does not affect the request itself.
### Resource overhead
As explained earlier in the performance impact section, the plugin doesn't cause a significant increase in system resources. It only uses a small amount of memory to store events for batching.

View File

@@ -0,0 +1,168 @@
---
title: ldap-auth
keywords:
- Apache APISIX
- API Gateway
- Plugin
- LDAP Authentication
- ldap-auth
description: This document contains information about the Apache APISIX ldap-auth Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `ldap-auth` Plugin can be used to add LDAP authentication to a Route or a Service.
This Plugin works with the Consumer object and the consumers of the API can authenticate with an LDAP server using [basic authentication](https://en.wikipedia.org/wiki/Basic_access_authentication).
This Plugin uses [lua-resty-ldap](https://github.com/api7/lua-resty-ldap) for connecting with an LDAP server.
## Attributes
For Consumer:
| Name | Type | Required | Description |
| ------- | ------ | -------- | -------------------------------------------------------------------------------- |
| user_dn | string | True | User dn of the LDAP client. For example, `cn=user01,ou=users,dc=example,dc=org`. This field supports saving the value in Secret Manager using the [APISIX Secret](../terminology/secret.md) resource. |
For Route:
| Name | Type | Required | Default | Description |
|----------|---------|----------|---------|------------------------------------------------------------------------|
| base_dn | string | True | | Base dn of the LDAP server. For example, `ou=users,dc=example,dc=org`. |
| ldap_uri | string | True | | URI of the LDAP server. |
| use_tls | boolean | False | `false` | If set to `true` uses TLS. |
| tls_verify| boolean | False | `false` | Whether to verify the server certificate when `use_tls` is enabled; If set to `true`, you must set `ssl_trusted_certificate` in `config.yaml`, and make sure the host of `ldap_uri` matches the host in server certificate. |
| uid | string | False | `cn` | uid attribute. |
## Enable plugin
First, you have to create a Consumer and enable the `ldap-auth` Plugin on it:
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
```shell
curl http://127.0.0.1:9180/apisix/admin/consumers -H "X-API-KEY: $admin_key" -X PUT -d '
{
"username": "foo",
"plugins": {
"ldap-auth": {
"user_dn": "cn=user01,ou=users,dc=example,dc=org"
}
}
}'
```
Now you can enable the Plugin on a specific Route or a Service as shown below:
```shell
curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key" -X PUT -d '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {
"ldap-auth": {
"base_dn": "ou=users,dc=example,dc=org",
"ldap_uri": "localhost:1389",
"uid": "cn"
},
},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```
## Example usage
After configuring the Plugin as mentioned above, clients can make requests with authorization to access the API:
```shell
curl -i -uuser01:password1 http://127.0.0.1:9080/hello
```
```shell
HTTP/1.1 200 OK
...
hello, world
```
If an authorization header is missing or invalid, the request is denied:
```shell
curl -i http://127.0.0.1:9080/hello
```
```shell
HTTP/1.1 401 Unauthorized
...
{"message":"Missing authorization in request"}
```
```shell
curl -i -uuser:password1 http://127.0.0.1:9080/hello
```
```shell
HTTP/1.1 401 Unauthorized
...
{"message":"Invalid user authorization"}
```
```shell
curl -i -uuser01:passwordfalse http://127.0.0.1:9080/hello
```
```shell
HTTP/1.1 401 Unauthorized
...
{"message":"Invalid user authorization"}
```
## Delete Plugin
To remove the `ldap-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 '
{
"methods": ["GET"],
"uri": "/hello",
"plugins": {},
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}'
```

View File

@@ -0,0 +1,420 @@
---
title: limit-conn
keywords:
- Apache APISIX
- API Gateway
- Limit Connection
description: The limit-conn plugin restricts the rate of requests by managing concurrent connections. Requests exceeding the threshold may be delayed or rejected, ensuring controlled API usage and preventing overload.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/limit-conn" />
</head>
## Description
The `limit-conn` Plugin limits the rate of requests by the number of concurrent connections. Requests exceeding the threshold will be delayed or rejected based on the configuration, ensuring controlled resource usage and preventing overload.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|------------|---------|----------|-------------|-------------------|-----------------|
| conn | integer | True | | > 0 | The maximum number of concurrent requests allowed. Requests exceeding the configured limit and below `conn + burst` will be delayed. |
| burst | integer | True | | >= 0 | The number of excessive concurrent requests allowed to be delayed per second. Requests exceeding the limit will be rejected immediately. |
| default_conn_delay | number | True | | > 0 | Processing latency allowed in seconds for concurrent requests exceeding `conn + burst`, which can be dynamically adjusted based on `only_use_default_delay` setting. |
| only_use_default_delay | boolean | False | false | | If false, delay requests proportionally based on how much they exceed the `conn` limit. The delay grows larger as congestion increases. For instance, with `conn` being `5`, `burst` being `3`, and `default_conn_delay` being `1`, 6 concurrent requests would result in a 1-second delay, 7 requests a 2-second delay, 8 requests a 3-second delay, and so on, until the total limit of `conn + burst` is reached, beyond which requests are rejected. If true, use `default_conn_delay` to delay all excessive requests within the `burst` range. Requests beyond `conn + burst` are rejected immediately. For instance, with `conn` being `5`, `burst` being `3`, and `default_conn_delay` being `1`, 6, 7, or 8 concurrent requests are all delayed by exactly 1 second each. |
| key_type | string | False | var | ["var","var_combination"] | The type of key. If the `key_type` is `var`, the `key` is interpreted a variable. If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. |
| key | string | False | remote_addr | | The key to count requests by. If the `key_type` is `var`, the `key` is interpreted a variable. The variable does not need to be prefixed by a dollar sign (`$`). If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. All variables should be prefixed by dollar signs (`$`). For example, to configure the `key` to use a combination of two request headers `custom-a` and `custom-b`, the `key` should be configured as `$http_custom_a $http_custom_b`. |
| rejected_code | integer | False | 503 | [200,...,599] | The HTTP status code returned when a request is rejected for exceeding the threshold. |
| rejected_msg | string | False | | non-empty | The response body returned when a request is rejected for exceeding the threshold. |
| allow_degradation | boolean | False | false | | If true, allow APISIX to continue handling requests without the Plugin when the Plugin or its dependencies become unavailable. |
| policy | string | False | local | ["local","redis","redis-cluster"] | The policy for rate limiting counter. If it is `local`, the counter is stored in memory locally. If it is `redis`, the counter is stored on a Redis instance. If it is `redis-cluster`, the counter is stored in a Redis cluster. |
| redis_host | string | False | | | The address of the Redis node. Required when `policy` is `redis`. |
| redis_port | integer | False | 6379 | [1,...] | The port of the Redis node when `policy` is `redis`. |
| redis_username | string | False | | | The username for Redis if Redis ACL is used. If you use the legacy authentication method `requirepass`, configure only the `redis_password`. Used when `policy` is `redis`. |
| redis_password | string | False | | | The password of the Redis node when `policy` is `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis`. |
| redis_database | integer | False | 0 | >= 0 | The database number in Redis when `policy` is `redis`. |
| redis_timeout | integer | False | 1000 | [1,...] | The Redis timeout value in milliseconds when `policy` is `redis` or `redis-cluster`. |
| redis_cluster_nodes | array[string] | False | | | The list of the Redis cluster nodes with at least two addresses. Required when policy is redis-cluster. |
| redis_cluster_name | string | False | | | The name of the Redis cluster. Required when `policy` is `redis-cluster`. |
| redis_cluster_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is |
| redis_cluster_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis-cluster`. |
## Examples
The examples below demonstrate how you can configure `limit-conn` in different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Apply Rate Limiting by Remote Address
The following example demonstrates how to use `limit-conn` to rate limit requests by `remote_addr`, with example connection and burst thresholds.
Create a Route with `limit-conn` Plugin to allow 2 concurrent requests and 1 excessive concurrent request. Additionally:
* Configure the Plugin to allow 0.1 second of processing latency for concurrent requests exceeding `conn + burst`.
* Set the key type to `vars` to interpret `key` as a variable.
* Calculate rate limiting count by request's `remote_address`.
* Set `policy` to `local` to use the local counter in memory.
* Customize the `rejected_code` to `429`.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"key_type": "var",
"key": "remote_addr",
"policy": "local",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send five concurrent requests to the route:
```shell
seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get"'
```
You should see responses similar to the following, where excessive requests are rejected:
```text
Response: 200
Response: 200
Response: 200
Response: 429
Response: 429
```
### Apply Rate Limiting by Remote Address and Consumer Name
The following example demonstrates how to use `limit-conn` to rate limit requests by a combination of variables, `remote_addr` and `consumer_name`.
Create a Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create a second Consumer `jane`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jane"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
```
Create a Route with `key-auth` and `limit-conn` Plugins, and specify in the `limit-conn` Plugin to use a combination of variables as the rate limiting key:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"key-auth": {},
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"key_type": "var_combination",
"key": "$remote_addr $consumer_name"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send five concurrent requests as the Consumer `john`:
```shell
seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "apikey: john-key"'
```
You should see responses similar to the following, where excessive requests are rejected:
```text
Response: 200
Response: 200
Response: 200
Response: 429
Response: 429
```
Immediately send five concurrent requests as the Consumer `jane`:
```shell
seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get" -H "apikey: jane-key"'
```
You should also see responses similar to the following, where excessive requests are rejected:
```text
Response: 200
Response: 200
Response: 200
Response: 429
Response: 429
```
### Rate Limit WebSocket Connections
The following example demonstrates how you can use the `limit-conn` Plugin to limit the number of concurrent WebSocket connections.
Start a [sample upstream WebSocket server](https://hub.docker.com/r/jmalloc/echo-server):
```shell
docker run -d \
-p 8080:8080 \
--name websocket-server \
--network=apisix-quickstart-net \
jmalloc/echo-server
```
Create a Route to the server WebSocket endpoint and enable WebSocket for the route. Adjust the WebSocket server address accordingly.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id": "ws-route",
"uri": "/.ws",
"plugins": {
"limit-conn": {
"conn": 2,
"burst": 1,
"default_conn_delay": 0.1,
"key_type": "var",
"key": "remote_addr",
"rejected_code": 429
}
},
"enable_websocket": true,
"upstream": {
"type": "roundrobin",
"nodes": {
"websocket-server:8080": 1
}
}
}'
```
Install a WebSocket client, such as [websocat](https://github.com/vi/websocat), if you have not already. Establish connection with the WebSocket server through the route:
```shell
websocat "ws://127.0.0.1:9080/.ws"
```
Send a "hello" message in the terminal, you should see the WebSocket server echoes back the same message:
```text
Request served by 1cd244052136
hello
hello
```
Open three more terminal sessions and run:
```shell
websocat "ws://127.0.0.1:9080/.ws"
```
You should see the last terminal session prints `429 Too Many Requests` when you try to establish a WebSocket connection with the server, due to the rate limiting effect.
### Share Quota Among APISIX Nodes with a Redis Server
The following example demonstrates the rate limiting of requests across multiple APISIX nodes with a Redis server, such that different APISIX nodes share the same rate limiting quota.
On each APISIX instance, create a Route with the following configurations. Adjust the address of the Admin API, Redis host, port, password, and database accordingly.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"limit-conn": {
"conn": 1,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "redis",
"redis_host": "192.168.xxx.xxx",
"redis_port": 6379,
"redis_password": "p@ssw0rd",
"redis_database": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send five concurrent requests to the route:
```shell
seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get"'
```
You should see responses similar to the following, where excessive requests are rejected:
```text
Response: 200
Response: 200
Response: 429
Response: 429
Response: 429
```
This shows the two routes configured in different APISIX instances share the same quota.
### Share Quota Among APISIX Nodes with a Redis Cluster
You can also use a Redis cluster to apply the same quota across multiple APISIX nodes, such that different APISIX nodes share the same rate limiting quota.
Ensure that your Redis instances are running in [cluster mode](https://redis.io/docs/management/scaling/#create-and-use-a-redis-cluster). A minimum of two nodes are required for the `limit-conn` Plugin configurations.
On each APISIX instance, create a Route with the following configurations. Adjust the address of the Admin API, Redis cluster nodes, password, cluster name, and SSL varification accordingly.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-conn-route",
"uri": "/get",
"plugins": {
"limit-conn": {
"conn": 1,
"burst": 1,
"default_conn_delay": 0.1,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_nodes": [
"192.168.xxx.xxx:6379",
"192.168.xxx.xxx:16379"
],
"redis_password": "p@ssw0rd",
"redis_cluster_name": "redis-cluster-1",
"redis_cluster_ssl": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send five concurrent requests to the route:
```shell
seq 1 5 | xargs -n1 -P5 bash -c 'curl -s -o /dev/null -w "Response: %{http_code}\n" "http://127.0.0.1:9080/get"'
```
You should see responses similar to the following, where excessive requests are rejected:
```text
Response: 200
Response: 200
Response: 429
Response: 429
Response: 429
```
This shows the two routes configured in different APISIX instances share the same quota.

View File

@@ -0,0 +1,507 @@
---
title: limit-count
keywords:
- Apache APISIX
- API Gateway
- Limit Count
description: The limit-count plugin uses a fixed window algorithm to limit the rate of requests by the number of requests within a given time interval. Requests exceeding the configured quota will be rejected.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/limit-count" />
</head>
## Description
The `limit-count` plugin uses a fixed window algorithm to limit the rate of requests by the number of requests within a given time interval. Requests exceeding the configured quota will be rejected.
You may see the following rate limiting headers in the response:
* `X-RateLimit-Limit`: the total quota
* `X-RateLimit-Remaining`: the remaining quota
* `X-RateLimit-Reset`: number of seconds left for the counter to reset
## Attributes
| Name | Type | Required | Default | Valid values | Description |
| ----------------------- | ------- | ----------------------------------------- | ------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| count | integer | True | | > 0 | The maximum number of requests allowed within a given time interval. |
| time_window | integer | True | | > 0 | The time interval corresponding to the rate limiting `count` in seconds. |
| key_type | string | False | var | ["var","var_combination","constant"] | The type of key. If the `key_type` is `var`, the `key` is interpreted a variable. If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. If the `key_type` is `constant`, the `key` is interpreted as a constant. |
| key | string | False | remote_addr | | The key to count requests by. If the `key_type` is `var`, the `key` is interpreted a variable. The variable does not need to be prefixed by a dollar sign (`$`). If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. All variables should be prefixed by dollar signs (`$`). For example, to configure the `key` to use a combination of two request headers `custom-a` and `custom-b`, the `key` should be configured as `$http_custom_a $http_custom_b`. If the `key_type` is `constant`, the `key` is interpreted as a constant value. |
| rejected_code | integer | False | 503 | [200,...,599] | The HTTP status code returned when a request is rejected for exceeding the threshold. |
| rejected_msg | string | False | | non-empty | The response body returned when a request is rejected for exceeding the threshold. |
| policy | string | False | local | ["local","redis","redis-cluster"] | The policy for rate limiting counter. If it is `local`, the counter is stored in memory locally. If it is `redis`, the counter is stored on a Redis instance. If it is `redis-cluster`, the counter is stored in a Redis cluster. |
| allow_degradation | boolean | False | false | | If true, allow APISIX to continue handling requests without the plugin when the plugin or its dependencies become unavailable. |
| show_limit_quota_header | boolean | False | true | | If true, include `X-RateLimit-Limit` to show the total quota and `X-RateLimit-Remaining` to show the remaining quota in the response header. |
| group | string | False | | non-empty | The `group` ID for the plugin, such that routes of the same `group` can share the same rate limiting counter. |
| redis_host | string | False | | | The address of the Redis node. Required when `policy` is `redis`. |
| redis_port | integer | False | 6379 | [1,...] | The port of the Redis node when `policy` is `redis`. |
| redis_username | string | False | | | The username for Redis if Redis ACL is used. If you use the legacy authentication method `requirepass`, configure only the `redis_password`. Used when `policy` is `redis`. |
| redis_password | string | False | | | The password of the Redis node when `policy` is `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis`. |
| redis_database | integer | False | 0 | >= 0 | The database number in Redis when `policy` is `redis`. |
| redis_timeout | integer | False | 1000 | [1,...] | The Redis timeout value in milliseconds when `policy` is `redis` or `redis-cluster`. |
| redis_cluster_nodes | array[string] | False | | | The list of the Redis cluster nodes with at least two addresses. Required when policy is redis-cluster. |
| redis_cluster_name | string | False | | | The name of the Redis cluster. Required when `policy` is `redis-cluster`. |
| redis_cluster_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is `redis-cluster`. |
| redis_cluster_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis-cluster`. |
## Examples
The examples below demonstrate how you can configure `limit-count` in different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
:::
### Apply Rate Limiting by Remote Address
The following example demonstrates the rate limiting of requests by a single variable, `remote_addr`.
Create a Route with `limit-count` plugin that allows for a quota of 1 within a 30-second window per remote address:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route",
"uri": "/get",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"key_type": "var",
"key": "remote_addr"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to verify:
```shell
curl -i "http://127.0.0.1:9080/get"
```
You should see an `HTTP/1.1 200 OK` response.
The request has consumed all the quota allowed for the time window. If you send the request again within the same 30-second time interval, you should receive an `HTTP/1.1 429 Too Many Requests` response, indicating the request surpasses the quota threshold.
### Apply Rate Limiting by Remote Address and Consumer Name
The following example demonstrates the rate limiting of requests by a combination of variables, `remote_addr` and `consumer_name`. It allows for a quota of 1 within a 30-second window per remote address and for each consumer.
Create a Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john"
}'
```
Create `key-auth` Credential for the consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create a second Consumer `jane`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jane"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
```
Create a Route with `key-auth` and `limit-count` plugins, and specify in the `limit-count` plugin to use a combination of variables as the rate limiting key:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route",
"uri": "/get",
"plugins": {
"key-auth": {},
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"key_type": "var_combination",
"key": "$remote_addr $consumer_name"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request as the Consumer `jane`:
```shell
curl -i "http://127.0.0.1:9080/get" -H 'apikey: jane-key'
```
You should see an `HTTP/1.1 200 OK` response with the corresponding response body.
This request has consumed all the quota set for the time window. If you send the same request as the Consumer `jane` within the same 30-second time interval, you should receive an `HTTP/1.1 429 Too Many Requests` response, indicating the request surpasses the quota threshold.
Send the same request as the Consumer `john` within the same 30-second time interval:
```shell
curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key'
```
You should see an `HTTP/1.1 200 OK` response with the corresponding response body, indicating the request is not rate limited.
Send the same request as the Consumer `john` again within the same 30-second time interval, you should receive an `HTTP/1.1 429 Too Many Requests` response.
This verifies the plugin rate limits by the combination of variables, `remote_addr` and `consumer_name`.
### Share Quota among Routes
The following example demonstrates the sharing of rate limiting quota among multiple routes by configuring the `group` of the `limit-count` plugin.
Note that the configurations of the `limit-count` plugin of the same `group` should be identical. To avoid update anomalies and repetitive configurations, you can create a Service with `limit-count` plugin and Upstream for routes to connect to.
Create a service:
```shell
curl "http://127.0.0.1:9180/apisix/admin/services" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-service",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"group": "srv1"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Create two Routes and configure their `service_id` to be `limit-count-service`, so that they share the same configurations for the Plugin and Upstream:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route-1",
"service_id": "limit-count-service",
"uri": "/get1",
"plugins": {
"proxy-rewrite": {
"uri": "/get"
}
}
}'
```
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route-2",
"service_id": "limit-count-service",
"uri": "/get2",
"plugins": {
"proxy-rewrite": {
"uri": "/get"
}
}
}'
```
:::note
The [`proxy-rewrite`](./proxy-rewrite.md) plugin is used to rewrite the URI to `/get` so that requests are forwarded to the correct endpoint.
:::
Send a request to Route `/get1`:
```shell
curl -i "http://127.0.0.1:9080/get1"
```
You should see an `HTTP/1.1 200 OK` response with the corresponding response body.
Send the same request to Route `/get2` within the same 30-second time interval:
```shell
curl -i "http://127.0.0.1:9080/get2"
```
You should receive an `HTTP/1.1 429 Too Many Requests` response, which verifies the two routes share the same rate limiting quota.
### Share Quota Among APISIX Nodes with a Redis Server
The following example demonstrates the rate limiting of requests across multiple APISIX nodes with a Redis server, such that different APISIX nodes share the same rate limiting quota.
On each APISIX instance, create a Route with the following configurations. Adjust the address of the Admin API, Redis host, port, password, and database accordingly.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route",
"uri": "/get",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"key": "remote_addr",
"policy": "redis",
"redis_host": "192.168.xxx.xxx",
"redis_port": 6379,
"redis_password": "p@ssw0rd",
"redis_database": 1
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to an APISIX instance:
```shell
curl -i "http://127.0.0.1:9080/get"
```
You should see an `HTTP/1.1 200 OK` response with the corresponding response body.
Send the same request to a different APISIX instance within the same 30-second time interval, you should receive an `HTTP/1.1 429 Too Many Requests` response, verifying routes configured in different APISIX nodes share the same quota.
### Share Quota Among APISIX Nodes with a Redis Cluster
You can also use a Redis cluster to apply the same quota across multiple APISIX nodes, such that different APISIX nodes share the same rate limiting quota.
Ensure that your Redis instances are running in [cluster mode](https://redis.io/docs/management/scaling/#create-and-use-a-redis-cluster). A minimum of two nodes are required for the `limit-count` plugin configurations.
On each APISIX instance, create a Route with the following configurations. Adjust the address of the Admin API, Redis cluster nodes, password, cluster name, and SSL varification accordingly.
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-count-route",
"uri": "/get",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429,
"key": "remote_addr",
"policy": "redis-cluster",
"redis_cluster_nodes": [
"192.168.xxx.xxx:6379",
"192.168.xxx.xxx:16379"
],
"redis_password": "p@ssw0rd",
"redis_cluster_name": "redis-cluster-1",
"redis_cluster_ssl": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to an APISIX instance:
```shell
curl -i "http://127.0.0.1:9080/get"
```
You should see an `HTTP/1.1 200 OK` response with the corresponding response body.
Send the same request to a different APISIX instance within the same 30-second time interval, you should receive an `HTTP/1.1 429 Too Many Requests` response, verifying routes configured in different APISIX nodes share the same quota.
### Rate Limit with Anonymous Consumer
does not need to authenticate and has less quotas. While this example uses [`key-auth`](./key-auth.md) for authentication, the anonymous Consumer can also be configured with [`basic-auth`](./basic-auth.md), [`jwt-auth`](./jwt-auth.md), and [`hmac-auth`](./hmac-auth.md).
Create a regular Consumer `john` and configure the `limit-count` plugin to allow for a quota of 3 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john",
"plugins": {
"limit-count": {
"count": 3,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create the `key-auth` Credential for the Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create an anonymous user `anonymous` and configure the `limit-count` Plugin to allow for a quota of 1 within a 30-second window:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "anonymous",
"plugins": {
"limit-count": {
"count": 1,
"time_window": 30,
"rejected_code": 429
}
}
}'
```
Create a Route and configure the `key-auth` Plugin to accept anonymous Consumer `anonymous` from bypassing the authentication:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "key-auth-route",
"uri": "/anything",
"plugins": {
"key-auth": {
"anonymous_consumer": "anonymous"
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
To verify, send five consecutive requests with `john`'s key:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -H 'apikey: john-key' -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that out of the 5 requests, 3 requests were successful (status code 200) while the others were rejected (status code 429).
```text
200: 3, 429: 2
```
Send five anonymous requests:
```shell
resp=$(seq 5 | xargs -I{} curl "http://127.0.0.1:9080/anything" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200": $count_200, "429": $count_429
```
You should see the following response, showing that only one request was successful:
```text
200: 1, 429: 4
```

View File

@@ -0,0 +1,284 @@
---
title: limit-req
keywords:
- Apache APISIX
- API Gateway
- Limit Request
- limit-req
description: The limit-req Plugin uses the leaky bucket algorithm to rate limit the number of the requests and allow for throttling.
---
<!--
#
# 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.
#
-->
<head>
<link rel="canonical" href="https://docs.api7.ai/hub/limit-req" />
</head>
## Description
The `limit-req` Plugin uses the [leaky bucket](https://en.wikipedia.org/wiki/Leaky_bucket) algorithm to rate limit the number of the requests and allow for throttling.
## Attributes
| Name | Type | Required | Default | Valid values | Description |
|-------------------|---------|----------|---------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| rate | integer | True | | > 0 | The maximum number of requests allowed per second. Requests exceeding the rate and below burst will be delayed. |
| burst | integer | True | | >= 0 | The number of requests allowed to be delayed per second for throttling. Requests exceeding the rate and burst will get rejected. |
| key_type | string | False | var | ["var", "var_combination"] | The type of key. If the `key_type` is `var`, the `key` is interpreted a variable. If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. |
| key | string | True | remote_addr | | The key to count requests by. If the `key_type` is `var`, the `key` is interpreted a variable. The variable does not need to be prefixed by a dollar sign (`$`). If the `key_type` is `var_combination`, the `key` is interpreted as a combination of variables. All variables should be prefixed by dollar signs (`$`). For example, to configure the `key` to use a combination of two request headers `custom-a` and `custom-b`, the `key` should be configured as `$http_custom_a $http_custom_b`. |
| rejected_code | integer | False | 503 | [200,...,599] | The HTTP status code returned when a request is rejected for exceeding the threshold. |
| rejected_msg | string | False | | non-empty | The response body returned when a request is rejected for exceeding the threshold. |
| nodelay | boolean | False | false | | If true, do not delay requests within the burst threshold. |
| allow_degradation | boolean | False | false | | If true, allow APISIX to continue handling requests without the Plugin when the Plugin or its dependencies become unavailable. |
| policy | string | False | local | ["local", "redis", "redis-cluster"] | The policy for rate limiting counter. If it is `local`, the counter is stored in memory locally. If it is `redis`, the counter is stored on a Redis instance. If it is `redis-cluster`, the counter is stored in a Redis cluster. |
| redis_host | string | False | | | The address of the Redis node. Required when `policy` is `redis`. |
| redis_port | integer | False | 6379 | [1,...] | The port of the Redis node when `policy` is `redis`. |
| redis_username | string | False | | | The username for Redis if Redis ACL is used. If you use the legacy authentication method `requirepass`, configure only the `redis_password`. Used when `policy` is `redis`. |
| redis_password | string | False | | | The password of the Redis node when `policy` is `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis`. |
| redis_database | integer | False | 0 | >= 0 | The database number in Redis when `policy` is `redis`. |
| redis_timeout | integer | False | 1000 | [1,...] | The Redis timeout value in milliseconds when `policy` is `redis` or `redis-cluster`. |
| redis_cluster_nodes | array[string] | False | | | The list of the Redis cluster nodes with at least two addresses. Required when policy is redis-cluster. |
| redis_cluster_name | string | False | | | The name of the Redis cluster. Required when `policy` is `redis-cluster`. |
| redis_cluster_ssl | boolean | False | false | | If true, use SSL to connect to Redis cluster when `policy` is |
| redis_cluster_ssl_verify | boolean | False | false | | If true, verify the server SSL certificate when `policy` is `redis-cluster`. |
## Examples
The examples below demonstrate how you can configure `limit-req` in different scenarios.
:::note
You can fetch the `admin_key` from `config.yaml` and save to an environment variable with the following command:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed 's/"//g')
```
### Apply Rate Limiting by Remote Address
The following example demonstrates the rate limiting of HTTP requests by a single variable, `remote_addr`.
Create a Route with `limit-req` Plugin that allows for 1 QPS per remote address:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '
{
"id": "limit-req-route",
"uri": "/get",
"plugins": {
"limit-req": {
"rate": 1,
"burst": 0,
"key": "remote_addr",
"key_type": "var",
"rejected_code": 429,
"nodelay": true
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send a request to verify:
```shell
curl -i "http://127.0.0.1:9080/get"
```
You should see an `HTTP/1.1 200 OK` response.
The request has consumed all the quota allowed for the time window. If you send the request again within the same second, you should receive an `HTTP/1.1 429 Too Many Requests` response, indicating the request surpasses the quota threshold.
### Implement API Throttling
The following example demonstrates how to configure `burst` to allow overrun of the rate limiting threshold by the configured value and achieve request throttling. You will also see a comparison against when throttling is not implemented.
Create a Route with `limit-req` Plugin that allows for 1 QPS per remote address, with a `burst` of 1 to allow for 1 request exceeding the `rate` to be delayed for processing:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-req-route",
"uri": "/get",
"plugins": {
"limit-req": {
"rate": 1,
"burst": 1,
"key": "remote_addr",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Generate three requests to the Route:
```shell
resp=$(seq 3 | xargs -I{} curl -i "http://127.0.0.1:9080/get" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200 responses: $count_200 ; 429 responses: $count_429"
```
You are likely to see that all three requests are successful:
```text
200 responses: 3 ; 429 responses: 0
```
To see the effect without `burst`, update `burst` to 0 or set `nodelay` to `true` as follows:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes/limit-req-route" -X PATCH \
-H "X-API-KEY: ${admin_key}" \
-d '{
"plugins": {
"limit-req": {
"nodelay": true
}
}
}'
```
Generate three requests to the Route again:
```shell
resp=$(seq 3 | xargs -I{} curl -i "http://127.0.0.1:9080/get" -o /dev/null -s -w "%{http_code}\n") && \
count_200=$(echo "$resp" | grep "200" | wc -l) && \
count_429=$(echo "$resp" | grep "429" | wc -l) && \
echo "200 responses: $count_200 ; 429 responses: $count_429"
```
You should see a response similar to the following, showing requests surpassing the rate have been rejected:
```text
200 responses: 1 ; 429 responses: 2
```
### Apply Rate Limiting by Remote Address and Consumer Name
The following example demonstrates the rate limiting of requests by a combination of variables, `remote_addr` and `consumer_name`.
Create a Route with `limit-req` Plugin that allows for 1 QPS per remote address and for each Consumer.
Create a Consumer `john`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "john"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/john/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-john-key-auth",
"plugins": {
"key-auth": {
"key": "john-key"
}
}
}'
```
Create a second Consumer `jane`:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"username": "jane"
}'
```
Create `key-auth` Credential for the Consumer:
```shell
curl "http://127.0.0.1:9180/apisix/admin/consumers/jane/credentials" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "cred-jane-key-auth",
"plugins": {
"key-auth": {
"key": "jane-key"
}
}
}'
```
Create a Route with `key-auth` and `limit-req` Plugins, and specify in the `limit-req` Plugin to use a combination of variables as the rate-limiting key:
```shell
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
-H "X-API-KEY: ${admin_key}" \
-d '{
"id": "limit-req-route",
"uri": "/get",
"plugins": {
"key-auth": {},
"limit-req": {
"rate": 1,
"burst": 0,
"key": "$remote_addr $consumer_name",
"key_type": "var_combination",
"rejected_code": 429
}
},
"upstream": {
"type": "roundrobin",
"nodes": {
"httpbin.org:80": 1
}
}
}'
```
Send two requests simultaneously, each for one Consumer:
```shell
curl -i "http://127.0.0.1:9080/get" -H 'apikey: jane-key' & \
curl -i "http://127.0.0.1:9080/get" -H 'apikey: john-key' &
```
You should receive `HTTP/1.1 200 OK` for both requests, indicating the request has not exceeded the threshold for each Consumer.
If you send more requests as either Consumer within the same second, you should receive an `HTTP/1.1 429 Too Many Requests` response.
This verifies the Plugin rate limits by the combination of variables, `remote_addr` and `consumer_name`.

View File

@@ -0,0 +1,118 @@
---
title: log-rotate
keywords:
- Apache APISIX
- API Gateway
- Plugin
- Log rotate
description: This document contains information about the Apache APISIX log-rotate Plugin.
---
<!--
#
# 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.
#
-->
## Description
The `log-rotate` Plugin is used to keep rotating access and error log files in the log directory at regular intervals.
You can configure how often the logs are rotated and how many logs to keep. When the number of logs exceeds, older logs are automatically deleted.
## Attributes
| Name | Type | Required | Default | Description |
|--------------------|---------|----------|---------|------------------------------------------------------------------------------------------------|
| interval | integer | True | 60 * 60 | Time in seconds specifying how often to rotate the logs. |
| max_kept | integer | True | 24 * 7 | Maximum number of historical logs to keep. If this number is exceeded, older logs are deleted. |
| max_size | integer | False | -1 | Max size(Bytes) of log files to be rotated, size check would be skipped with a value less than 0 or time is up specified by interval. |
| enable_compression | boolean | False | false | When set to `true`, compresses the log file (gzip). Requires `tar` to be installed. |
## Enable Plugin
To enable the Plugin, add it in your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
- log-rotate
plugin_attr:
log-rotate:
interval: 3600 # rotate interval (unit: second)
max_kept: 168 # max number of log files will be kept
max_size: -1 # max size of log files will be kept
enable_compression: false # enable log file compression(gzip) or not, default false
```
## Example usage
Once you enable the Plugin as shown above, the logs will be stored and rotated based on your configuration.
In the example below the `interval` is set to `10` and `max_kept` is set to `10`. This will create logs as shown:
```shell
ll logs
```
```shell
total 44K
-rw-r--r--. 1 resty resty 0 Mar 20 20:32 2020-03-20_20-32-40_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:32 2020-03-20_20-32-40_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:32 2020-03-20_20-32-50_access.log
-rw-r--r--. 1 resty resty 2.8K Mar 20 20:32 2020-03-20_20-32-50_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:32 2020-03-20_20-33-00_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:33 2020-03-20_20-33-00_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-33-10_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:33 2020-03-20_20-33-10_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-33-20_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:33 2020-03-20_20-33-20_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-33-30_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:33 2020-03-20_20-33-30_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-33-40_access.log
-rw-r--r--. 1 resty resty 2.8K Mar 20 20:33 2020-03-20_20-33-40_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-33-50_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:33 2020-03-20_20-33-50_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:33 2020-03-20_20-34-00_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:34 2020-03-20_20-34-00_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:34 2020-03-20_20-34-10_access.log
-rw-r--r--. 1 resty resty 2.4K Mar 20 20:34 2020-03-20_20-34-10_error.log
-rw-r--r--. 1 resty resty 0 Mar 20 20:34 access.log
-rw-r--r--. 1 resty resty 1.5K Mar 20 21:31 error.log
```
If you have enabled compression, the logs will be as shown below:
```shell
total 10.5K
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:33 2020-03-20_20-33-50_access.log.tar.gz
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:33 2020-03-20_20-33-50_error.log.tar.gz
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:33 2020-03-20_20-34-00_access.log.tar.gz
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:34 2020-03-20_20-34-00_error.log.tar.gz
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:34 2020-03-20_20-34-10_access.log.tar.gz
-rw-r--r--. 1 resty resty 1.5K Mar 20 20:34 2020-03-20_20-34-10_error.log.tar.gz
-rw-r--r--. 1 resty resty 0 Mar 20 20:34 access.log
-rw-r--r--. 1 resty resty 1.5K Mar 20 21:31 error.log
```
## Delete Plugin
To remove the `log-rotate` Plugin, you can remove it from your configuration file (`conf/config.yaml`):
```yaml title="conf/config.yaml"
plugins:
# - log-rotate
```

Some files were not shown because too many files have changed in this diff Show More