Featured image of post 基于 tailscale 的 Mailu 邮件服务器内网穿透部署

基于 tailscale 的 Mailu 邮件服务器内网穿透部署

利用 Tailscale 将内网的 Mailu 邮件服务器安全地暴露到公网 VPS,实现邮件收发功能,减少对 VPS 性能的依赖

背景

Mailu 是一个基于 Docker 的开源邮件服务器解决方案,提供了完整的邮件服务功能,包括 SMTP、IMAP、Webmail 等。Tailscale 是一个基于 WireGuard 的零配置 VPN 解决方案,可以轻松地将内网设备连接到公网。 在 基于 FRP 的 Mailu 邮件服务器内网穿透部署 一文中,我们介绍了如何使用 FRP 将内网的 Mailu 邮件服务器暴露到公网。但在有的情况下,我们已经启用了 tailscale,并且不希望再额外部署 FRP 服务端,从而节省 VPS 资源。因此本文将介绍如何利用 Tailscale 将内网的 Mailu 邮件服务器安全地暴露到公网 VPS,实现邮件收发功能。

准备工作

网络环境

略。与 基于 FRP 的 Mailu 邮件服务器内网穿透部署 一文中准备工作相同,不同的是,网络穿透通过 tailscale 实现,不用再配置 FRPC 与 FRPS。

端口转发配置

通过 tailscale, 我们不再需要在本地再额外部署一个 FRPC 客户端来接收处理对端请求,而是直接在 VPS 上通过 tailscale 将请求基于端口转发到内网的 Mailu 服务器上。

这里我们使用基于 rust 编写的端口转发客户端 realm,他配置简单,rust 编写,性能也不错。

从 Github Releases 页面下载对应平台的二进制文件,上传到 VPS 上并解压得到 realm 可执行文件。

然后创建 realm 的配置文件 realm.toml,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[network]
send_proxy = true
send_proxy_version = 2

[[endpoints]]
listen = "102.25.3.5:25"  # VPS 公网 IP 和端口
remote = "100.123.123.123:10025"  # Mailu SMTP 100.123.123.123 为 Mailu 服务器的 tailscale IP 地址
listen_interface = "eth0"  # VPS 上的网卡

[[endpoints]]
listen = "102.25.3.5:993"
remote = "100.123.123.123:10993"
listen_interface = "eth0"

[[endpoints]]
listen = "102.25.3.5:465"
remote = "100.123.123.123:10465"
listen_interface = "eth0"

假设 realm 的可执行文件和配置文件都放在 /opt/realm/ 目录下,则可以通过以下命令启动 realm

1
2
cd /opt/realm
./realm -c realm.toml

为了方便管理,我们可以创建一个 systemd 服务单元文件 /etc/systemd/system/realm.service,内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Unit]
Description=Realm Tailscale Port Forwarding Service
After=network.target tailscaled.service

[Service]
ExecStart=/opt/realm/realm -c /opt/realm/realm.toml
Restart=always

[Install]
WantedBy=multi-user.target

然后通过以下命令启用并启动 realm 服务:

1
2
sudo systemctl daemon-reload
sudo systemctl enable realm --now

设置 UFW 防火墙规则(VPS主机)

在 VPS 上设置 UFW 防火墙规则,仅允许内网服务器的公网 IP 访问 FRP 服务端口:

1
2
3
4
5
6
sudo ufw allow from 83.21.3.9 to any port 9000 proto udp

# 允许邮件相关端口,确保 ufw 的默认策略是 deny
sudo ufw allow 25,465,993/tcp
sudo ufw reload  # 确保 ufw 此前已经正确配置,不会导致无法连接
sudo ufw status verbose

Mailu 配置(内网主机)

可以参考此前的【mailu邮件服务部署】文章来配置基本的 Mailu 邮件服务器,DNS 配置方面保持使用公网 IP 配置即可,其余的配置需要针对内网部署做出以下调整:

mailu.env 配置(内网主机)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Will relay all outgoing mails if configured
RELAYHOST=[102.25.3.5]:2525  # 将使用 VPS 作为内网主机的发件中转服务器
# 注意上面使用 [] 扩起来是为关闭检查,避免检查循环
OUTBOUND_TLS_LEVEL=encrypt  # 强制使用 TLS 加密连接确保中转安全

# 启用 PROXY Protocol 支持的端口列表
PROXY_PROTOCOL=25,993,465

