使用Nginx安全代理邮箱

最近遇到客户提出对邮箱进行代理并添加安全限制,只允许特定客户端才能连接访问,因此特对此做研究

文档目标

本文档描述如何基于WorkPlus中的邮箱功能,为邮箱提供更安全的访问。邮箱的imap,smtp,pop3等并非HTTP协议,都是TCP协议,因此需要对它们的安全方案额外考量。

目标是达到使用此安全方案的邮箱服务,将只有WorkPlus的App才能正常访问邮箱

仅包含Imap,Smtp,Pop3协议,Exchange安全方案参照另一文档

基本思路

使用Nginx的Mail邮箱代理协议功能,做二次验证;

第一次验证将由Workplus App客户端配合后台Api完成,第二次验证为邮箱本身的用户名密码验证.

具体实现思路

使用Nginx代理邮箱模块

Nginx提供了对邮箱的代理支持,详见官方说明文档

ngx_mail_auth_http_module

官方配置指引

这个邮箱代理模块的作用主要是:

  • 代理邮箱的imap,pop3,stmp协议,隐藏真实IP,仅对外提供代理服务器IP
  • 提供了一个auth_http的授权验证模块,允许自定义授权逻辑,当通过时,向nginx返回协议的真实IP和端口就可以进行连接

使用auth_http

nginx中的邮箱代理中的auth_http的作用是:

它将设置的邮箱配置信息,如Imap服务器,端口等,封装请求到一个http请求中(由用户配置),用户可以处理,认为可以放行的,提供 “Auth-Status”值为”OK”,”Auth-Server”值为协议真实IP,”Auth-Port”为邮箱协议真实端口。

  • 如果验证通过,返回”Auth-Status”,”Auth-Server”,”Auth-Port”三个参数给nginx,nginx就会正常代理
  • 未有效返回上述三个参数,不会正常代理

最终方案建议

因此基于以上,我们提供的安全方案思路为

  • 使用Nginx代理邮箱各种协议 (包含Imap,Pop3,smtp等)
  • 要求用户配置邮箱时,将协议的IP及端口指向我们的nginx中的配置
  • 客户端配合NGINX安全措施,给用户添加的密码加入一些特有的验证码
  • 我们在auth_http中会对验证码进行验证,通过则放行,不通过则不放行;
  • 一旦放行,我们将用户输入的密码,去除验证码,将真实密码传递过去,交由邮箱自己做二次验证
  • 如果验证码不通过,拒绝

实际验证

验证背景

邮箱: foreverht.com

收邮箱协议: imap.foreverht.com 143 (IP:42.120.214.12)

发邮箱协议: smtp.foreverht.com 25 (IP:42.120.219.29)

nginx配置参考

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
mail {
server_name localhost;
proxy_pass_error_message on;
xclient off;
smtp_auth_reproxy on;
proxy on;
imap_capabilities "IMAP4rev1" "UIDPLUS"; ## default
smtp_capabilities "SIZE 10485760" ENHANCEDSTATUSCODES 8BITMIME DSN;

#授权auth_http
auth_http http://172.16.1.187:8080/mailAuth;

server {
listen 8886;
protocol pop3;
pop3_auth plain apop cram-md5;
}


server {
listen 8143;
protocol imap;
}

server {
listen 8825;
protocol smtp;
}

}

授权auth_http代码参考

一个授权auth_http参考, 以foreverht.com邮箱的imap以及smtp协议做测试 (Java版)

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
@RequestMapping("/mailAuth")
public ResponseEntity mailAuth(HttpServletRequest request, HttpServletResponse response) {
String protocol = request.getHeader("auth-protocol");
String username = request.getHeader("auth-user");
String password = request.getHeader("auth-pass");

String ip = null;

int port = 0;

if ("imap".equals(protocol)){
ip = "42.120.214.12";
port = 143;
}else if("smtp".equals(protocol)){
ip = "42.120.219.29";
port = 25;
}
//要求用户密码以 (真实密码+workplus)传入,未以workplus结束的密码不给通过
if (password.endsWith("workplus")){
response.setHeader("Auth-Status","OK");
response.setHeader("Auth-Server",ip);
response.setHeader("Auth-Port",String.valueOf(port));
response.setHeader("Auth-User",username);
//将密码中的workplus字样去除掉,以真实密码传入过去
response.setHeader("Auth-Pass",password.substring(0,password.indexOf("workplus")));
}else {
response.setHeader("Auth-Status","Invalid");
}

return ResponseEntity.status(204).build();
}

如代码所述,在auth_http中

  • 如果验证通过,返回”Auth-Status”,”Auth-Server”,”Auth-Port”三个参数给nginx,nginx就会正常代理
  • 未有效返回上述三个参数,不会正常代理

结果及问题

经过上述配置,结果实际验证为imap收邮箱可行,smtp发邮箱不可行

SMTP发邮件的问题

Nginx的Mail配置有一个默认的策略,认为经过auth_http的授权后,不再需要smtp的二次授权了(smtp协议的密码是可选的)

因此nginx的ngx_mail_auth_http_module模块压根没将用户名密码传给真实的smtp服务器.

因此,使用ngx_mail_auth_http_module模块,无法二次授权

解决方案

有两种解决方案

  1. 由于已经经过auth_http一次授权了,可以考虑smtp不再验证授权,开放设置smtp密码为可选
  2. 修改ngx_mail_auth_http_module模块逻辑,向smtp服务器传入用户名密码,进行二次授权

最终,考虑到第一种方案可行性低,大多数客户不会同意,因此进行第二种方案的研究

最终结果

经过修改nginx的相关源码,测试有效,可以二次授权

代码修改基于nginx-1.14.0源码而修改,修改逻辑另外文档记载以备份,在此不具体描述;

实际效果

正确配置验证

设置图

如上图所示配置,我们将IMAP,SMTP服务器IP指向nginx配置的IP172.16.1.220

然后密码是使用真实密码+workplu的逻辑传入

经过配置,可以有效的收取到邮件;

我们测试邮件发送

发送测试

测试发送有效

错误配置下的验证

当我们的密码未有按照上述规则配置,输入真实的密码,测试效果如下

错误配置

上述验证在Workplus的手机端(包含iOS与Android两个平台中) 同样验证通过

在MAC版的网易邮箱大师,Foxmail中同样验证通过

未有在其它平台中验证测试

结论

基于上述验证,得出如下结论

  • 可以使用nginx配置邮箱协议 ,进行代理 ,隐藏真实IP与端口
  • 可以使用auth_http做第一次验证
  • 在Workplus中考虑结合access_token配合auth_http做验证
  • 需修改nginx源码以支持smtp协议的二次验证

附录一 (安装与配置)

以下安装基于CentOS7

1
2
3
4
5
6
7
8
//安装下载工具
yum install wget
//安装编辑工具
yum install vim
//安装编译工具
yum install gcc
yum -y install pcre-devel
yum install -y zlib-devel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//下载openssl源码
wget https://www.openssl.org/source/openssl-1.0.2p.tar.gz
//下载nginx源码
wget http://nginx.org/download/nginx-1.14.0.tar.gz

tar -xvf openssl-1.0.2p.tar.gz

tar -xvf nginx-1.14.0.tar.gz

cd nginx-1.14.0
#请替换修改的源码文件先
./configure --with-mail --with-mail_ssl_module --with-openssl=/tmp/openssl-1.0.2p
make
make install

附录二

为使nginx的smtp支持二次验证,修改了以下源码 (参照了开源的项目修改而来)

src/mail/ngx_mail.h

src/mail/ngx_mail_auth_http_module.c