一个供联调服务器使用的nginx代理服务架构

背景:

公司需要和很多外部商户合作连调。目前是每一个项目都会同时修改很多应用系统,有的项目修改的需要被外部商户访问。目前是都会给需要被外部商户访问的系统分配一个公网的IP,并且还需要网络工程师给每一个IP地址配置一堆网络白名单。目前的工作流程是开发/测试申请向我们申请开通某个测试服务器的公网IP地址。然后我们会先向网络安全工程师提出申请,评估同意后再向网工提需求新建公网IP,并配置好商户的IP白名单。整个流程麻烦而且效率低,影响所有人的工作效率。所以想了下不如搞个代理服务器来解决整个问题。只需要使用一台或者几台nginx服务器做代理即可,而且是可以提供HTTPS服务。

基本思路:

代理服务器具有公网IP地址,其实就是上机路由器会把几个使用的端口做NAT到私网的这个代理服务器,外围的网络防火墙是全部开放此服务器的80,8080,443端口(22端口不开),对商户的IP限制由iptables+nginx做。如果某些系统由于特殊原因需要直接向公网开发的话则另外在一个单独的代理服务器上做。由于有的时候让商户绑定host可能比较困难,因此没有使用虚拟主机的方式,而是直接给URL里添加一段。比如对于APP1和APP2,如果它们发布的WS服务是这样:

APP1 http://APP1.net:8080/services/appser1?wsdl
APP2 http://APP2.net:8080/services/appser2?wsdl
那么提供给外部商户的URL可以改成是

APP1 http://APP1.net:8080/APP1_net/services/appser1?wsdl
APP2 http://APP2.net:8080/APP2_net/services/appser2?wsdl

