通过OpenResty安全的暴露内网服务

背景

虽然有购入阿里云服务器,但贫穷限制了配置,只能勉强部署上直接面向用户的服务,而像是jenkins、jira、gitlab、数据处理这些配套设施都只能往内网机器塞。

可有时候吧,又免不掉外部访问的需求,通过路由器端口映射的方式将服务直接开放出去倒是容易,但面对着公网的黑暗森林,你永远都不知道有多少双眼睛在盯着你的开放端口,所以真要面向公网开放服务,以下的问题就必须的优先解决:

  • 网络安全,因为只有自己使用,通过IP访问白名单的形式来控制是最简单的;
  • 公网IP,因为ADSL拨号IP每次都会发生变化,所以在外面的时候如何获取到最新的公网IP
  • 白名单IP,当无法访问内网的情况下,如何更新设置访问IP白名单

方案

先看一下整体架构图,从左边看起:

  • 首先请求从互联网进入,经过路由器的端口映射直接转发到内网Nginx的服务上
  • 借助于OpenResty,讲请求IP与Redis中的IP白名单进行比对,如果不一致请求就被挡住了
  • IP白名单校验通过后讲请求根据uri转发至后端服务
  • IP上报进程,每隔一段时间就请求一次部署在阿里云的主机服务,这样就能记录下当前外网的最新IP
  • 白名单刷新进程,请求阿里云主机的白名单设置接口,不管是否有变化都更新进Redis中

IP白名单限制

IP白名单限制依赖OpenResty提供的LuaJIT环境,安装可以直接参考官网说明,找到对应系统的优编译版安装起来很方便。

关键lua代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
-- 获取请求ip
local ip = ngx.var.remote_addr

-- 判断是否有获取到远程ip
if ( nil == ip or string.len(ip) < 4 ) then
return ngx.exit(403);
end

-- 连接redis
local redis_ip = "127.0.0.1"
local redis_port = 6379
local redis = require("resty.redis")
local red = redis:new()
red:set_timeout(10000)

local ok , err = red:connect(redis_ip, redis_port)
if not ok then
redis_close(red)
return ngx.exit(502);
end

-- 获取白名单ip
local white_ip = red:get("whiteip")
if ( white_ip == ngx.null or white_ip == nil ) then
return ngx.exit(403);
end

-- 放入连接池
local ok, err = red:set_keepalive(10000, 5)
if not ok then
return ngx.exit(502);
end

-- 判断是否为白名单ip
if ( ip ~= white_ip ) then
return ngx.exit(403);
end

-- 正常业务逻辑
return

然后在nginx的配置文件中引入

1
2
3
4
5
6
location /xxx {
access_by_lua_file scripts/auth.lua;
proxy_set_header Host $host:8888;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass ...
}

服务转发

Jenkins

对于Jenkins,需要修改配置文件[/etc/default/jenkins],在启动参数JENKINS_ARGS上添加上 –prefix的设置

1
JENKINS_ARGS="--webroot=/var/cache/$NAME/war --httpPort=$HTTP_PORT --prefix=/jenkins"

在使用了自签https证书的情况下,还需要通过proxy_redirect来改写location的http地址为https地址,最终的ngxin配置如下:

1
2
3
4
5
6
7
8
location /jenkins {
default_type text/html;
access_by_lua_file scripts/auth.lua;
proxy_set_header Host $host:8888;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8001;
proxy_redirect http://jenkins.my.com:8001/jenkins/ https://jenkins.my.com:8001/jenkins/;
}

Jira

针对Jira,则需要修改配置文件[/opt/atlassian/jira/conf/server.xml]
首先是标签的path属性,修改为“/jira”

1
<Context path="/jira"  ...

然后是添加一个的标签,用于https的连接

1
<Connector port="8002" ... redirectPort="8443" ... secure="true" scheme="https" ... proxyName="jira.my.com" proxyPort="8888"/>

最后在nginx配置地址转发

1
2
3
4
5
6
7
location /jira {
default_type text/html;
access_by_lua_file scripts/auth.lua;
proxy_set_header Host $host:8888;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass http://127.0.0.1:8002;
}

IP上报/白名单更新

通过shell脚本结合curl/redis-cli来实现就好了,逻辑比较简单,就不赘述了,最后结合crontab来定时执行就好了。