Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(L4): support TLS over TCP upstream #6030

Merged
merged 1 commit into from
Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/centos7-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 6 additions & 0 deletions apisix/cli/ngx_tpl.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down
5 changes: 4 additions & 1 deletion apisix/schema_def.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
32 changes: 32 additions & 0 deletions apisix/upstream.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 = {}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down
8 changes: 5 additions & 3 deletions docs/en/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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:**

Expand Down
26 changes: 23 additions & 3 deletions docs/en/latest/stream-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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.
7 changes: 5 additions & 2 deletions docs/zh/latest/admin-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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`,可以用了配置上游连接保持的参数。
Expand Down
26 changes: 23 additions & 3 deletions docs/zh/latest/stream-proxy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand All @@ -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
Expand All @@ -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"。
12 changes: 12 additions & 0 deletions t/APISIX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -414,7 +416,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()
}
Expand Down
Loading