nginx的配置nginx.conf:
worker_processes  1;
error_log  logs/error.log ;
pid        logs/nginx.pid;
events {
worker_connections  1024;
}
http {
include       mime.types;
default_type  application/octet-stream;
sendfile        on;
keepalive_timeout  65;
gzip  off;
log_format  main  ‘$remote_addr -> $upstream_addr $host $remote_user [$time_local] “$request”‘
‘ $status $body_bytes_sent “$http_referer” ‘
‘$http_user_agent “$http_x_forwarded_for”  $request_time $upstream_response_time’;
server {
listen       8080;
proxy_set_header Host “$host:$server_port”;
proxy_set_header ORIG_CLIENT_IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_buffer_size   128k;
proxy_buffers   8 256k;
proxy_busy_buffers_size   256k;
proxy_http_version 1.1;
proxy_set_header Connection “keep-alive”;
access_log  logs/access.log  main;
location / {
root   html;
index  index.html index.htm;
if ( $uri ~* ^/([a-z_0-9]+)/(.*) ) {
set $appenv $1_$server_port;
set $myurl $2;
rewrite ^  /$myurl break;
proxy_pass http://$appenv;
}

allow 10.3.3.2;
deny all;

}
}
server {
listen       80;
proxy_set_header Host “$host:$server_port”;
proxy_set_header ORIG_CLIENT_IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_buffer_size   128k;
proxy_buffers   8 256k;
proxy_busy_buffers_size   256k;
proxy_http_version 1.1;
proxy_set_header Connection “keep-alive”;
access_log  logs/access.log  main;
location / {
root   html;
index  index.html index.htm;
if ( $uri ~* ^/([a-z_0-9]+)/(.*) ) {
set $appenv $1_$server_port;
set $myurl $2;
rewrite ^  /$myurl break;
proxy_pass http://$appenv;
}

allow 10.3.3.2;
deny all;

}
}
server {
listen       443 ssl;
server_name  localhost;
proxy_set_header Host $host:”80″;
proxy_set_header ORIG_CLIENT_IP $remote_addr;
proxy_set_header X-Forwarded-By $server_addr:$server_port;
proxy_set_header X-Forwarded-For $remote_addr;
client_max_body_size    10m;
client_body_buffer_size 128k;
proxy_buffer_size   128k;
proxy_buffers   8 256k;
proxy_busy_buffers_size   256k;
proxy_http_version 1.1;
proxy_set_header Connection “keep-alive”;
access_log  logs/access.log  main;
ssl on;
ssl_certificate      newshujiang.crt;
ssl_certificate_key  newshujiang.key;
ssl_session_timeout  5m;
ssl_protocols  SSLv2 SSLv3 TLSv1;
ssl_ciphers  HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers   on;
location / {
root   html;
index  index.html index.htm;
if ( $uri ~* ^/([a-z_0-9]+)/(.*) ) {

set $p 80;
set $appenv $1_$p;
set $myurl $2;
rewrite ^  /$myurl break;
proxy_pass http://$appenv;
}

allow 10.3.3.2;
deny all;

}

}
include upstream/*.conf;
}

其中upstream的命令方式为
APPX.net:80—> APPX_net_80

APPX.net:443—> APPX_net_80
APPX.net:8080—> APPX_net_8080

#APP1_net.conf

upstream APP1_net_8080 {
server APP1.net:8080;
}

upstream APP1_net_80 {
server APP1.net:80;
}

#APP2_net.conf

upstream APP2_net_8080 {
server APP2.net:8080;
}
upstream APP2_net_80 {
server APP2.net:80;
}

这样配置的话当需要添加需要被外部商户访问的系统时,只需要添加APPx_net.conf,里面的内容类似:

upstream APPx_net_8080 {
server APPx.net:8080;
}

upstream APPx_net_80 {
server APPx.net:80;
}

到upstream文件夹下即可,然后重新加载文件即可。
需要注意的是xfire协议需要http头的host里包含host和port。如果监听443,则需要对应的修改掉。
    proxy_set_header Host “$host:$server_port”;https段使用proxy_set_header Host “$host:80”;
起初Host没有带端口始终不行,可以直接把nginx的错误日志配置为debug模式进行调试,另外也需要把应用框架日志调整为debug模式,里面会显示收到的请求以及处理后的状态。调试的时候可以直接使用curl -I 发起head请求,另外也可以借助于nginx的echo模块来查看url rewrite时使用的表达式是是否正确的进行了匹配。今天遇到的问题主要是nginx反向代理的时候和xfire的兼容性问题,xfire必须要收到的header里的Host里是 host:port的形式,否则肯定显示404。另外就是之前使用$request_uri ~* ^/([a-z_0-9]+)/(.*),这样会造成rewrite后的upstream里nginx去向后端服务器请求的时候多带了参数$args,也会报404的。

采用这个实际上是安全控制比较松散一点,只要一个商户可以访问这个IP,那么就可以访问代理服务器上配置了的所有系统,如果觉得这样不安全的话可以这样调整一下,这样就可以精确地对代理服务器后端的每一个服务都进行控制,只是配置起来感觉每次新增一个服务的就需要多配置一些地方。不过也可以直接另外写脚本来根据元数据自动生成这样的配置文件,这样就比较好了。

location / {
root   html;
index  index.html index.htm;
if ( $uri ~* ^/APPx_net/(.*) ) {

set $appenv $1_$server_port;
set $myurl $2;
rewrite ^  /$myurl break;
proxy_pass http://APPx_net;

}

allow 10.3.3.2;
deny all;
}

nginx的前面可以使用iptables来对IP进行白名单限制,每当新增合作方的时候

iptables -t filter -A INPUT  -i eth1 -p tcp  -m multiport –destination-port 443,80,8080 –j DROP

如果要开启10.23.66.235对代理服务器443端口和8080端口的权限:

iptables -t filter -I INPUT  -i eth1 -s 10.23.66.235 -p tcp  -m multiport –destination-port 443,8080 –j ACCEPT
@更新一下

对于8080端口的WS服务的话可以直接使用

    proxy_set_header Host  $http_host,

这样可以直接使用客户端发送的请求的host值,这样就不会有host值引起的问题。

此条目发表在Admin, nginx分类目录。将固定链接加入收藏夹。

发表回复