# IPs for nginx set_real_ip_from (CIDR list separated by commas)
REAL_IP_FROM=192.168.203.1/32
# Mailu 容器所在内网 IP 段,将被用于获取真实 IP

注意到,PROXY 相关的配置也是相同的,因为我们在 realm 的配置中启用了 PROXY 协议支持。

docker-compose.yml 配置(内网主机)

注意到其中 25,465,993 端口映射到 gmk 的 tailscale ip 下,这是为了获取真实 IP 所必须的。假设 gmk 的 tailscale IP 是 100.123.45.67,则可以在 docker-compose.yml 中进行如下配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
services:
  front:
    image: ${DOCKER_ORG:-ghcr.io/mailu}/${DOCKER_PREFIX:-}nginx:${MAILU_VERSION:-2024.06}
    restart: always
    env_file: mailu.env
    logging:
      driver: journald
      options:
        tag: mailu-front
    ports:
      # Mailu 在获取到 tls 证书后会自动跳转到 443 端口
      - "192.168.99.2:7443:443"  # 转发到 VPS 上的 5555 端口,可通过 Caddy 等反向代理访问
      - "192.168.99.2:7780:80"
      - "100.123.45.67:10025:25"   # tailscale ip,对应到 realm 中配置的 remote 端口
      - "100.123.45.67:10465:465"  # tailscale ip,对应到 realm 中配置的 remote 端口
      - "100.123.45.67:10993:993"  # tailscale ip,对应到 realm 中配置的 remote 端口
    networks:
      - default
      - webmail
    volumes:
      - "/opt/mailu/certs:/certs"
      - "/opt/mailu/overrides/nginx:/overrides:ro"

配置发件(内网主机)

为避免内网主机与 VPS 邮件通信时出现相同 hostname 的问题,在内网主机当中设定配置 Mailu 的 postfix 服务。 在内网主机的 $MAILU_ROOT/overrides/postfix/ 路径下创建文件 postfix.cf 来替换关键配置:

1
2
# $MAILU_ROOT/overrides/postfix/postfix.cf
smtp_helo_name = mailu-sender.local

中转发件服务器配置(VPS 主机)

在 VPS 主机上安装 postfix 作为中转发件服务器:

1
sudo apt install postfix

在配置过程中选择 “Internet with smarthost”。

Postfix 配置(VPS 主机)

过滤在消息体中出现的内网主机的公网 IP

创建 /etc/postfix/header_checks 文件,添加以下配置:

1
/^Received:.*\[83\.21\.3\.9\].*/ IGNORE

该配置用于过滤掉内网主机的公网 IP,避免隐私泄露问题,仅暴露 VPS 的公网 IP。

修改 Postfix 主配置文件

编辑 /etc/postfix/main.cf,添加或修改以下配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# /etc/postfix/main.cf

# 借用 Caddy 生成的证书,确保路径正确,如果没有Caddy保持原样即可,也同样可以加密
smtpd_tls_cert_file=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.hlmg.tech/mail.hlmg.tech.crt
smtpd_tls_key_file=/var/lib/caddy/.local/share/caddy/certificates/acme-v02.api.letsencrypt.org-directory/mail.hlmg.tech/mail.hlmg.tech.key
smtpd_tls_security_level=encrypt  # 将原本的 may 改为强制 encrypt

myhostname = smtp.hlmg.tech  # 确保与 VPS 的 DNS 记录一致,用于 spf 验证
# 一般来说跟内网主机 Mailu 上面配置的 hostname 可以不一致(比如内网设置为 mail.hlmg.tech)

mydestination = $myhostname, localhost.localdomain, localhost

# ⚠️⚠️⚠️ 下面的配置非常重要 ⚠️⚠️⚠️ 
mynetworks = 127.0.0.1/32 83.21.3.9/32  # 仅允许内网主机的公网 IP 访问
# 上面这个 IP 如果不加限制会导致任何人都能通过你的 VPS 发送邮件,进而被滥用发送垃圾邮件

# 在末尾添加过滤配置
header_checks = regexp:/etc/postfix/header_checks

修改 Postfix 监听端口

编辑 /etc/postfix/master.cf,修改以下配置:

1
2
3
4
# /etc/postfix/master.cf
# smtp      inet  n       -       y       -       -       smtpd
2525    inet    n       -       y       -       -       smtpd
# 将 smtp 端口改为 2525 端口,可以避免与系统自带的 smtp 端口冲突

应用配置并检查测试

重启 postfix 服务:

1
2
3
4
5
6
7
8
sudo systemctl restart postfix

# 检查2525端口是否正确监听
sudo netstat -tulnp | grep 2525

# 开放 ufw 防火墙端口
sudo ufw allow from 83.21.3.9 to any port 2525 proto tcp # 仅允许内网主机的公网 IP 访问
sudo ufw reload

到目前为止,已经可以正常进行收发邮件了🎉,启动 mailu(local), realm(vps), postfix(vps) 即可可以使用邮件客户端执行测试,测试中注意通过查看完整消息体检查是否还有内网主机的公网 IP 被记录。

Fail2ban 配置(内网主机 + VPS 主机联动)

现在的配置是没有攻击阻断的,即爆破攻击没有限制,接下来我们通过在内网主机配置 fail2ban 检查攻击,然后通过自动化联动在公网主机配置 iptables 阻断攻击。

在内网主机上安装 fail2ban:

1
sudo apt install fail2ban

内网主机 fail2ban 配置

过滤器配置(内网主机)

/etc/fail2ban/filter.d/mailu-bad-auth-bots.conf 创建过滤器:

1
2
3
4
5
6
7
8
# /etc/fail2ban/filter.d/mailu-bad-auth-bots.conf
# Fail2Ban configuration file for mailu-front log file
[Definition]
failregex = ^.* mailu-front\[\d+\]: .* Info: Disconnected: Connection closed:.*user=<>, rip=<HOST>,.*$
            ^.* mailu-front\[\d+\]: .* client login failed: \"AUTH not supported\" while in http auth state, client: <HOST>, server: .*$

ignoreregex =
journalmatch = CONTAINER_TAG=mailu-front

Jail 配置(内网主机)

/etc/fail2ban/jail.d/mailu-bad-auth-bots.conf 创建 jail:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/fail2ban/jail.d/mailu-bad-auth-bots.conf
[mailu-bad-auth-bots]
enabled = true
backend = systemd
filter = mailu-bad-auth-bots
bantime = 604800
findtime = 8000
maxretry = 3
action = mailu-sync-vps
ignoreip = 83.21.3.9/24  # 内网主机的公网 IP 段,避免误封

联动脚本配置(内网主机)

/etc/fail2ban/action.d/mailu-sync-vps.conf 创建 action:

1
2
3
4
5
6
7
8
# /etc/fail2ban/action.d/mailu-sync-vps.conf
[Definition]

actionstart = /opt/fail2ban-sync/mailu-sync-to-vps.sh init
actionstop = /opt/fail2ban-sync/mailu-sync-to-vps.sh flush

actionban = /opt/fail2ban-sync/mailu-sync-to-vps.sh ban <ip>
actionunban = /opt/fail2ban-sync/mailu-sync-to-vps.sh unban <ip>

其中涉及到的脚本 mailu-sync-to-vps.sh 需要自行创建,并放在自己设定好的位置上,其原理是通过 ssh 执行 iptables 阻断,其内容如下:

 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
# mailu-sync-to-vps.sh,用于 fail2ban 联动阻断攻击
#!/bin/bash
VPS_HOST="vps"  # 通过 ssh 配置的 VPS 主机别名,确保能通过 ssh 免密登录
IPSET_NAME="f2b-bad-auth-bots"

case "$1" in
    ban)
        ssh $VPS_HOST "ipset add -exist $IPSET_NAME $2"
        ;; # 通过 ipset 限制黑名单
    unban)
        ssh $VPS_HOST "ipset del -exist $IPSET_NAME $2"
        ;;
    init)  
        ssh $VPS_HOST "ipset create $IPSET_NAME hash:net -exist && \
        iptables -I INPUT -p tcp --dport 25 -m set --match-set $IPSET_NAME src -j DROP && \
        iptables -I INPUT -p tcp --dport 465 -m set --match-set $IPSET_NAME src -j DROP && \
        iptables -I INPUT -p tcp --dport 993 -m set --match-set $IPSET_NAME src -j DROP"
        ;; # 初始化 iptables 规则,仅需执行一次
    flush)
        ssh $VPS_HOST "iptables -D INPUT -p tcp --dport 25 -m set --match-set $IPSET_NAME src -j DROP && \
        iptables -D INPUT -p tcp --dport 465 -m set --match-set $IPSET_NAME src -j DROP && \
        iptables -D INPUT -p tcp --dport 993 -m set --match-set $IPSET_NAME src -j DROP && \
        ipset destroy $IPSET_NAME"
        ;; # 清理 iptables 规则,仅需执行一次
