You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by sp...@apache.org on 2021/06/11 09:23:26 UTC

[apisix] branch master updated: feat(stream): accept tls over tcp (#4409)

This is an automated email from the ASF dual-hosted git repository.

spacewander pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git


The following commit(s) were added to refs/heads/master by this push:
     new b822423  feat(stream): accept tls over tcp (#4409)
b822423 is described below

commit b822423b9a1f0f42537462356697d128c4fa897d
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Fri Jun 11 17:23:16 2021 +0800

    feat(stream): accept tls over tcp (#4409)
---
 README.md                                   |   2 +-
 apisix/cli/ngx_tpl.lua                      |  13 +++-
 apisix/cli/ops.lua                          |  35 +++++++++
 apisix/init.lua                             |  19 +++++
 apisix/router.lua                           |   6 ++
 ci/linux_apisix_current_luarocks_runner.sh  |   3 +
 conf/config-default.yaml                    |   5 +-
 docs/ar/README.md                           |   2 +-
 docs/en/latest/admin-api.md                 |   2 +-
 docs/en/latest/{https.md => certificate.md} |   2 +-
 docs/en/latest/config.json                  |   2 +-
 docs/en/latest/grpc-proxy.md                |   2 +-
 docs/en/latest/stream-proxy.md              |  38 +++++++++-
 docs/es/latest/README.md                    |   2 +-
 docs/zh/latest/README.md                    |   2 +-
 docs/zh/latest/admin-api.md                 |   2 +-
 docs/zh/latest/{https.md => certificate.md} |   2 +-
 docs/zh/latest/config.json                  |   2 +-
 docs/zh/latest/grpc-proxy.md                |   2 +-
 docs/zh/latest/stream-proxy.md              |  32 +++++++++
 t/APISIX.pm                                 |  55 +++++++++++++++
 t/cli/test_tls_over_tcp.sh                  |  58 +++++++++++++++
 t/stream-node/tls.t                         | 106 ++++++++++++++++++++++++++++
 utils/create-ssl.py                         |  41 +++++++++++
 24 files changed, 416 insertions(+), 19 deletions(-)

diff --git a/README.md b/README.md
index 7c752db..e5e1b27 100644
--- a/README.md
+++ b/README.md
@@ -74,7 +74,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against
   - Proxy Protocol
   - Proxy Dubbo: Dubbo Proxy based on Tengine.
   - HTTP(S) Forward Proxy
-  - [SSL](docs/en/latest/https.md): Dynamically load an SSL certificate.
+  - [SSL](docs/en/latest/certificate.md): Dynamically load an SSL certificate.
 
 - **Full Dynamic**
 
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index eeb36f4..6d48056 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -99,13 +99,22 @@ stream {
     }
 
     server {
-        {% for _, addr in ipairs(stream_proxy.tcp or {}) do %}
-        listen {*addr*} {% if enable_reuseport then %} reuseport {% end %} {% if proxy_protocol and proxy_protocol.enable_tcp_pp then %} proxy_protocol {% end %};
+        {% for _, item in ipairs(stream_proxy.tcp or {}) do %}
+        listen {*item.addr*} {% if item.tls then %} ssl {% end %} {% if enable_reuseport then %} reuseport {% end %} {% if proxy_protocol and proxy_protocol.enable_tcp_pp then %} proxy_protocol {% end %};
         {% end %}
         {% for _, addr in ipairs(stream_proxy.udp or {}) do %}
         listen {*addr*} udp {% if enable_reuseport then %} reuseport {% end %};
         {% end %}
 
+        {% if tcp_enable_ssl then %}
+        ssl_certificate      {* ssl.ssl_cert *};
+        ssl_certificate_key  {* ssl.ssl_cert_key *};
+
+        ssl_certificate_by_lua_block {
+            apisix.stream_ssl_phase()
+        }
+        {% end %}
+
         {% if proxy_protocol and proxy_protocol.enable_tcp_pp_to_upstream then %}
         proxy_protocol on;
         {% end %}
diff --git a/apisix/cli/ops.lua b/apisix/cli/ops.lua
index 8f8716b..49d0be1 100644
--- a/apisix/cli/ops.lua
+++ b/apisix/cli/ops.lua
@@ -192,6 +192,25 @@ local config_schema = {
                                     {
                                         type = "string",
                                     },
+                                    {
+                                        type = "object",
+                                        properties = {
+                                            addr = {
+                                                anyOf = {
+                                                    {
+                                                        type = "integer",
+                                                    },
+                                                    {
+                                                        type = "string",
+                                                    },
+                                                }
+                                            },
+                                            tls = {
+                                                type = "boolean",
+                                            }
+                                        },
+                                        required = {"addr"}
+                                    },
                                 },
                             },
                             uniqueItems = true,
@@ -435,6 +454,21 @@ Please modify "admin_key" in conf/config.yaml .
     yaml_conf.apisix.ssl.ssl_cert = "cert/ssl_PLACE_HOLDER.crt"
     yaml_conf.apisix.ssl.ssl_cert_key = "cert/ssl_PLACE_HOLDER.key"
 
+    local tcp_enable_ssl
+    -- compatible with the original style which only has the addr
+    if yaml_conf.apisix.stream_proxy and yaml_conf.apisix.stream_proxy.tcp then
+        local tcp = yaml_conf.apisix.stream_proxy.tcp
+        for i, item in ipairs(tcp) do
+            if type(item) ~= "table" then
+                tcp[i] = {addr = item}
+            else
+                if item.tls then
+                    tcp_enable_ssl = true
+                end
+            end
+        end
+    end
+
     local dubbo_upstream_multiplex_count = 32
     if yaml_conf.plugin_attr and yaml_conf.plugin_attr["dubbo-proxy"] then
         local dubbo_conf = yaml_conf.plugin_attr["dubbo-proxy"]
@@ -460,6 +494,7 @@ Please modify "admin_key" in conf/config.yaml .
         error_log = {level = "warn"},
         enabled_plugins = enabled_plugins,
         dubbo_upstream_multiplex_count = dubbo_upstream_multiplex_count,
+        tcp_enable_ssl = tcp_enable_ssl,
     }
 
     if not yaml_conf.apisix then
diff --git a/apisix/init.lua b/apisix/init.lua
index f3b53f0..de9ac1e 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -761,6 +761,25 @@ function _M.http_control()
 end
 
 
+function _M.stream_ssl_phase()
+    local ngx_ctx = ngx.ctx
+    local api_ctx = ngx_ctx.api_ctx
+
+    if api_ctx == nil then
+        api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
+        ngx_ctx.api_ctx = api_ctx
+    end
+
+    local ok, err = router.router_ssl.match_and_set(api_ctx)
+    if not ok then
+        if err then
+            core.log.error("failed to fetch ssl config: ", err)
+        end
+        ngx_exit(-1)
+    end
+end
+
+
 function _M.stream_init(args)
     core.log.info("enter stream_init")
 
diff --git a/apisix/router.lua b/apisix/router.lua
index 840f0cd..9bdafeb 100644
--- a/apisix/router.lua
+++ b/apisix/router.lua
@@ -106,9 +106,15 @@ end
 
 
 function _M.stream_init_worker()
+    local router_ssl_name = "radixtree_sni"
+
     local router_stream = require("apisix.stream.router.ip_port")
     router_stream.stream_init_worker(filter)
     _M.router_stream = router_stream
+
+    local router_ssl = require("apisix.ssl.router." .. router_ssl_name)
+    router_ssl.init_worker()
+    _M.router_ssl = router_ssl
 end
 
 
diff --git a/ci/linux_apisix_current_luarocks_runner.sh b/ci/linux_apisix_current_luarocks_runner.sh
index e055c4c..3ca7c0e 100755
--- a/ci/linux_apisix_current_luarocks_runner.sh
+++ b/ci/linux_apisix_current_luarocks_runner.sh
@@ -58,6 +58,9 @@ script() {
     # apisix cli test
     ./utils/set-dns.sh
 
+    # install test dependencies
+    sudo pip install requests
+
     for f in ./t/cli/test_*.sh; do
         sudo PATH="$PATH" "$f"
     done
diff --git a/conf/config-default.yaml b/conf/config-default.yaml
index 3aefd6f..c2c124e 100644
--- a/conf/config-default.yaml
+++ b/conf/config-default.yaml
@@ -98,8 +98,9 @@ apisix:
     ssl: 'radixtree_sni'          # radixtree_sni: match route by SNI(base on radixtree)
   #stream_proxy:                  # TCP/UDP proxy
   #  tcp:                         # TCP proxy port list
-  #    - 9100
-  #    - "127.0.0.1:9101"
+  #    - addr: 9100
+  #      tls: true
+  #    - addr: "127.0.0.1:9101"
   #  udp:                         # UDP proxy port list
   #    - 9200
   #    - "127.0.0.1:9201"
diff --git a/docs/ar/README.md b/docs/ar/README.md
index c2ac6b0..26ef9fe 100644
--- a/docs/ar/README.md
+++ b/docs/ar/README.md
@@ -79,7 +79,7 @@
   - بروتوكول الوكيل
   - الوكيل  Dubbo: Dubbo يعتمد على  Tengine.
   - HTTP(S)	وكيل إعادة التوجيه
-  - [SSL](docs/en/latest/https.md): تحميل شهادة SSL ديناميكيًا.
+  - [SSL](docs/en/latest/certificate.md): تحميل شهادة SSL ديناميكيًا.
 
 - **ديناميكية كاملة**
 
diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md
index 2ea7aef..bf60739 100644
--- a/docs/en/latest/admin-api.md
+++ b/docs/en/latest/admin-api.md
@@ -812,7 +812,7 @@ Config Example:
 }
 ```
 
-More examples can be found in [HTTPS](./https.md).
+More examples can be found in [Certificate](./certificate.md).
 
 ## Global Rule
 
diff --git a/docs/en/latest/https.md b/docs/en/latest/certificate.md
similarity index 99%
rename from docs/en/latest/https.md
rename to docs/en/latest/certificate.md
index 30c2325..5507e5e 100644
--- a/docs/en/latest/https.md
+++ b/docs/en/latest/certificate.md
@@ -1,5 +1,5 @@
 ---
-title: HTTPS
+title: Certificate
 ---
 
 <!--
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index 241acbf..39ff50c 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -185,7 +185,7 @@
         },
         {
           "type": "doc",
-          "id": "https"
+          "id": "certificate"
         },
         {
           "type": "doc",
diff --git a/docs/en/latest/grpc-proxy.md b/docs/en/latest/grpc-proxy.md
index ca4d05b..dd71f03 100644
--- a/docs/en/latest/grpc-proxy.md
+++ b/docs/en/latest/grpc-proxy.md
@@ -36,7 +36,7 @@ gRPC client -> APISIX -> gRPC/gRPCS server
 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 TLS‑encrypted HTTP/2 to expose gRPC service, so need to [config SSL certificate](https.md)
+* attention: APISIX use TLS‑encrypted 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/iresty/grpc_server_example)
 
diff --git a/docs/en/latest/stream-proxy.md b/docs/en/latest/stream-proxy.md
index 1cfa05c..627d029 100644
--- a/docs/en/latest/stream-proxy.md
+++ b/docs/en/latest/stream-proxy.md
@@ -23,12 +23,12 @@ title: Stream Proxy
 
 TCP is the protocol for many popular applications and services, such as LDAP, MySQL, and RTMP. UDP (User Datagram Protocol) is the protocol for many popular non-transactional applications, such as DNS, syslog, and RADIUS.
 
-APISIX can dynamic load balancing TCP/UDP proxy. In Nginx world, we call TCP/UDP proxy to stream proxy, we followed this statement.
+APISIX can dynamically load balancing TCP/UDP proxy. In Nginx world, we call TCP/UDP proxy to stream proxy, we followed this statement.
 
 ## How to enable stream proxy?
 
 Setting the `stream_proxy` option in `conf/config.yaml`, specify a list of addresses that require dynamic proxy.
-By default, no any stream proxy is enabled.
+By default, no stream proxy is enabled.
 
 ```yaml
 apisix:
@@ -82,6 +82,38 @@ curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f03
 }'
 ```
 
-It means APISIX will proxy the request to `127.0.0.1:1995` which the server address is `127.0.0.1` and the server port is equal `2000`.
+It means APISIX will proxy the request to `127.0.0.1:1995` which the server address is `127.0.0.1` and the server port is equal to `2000`.
 
 Read [Admin API's Stream Route section](./admin-api.md#stream-route) for the complete options list.
+
+## Accept TLS over TCP
+
+APISIX can accept TLS over TCP.
+
+First of all, we need to enable TLS for the TCP address:
+
+```yaml
+apisix:
+  stream_proxy: # TCP/UDP proxy
+    tcp: # TCP proxy address list
+      - addr: 9100
+        tls: true
+```
+
+Second, we need to configure certificate for the given SNI.
+See [Admin API's SSL section](./admin-api.md#ssl) for how to do.
+
+Third, we need to configure a stream route to match and proxy it to the upstream:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "remote_addr": "127.0.0.1",
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1995": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
diff --git a/docs/es/latest/README.md b/docs/es/latest/README.md
index 9f2d086..dd5c0e3 100644
--- a/docs/es/latest/README.md
+++ b/docs/es/latest/README.md
@@ -76,7 +76,7 @@ ensayos A/B, ensayos de despliegue de canarios (canary release), despliegue azul
   - Proxy de Protocolo
   - Proxy Dubbo: Proxy de Dubbo basado en Tengine.
   - Proxy de HTTP(S) hacia adelante
-  - [SSL](../../en/latest/https.md): Carga dinámica de certificado SSL.
+  - [SSL](../../en/latest/certificate.md): Carga dinámica de certificado SSL.
 
 - **Completamente Dinámico**
 
diff --git a/docs/zh/latest/README.md b/docs/zh/latest/README.md
index 198dede..eb470c0 100644
--- a/docs/zh/latest/README.md
+++ b/docs/zh/latest/README.md
@@ -78,7 +78,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
   - Proxy Protocol
   - Dubbo 代理:基于 Tengine,可以实现 Dubbo 请求的代理。
   - HTTP(S) 反向代理
-  - [SSL](https.md):动态加载 SSL 证书。
+  - [SSL](certificate.md):动态加载 SSL 证书。
 
 - **全动态能力**
 
diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md
index 44135b5..499b1f4 100644
--- a/docs/zh/latest/admin-api.md
+++ b/docs/zh/latest/admin-api.md
@@ -815,7 +815,7 @@ ssl 对象 json 配置内容:
 }
 ```
 
-更多的配置示例见 [HTTPS](./https.md)。
+更多的配置示例见 [证书](./certificate.md)。
 
 [Back to TOC](#目录)
 
diff --git a/docs/zh/latest/https.md b/docs/zh/latest/certificate.md
similarity index 99%
rename from docs/zh/latest/https.md
rename to docs/zh/latest/certificate.md
index e8ed5d2..831740f 100644
--- a/docs/zh/latest/https.md
+++ b/docs/zh/latest/certificate.md
@@ -1,5 +1,5 @@
 ---
-title: HTTPS
+title: 证书
 ---
 
 <!--
diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json
index 1d60963..d596e85 100644
--- a/docs/zh/latest/config.json
+++ b/docs/zh/latest/config.json
@@ -168,7 +168,7 @@
         },
         {
           "type": "doc",
-          "id": "https"
+          "id": "certificate"
         },
         {
           "type": "doc",
diff --git a/docs/zh/latest/grpc-proxy.md b/docs/zh/latest/grpc-proxy.md
index 64a0981..e57a652 100644
--- a/docs/zh/latest/grpc-proxy.md
+++ b/docs/zh/latest/grpc-proxy.md
@@ -35,7 +35,7 @@ title: gRPC 代理
 在指定 Route 中,代理 gRPC 服务接口:
 
 * 注意:这个 Route 对应的 Upstream 的 `scheme` 必须设置为 `grpc` 或者 `grpcs`。
-* 注意: APISIX 使用 TLS 加密的 HTTP/2 暴露 gRPC 服务, 所以需要先 [配置 SSL 证书](https.md);
+* 注意: APISIX 使用 TLS 加密的 HTTP/2 暴露 gRPC 服务, 所以需要先 [配置 SSL 证书](certificate.md);
 * 注意: APISIX 也支持通过纯文本的 HTTP/2 暴露 gRPC 服务,这不需要依赖 SSL,通常用于内网环境代理gRPC服务
 * 下面例子所代理的 gRPC 服务可供参考:[grpc_server_example](https://github.com/api7/grpc_server_example)。
 
diff --git a/docs/zh/latest/stream-proxy.md b/docs/zh/latest/stream-proxy.md
index 12c274d..d68587e 100644
--- a/docs/zh/latest/stream-proxy.md
+++ b/docs/zh/latest/stream-proxy.md
@@ -83,3 +83,35 @@ curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f03
 例子中 APISIX 会把服务器地址为 `127.0.0.1`, 端口为 `2000` 代理到上游地址 `127.0.0.1:1995`。
 
 完整的匹配选项列表参见 [Admin API 的 Stream Route](./admin-api.md#stream-route)。
+
+## 接收 TLS over TCP
+
+APISIX 支持接收 TLS over TCP。
+
+首先,我们需要给对应的 TCP 地址启用 TLS:
+
+```yaml
+apisix:
+  stream_proxy: # TCP/UDP proxy
+    tcp: # TCP proxy address list
+      - addr: 9100
+        tls: true
+```
+
+接着,我们需要为给定的 SNI 配置证书。
+具体步骤参考 [Admin API 的 SSL](./admin-api.md#ssl)。
+
+然后,我们需要配置一个 route,匹配连接并代理到上游:
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "remote_addr": "127.0.0.1",
+    "upstream": {
+        "nodes": {
+            "127.0.0.1:1995": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
diff --git a/t/APISIX.pm b/t/APISIX.pm
index f8bd235..200dd86 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -238,6 +238,52 @@ _EOC_
     my $timeout = $block->timeout // 5;
     $block->set_value("timeout", $timeout);
 
+    my $stream_tls_request = $block->stream_tls_request;
+    if ($stream_tls_request) {
+        # generate a springboard to send tls stream request
+        $block->set_value("stream_conf_enable", 1);
+        $block->set_value("request", "GET /stream_tls_request");
+
+        my $sni = "nil";
+        if ($block->stream_sni) {
+            $sni = '"' . $block->stream_sni . '"';
+        }
+        chomp $stream_tls_request;
+
+        my $config = <<_EOC_;
+            location /stream_tls_request {
+                content_by_lua_block {
+                    local sock = ngx.socket.tcp()
+                    local ok, err = sock:connect("127.0.0.1", 2005)
+                    if not ok then
+                        ngx.say("failed to connect: ", err)
+                        return
+                    end
+
+                    local sess, err = sock:sslhandshake(nil, $sni, false)
+                    if not sess then
+                        ngx.say("failed to do SSL handshake: ", err)
+                        return
+                    end
+
+                    local bytes, err = sock:send("$stream_tls_request")
+                    if not bytes then
+                        ngx.say("send stream request error: ", err)
+                        return
+                    end
+                    local data, err = sock:receive("*a")
+                    if not data then
+                        sock:close()
+                        ngx.say("receive stream response error: ", err)
+                        return
+                    end
+                    ngx.print(data)
+                }
+            }
+_EOC_
+        $block->set_value("config", $config)
+    }
+
     my $stream_enable = $block->stream_enable;
     my $stream_conf_enable = $block->stream_conf_enable;
     my $extra_stream_config = $block->extra_stream_config // '';
@@ -297,6 +343,15 @@ _EOC_
     }
 
     my $stream_server_config = $block->stream_server_config // <<_EOC_;
+    listen 2005 ssl;
+    ssl_certificate             cert/apisix.crt;
+    ssl_certificate_key         cert/apisix.key;
+    lua_ssl_trusted_certificate cert/apisix.crt;
+
+    ssl_certificate_by_lua_block {
+        apisix.stream_ssl_phase()
+    }
+
     preread_by_lua_block {
         -- wait for etcd sync
         ngx.sleep($wait_etcd_sync)
diff --git a/t/cli/test_tls_over_tcp.sh b/t/cli/test_tls_over_tcp.sh
new file mode 100755
index 0000000..1c7f255
--- /dev/null
+++ b/t/cli/test_tls_over_tcp.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/env bash
+
+#
+# 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.
+#
+
+. ./t/cli/common.sh
+
+# check tls over tcp proxy
+echo "
+apisix:
+    stream_proxy:
+        tcp:
+            - addr: 9100
+              tls: true
+nginx_config:
+    stream_configuration_snippet: |
+        server {
+            listen 9101;
+            return \"OK FROM UPSTREAM\";
+        }
+
+" > conf/config.yaml
+
+make run
+sleep 0.1
+
+ ./utils/create-ssl.py t/certs/mtls_server.crt t/certs/mtls_server.key test.com
+
+curl -k -i http://127.0.0.1:9080/apisix/admin/stream_routes/1  \
+    -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d \
+    '{"upstream":{"nodes":{"127.0.0.1:9101":1},"type":"roundrobin"}}'
+
+sleep 0.1
+if ! echo -e 'mmm' | \
+    openssl s_client -connect 127.0.0.1:9100 -servername test.com -CAfile t/certs/mtls_ca.crt \
+        -ign_eof | \
+    grep 'OK FROM UPSTREAM';
+then
+    echo "failed: should proxy tls over tcp"
+    exit 1
+fi
+
+make stop
+echo "passed: proxy tls over tcp"
diff --git a/t/stream-node/tls.t b/t/stream-node/tls.t
new file mode 100644
index 0000000..bd525d1
--- /dev/null
+++ b/t/stream-node/tls.t
@@ -0,0 +1,106 @@
+#
+# 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';
+
+log_level('info');
+no_root_location();
+worker_connections(1024);
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: set stream / ssl
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local t = require("lib.test_admin")
+
+            local ssl_cert = t.read_file("t/certs/apisix.crt")
+            local ssl_key =  t.read_file("t/certs/apisix.key")
+            local data = {
+                cert = ssl_cert, key = ssl_key,
+                sni = "test.com",
+            }
+            local code, body = t.test('/apisix/admin/ssl/1',
+                ngx.HTTP_PUT,
+                core.json.encode(data)
+            )
+
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+
+            local code, body = t.test('/apisix/admin/stream_routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "upstream": {
+                        "nodes": {
+                            "127.0.0.1:1995": 1
+                        },
+                        "type": "roundrobin"
+                    }
+                }]]
+            )
+
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: hit route
+--- stream_tls_request
+mmm
+--- stream_sni: test.com
+--- response_body
+hello world
+
+
+
+=== TEST 3: wrong sni
+--- stream_tls_request
+mmm
+--- stream_sni: xx.com
+--- error_log
+failed to find any SSL certificate by SNI: xx.com
+
+
+
+=== TEST 4: missing sni
+--- stream_tls_request
+mmm
+--- error_log
+failed to find SNI
diff --git a/utils/create-ssl.py b/utils/create-ssl.py
new file mode 100755
index 0000000..93f2068
--- /dev/null
+++ b/utils/create-ssl.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+# coding: utf-8
+#
+# 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 sys
+# sudo pip install requests
+import requests
+
+# Usage: ./create-ssl.py t.crt t.key test.com
+if len(sys.argv) <= 3:
+    print("bad argument")
+    sys.exit(1)
+with open(sys.argv[1]) as f:
+    cert = f.read()
+with open(sys.argv[2]) as f:
+    key = f.read()
+sni = sys.argv[3]
+api_key = "edd1c9f034335f136f87ad84b625c8f1"
+resp = requests.put("http://127.0.0.1:9080/apisix/admin/ssl/1", json={
+    "cert": cert,
+    "key": key,
+    "snis": [sni],
+}, headers={
+    "X-API-KEY": api_key,
+})
+print(resp.status_code)
+print(resp.text)