This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 4278bc9ae5 fix(redis): send the TLS SNI and add redis_server_name
(#13938)
4278bc9ae5 is described below
commit 4278bc9ae5e6322a0ef222e047e8395f05829b62
Author: Mohammad Izzraff Janius
<[email protected]>
AuthorDate: Tue Sep 15 16:15:42 2026 +0900
fix(redis): send the TLS SNI and add redis_server_name (#13938)
---
apisix/plugins/limit-count/util.lua | 14 +-
apisix/utils/redis-schema.lua | 3 +
apisix/utils/redis.lua | 14 +-
docs/en/latest/plugins/ai-cache.md | 1 +
docs/en/latest/plugins/ai-rate-limiting.md | 1 +
docs/en/latest/plugins/graphql-limit-count.md | 1 +
docs/en/latest/plugins/limit-conn.md | 1 +
docs/en/latest/plugins/limit-count.md | 1 +
docs/en/latest/plugins/limit-req.md | 1 +
docs/zh/latest/plugins/ai-cache.md | 1 +
docs/zh/latest/plugins/graphql-limit-count.md | 1 +
docs/zh/latest/plugins/limit-conn.md | 1 +
docs/zh/latest/plugins/limit-count.md | 1 +
docs/zh/latest/plugins/limit-req.md | 1 +
t/utils/redis-schema.t | 41 +++
t/utils/redis-sni.t | 478 ++++++++++++++++++++++++++
16 files changed, 559 insertions(+), 2 deletions(-)
diff --git a/apisix/plugins/limit-count/util.lua
b/apisix/plugins/limit-count/util.lua
index 19c1751b2d..7b66c89e3b 100644
--- a/apisix/plugins/limit-count/util.lua
+++ b/apisix/plugins/limit-count/util.lua
@@ -45,7 +45,7 @@ function _M.redis_cli(conf)
-- so connections with different databases, credentials or TLS settings
-- must not share the default host:port keepalive pool, otherwise a
-- reused connection may be bound to an unexpected database or user, or
- -- skip the expected certificate verification
+ -- skip the expected certificate verification / present the wrong SNI
local scheme = "redis"
if conf.redis_ssl then
scheme = conf.redis_ssl_verify and "rediss-verify" or "rediss"
@@ -56,11 +56,23 @@ function _M.redis_cli(conf)
-- digest instead of the plaintext credentials in the pool name
pool = pool .. "#" .. crc32((conf.redis_username or "") .. ":" ..
conf.redis_password)
end
+ if conf.redis_ssl and conf.redis_server_name then
+ pool = pool .. "#" .. conf.redis_server_name
+ end
+
+ local server_name
+ if conf.redis_ssl then
+ server_name = conf.redis_server_name or conf.redis_host
+ if core.utils.parse_ipv4(server_name) or
core.utils.parse_ipv6(server_name) then
+ server_name = nil
+ end
+ end
local sock_opts = {
ssl = conf.redis_ssl,
ssl_verify = conf.redis_ssl_verify,
pool = pool,
+ server_name = server_name,
}
local ok, err = red:connect(conf.redis_host, conf.redis_port or 6379,
sock_opts)
diff --git a/apisix/utils/redis-schema.lua b/apisix/utils/redis-schema.lua
index 991919a2a9..b722d5b8f6 100644
--- a/apisix/utils/redis-schema.lua
+++ b/apisix/utils/redis-schema.lua
@@ -44,6 +44,9 @@ local policy_to_additional_properties = {
redis_ssl_verify = {
type = "boolean", default = false,
},
+ redis_server_name = {
+ type = "string", minLength = 1,
+ },
redis_keepalive_timeout = {
type = "integer", minimum = 1000, default = 10000
},
diff --git a/apisix/utils/redis.lua b/apisix/utils/redis.lua
index 5634185925..8af17db692 100644
--- a/apisix/utils/redis.lua
+++ b/apisix/utils/redis.lua
@@ -31,7 +31,7 @@ local function redis_cli(conf)
-- so connections with different databases, credentials or TLS settings
-- must not share the default host:port keepalive pool, otherwise a
-- reused connection may be bound to an unexpected database or user, or
- -- skip the expected certificate verification
+ -- skip the expected certificate verification / present the wrong SNI
local scheme = "redis"
if conf.redis_ssl then
scheme = conf.redis_ssl_verify and "rediss-verify" or "rediss"
@@ -42,11 +42,23 @@ local function redis_cli(conf)
-- digest instead of the plaintext credentials in the pool name
pool = pool .. "#" .. crc32((conf.redis_username or "") .. ":" ..
conf.redis_password)
end
+ if conf.redis_ssl and conf.redis_server_name then
+ pool = pool .. "#" .. conf.redis_server_name
+ end
+
+ local server_name
+ if conf.redis_ssl then
+ server_name = conf.redis_server_name or conf.redis_host
+ if core.utils.parse_ipv4(server_name) or
core.utils.parse_ipv6(server_name) then
+ server_name = nil
+ end
+ end
local sock_opts = {
ssl = conf.redis_ssl,
ssl_verify = conf.redis_ssl_verify,
pool = pool,
+ server_name = server_name,
}
local ok, err = red:connect(conf.redis_host, conf.redis_port or 6379,
sock_opts)
diff --git a/docs/en/latest/plugins/ai-cache.md
b/docs/en/latest/plugins/ai-cache.md
index 3443f86b84..6018ddf1e7 100644
--- a/docs/en/latest/plugins/ai-cache.md
+++ b/docs/en/latest/plugins/ai-cache.md
@@ -89,6 +89,7 @@ For the `passthrough` protocol, `ai-proxy` forwards the
client's request method,
| redis_timeout | integer | False | 1000 | >= 1 | Redis timeout value in
milliseconds. |
| redis_ssl | boolean | False | false | | If true, use SSL to connect to
Redis. |
| redis_ssl_verify | boolean | False | false | | If true, verify the Redis
server SSL certificate. |
+| redis_server_name | string | False | | | TLS SNI when `redis_ssl` is true.
Defaults to `redis_host`. When `redis_ssl_verify` is true the certificate must
also match this name, so set it when `redis_host` is an alias the certificate
does not cover. |
| redis_keepalive_timeout | integer | False | 10000 | >= 1000 | Keepalive
timeout, in milliseconds, for the Redis connection pool. |
| redis_keepalive_pool | integer | False | 100 | >= 1 | Maximum number of
connections in the Redis keepalive pool. |
diff --git a/docs/en/latest/plugins/ai-rate-limiting.md
b/docs/en/latest/plugins/ai-rate-limiting.md
index acf1367a9b..4d8757fae9 100644
--- a/docs/en/latest/plugins/ai-rate-limiting.md
+++ b/docs/en/latest/plugins/ai-rate-limiting.md
@@ -65,6 +65,7 @@ The `ai-rate-limiting` Plugin enforces token-based rate
limiting for requests se
| redis_timeout | integer | False | 1000 | [1,...] | The Redis timeout value
in milliseconds when `policy` is `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | If true, use SSL to connect to Redis
when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server
SSL certificate when `policy` is `redis`. |
+| redis_server_name | string | False | | | TLS SNI when `policy` is `redis`
and `redis_ssl` is true. Defaults to `redis_host`. When `redis_ssl_verify` is
true the certificate must also match this name, so set it when `redis_host` is
an alias the certificate does not cover. |
| redis_cluster_nodes | array[string] | False | | | The list of Redis cluster
nodes with at least one address. 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 when `policy` is `redis-cluster`. |
diff --git a/docs/en/latest/plugins/graphql-limit-count.md
b/docs/en/latest/plugins/graphql-limit-count.md
index f5c1f4979b..e74e2fc743 100644
--- a/docs/en/latest/plugins/graphql-limit-count.md
+++ b/docs/en/latest/plugins/graphql-limit-count.md
@@ -75,6 +75,7 @@ This Plugin shares the same schema as the
[limit-count](./limit-count.md) Plugin
| redis_password | string | False | | | Password of the Redis node. Used when
`policy` is `redis` or `redis-cluster`. |
| redis_ssl | boolean | False | false | | When true, use SSL to connect to
Redis. Used when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | When true, verify the Redis
server SSL certificate. Used when `policy` is `redis`. |
+| redis_server_name | string | False | | | TLS SNI when `policy` is `redis`
and `redis_ssl` is true. Defaults to `redis_host`. When `redis_ssl_verify` is
true the certificate must also match this name, so set it when `redis_host` is
an alias the certificate does not cover. |
| redis_database | integer | False | 0 | >= 0 | The Redis database number.
Used when `policy` is `redis`. |
| redis_timeout | integer | False | 1000 | [1,...] | Redis timeout in
milliseconds. Used when `policy` is `redis` or `redis-cluster`. |
| redis_cluster_nodes | array[string] | False | | | List of Redis cluster node
addresses. Required when `policy` is `redis-cluster`. |
diff --git a/docs/en/latest/plugins/limit-conn.md
b/docs/en/latest/plugins/limit-conn.md
index a92ecf2db3..56902f13f1 100644
--- a/docs/en/latest/plugins/limit-conn.md
+++ b/docs/en/latest/plugins/limit-conn.md
@@ -57,6 +57,7 @@ The `limit-conn` Plugin limits the rate of requests by the
number of concurrent
| 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
when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server
SSL certificate when `policy` is `redis`. |
+| redis_server_name | string | False | | | TLS SNI when `policy` is `redis`
and `redis_ssl` is true. Defaults to `redis_host`. When `redis_ssl_verify` is
true the certificate must also match this name, so set it when `redis_host` is
an alias the certificate does not cover. |
| 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_keepalive_timeout | integer | False | 10000 | >= 1000 | Keepalive
timeout in milliseconds for Redis when `policy` is `redis` or `redis-cluster`. |
diff --git a/docs/en/latest/plugins/limit-count.md
b/docs/en/latest/plugins/limit-count.md
index 19fc6a6ac4..c1b938c8e9 100644
--- a/docs/en/latest/plugins/limit-count.md
+++ b/docs/en/latest/plugins/limit-count.md
@@ -81,6 +81,7 @@ Redis Sentinel, sliding windows, and delayed Redis
synchronization are supported
| redis_password | string | False
| | | The password of the
Redis node when `policy` is `redis`, `redis-cluster`, or `redis-sentinel`. |
| redis_ssl | boolean | False
| false | | If true, use SSL to
connect to Redis when `policy` is `redis`. |
| redis_ssl_verify | boolean | False
| false | | If true, verify the
server SSL certificate when `policy` is `redis`. |
+| redis_server_name | string | False
| | | TLS SNI when `policy`
is `redis` and `redis_ssl` is true. Defaults to `redis_host`. When
`redis_ssl_verify` is true the certificate must also match this name, so set it
when `redis_host` is an alias the certificate does not cover. |
| redis_database | integer | False
| 0 | >= 0 | The database number
in Redis when `policy` is `redis` or `redis-sentinel`. |
| redis_timeout | integer | False
| 1000 | [1,...] | The Redis timeout
value in milliseconds when `policy` is `redis` or `redis-cluster`. |
| redis_keepalive_timeout | integer | False
| 10000 (`redis` and `redis-cluster`); 60000 (`redis-sentinel`) | >=
1000 (`redis` and `redis-cluster`); >= 1 (`redis-sentinel`) | Keepalive timeout
in milliseconds for Redis connections. |
diff --git a/docs/en/latest/plugins/limit-req.md
b/docs/en/latest/plugins/limit-req.md
index 59c65d8e54..eaf771ea3a 100644
--- a/docs/en/latest/plugins/limit-req.md
+++ b/docs/en/latest/plugins/limit-req.md
@@ -64,6 +64,7 @@ The `limit-req` Plugin supports two modes of rate limiting:
| 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
when `policy` is `redis`. |
| redis_ssl_verify | boolean | False | false | | If true, verify the server
SSL certificate when `policy` is `redis`. |
+| redis_server_name | string | False | | | TLS SNI when `policy` is `redis`
and `redis_ssl` is true. Defaults to `redis_host`. When `redis_ssl_verify` is
true the certificate must also match this name, so set it when `redis_host` is
an alias the certificate does not cover. |
| 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_keepalive_timeout | integer | False | 10000 | ≥ 1000 | Keepalive
timeout in milliseconds for redis when `policy` is `redis` or `redis-cluster`. |
diff --git a/docs/zh/latest/plugins/ai-cache.md
b/docs/zh/latest/plugins/ai-cache.md
index db4d50fc2b..794313ff4a 100644
--- a/docs/zh/latest/plugins/ai-cache.md
+++ b/docs/zh/latest/plugins/ai-cache.md
@@ -89,6 +89,7 @@ import TabItem from '@theme/TabItem';
| redis_timeout | integer | 否 | 1000 | >= 1 | Redis 超时时间,单位为毫秒。 |
| redis_ssl | boolean | 否 | false | | 如果为 true,则使用 SSL 连接 Redis。 |
| redis_ssl_verify | boolean | 否 | false | | 如果为 true,则校验 Redis 服务器的 SSL 证书。 |
+| redis_server_name | string | 否 | | | 当 `redis_ssl` 为 true 时使用的 TLS SNI。默认使用
`redis_host`。当 `redis_ssl_verify` 为 true 时,证书还必须与该名称匹配,因此当 `redis_host`
是证书未覆盖的别名时请设置此项。 |
| redis_keepalive_timeout | integer | 否 | 10000 | >= 1000 | Redis
连接池的保活超时时间,单位为毫秒。 |
| redis_keepalive_pool | integer | 否 | 100 | >= 1 | Redis 保活连接池中的最大连接数。 |
diff --git a/docs/zh/latest/plugins/graphql-limit-count.md
b/docs/zh/latest/plugins/graphql-limit-count.md
index 96c2b8347c..5bfc13aa33 100644
--- a/docs/zh/latest/plugins/graphql-limit-count.md
+++ b/docs/zh/latest/plugins/graphql-limit-count.md
@@ -70,6 +70,7 @@ description: graphql-limit-count 插件使用固定窗口算法,基于 GraphQL
| redis_password | string | 否 | | | Redis 节点密码。`policy` 为 `redis` 或
`redis-cluster` 时使用。 |
| redis_ssl | boolean | 否 | false | | 为 true 时使用 SSL 连接 Redis。`policy` 为
`redis` 时使用。 |
| redis_ssl_verify | boolean | 否 | false | | 为 true 时验证 Redis 服务端 SSL
证书。`policy` 为 `redis` 时使用。 |
+| redis_server_name | string | 否 | | | 当 `policy` 为 `redis` 且 `redis_ssl` 为
true 时使用的 TLS SNI。默认使用 `redis_host`。当 `redis_ssl_verify` 为 true
时,证书还必须与该名称匹配,因此当 `redis_host` 是证书未覆盖的别名时请设置此项。 |
| redis_database | integer | 否 | 0 | >= 0 | Redis 数据库编号。`policy` 为 `redis`
时使用。 |
| redis_timeout | integer | 否 | 1000 | [1,...] | Redis 超时时间(毫秒)。`policy` 为
`redis` 或 `redis-cluster` 时使用。 |
| redis_cluster_nodes | array[string] | 否 | | | Redis 集群节点地址列表。`policy` 为
`redis-cluster` 时必填。 |
diff --git a/docs/zh/latest/plugins/limit-conn.md
b/docs/zh/latest/plugins/limit-conn.md
index 85d75a6f19..f5cba87668 100644
--- a/docs/zh/latest/plugins/limit-conn.md
+++ b/docs/zh/latest/plugins/limit-conn.md
@@ -57,6 +57,7 @@ import TabItem from '@theme/TabItem';
| redis_password | string | 否 | | | 当 `policy` 为 `redis` 或 `redis-cluster`
时,Redis 节点的密码。|
| redis_ssl | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis` 时使用 SSL
连接到 Redis。|
| redis_ssl_verify | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis`
时验证服务器 SSL 证书。|
+| redis_server_name | string | 否 | | | 当 `policy` 为 `redis` 且 `redis_ssl` 为
true 时使用的 TLS SNI。默认使用 `redis_host`。当 `redis_ssl_verify` 为 true
时,证书还必须与该名称匹配,因此当 `redis_host` 是证书未覆盖的别名时请设置此项。|
| redis_database | integer | 否 | 0 | >= 0 | 当 `policy` 为 `redis` 时,Redis
中的数据库编号。|
| redis_timeout | integer | 否 | 1000 | >= 1 | 当 `policy` 为 `redis` 或
`redis-cluster` 时,Redis 超时值(以毫秒为单位)。|
| redis_keepalive_timeout | integer | 否 | 10000 | >= 1000 | 当 `policy` 为
`redis` 或 `redis-cluster` 时,Redis 的空闲连接超时时间(以毫秒为单位)。|
diff --git a/docs/zh/latest/plugins/limit-count.md
b/docs/zh/latest/plugins/limit-count.md
index 69202ff51c..2029f3e29b 100644
--- a/docs/zh/latest/plugins/limit-count.md
+++ b/docs/zh/latest/plugins/limit-count.md
@@ -82,6 +82,7 @@ APISIX 3.18.0 及后续版本支持 Redis Sentinel、滑动窗口和 Redis 延
| redis_password | string | 否 | | | 当 `policy` 为 `redis`、`redis-cluster` 或
`redis-sentinel` 时,Redis 节点的密码。 |
| redis_ssl | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis` 时使用 SSL
连接 Redis。 |
| redis_ssl_verify | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis`
时验证服务器 SSL 证书。 |
+| redis_server_name | string | 否 | | | 当 `policy` 为 `redis` 且 `redis_ssl` 为
true 时使用的 TLS SNI。默认使用 `redis_host`。当 `redis_ssl_verify` 为 true
时,证书还必须与该名称匹配,因此当 `redis_host` 是证书未覆盖的别名时请设置此项。 |
| redis_database | integer | 否 | 0 | >= 0 | 当 `policy` 为 `redis` 或
`redis-sentinel` 时,Redis 中的数据库编号。 |
| redis_timeout | integer | 否 | 1000 | [1,...] | 当 `policy` 为 `redis` 或
`redis-cluster` 时,Redis 超时值(以毫秒为单位)。 |
| redis_keepalive_timeout | integer | 否 | `redis` 和 `redis-cluster` 为
10000;`redis-sentinel` 为 60000 | `redis` 和 `redis-cluster` >=
1000;`redis-sentinel` >= 1 | Redis 空闲连接超时时间,单位为毫秒。 |
diff --git a/docs/zh/latest/plugins/limit-req.md
b/docs/zh/latest/plugins/limit-req.md
index 91d92b8f68..40a3e8aaaf 100644
--- a/docs/zh/latest/plugins/limit-req.md
+++ b/docs/zh/latest/plugins/limit-req.md
@@ -64,6 +64,7 @@ import TabItem from '@theme/TabItem';
| redis_password | string | 否 | | | 当 `policy` 为 `redis` 或 `redis-cluster`
时,Redis 节点的密码。|
| redis_ssl | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis` 时使用 SSL
连接到 Redis。|
| redis_ssl_verify | boolean | 否 | false | | 如果为 true,则在 `policy` 为 `redis`
时验证服务器 SSL 证书。|
+| redis_server_name | string | 否 | | | 当 `policy` 为 `redis` 且 `redis_ssl` 为
true 时使用的 TLS SNI。默认使用 `redis_host`。当 `redis_ssl_verify` 为 true
时,证书还必须与该名称匹配,因此当 `redis_host` 是证书未覆盖的别名时请设置此项。|
| redis_database | integer | 否 | 0 | >= 0 | 当 `policy` 为 `redis` 时,Redis
中的数据库编号。|
| redis_timeout | integer | 否 | 1000 | [1,...] | 当 `policy` 为 `redis` 或
`redis-cluster` 时,Redis 超时值(以毫秒为单位)。|
| redis_keepalive_timeout | integer | 否 | 10000 | ≥ 1000 | 当 `policy` 为
`redis` 或 `redis-cluster` 时,与 Redis 的空闲连接超时时间,单位为毫秒。|
diff --git a/t/utils/redis-schema.t b/t/utils/redis-schema.t
index 493aedcb94..7557efee7c 100644
--- a/t/utils/redis-schema.t
+++ b/t/utils/redis-schema.t
@@ -152,3 +152,44 @@ done
--- response_body
3600
60
+
+
+
+=== TEST 3: redis_server_name is accepted for redis policy and rejected when
empty
+--- config
+ location /t {
+ content_by_lua_block {
+ local plugin = require("apisix.plugins.limit-count")
+
+ local ok, err = plugin.check_schema({
+ count = 2,
+ time_window = 60,
+ policy = "redis",
+ redis_host = "127.0.0.1",
+ redis_ssl = true,
+ redis_server_name = "redis.example.com",
+ })
+ if not ok then
+ ngx.say(err)
+ return
+ end
+ ngx.say("passed")
+
+ ok, err = plugin.check_schema({
+ count = 2,
+ time_window = 60,
+ policy = "redis",
+ redis_host = "127.0.0.1",
+ redis_ssl = true,
+ redis_server_name = "",
+ })
+ if ok then
+ ngx.say("empty redis_server_name should fail")
+ return
+ end
+ ngx.say("rejected")
+ }
+ }
+--- response_body
+passed
+rejected
diff --git a/t/utils/redis-sni.t b/t/utils/redis-sni.t
new file mode 100644
index 0000000000..5ad1942bfc
--- /dev/null
+++ b/t/utils/redis-sni.t
@@ -0,0 +1,478 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+no_long_string();
+no_shuffle();
+no_root_location();
+
+add_block_preprocessor(sub {
+ my ($block) = @_;
+
+ # TLS fronts in front of the plain redis on 6379, like a name-routed cloud
+ # endpoint: 6395 only reaches redis when the SNI is "test.com", any other
+ # SNI (or none) lands on the plain HTTP test server; 6396 reaches redis
+ # regardless of SNI. test.com resolves to 127.0.0.1 via /etc/hosts.
+ # Only the request blocks enable it: stream_enable replaces location /t.
+ $block->set_value("extra_stream_config", <<_EOC_);
+ server {
+ listen 6395 ssl;
+ server_name test.com;
+ ssl_certificate ../../certs/apisix.crt;
+ ssl_certificate_key ../../certs/apisix.key;
+ proxy_pass 127.0.0.1:6379;
+ }
+ server {
+ listen 6395 ssl default_server;
+ server_name _;
+ ssl_certificate ../../certs/apisix.crt;
+ ssl_certificate_key ../../certs/apisix.key;
+ proxy_pass 127.0.0.1:1980;
+ }
+ server {
+ listen 6396 ssl;
+ ssl_certificate ../../certs/apisix.crt;
+ ssl_certificate_key ../../certs/apisix.key;
+ proxy_pass 127.0.0.1:6379;
+ }
+_EOC_
+
+ if (!$block->request) {
+ $block->set_value("request", "GET /t");
+ }
+
+ if (!$block->error_log && !$block->no_error_log) {
+ $block->set_value("no_error_log", "[error]\n[alert]");
+ }
+
+ my $extra_init_worker_by_lua = $block->extra_init_worker_by_lua // "";
+ $extra_init_worker_by_lua .= <<_EOC_;
+ require("lib.test_redis").flush_all()
+_EOC_
+ $block->set_value("extra_init_worker_by_lua", $extra_init_worker_by_lua);
+});
+
+run_tests;
+
+__DATA__
+
+=== TEST 1: limit-count: redis behind a TLS front that routes by SNI
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "test.com",
+ "redis_port": 6395,
+ "redis_ssl": true,
+ "redis_ssl_verify": false
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 2: the SNI reaches redis, the counter works
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+
+
+
+=== TEST 3: limit-count: redis_ssl_verify also checks the certificate against
redis_host
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "test.com",
+ "redis_port": 6395,
+ "redis_ssl": true,
+ "redis_ssl_verify": true
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 4: verified connection, the counter works
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+
+
+
+=== TEST 5: limit-count: an IP literal redis_host sends no SNI and skips the
host check
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "127.0.0.1",
+ "redis_port": 6396,
+ "redis_ssl": true,
+ "redis_ssl_verify": true
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 6: the counter works although the certificate is for test.com
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+
+
+
+=== TEST 7: limit-req (shared redis util): redis behind the SNI-routed front
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-req": {
+ "rate": 4,
+ "burst": 1,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "test.com",
+ "redis_port": 6395,
+ "redis_ssl": true,
+ "redis_ssl_verify": true
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 8: the SNI reaches redis, the limiter works
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200]
+
+
+
+=== TEST 9: limit-count: redis_server_name overrides the SNI when redis_host
is an IP
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "127.0.0.1",
+ "redis_port": 6395,
+ "redis_ssl": true,
+ "redis_ssl_verify": true,
+ "redis_server_name": "test.com"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 10: the SNI-routed front reaches redis and the certificate matches
the SNI
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]
+
+
+
+=== TEST 11: two routes with different redis_server_name must not share a
keepalive pool
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ for i, sni in ipairs({"test.com", "other.com"}) do
+ local code, body = t('/apisix/admin/routes/' .. i,
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello]] .. (i == 1 and "" or "1") .. [[",
+ "plugins": {
+ "limit-count": {
+ "count": 100,
+ "time_window": 60,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "127.0.0.1",
+ "redis_port": 6395,
+ "redis_ssl": true,
+ "redis_server_name": "]] .. sni .. [["
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ ngx.say(body)
+ return
+ end
+ end
+ ngx.say("passed")
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 12: the route whose SNI lands on the HTTP server never borrows the
other route's redis connection
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello1", "GET /hello", "GET /hello1"]
+--- error_code eval
+[200, 500, 200, 500]
+--- no_error_log
+[alert]
+
+
+
+=== TEST 13: limit-count: a DNS redis_host not covered by the certificate
fails the host check
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "admin.apisix.dev",
+ "redis_port": 6396,
+ "redis_ssl": true,
+ "redis_ssl_verify": true
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 14: the certificate is for test.com, so the verified connection is
refused
+--- stream_enable
+--- request
+GET /hello
+--- error_code: 500
+--- error_log
+certificate host mismatch
+
+
+
+=== TEST 15: limit-count: redis_server_name names the certificate host for
that DNS alias
+--- config
+ location /t {
+ content_by_lua_block {
+ local t = require("lib.test_admin").test
+ local code, body = t('/apisix/admin/routes/1',
+ ngx.HTTP_PUT,
+ [[{
+ "uri": "/hello",
+ "plugins": {
+ "limit-count": {
+ "count": 2,
+ "time_window": 60,
+ "rejected_code": 503,
+ "key": "remote_addr",
+ "policy": "redis",
+ "redis_host": "admin.apisix.dev",
+ "redis_port": 6396,
+ "redis_ssl": true,
+ "redis_ssl_verify": true,
+ "redis_server_name": "test.com"
+ }
+ },
+ "upstream": {
+ "nodes": {
+ "127.0.0.1:1980": 1
+ },
+ "type": "roundrobin"
+ }
+ }]]
+ )
+ if code >= 300 then
+ ngx.status = code
+ end
+ ngx.say(body)
+ }
+ }
+--- response_body
+passed
+
+
+
+=== TEST 16: the override matches the certificate, the counter works
+--- stream_enable
+--- pipelined_requests eval
+["GET /hello", "GET /hello", "GET /hello"]
+--- error_code eval
+[200, 200, 503]