esac

确保脚本有执行权限:

1
sudo chmod +x /opt/fail2ban-sync/mailu-sync-to-vps.sh

执行测试,来验证脚本可以正常在 VPS 上创建阻断的 iptables 规则:

 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
# 首先在 VPS 上安装 iptables 与 ipset
sudo apt install iptables ipset -y

# 下面是测试流程---------------------------------------------

# 在内网主机上调用,对 VPS 执行初始化
/opt/fail2ban-sync/mailu-sync-to-vps.sh init

# 在 VPS 上查看 iptables 规则
sudo iptables -L -n | grep f2b-bad-auth-bots
sudo ipset list  # 查看 ipset 列表是否正确创建
-----------------------------------------------------------

# 在内网主机上调用,对 VPS 执行封禁
/opt/fail2ban-sync/mailu-sync-to-vps.sh ban 1.2.3.4

# 在 VPS 上查看 ipset 列表是否正确添加
sudo ipset list
-----------------------------------------------------------

# 在内网主机上调用,对 VPS 执行解封
/opt/fail2ban-sync/mailu-sync-to-vps.sh unban 1.2.3.4

# 在 VPS 上查看 ipset 列表是否正确删除
sudo ipset list
-----------------------------------------------------------

# 在内网主机上调用,对 VPS 执行清理
/opt/fail2ban-sync/mailu-sync-to-vps.sh flush

# 在 VPS 上查看 iptables 规则与 ipset 列表是否正确删除
sudo iptables -L -n | grep f2b-bad-auth-bots
sudo ipset list

如果一切正确,则重启内网主机上的 fail2ban 服务:

1
sudo systemctl restart fail2ban

如果一切顺利,这时候在 VPS 上应该已经创建好空的 iptables 规则与 ipset 列表,等待内网主机的 fail2ban 进行联动阻断。

1
2
sudo iptables -L -n | grep f2b-bad-auth-bots
sudo ipset list

检查

最后,检查 ufw 防火墙规则,确保只允许内网主机的公网 IP 访问 FRP 与邮件端口:

1
sudo ufw status verbose

应当看到类似如下的规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), deny (routed)
New profiles: skip

To                         Action      From
--                         ------      ----    
9000/udp                   ALLOW IN    83.21.3.9            
2525/tcp                   ALLOW IN    83.21.3.9            
80,443/tcp                 ALLOW IN    Anywhere                  
25,465,993/tcp             ALLOW IN    Anywhere                  
443/udp                    ALLOW IN    Anywhere                        
80,443/tcp (v6)            ALLOW IN    Anywhere (v6)             
25,465,993/tcp (v6)        ALLOW IN    Anywhere (v6)             
443/udp (v6)               ALLOW IN    Anywhere (v6)

观察攻击阻断能力

在正式部署之前,最好先观察一段时间,确保 fail2ban 能够正确识别攻击并通过联动脚本在 VPS 上阻断攻击。 可以通过查看内网主机的 fail2ban 结果来确认是否有攻击被识别:

1
sudo fail2ban-client status mailu-bad-auth-bots

如果有攻击被识别,在 VPS 上检查相关的 iptables 规则与 ipset 列表,确认攻击 IP 是否被正确阻断:

1
2
sudo iptables -L -n | grep f2b-bad-auth-bots
sudo ipset list

如果一切顺利,至此基于 tailscale 内网穿透的 Mailu 邮件服务器部署完成,并且具备了基本的攻击阻断能力。可以开始使用邮件客户端进行收发邮件的测试。

结语

邮件服务器的使用通常面临大量的攻击,特别是暴力破解攻击。通过将 Mailu 部署在内网环境中,并利用 tailscale 实现内网穿透,可以有效减少对 VPS 性能的依赖。同时,结合 fail2ban 与 iptables 的联动,可以增强邮件服务器的安全性,阻断恶意攻击,确保邮件服务的稳定运行。

使用 Hugo 构建
主题 StackJimmy 设计