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 2022/01/11 02:30:01 UTC

[apisix] branch master updated: feat(L4): support TLS over TCP upstream (#6030)

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 eb0bf87  feat(L4): support TLS over TCP upstream (#6030)
eb0bf87 is described below

commit eb0bf87b3865b4192991f963594bc24b54d9aff9
Author: 罗泽轩 <sp...@gmail.com>
AuthorDate: Tue Jan 11 10:29:56 2022 +0800

    feat(L4): support TLS over TCP upstream (#6030)
---
 .github/workflows/centos7-ci.yml |   2 +-
 apisix/cli/ngx_tpl.lua           |   6 ++
 apisix/schema_def.lua            |   5 +-
 apisix/upstream.lua              |  32 +++++++++
 docs/en/latest/admin-api.md      |   8 ++-
 docs/en/latest/stream-proxy.md   |  26 ++++++-
 docs/zh/latest/admin-api.md      |   7 +-
 docs/zh/latest/stream-proxy.md   |  26 ++++++-
 t/APISIX.pm                      |  12 ++++
 t/stream-node/upstream-tls.t     | 146 +++++++++++++++++++++++++++++++++++++++
 utils/linux-install-openresty.sh |   2 +-
 11 files changed, 258 insertions(+), 14 deletions(-)

diff --git a/.github/workflows/centos7-ci.yml b/.github/workflows/centos7-ci.yml
index c136202..bb306f9 100644
--- a/.github/workflows/centos7-ci.yml
+++ b/.github/workflows/centos7-ci.yml
@@ -44,7 +44,7 @@ jobs:
       run: |
         export VERSION=${{ steps.branch_env.outputs.version }}
         sudo gem install --no-document fpm
-        git clone -b v2.6.0 https://github.com/api7/apisix-build-tools.git
+        git clone -b v2.7.0 https://github.com/api7/apisix-build-tools.git
 
         # move codes under build tool
         mkdir ./apisix-build-tools/apisix
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index b3fd0d6..d7d5da4 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -148,6 +148,12 @@ stream {
 
         proxy_pass apisix_backend;
 
+        {% if use_apisix_openresty then %}
+        set $upstream_sni "apisix_backend";
+        proxy_ssl_server_name on;
+        proxy_ssl_name $upstream_sni;
+        {% end %}
+
         log_by_lua_block {
             apisix.stream_log_phase()
         }
diff --git a/apisix/schema_def.lua b/apisix/schema_def.lua
index 62b0836..6300e10 100644
--- a/apisix/schema_def.lua
+++ b/apisix/schema_def.lua
@@ -452,7 +452,10 @@ local upstream_schema = {
         },
         scheme = {
             default = "http",
-            enum = {"grpc", "grpcs", "http", "https"}
+            enum = {"grpc", "grpcs", "http", "https", "tcp", "tls", "udp"},
+            description = "The scheme of the upstream." ..
+                " For L7 proxy, it can be one of grpc/grpcs/http/https." ..
+                " For L4 proxy, it can be one of tcp/tls/udp."
         },
         labels = labels_def,
         discovery_type = {
diff --git a/apisix/upstream.lua b/apisix/upstream.lua
index 4e23fbf..4f2f0fe 100644
--- a/apisix/upstream.lua
+++ b/apisix/upstream.lua
@@ -24,6 +24,7 @@ local error = error
 local tostring = tostring
 local ipairs = ipairs
 local pairs = pairs
+local ngx_var = ngx.var
 local is_http = ngx.config.subsystem == "http"
 local upstreams
 local healthcheck
@@ -39,6 +40,19 @@ else
     end
 end
 
+local set_stream_upstream_tls
+if not is_http then
+    local ok, apisix_ngx_stream_upstream = pcall(require, "resty.apisix.stream.upstream")
+    if ok then
+        set_stream_upstream_tls = apisix_ngx_stream_upstream.set_tls
+    else
+        set_stream_upstream_tls = function ()
+            return nil, "need to build APISIX-OpenResty to support TLS over TCP upstream"
+        end
+    end
+end
+
+
 
 local HTTP_CODE_UPSTREAM_UNAVAILABLE = 503
 local _M = {}
@@ -286,6 +300,19 @@ function _M.set_by_route(route, api_ctx)
             return 503, err
         end
 
+        local scheme = up_conf.scheme
+        if scheme == "tls" then
+            local ok, err = set_stream_upstream_tls()
+            if not ok then
+                return 503, err
+            end
+
+            local sni = apisix_ssl.server_name()
+            if sni then
+                ngx_var.upstream_sni = sni
+            end
+        end
+
         return
     end
 
@@ -451,6 +478,11 @@ local function filter_upstream(value, parent)
 
     value.parent = parent
 
+    if not is_http and value.scheme == "http" then
+        -- For L4 proxy, the default scheme is "tcp"
+        value.scheme = "tcp"
+    end
+
     if not value.nodes then
         return
     end
diff --git a/docs/en/latest/admin-api.md b/docs/en/latest/admin-api.md
index b0fa7f9..017cbc3 100644
--- a/docs/en/latest/admin-api.md
+++ b/docs/en/latest/admin-api.md
@@ -554,7 +554,7 @@ In addition to the basic complex equalization algorithm selection, APISIX's Upst
 |desc            |optional|upstream usage scenarios, and more.||
 |pass_host       |optional| `host` option when the request is sent to the upstream, can be one of [`pass`, `node`, `rewrite`], the default option is `pass`. `pass`: Pass the client's host transparently to the upstream; `node`: Use the host configured in the node of `upstream`; `rewrite`: Use the value of the configuration `upstream_host`.||
 |upstream_host   |optional|Specify the host of the upstream request. This option is only valid if the `pass_host` is `rewrite`.||
-|scheme          |optional |The scheme used when talk with the upstream. The value is one of ['http', 'https', 'grpc', 'grpcs'], default to 'http'.||
+|scheme          |optional |The scheme used when talk with the upstream. For L7 proxy, the value is one of ['http', 'https', 'grpc', 'grpcs']. For L7 proxy, the value is one of ['tcp', 'udp', 'tls']. Default to 'http'. See below for more details.||
 |labels          |optional |Key/value pairs to specify attributes|{"version":"v2","build":"16","env":"production"}|
 |create_time     |optional| epoch timestamp in second, like `1602883670`, will be created automatically if missing|1602883670|
 |update_time     |optional| epoch timestamp in second, like `1602883670`, will be created automatically if missing|1602883670|
@@ -581,13 +581,15 @@ In addition to the basic complex equalization algorithm selection, APISIX's Upst
 1. When it is `vars_combinations`, the `key` is required. The `key` can be any [Nginx builtin variables](http://nginx.org/en/docs/varindex.html) combinations, such as `$request_uri$remote_addr`.
 1. If there is no value for either `hash_on` or `key`, `remote_addr` will be used as key.
 
+Features below require APISIX to run on [APISIX-OpenResty](./how-to-build.md#step-6-build-openresty-for-apache-apisix):
+
+The `scheme` can be set to `tls`, which actually means "TLS over TCP".
+
 `tls.client_cert/key` can be used to communicate with upstream via mTLS.
 Their formats are the same as SSL's `cert` and `key` fields.
-This feature requires APISIX to run on [APISIX-OpenResty](./how-to-build.md#step-6-build-openresty-for-apache-apisix).
 
 `keepalive_pool` allows the upstream to have its separate connection pool.
 Its children fields, like `requests`, can be used to configure the upstream keepalive options.
-This feature requires APISIX to run on [APISIX-OpenResty](./how-to-build.md#step-6-build-openresty-for-apache-apisix).
 
 **Config Example:**
 
diff --git a/docs/en/latest/stream-proxy.md b/docs/en/latest/stream-proxy.md
index 078e374..34797ec 100644
--- a/docs/en/latest/stream-proxy.md
+++ b/docs/en/latest/stream-proxy.md
@@ -166,9 +166,9 @@ Let's take another real world example:
 
 Read [Admin API's Stream Route section](./admin-api.md#stream-route) for the complete options list.
 
-## Accept TLS over TCP
+## Accept TLS over TCP connection
 
-APISIX can accept TLS over TCP.
+APISIX can accept TLS over TCP connection.
 
 First of all, we need to enable TLS for the TCP address:
 
@@ -189,7 +189,6 @@ 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
@@ -215,3 +214,24 @@ curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f03
 ```
 
 In this case, a connection handshaked with SNI `a.test.com` will be proxied to `127.0.0.1:5991`.
+
+## Proxy to TLS over TCP upstream
+
+APISIX also supports proxying to TLS over TCP upstream.
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "upstream": {
+        "scheme": "tls",
+        "nodes": {
+            "127.0.0.1:1995": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+By setting the `scheme` to "tls", APISIX will do TLS handshake with the upstream.
+
+When the client is also speaking TLS over TCP, the SNI from the client will pass through to the upstream. Otherwise, a dummy SNI "apisix_backend" will be used.
diff --git a/docs/zh/latest/admin-api.md b/docs/zh/latest/admin-api.md
index 404604e..6257f24 100644
--- a/docs/zh/latest/admin-api.md
+++ b/docs/zh/latest/admin-api.md
@@ -559,7 +559,7 @@ APISIX 的 Upstream 除了基本的负载均衡算法选择外,还支持对上
 | desc           | 可选                               | 辅助           | 上游服务描述、使用场景等。                                                                                                                                                                                                                                                                                                                                  |                                                  |
 | pass_host      | 可选                               | 枚举           | 请求发给上游时的 host 设置选型。 [`pass`,`node`,`rewrite`] 之一,默认是`pass`。`pass`: 将客户端的 host 透传给上游; `node`: 使用 `upstream`  node 中配置的 host; `rewrite`: 使用配置项 `upstream_host` 的值。                                                                                                                                                                        |                                                  |
 | upstream_host  | 可选                               | 辅助           | 指定上游请求的 host,只在 `pass_host` 配置为 `rewrite` 时有效。                                                                                                                                                                                                                                                                                                                  |                                                  |
-| scheme         | 可选                               | 辅助           | 跟上游通信时使用的 scheme。需要是 ['http', 'https', 'grpc', 'grpcs'] 其中的一个,默认是 'http'。                                                                                                                                                                                                                                                            |
+| scheme         | 可选                               | 辅助           | 跟上游通信时使用的 scheme。对于 7 层代理,需要是 ['http', 'https', 'grpc', 'grpcs'] 其中的一个。对于 4 层代理,需要是 ['tcp', 'udp', 'tls'] 其中的一个。默认是 'http'。细节见下文。                                                                                                                                                                                                                                                           |
 | labels         | 可选                               | 匹配规则       | 标识附加属性的键值对                                                                                                                                                                                                                                                                                                                                        | {"version":"v2","build":"16","env":"production"} |
 | create_time    | 可选                               | 辅助           | 单位为秒的 epoch 时间戳,如果不指定则自动创建                                                                                                                                                                                                                                                                                                               | 1602883670                                       |
 | update_time    | 可选                               | 辅助           | 单位为秒的 epoch 时间戳,如果不指定则自动创建                                                                                                                                                                                                                                                                                                               | 1602883670                                       |
@@ -585,9 +585,12 @@ APISIX 的 Upstream 除了基本的负载均衡算法选择外,还支持对上
 4. 设为 `consumer` 时,`key` 不需要设置。此时哈希算法采用的 `key` 为认证通过的 `consumer_name`。
 5. 如果指定的 `hash_on` 和 `key` 获取不到值时,就是用默认值:`remote_addr`。
 
+以下特性需要 APISIX 运行于 [APISIX-OpenResty](./how-to-build.md#步骤6:为-Apache-APISIX-构建-OpenResty):
+
+`scheme` 可以设置成 `tls`,表示 "TLS over TCP"。
+
 `tls.client_cert/key` 可以用来跟上游进行 mTLS 通信。
 他们的格式和 SSL 对象的 `cert` 和 `key` 一样。
-这个特性需要 APISIX 运行于 [APISIX-OpenResty](./how-to-build.md#步骤6:为-Apache-APISIX-构建-OpenResty)。
 
 `keepalive_pool` 允许 upstream 对象有自己单独的连接池。
 它下属的字段,比如 `requests`,可以用了配置上游连接保持的参数。
diff --git a/docs/zh/latest/stream-proxy.md b/docs/zh/latest/stream-proxy.md
index 9b0d840..396db99 100644
--- a/docs/zh/latest/stream-proxy.md
+++ b/docs/zh/latest/stream-proxy.md
@@ -163,9 +163,9 @@ curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f03
 
 完整的匹配选项列表参见 [Admin API 的 Stream Route](./admin-api.md#stream-route)。
 
-## 接收 TLS over TCP
+## 接收 TLS over TCP 连接
 
-APISIX 支持接收 TLS over TCP。
+APISIX 支持接收 TLS over TCP 连接。
 
 首先,我们需要给对应的 TCP 地址启用 TLS:
 
@@ -186,7 +186,6 @@ mTLS 也是支持的,参考 [保护路由](./mtls.md#保护路由)。
 ```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
@@ -212,3 +211,24 @@ curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f03
 ```
 
 在这里,握手时发送 SNI `a.test.com` 的连接会被代理到 `127.0.0.1:5991`。
+
+## 代理到 TLS over TCP 上游
+
+APISIX 还支持代理到 TLS over TCP 上游。
+
+```shell
+curl http://127.0.0.1:9080/apisix/admin/stream_routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
+{
+    "upstream": {
+        "scheme": "tls",
+        "nodes": {
+            "127.0.0.1:1995": 1
+        },
+        "type": "roundrobin"
+    }
+}'
+```
+
+通过设置 `scheme` 为 tls,APISIX 将与上游进行 TLS 握手。
+
+当客户端也使用 TLS over TCP,客户端发送的 SNI 将传递给上游。否则,将使用一个假的 SNI "apisix_backend"。
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 23daaa3..6050bd2 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -251,6 +251,8 @@ _EOC_
     if ($stream_tls_request) {
         # generate a springboard to send tls stream request
         $block->set_value("stream_conf_enable", 1);
+        # avoid conflict with stream_enable
+        $block->set_value("stream_enable");
         $block->set_value("request", "GET /stream_tls_request");
 
         my $sni = "nil";
@@ -420,7 +422,17 @@ _EOC_
     }
 
     proxy_pass apisix_backend;
+_EOC_
+
+    if ($version =~ m/\/apisix-nginx-module/) {
+        $stream_server_config .= <<_EOC_;
+    proxy_ssl_server_name on;
+    proxy_ssl_name \$upstream_sni;
+    set \$upstream_sni "apisix_backend";
+_EOC_
+    }
 
+    $stream_server_config .= <<_EOC_;
     log_by_lua_block {
         apisix.stream_log_phase()
     }
diff --git a/t/stream-node/upstream-tls.t b/t/stream-node/upstream-tls.t
new file mode 100644
index 0000000..a9fce58
--- /dev/null
+++ b/t/stream-node/upstream-tls.t
@@ -0,0 +1,146 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX;
+
+my $nginx_binary = $ENV{'TEST_NGINX_BINARY'} || 'nginx';
+my $version = eval { `$nginx_binary -V 2>&1` };
+
+if ($version !~ m/\/apisix-nginx-module/) {
+    plan(skip_all => "apisix-nginx-module not installed");
+} else {
+    plan('no_plan');
+}
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!$block->request) {
+        $block->set_value("stream_enable", 1);
+
+        my $stream_config = $block->stream_config // '';
+        $stream_config .= <<_EOC_;
+        server {
+            listen 8765 ssl;
+            ssl_certificate             cert/apisix.crt;
+            ssl_certificate_key         cert/apisix.key;
+
+            content_by_lua_block {
+                local sock = ngx.req.socket()
+                local data = sock:receive("1")
+                ngx.say("hello ", ngx.var.ssl_server_name)
+            }
+        }
+_EOC_
+
+        $block->set_value("extra_stream_config", $stream_config);
+    }
+
+    if ((!defined $block->error_log) && (!defined $block->no_error_log)) {
+        $block->set_value("no_error_log", "[error]");
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: set upstream & stream_routes (id: 1)
+--- config
+    location /t {
+        content_by_lua_block {
+            local t = require("lib.test_admin").test
+            local code, body = t('/apisix/admin/upstreams/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "scheme": "tls",
+                    "nodes": {
+                        "localhost:8765": 1
+                    },
+                    "type": "roundrobin"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+                return
+            end
+            local code, body = t('/apisix/admin/stream_routes/1',
+                ngx.HTTP_PUT,
+                [[{
+                    "remote_addr": "127.0.0.1",
+                    "upstream_id": "1"
+                }]]
+            )
+            if code >= 300 then
+                ngx.status = code
+            end
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 2: hit route
+--- stream_request
+mmm
+--- stream_response
+hello apisix_backend
+
+
+
+=== TEST 3: set 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
+
+            ngx.say(body)
+        }
+    }
+--- request
+GET /t
+--- response_body
+passed
+
+
+
+=== TEST 4: hit route
+--- stream_tls_request
+mmm
+--- stream_sni: test.com
+--- response_body
+hello test.com
diff --git a/utils/linux-install-openresty.sh b/utils/linux-install-openresty.sh
index 017ebf5..9a0a4e9 100755
--- a/utils/linux-install-openresty.sh
+++ b/utils/linux-install-openresty.sh
@@ -26,7 +26,7 @@ sudo apt-get update
 
 if [ "$OPENRESTY_VERSION" == "source" ]; then
     cd ..
-    wget https://raw.githubusercontent.com/api7/apisix-build-tools/v2.6.0/build-apisix-base.sh
+    wget https://raw.githubusercontent.com/api7/apisix-build-tools/v2.7.0/build-apisix-base.sh
     chmod +x build-apisix-base.sh
     ./build-apisix-base.sh latest