NGINX 向云原生演进,All in OpenNJet
1. FTP/FTPS协议知识
FTP rfc959:
https://www.rfc-editor.org/rfc/inline-errata/rfc959.html
FTPS rfc2228:
https://docs.huihoo.com/rfc/RFC2228.txt
1.1 FTP
文件传输协议(File Transfer Protocol,FTP),基于该协议FTP客户端与服务端可以实现共享文件、上传文件、下载、删除文件。FTP服务器端可以同时提供给多人共享使用。
FTP服务是Client/Server(简称C/S)模式,基于FTP协议实现FTP文件对外共享及传输的软件称之为FTP服务器源端,客户端程序基于FTP协议,则称之为FTP客户端,FTP客户端可以向FTP服务器上传、下载文件。
FTP上传和下载文件需要有两个tcp连接:
一个是控制连接(port:21),控制连接用于在两个主机之间传输控制信息,如口令,用户标识,存放、获取文件等命令
一个是数据连接(port:20)。数据连接用于实际发送一个文件,发送完文件之后数据连接会关闭
关于数据连接的建立实际又有两种模式:主动模式和被动模式
1.1.1 主动模式 Port(服务端连接客户端)
客户端开启一个端口N(>1023)向服务端的21端口,建立连接,同时开启一个N+1端口监听,告诉服务端,我监听的是N+1端口,服务端接到请求之后,用自己的20端口连接到客户端的N+1端口,进行传输
1.1.2 被动模式 Passive(客户端连接服务端)
客户端同时随机开启两个端口(比如1024,1025),一个端口(1024)跟服务端的21端口建立连接。服务端接到请求之后,随机会开启一个端口(1027)并告诉客户端我开启的是1027端口,客户端用另一个端口(1025)与服务端的(1027)端口进行连接,传输数据
1.2 FTPS(安全的FTP)
纯格式”FTP的一个重要问题是它不安全——用户名、密码和数据都是以明文形式通过网络发送的。这意味着嗅探网络的窃听者在获取传输文件的凭证和副本方面不会遇到什么麻烦。
FTPS协议是通过使用SSL/TLS协议加密通信来解决这个问题的,SSL/TLS协议是专门为保护网络连接而设计的。
而FTPS也有两种形式,隐式FTPS和显式FTPS,但始终首选显式模式
1.2.1 隐式FTPS
FTP服务器要求FTP客户必须初始化SSL握手过程并和FTP服务器之间建立安全的加密控制连接, 加密控制连接建立之后FTP命令才能够被送到FTP服务器. 如果FTP客户不支持SSL功能,或它和服务器之间没有建立安全的加密控制连接,FTP服务器将不对来自FTP客户的命令做出任何反应
1.2.2 显式FTPS
当FTP客户端希望使用安全的加密控制连接时, 它需要显式的发出AUTH命令例如 “AUTH TLS” 或 “AUTH SSL” 以初始化SSL握手过程并和FTP服务器之间建立安全的加密控制连接. AUTH命令必须在FTP客户登录之前发出. 如果FTP客户端没有发出AUTH命令,它和FTP服务器之间的控制连接将保持未加密状态
1.2.3 保护控制和数据通道
FTP会话使用两个通道:控制通道和数据通道。每次会话中只使用一个控制通道,但可以使用多个数据通道——每次数据传输一个通道。AUTH命令只保护控制通道。在PBSZ和PROT命令发出之前,数据通道是不安全的。这些命令告诉服务器后续数据通道是否应该是安全的。
客户端可以以不加密的方式连接FTPS服务器,然后根据请求切换到安全模式。为此,客户端发出AUTH命令,根据该命令,客户端和服务器协商建立安全连接。切换后,所有FTP命令都被加密,但重要的是,除非提供进一步的命令,否则数据不会被加密。
1.2.4 FTPS协议命令
使用三个命令:AUTH、PBSZ和PROT。其中之一,PBSZ,似乎是多余的,可能只是为了满足RFC规范而包含它。
1.2.4.1 AUTH(认证)
AUTH命令使用一个参数来定义要使用的安全机制,通常是“SSL”或“TLS”。
AUTH TLS
使用此命令,将尝试在控制通道上协商TLS连接。服务器试图通过发送其证书(服务器验证)向客户端验证自己。它还可能涉及客户端将其证书发送到服务器(客户端验证)。
1.2.4.2 PBSZ保护缓冲区大小
PBSZ命令用于定义安全机制在对数据通道上的数据进行加密时要使用的缓冲区大小。然而,对于TLS,这个设置是多余的,并且总是将值“0”作为参数传递。
1.2.4.3 PROT(数据通道保护级别)
PROT定义数据通道是否需要保护。数据通道为Clear(默认值)或Private。Clear表示在数据通道上不使用任何安全性(即文件传输不加密),Private表示应该对数据通道进行加密。
2. OpenNJet支持FTP/FTPS反向代理
2.1 功能支持
- 支持明文的FTP反向代理
- 支持隐式FTPS模式(客户端和服务端先建立加密连接,再开始发送命令,数据连接也需要加密)
2.2 系统依赖
需要系统上先执行如下命令:
modprobe nf_conntrack_ipv4
modprobe nf_conntrack_ipv6
2.3 FTP 被动模式PASV指令介绍
客户端发送PASV指令:
“PASV rn”
服务端返回开发地址和端口格式如下:
"%d Entering Passive Mode (%d,%d,%d,%d,%d,%d).rn"
服务端会返回一个状态码,并携带服务端开放的数据端口过来。
(IP1,IP2,IP3,IP4,PORT1,PORT2)
解析方式:
ip = IP1.IP2.IP3.IP4
port = PORT1 * 256 + PORT2
通过上面的解析,就得到了服务端开放的数据传输的ip和端口
2.4 方案
2.4.1 配置示例
需要系统上先执行如下命令:
stream {
#真正的ftp服务器地址
upstream ctl_upstream {
server 192.168.40.91:21;
}
#控制通道
server {
listen 21;
#由控制通道负责zone的创建, port 用来跟ftp server的数据port做一个映射关系,避免冲突
ftp_ctrl zone=ftp_zone:10M proxy_ip=192.168.40.136 min_port=10000 max_port=10010;
proxy_pass ctl_upstream;
}
#数据通道,ftp_ctrl设置的port范围的数据需要都转到本端口(10001)
server {
listen 10000-10010;
njtmesh_dest on; #必须该指令,用于获取真实的目标地址
#配置ftp_data后不能再配置proxy_pass指令
ftp_data zone=ftp_zone; #zone要与控制通道zone一致,读取数据port映射
}
}
2.4.2 指令设计
- 控制通道指令
Syntax |
ftp_ctrl zone={ftp_zone:10M} proxy_ip={192.168.40.136} min_port={12000} max_port={13000}; |
Default |
– |
Context |
stream,server |
- 参数说明
端口范围跟真实的ftp服务端端口范围没有必然联系,对于客户端来说,只会看到代理的端口范围,并发数受代理端口范围限制
参数 |
类型 |
必填 |
描述 |
zone |
string |
是 |
配置共享内存以及大小 |
proxy_ip |
string |
是 |
ftp代理的IP,因为可能存在默认的127.0.0.1或者多ip情况,必须明确填写ip |
min_port |
int |
是 |
ftp 代理支持的端口范围下限 |
max_port |
int |
是 |
ftp 代理支持的端口范围上限(max_port-min_port 表示ftp代理支持的最多ftp并发数据连接) |
- 数据通道指令:
Syntax |
ftp_data zone={ftp_zone}; |
Default |
– |
Context |
stream,server |
- 参数说明
参数 |
类型 |
必填 |
描述 |
zone |
string |
是 |
设置共享内存名称,由控制通道创建,此处只需要指定共享内存名称,与控制通道共享内存名称保持一致 |
2.5 测试
2.5.1 系统依赖
需要系统上先执行如下命令:
modprobe nf_conntrack_ipv4
modprobe nf_conntrack_ipv6
2.5.2 FTP服务器和客户端搭建
分别在90机器和91机器启动了两个ftp server
systemctl start|stop|restart vsftpd
90机器: ftpuser/ftpuser /home/ftpuser/
91机器: root/root /home/root/
2.5.3 数据连接多端口监听方式
数据连接监听多端口与配置的控制连接设置的端口范围一致,这种方式会将该范围的端口全部作为监听端口
比如,控制连接中配置端口范围为10000-10010, 那么数据连接监听多端口为 listen 10000-10010
2.5.3.1 明文FTP模式
2.5.3.1.1 配置示例
OpenNJet配置:
helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/ctrl.conf;
load_module modules/njt_http_location_module.so;
load_module modules/njt_stream_ftp_proxy_module.so; #加载ftp代理模块
user root;
worker_processes 1;
cluster_name helper;
node_name node1;
error_log logs/error.log info;
pid logs/njet.pid;
events {
worker_connections 1024;
}
stream {
upstream ctl_upstream {
hash $remote_addr consistent;
server 192.168.40.91:21;
}
server {
listen 21;
ftp_ctrl zone=ftp_zone:10M proxy_ip=192.168.40.136 min_port=10000 max_port=10010;
proxy_pass ctl_upstream;
proxy_timeout 2000s; #ftp连接默认10分钟会断开,如果传输大文件超过10分钟时,配置该指令
}
server {
#不能再配置proxy_pass指令
listen 10000-10010;
njtmesh_dest on; #一定要配置该指令,能够获取真实端口
ftp_data zone=ftp_zone;
}
}
vsftpd服务端建议配置:
#配置为ftp 被动模式
pasv_enable=YES
#配置被动模式数据端口范围
pasv_max_port=60000
pasv_min_port=60100
#这个配置是允许user_list文件里配置的用户可以访问
userlist_deny=no
2.5.3.1.2 测试效果
ftp客户端,192.168.40.136为我们的ftp代理
[root@node90 njet1.0]# ftp 192.168.40.136
Connected to 192.168.40.136 (192.168.40.136).
220 (vsFTPd 3.0.2)
Name (192.168.40.136:root): root
331 Please specify the password.
Password:
230 Login successful.
Remote system type is UNIX.
Using binary mode to transfer files.
ftp> get 91.conf #此处下载91.conf文件
local: 91.conf remote: 91.conf
227 Entering Passive Mode (192,168,40,136,39,16).
150 Opening BINARY mode data connection for 91.conf (41 bytes).
226 Transfer complete. #显示下载成功,可在本地看到该文件
41 bytes received in 0.0146 secs (2.81 Kbytes/sec)
ftp> exit
OpenNJet日志
#ftp客户端连接到ftp代理
2023/10/30 13:54:31 [info] 4977#0: *1 client 192.168.40.90:54990 connected to 0.0.0.0:21
#ftp代理连接到ftpserver
2023/10/30 13:54:31 [info] 4977#0: *1 proxy 192.168.40.136:43174 connected to 192.168.40.91:21
#ftp代理分配代理数据端口
2023/10/30 13:54:45 [debug] 4977#0: ftp_proxy now_port is:10000 next empty port is:10001 used_port_num:1 freed_port_num:10 cip:192.168.40.90 cport:54990 sip:192.168.40.91 sport:61299
#ftp客户端连接代理数据端口
2023/10/30 13:54:45 [info] 4977#0: *3 client 192.168.40.90:61476 connected to 0.0.0.0:10000
#ftp代理替换数据端口为真正的ftpserver分配的数据端口
2023/10/30 13:54:45 [debug] 4977#0: ftp data replace server addr:192.168.40.91:61299
#ftp代理数据连接去连接真正的ftpserver分配的数据端口
2023/10/30 13:54:45 [info] 4977#0: *3 proxy 192.168.40.136:36322 connected to 192.168.40.91:61299
#数据传输完成,ftpserver断开连接
2023/10/30 13:54:45 [info] 4977#0: *3 upstream disconnected, bytes from/to client:0/41, bytes from/to upstream:41/0
#ftp代理释放掉分配的数据代理端口资源
2023/10/30 13:54:45 [debug] 4977#0: ftp_proxy data free_port:10000 cip:192.168.40.90 cport:61476 used_port_num:0 freed_port_num:11
2.5.3.2 隐式FTPS模式
需要ftp server配置require_ssl_reuse=NO, 表示数据连接不共用控制连接的ssl通道
2.5.3.2.1 配置示例
OpenNJet配置
helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/ctrl.conf;
load_module modules/njt_http_location_module.so;
load_module modules/njt_stream_ftp_proxy_module.so; #加载ftp代理模块
user root;
worker_processes 1;
cluster_name helper;
node_name node1;
error_log logs/error.log info;
pid logs/njet.pid;
events {
worker_connections 1024;
}
stream {
upstream ctl_upstream {
hash $remote_addr consistent;
server 192.168.40.91:990;
}
server {
listen 21 ssl;
ftp_ctrl zone=ftp_zone:10M proxy_ip=192.168.40.136 min_port=10000 max_port=10010;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
ssl_certificate /etc/vsftpd/.sslkey/vsftpd.pem;
ssl_certificate_key /etc/vsftpd/.sslkey/vsftpd.pem;
proxy_ssl on;
proxy_pass ctl_upstream;
proxy_timeout 2000s; #ftp连接默认10分钟会断开,如果传输大文件超过10分钟时,配置该指令
}
server {
listen 10000-10010 ssl;
njtmesh_dest on; #一定要配置该指令,能够获取真实端口
ftp_data zone=ftp_zone;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
ssl_certificate /etc/vsftpd/.sslkey/vsftpd.pem;
ssl_certificate_key /etc/vsftpd/.sslkey/vsftpd.pem;
proxy_ssl on;
}
}
vsftpd配置示例
#配置服务端为隐式ftp
implicit_ssl=YES
#开启ssl
ssl_enable=YES
ssl_sslv2=YES
ssl_sslv3=YES
ssl_tlsv1=YES
#控制连接与数据连接不共用ssl session, 一定要配置
require_ssl_reuse=NO
#配置ssl证书
rsa_cert_file=/etc/vsftpd/.sslkey/vsftpd.pem
#一般隐式ftp 端口会只用990端口,如果不配置,vsftpd默认还是21,
#有的ftp服务可能会默认为990,此处明确指定端口为990
listen_port=990
#配置ftp server 日志文件
dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd.log
xferlog_file=/var/log/xferlog
#配置为ftp 被动模式
pasv_enable=YES
#配置被动模式数据端口范围,这个端口范围跟ftp代理的端口范围没有必然关系
pasv_max_port=60000
pasv_min_port=60100
#这个配置是允许user_list文件里配置的用户可以访问
userlist_deny=no
2.5.3.2.2 测试效果
OpenNJet日志, 跟明文ftp一致
2023/11/02 14:53:40 [info] 26818#0: *1 upstream disconnected, bytes from/to client:386/1266, bytes from/to upstream:1266/386
2023/11/02 14:54:38 [info] 26818#0: *15 client 192.168.40.205:61445 connected to 0.0.0.0:21
2023/11/02 14:54:38 [info] 26818#0: *15 proxy 192.168.40.136:33732 connected to 192.168.40.91:990
2023/11/02 14:54:39 [debug] 26818#0: ftp_proxy now_port is:10000 next empty port is:10001 used_port_num:1 freed_port_num:10 cip:192.168.40.205 cport:61445 sip:192.168.40.91 sport:60627
2023/11/02 14:54:39 [info] 26818#0: *17 client 192.168.40.205:61446 connected to 0.0.0.0:10000
2023/11/02 14:54:39 [debug] 26818#0: ftp data replace server addr:192.168.40.91:60627
2023/11/02 14:54:39 [info] 26818#0: *17 proxy 192.168.40.136:46632 connected to 192.168.40.91:60627
2023/11/02 14:56:12 [info] 26818#0: *17 upstream disconnected, bytes from/to client:0/1064925119, bytes from/to upstream:1064925119/0
2023/11/02 14:56:12 [debug] 26818#0: ftp_proxy data free_port:10000 cip:192.168.40.205 cport:61446 used_port_num:0 freed_port_num:11
2.5.4 数据连接单端口监听方式
数据连接设置一个监听端口,即可完成所有数据连接端口的连接
需要依赖range模块(range内核端口数据转发模块 )提供的端口流量转发功能
2.5.4.1 隐式FTPS模式
需要ftp server配置require_ssl_reuse=NO, 表示数据连接不共用控制连接的ssl通道
2.5.4.1.1 配置示例
OpenNJet配置
helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/ctrl.conf;
load_module modules/njt_http_location_module.so;
load_module modules/njt_range_module.so; #range 模块
load_module modules/njt_http_dyn_range_module.so; #range 动态修改模块,依赖于上面的range模块
load_module modules/njt_stream_ftp_proxy_module.so; #加载ftp代理模块
user root;
worker_processes 1;
cluster_name helper;
node_name node1;
error_log logs/error.log info;
pid logs/njet.pid;
events {
worker_connections 1024;
}
#默认路径为/usr/sbin/iptables, 如果系统路径不一致需要调用该指令设置下路径
range iptables_path=/usr/sbin/iptables;
#range 静态配置
#配置流量转发规则,将控制连接设置的11000-12000端口范围流量转发到数据连接配置的10001端口上
range type=tcp src_ports=11000:12000 dst_port=10001;
stream {
upstream ctl_upstream {
hash $remote_addr consistent;
server 192.168.40.91:990;
}
server {
listen 21 ssl;
ftp_ctrl zone=ftp_zone:10M proxy_ip=192.168.40.136 min_port=11000 max_port=12000;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
ssl_certificate /etc/vsftpd/.sslkey/vsftpd.pem;
ssl_certificate_key /etc/vsftpd/.sslkey/vsftpd.pem;
proxy_ssl on;
proxy_pass ctl_upstream;
proxy_timeout 2000s; #ftp连接默认10分钟会断开,如果传输大文件超过10分钟时,配置该指令
}
server {
listen 10001 ssl; #此处配置一个随机端口监听即可
njtmesh_dest on; #一定要配置该指令,能够获取真实端口
ftp_data zone=ftp_zone;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
ssl_certificate /etc/vsftpd/.sslkey/vsftpd.pem;
ssl_certificate_key /etc/vsftpd/.sslkey/vsftpd.pem;
proxy_ssl on;
}
}
vsftpd配置示例
#配置服务端为隐式ftp
implicit_ssl=YES
#开启ssl
ssl_enable=YES
ssl_sslv2=YES
ssl_sslv3=YES
ssl_tlsv1=YES
#控制连接与数据连接不共用ssl session, 一定要配置
require_ssl_reuse=NO
#配置ssl证书
rsa_cert_file=/etc/vsftpd/.sslkey/vsftpd.pem
#一般隐式ftp 端口会只用990端口,如果不配置,vsftpd默认还是21,
#有的ftp服务可能会默认为990,此处明确指定端口为990
listen_port=990
#配置ftp server 日志文件
dual_log_enable=YES
vsftpd_log_file=/var/log/vsftpd.log
xferlog_file=/var/log/xferlog
#配置为ftp 被动模式
pasv_enable=YES
#配置被动模式数据端口范围,这个端口范围跟ftp代理的端口范围没有必然关系
pasv_max_port=60000
pasv_min_port=60100
#这个配置是允许user_list文件里配置的用户可以访问
userlist_deny=no
2.5.4.1.2 测试效果
ftp客户端日志,实际分配的数据端口为11000, OpenNjet range模块会将11000端口流量转发到10001端口上
OpenNJet日志
#ftp 客户端连接控制端口21
2023/11/20 16:16:22 [info] 26775#0: *1 client 192.168.40.205:52209 connected to 0.0.0.0:21
#ftp 代理连接ftp server 990端口
2023/11/20 16:16:23 [info] 26775#0: *1 proxy 192.168.40.136:40738 connected to 192.168.40.91:990
#ftp客户端连接10001端口, 该流量由range模块转发而来,如上面ftp客户端截图,实际数据端口为11000
2023/11/20 16:16:23 [info] 26773#0: *3 client 192.168.40.205:52210 connected to 0.0.0.0:10001
2023/11/20 16:16:23 [info] 26773#0: *3 proxy 192.168.40.136:38672 connected to 192.168.40.91:64820
2023/11/20 16:16:23 [info] 26773#0: *3 upstream disconnected, bytes from/to client:0/3568, bytes from/to upstream:3568/0
2023/11/20 16:16:32 [info] 26774#0: *5 client 192.168.40.205:52215 connected to 0.0.0.0:10001
2023/11/20 16:16:32 [info] 26774#0: *5 proxy 192.168.40.136:49256 connected to 192.168.40.91:62390
2023/11/20 16:16:32 [info] 26774#0: *5 upstream disconnected, bytes from/to client:0/11374, bytes from/to upstream:11374/0
2023/11/20 16:16:37 [info] 26774#0: *7 client 192.168.40.205:52230 connected to 0.0.0.0:10001
2023/11/20 16:16:37 [info] 26774#0: *7 proxy 192.168.40.136:37246 connected to 192.168.40.91:64145
2023/11/20 16:16:37 [info] 26774#0: *7 client disconnected, bytes from/to client:3578/0, bytes from/to upstream:0/3578
2023/11/20 16:16:37 [info] 26775#0: *9 client 192.168.40.205:52231 connected to 0.0.0.0:10001
2023/11/20 16:16:37 [info] 26775#0: *9 proxy 192.168.40.136:42000 connected to 192.168.40.91:64166
2023/11/20 16:16:37 [info] 26775#0: *9 upstream disconnected, bytes from/to client:0/3568, bytes from/to upstream:3568/0
2.5.4.2 明文FTP模式
2.5.4.2.1 配置示例
OpenNJet配置
helper broker modules/njt_helper_broker_module.so conf/mqtt.conf;
helper ctrl modules/njt_helper_ctrl_module.so conf/ctrl.conf;
load_module modules/njt_http_location_module.so;
load_module modules/njt_range_module.so; #range 模块
load_module modules/njt_http_dyn_range_module.so; #range 动态修改模块,依赖于上面的range模块
load_module modules/njt_stream_ftp_proxy_module.so; #加载ftp代理模块
user root;
worker_processes 1;
cluster_name helper;
node_name node1;
error_log logs/error.log info;
pid logs/njet.pid;
events {
worker_connections 1024;
}
#默认路径为/usr/sbin/iptables, 如果系统路径不一致需要调用该指令设置下路径
range iptables_path=/usr/sbin/iptables;
#range 静态配置
#配置流量转发规则,将控制连接设置的11000-12000端口范围流量转发到数据连接配置的10001端口上
range type=tcp src_ports=11000:12000 dst_port=10001;
stream {
upstream ctl_upstream {
hash $remote_addr consistent;
server 192.168.40.91:990;
}
server {
listen 21;
ftp_ctrl zone=ftp_zone:10M proxy_ip=192.168.40.136 min_port=11000 max_port=12000;
proxy_pass ctl_upstream;
proxy_timeout 2000s; #ftp连接默认10分钟会断开,如果传输大文件超过10分钟时,配置该指令
}
server {
listen 10001 ssl; #此处配置一个随机端口监听即可
njtmesh_dest on; #一定要配置该指令,能够获取真实端口
ftp_data zone=ftp_zone;
}
}
vsftpd配置示例
#配置为ftp 被动模式
pasv_enable=YES
#配置被动模式数据端口范围
pasv_max_port=60000
pasv_min_port=60100
#这个配置是允许user_list文件里配置的用户可以访问
userlist_deny=no
OpenNJet 最早是基于 NGINX1.19 基础 fork 并独立演进,具有高性能、稳定、易扩展的特点,同时也解决了 NGINX 长期存在的难于动态配置、管理功能影响业务等问题。 邮件组 官网