全网统一账户实践

分享下目前我们全网的账号管理体系。

整体的账户管理思路是分而治之。主要分为下面三类账户:
1. 办公网账户,也就是大家熟悉的域账户。对于办公网账户,全网用户一人一账户,在 OpenLDAP 的基础上做了一些开发,这是进入公司内部的大门,所有新入职的员工都会分配一个该账号,不管是在办公室连接 Wi-Fi 还是在家连接 anyconnect VPN,访问 confluence/jira 等基础办公设置,都需要通过此账户进行登录认证。
2. 生产网账户,主要用来访问线上、线下机器资源。工程师访问线上生产、测试机器,登录线下自助机树莓派(raspberry) 均需要通过此账户认证,整个 user/group 的分配、HBAC/sudo 的控制、密码/公钥的管理均在 freeIPA/IDM 上实现,freeIPA 是 RedHat 支持的一整套集成安全信息管理解决方案系统,又称 IDM。
3. 数据库账户,这块比较小众,简单带过。
下面会针对上面两大块分别介绍。

办公网账户

该账号与所有人息息相关,从入职第一天起到你的 lastday,都需要登录该账户才可以访问办公资源,包括常见的 Wi-Fi/Gitlab/Jira/Confluence/Jenkins/Cisco Anyconnect VPN/Zabbix/Grafana/跳板机(intermediate host) 以及自己开发的各种内部系统。

这里面需要先简单描述下目前我们的多网分离的架构,多网是指办公网(包含国际线路)、生产网、测试网、专线网(到医院 HIS/LIS 等信息系统)、OOB(带外管理网),这五张大网是处于「部分」分离的状态的,比如,办公网到生产网/测试网/专线网/OOB 是完全分离的状态,除非通过跳板机(后面全部以 ih 代指)登录之后再访问;生产网跟测试网也是几乎分离的,除了极个别的公共服务。以办公网登录生产、测试网为例,在进入用户验证这步之前,会先通过 IP <-> MAC <-> 用户身份的一一映射绑定,先通过最基本 IP:PORT 的 ACL 方式来控制住大部分的请求。所以在你能通过网页打开 git,通过 ssh 协议 pull/push git 仓库的时候,说明已经通过 TCP/IP 层面的验证机制以及用户级别的验证机制,具体如何实现的会在后面的博客中说明。

默认情况下,OpenLDAP 支持的 schema 非常有限;另外不同应用接入方式都不大一样,需要逐一尝试。

比如 LDAP 跟 Anyconnect 的对接,官方提供的 schema 仅仅基本可用,离生产还有一段距离,默认只支持添加一组 IP/Netmask,以及一条 IP 层面的 ACL,为此需要自行扩展维护一套 cisco.ldif 文件,这个是我们自行维护的一个 schema 文件,根据我们业务的情况,新增了一组 IP/netmask 以及若干的 ACL。下面是一个普通用户的数据文件示例:

从 slapcat 导出的字段数据可以看到,为了实现 Wi-Fi 账号跟 OpenLDAP 的共享,加入了 samba 相关的 schema,具体的可以看这个文件

再比如,对于 anyconnect/ih 的认证,需要增加两步验证机制,这里引入 OTP(privacyIDEA) 以及 Radius 作为跟 OpenLDAP 的桥梁例,具体的交互图可以看这里:

最终实现了用户的 CRUD 在 OpenLDAP 层面控制。用户的两步验证,下图可以看到,privacyIDEA 支持十余种的 token,包括 hOTP/mOTP/sms/email/Yubikey 等等:

privacyIDEA 不仅仅支持我们使用的 ldapresolver,sql/passwd/scim 的 resolver 都有很好的支持。目前我们使用 Google Authenticator 是基于事件的 HOTP(RFC4226):

用来管理 OpenLDAP 的工具有不少,包括我们使用的经典的 phpldapadmin,除此之外,fusiondirectoryweb2ldap 都是不错的选择。同时为了支持用户的自助修改、重置密码,定期修改用户密码,我们引入了 LTB,一个非常神奇的 LDAP 工具箱集合,目前仅支持通过邮件找回密码的链接来重置密码:

除了自助机服务,LTB 还支持 LDAP 性能、用户使用数据的监控等若干非常实用的脚本。
这么重要的基础服务稳定性肯定是需要保障的,通过 syncprov 模块实现双 master 的写的高可用,通过多个 slave 同步 master,前端挂 Haproxy 实现读的高可用:

对用户来说,他们看到的是下面这个样子:

PACKT 出版的 Mastering OpenLDAP 是本通俗易懂的书,浏览一遍之后应该能轻松应对上面的内容。 

生产网账户

这里的生产网泛指包含线上生产测试服务器线下自助机树莓派等在内的所有 *nix 系统。所有需要登录如上机器的用户都需要获得该账户的权限之后,才能访问基于 HBAC 机制的主机以及颗粒度更细致的用户执行权限。

整个系统基于 freeIPA/IDM 实现,默认情况下,每台机器有一个专门用来运行进程(我们所有的 JVM 进程都跑在 vm 上,不会出现混跑的状态)的 worker 用户,所有的相关的 bin/, log/, lib/ 等目录文件都通过 Jenkins 统一打包到 ~/worker/ 下面,实现跟系统文件的隔离。/home/worker/ 的权限如下:

worker/ 下面所有的目录文件权限都如上所示。这样,只要用户在 bm_be_sre 这个组下面,就能实现 worker/ 下面也即 JVM 相关服务的读取权限:

下图可以看到,appCenter 这个服务是跑在 worker 下面的:

类似的,如果想实现 worker/ 下面的写操作,比如执行 bin/ 下面的脚本,将用户加到 30002(sre) 这个组即可,这里完全可以各自的需求自定义。所有的 sudo/su 相关权限设置全部在 IPA 的 Policy 操作即可,比如下面这个赋予的是 sre 这个组的用户可以执行 /bin/su – worker、/bin/su worker 这两个单独的比较危险的命令以及 sre_allow 命令行组里面基础命令(/bin/chmod, /bin/chown, /bin/cp 等等):

目前单台 8G/4core 的 IPA 支撑着 1000+ 的线上生产测试的物理虚拟机以及线下 100+ 的树莓派,应付起来绰绰有余,CPU 平均 1% 利用率、disk IO 平均 10%。

对我们的用户来说,在经过办公网/VPN 的 IP ACL 过滤之后,通过办公网账号结合 OTP 登录线上的任意一台跳板机(ih),然后通过 ih 再登录线上的机器,跟办公网账户类似的是,需要定期修改密码。对有权限看到的用户来说,他们看到的是下面这样

以上的两块账户体系算是把当年在阿里的基础核心账户体系复制了一遍,对最终用户来说,体验相对来说会更好些,当然我们没法也暂时没必要实现类似阿里郎那种监控每台入网设备所有操作的系统。

需要注意的是,在 IPA 上新增的用户,或者新增 sudo 权限,由于 sssd 的缓存,不会立即生效,可以通过 sss_cache 或者删除缓存文件可以让规则立即生效,这部分可以以类似 hook 的方式触发,每次修改用户权限的时候自动触发该操作。

freeIPA 结尾的 A 表示 audit,实际情况是目前还不支持 audit 功能,所以这块功能我们通过 snoopy 结合 rsyslog 方式进行收集审计

对于办公网账户,目前通过 python-ldap 实现了多个层次的自助以及服务的打通,freeIPA 还未进行这块的开发工作,最终的效果类比阿里内外,一个 portal 实现不同用户的自助服务。

数据库账户

第三块,或者说非常小众的一块账户是 DB 的账户,这里面主要涉及 MySQL 以及 NoSQL(Redis/ElasticSearch) 的账户划分管理审计。

MySQL 的账户管理我们通过 saltstack 的 mysql_user 进行管理,我们用的saltstack 是 2015.8.7,对 MySQL 5.7 的支持有不少 bug,2016 开头的版本对此都修复了,嫌麻烦的不妨直接把 salt 给升级了或者做 diff 给 2015.8.7 打 patch,主要原因是 5.7 之前存储用户表的密码字段是 password 而 5.7 之后变成了 authentication_string。

目前所有的 MySQL 账户用户通过 git 管理而密码通过 salt pillar 管理,审计则使用了 Percona 的 Audit Log Plugin 通过 rsyslog 打到中心服务器收集分析。

对于 Redis/ES 来说,由于上面存储的数据为非敏感数据,所有的都通过 IP 层面的 ACL 进行控制访问。

OpenVPN Connectivity Issue in Public Network

We established a ovpn tunnel between 2 IDCs in September 2014, and we have monitored the availability and performance of two ends for a long time. The geographical distance between 2 IDCs are quite short, but with different telecom carries. mtr shows that there exits ahout 7 hops from one end to the other. The below screenshot shows the standard ping loss.


The result is quite interesting. At first we used UDP protocol, and we often experienced network disconnection issue, later we switched to TCP, and it improved a lot. From the digram, the average package is 1.11%.
Why 1.11%, what I can explain is the tunnel is often fully saturated during the peak hour, and this can't solved at the moment, so no matter what protocol, the package loss should exists. Another possible reason is the complexity of public network which I can't quantitate.
The current plan works during current background, but no "how many 9s" guarantee. Anyway, if we want to achieve more stable connectivity, a DLL(dedicated leased line) is a better choice.

耗时两天排查问题的小结

最近在线上遇到了一个比较棘手的 ops 问题,我花了两个工作日加上中间的一个晚上时段才最终发现了问题的根源,下面分享下我排查问题的思路以及步骤,先介绍下背景。
三个 IDC,BTC, DG, DL,其中 BTC, DG 是通过光纤直连,内网互通,DL 通过与 BTC 之间建立 OpenVPN tunnel 实现了三个 IDC 内网的打通。这三个 IDC 部分的 VLAN 网段信息分别如下:
BTC: 172.18.10.0/24,BTC 这台机器的 OpenVPN client IP 为 172.18.10.254
DG: 172.20.0.0/24, 172.20.100.0/24, 172.20.200.0/24 等等
DL: 192.168.1.0/24,192.168.1.24 这台 OpenVPN 作为 server 跟 BTC 的 172.18.10.254 建立起 tunnel
由于涉及到公司的商业信息,以上是我简化后的模型,实际的情况、涉及到的因素要比这个复杂的多。

某日下午我做完一个常规性的变更之后,收到反馈说从 DG 172.20.100.0/24 的机器无法访问位于 DL 192.168.1.26/24 的这台机器。
很自然的,我从 172.20.100.0/24 上挑选了 172.20.100.1 这台机器,登录上去,确认了下面的几件事:
1. 基本的网络连同性,ping 192.168.1.26,发现无法 ping 通。

2. 看 route 信息,通过 mtr 看路由信息:
$ sudo mtr 192.168.1.26 -n -r
HOST: host-1.umops.us             Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 172.20.100.249                 0.0%    10    0.5   2.0   0.5  10.4   3.3
  2. 172.16.1.254                  0.0%    10    3.8   2.1   0.7   7.4   2.3
  3. 172.18.10.254                  0.0%    10    0.3   0.4   0.3   0.4   0.0
  4. ???
可以很清楚的看到,到了 BTC 的 OpenVPN client 之后就断了,也就是没有路由指向了。

2. 基本的 networking,从 ifconfig 以及 /etc/sysconfig/network-scripts/ 来看,没有问题

3. 基本的 route:
jaseywang@host-1:~$ route  -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
172.20.100.0     0.0.0.0         255.255.255.0   U     0      0        0 bond0
0.0.0.0         172.20.100.254   0.0.0.0         UG    0      0        0 bond0
没有问题,默认 GW 直接上我们 Nexus 7000 的 HSRP VIP(172.20.100.254),上面看到的 172.20.100.249 是 RIP。

接着我把 172.20.100.0/24 的其他几台机器挨个试了一遍,发现现象跟上面一样。

接着,我登录到 DG 的其他网段比如 172.20.0.0/24 的机器上做同样的测试,可以发现完全没有问题:
$ sudo mtr 192.168.1.26 -n -r
HOST: host-2.umops.us               Loss%   Snt   Last   Avg  Best  Wrst StDev
  1. 172.20.0.248                  0.0%    10    0.7   0.5   0.4   0.7   0.1
  2. 172.16.1.254                  0.0%    10    5.7   2.3   0.6   8.6   2.8
  3. 172.18.10.254                  0.0%    10    0.3   0.3   0.3   0.4   0.0
  4. 192.168.4.1                   0.0%    10    6.9   5.6   2.6  24.5   6.8
  5. 192.168.1.26                  0.0%    10   17.6  10.2   2.9  36.2  10.5

当时第一想法是,是不是 DG 的 SW 配置有问题,是不是把 172.20.100.0/24 这个网段在某些关键的点上漏掉了,于是让我们的 ne 上去确认,反馈说没有问题。事后总结起来,其实从 mtr 上就能看到应该不是 SW 的问题,mtr 可以看到到了 hop 4 才中断,否则第一或者第二 hop 就断了。

并且诡异的是,DG 的其他网段的机器都没有问题,唯独 172.20.100.0/24 的有问题,我依然认为是整个从端到端的某个设备或者服务对 172.20.100.0/24 这个网段少做或者多做了一些变更,但是从端到端,期间涉及众多的机器,众多的不同同层的因素,从服务器到交换机再加上 VPN,确实需要考虑进去的因素比较多,暂时还没有很好的缩小问题的方式。

第一天晚上回去的时候想起来可以反向试试,于是从 192.168.1.26 这台机器 mtr 到 172.20.100.1,hop 如下:
$ mtr 172.20.100.1 -r -n
HOST: github-jaseywang-me            Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|– 192.168.1.24               0.0%    10    0.5   0.4   0.3   0.5   0.1
  2.|– ???                       100.0    10    0.0   0.0   0.0   0.0   0.0
可以发现,IDGP 包到了 DL 的 OpenVPN server 就中止了。

至此,我基本可以将问题的范围缩小到连接 BTC 跟 DL 的两台对端的 OpenVPN 机器上,并且从 hops 信息来看,应该是 DL 的 OpenVPN server 有问题的概率更大。

晚上回去之后,我一边在 192.168.1.26 这台机器上 mtr 172.20.100.1,一边通过 tcpdump 抓包,可以发现只有 syn,没有任何的 ack。同样的,mtr 172.20.0.1 这类的机器却没有任何的问题,有 request/reply。这更加确定了我之前的想法,一定是这个端到端的跨越了 3 个 IDC 的某个子系统出了点问题,并且这个问题可以归结于,把 172.20.100.0/24 这个段给特殊对待了。

至于是哪台机器的哪个服务出现了问题,这个就是我那天晚上以及第二天上午做的工作。当天晚上,我又确认了下面的几件事:
1. 一一排查了这两台 OpenVPN 机器的 iptables,为了以防万一,我清空了 INPUT 做测试,现象依旧
2. 再三确定 SNAT 对 OpenVPN 影响很大的这个 chain 配置没有问题
3. selinux 关闭了,sysctl 里面也没有特殊的开关
4. 基础的系统配置,包括我最开始提到的 networking、route 等信息
5. 172.20.100.0/24 里面的机器跟其他比如 172.20.0.0/24 机器的差异,由于都是统一定制安装的,在基础的系统层面根本找不出差异
6. OpenVPN 的配置,把 client, server 的两个文件检查了好几遍,没有发现异常

第二天上午继续排查了两个多小时,能想到的可能会涉及到的方面都想到了,依然没有结论。转而向我们的总监求助,跟他描述了下问题的情况,跟我一样,一样觉得很诡异,同样的配置,其他 VLAN 的机器完全没问题,就这一个 VLAN 的有问题。要是整个 DG 的 VLAN 都不通,那还能说明些问题,现在仅仅这一个 VLAN 不通。
下午,我们又从下面几个方面入手逐一排查:
1. 依然怀疑是 route 层面有问题,并且是 DG 那台 OpenVPN server 本机的 route 有问题,于是直接上了 iproute2 来确定 route table 是否正确,我也临时熟悉了 iproute2 的一些基本概念。通过下面几条 cli 判断了几个不同类型的 routing table 的情况:
# ip rule list
# ip route get 172.20.100.1

2. tracapath 继续跟踪了番,现象依然是到了 DG 的 OpenVPN server 就断了

3. 通过 starce 查看系统的调用显示跟上面一样的现象

4. 由于这两台 OpenVPN 的机器都是跑在 Xen 上的 instance,怀疑可能是由于 VM 某些方面的问题导致的,但是考虑到使用的是 bridge 并且要排除 VM 导致可能性的的繁杂程度,暂时放弃深挖。

5. bonding 方面的影响,由于我们对 DG 的所有机器刚做完 10G 的升级,包括 172.20.100.1 这台机器都使用了 bonding,尽管没没有理由怀疑是 bonding 造成的,我们还是把这台机器的 bondin 给拆了,换成了普通的单网卡模式进行测试,结果很明显

6. 抓包,在两端的 OpenVPN 上抓包以及在上联的交换机做 mirror,得到的现象跟上面一样

7. 扩大 OpenVPN server 的 netmask,既然 172.20.100.0/24 这个跟其他的诸如 172.20.0.0/24 等地位一样,将原先的 /24 扩大到 /16 以包含所有的 IP,就没有理由不通了,这个是修改前的 route:
# route  -n

172.20.100.0     0.0.0.0         255.255.255.0   U     0      0        0 tun0
172.20.0.0       0.0.0.0         255.255.255.0   U     0      0        0 tun0

修改后的 route:
# route  -n

172.20..0       0.0.0.0         255.255.0.0   U     0      0        0 tun0

意外的是,依然不通

8. 在 DG 新启用了一个 172.20.150.0/24 gateway 为 172.20.150.254 的 VIP,在 DL 的 OpenVPN server 上添加到到这个 IP 的路由:
# route  add -net 172.20.150.0  netmask 255.255.255.0 tun0

在 192.168.1.26 的机器上测试,现象依旧:
$ mtr 172.20.150.254 -r -n
HOST: github-jaseywang-me            Loss%   Snt   Last   Avg  Best  Wrst StDev
  1.|– 192.168.1.24               0.0%    10    0.4   0.5   0.4   1.1   0.2
  2.|– ???                       100.0    10    0.0   0.0   0.0   0.0   0.0

把以上所有的现象以及测试结果综合起来看,几乎可以可以确定就是 OpenVPN server 的问题,但是哪里有问题,依然不清楚。 这时我们突然想到还有这台 OpenVPN server 的 log 没仔细检查一边,google 了下,通过 "verb 5" 提高了 debug 级别,这下就能很详细的看到 OpenVPN 打的 log。log 的格式比较奇怪,像下面这样的,但是这不重要,下面这么多的字符,好好看,能发现 "packet dropped" 的关键词:
wrWrWRwRwrWRwrWRwRwrWrWRwRFri Apr 18 17:05:02 2014 us=291047 btc/111.111.111.111:45721 MULTI: bad source address from client [111.111.111.111], packet dropped
RwRwRwrWRwRwRwRwRwRwrWRwrWRwrWrWRwrWRwRwrWRwrWrWRwRwrWrWrWRwrWRwRw
RwRwRwRFri Apr 18 17:05:10 2014 us=132323 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.38 -> btc/111.111.111.111:45721
RwrWRwRwRwrWrWrWRwRwrWrWRwrWRwRwRwrWrWrWRwRwrWrWRwrWRwRwRwrWrWR
i Apr 18 17:06:07 2014 us=891600 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.126 -> btc/111.111.111.111:45721
RwrWrWRwrWrWRwrWRwRFri Apr 18 17:06:29 2014 us=146563 btc/111.111.111.111:45721 MULTI: Learn: 10.8.0.26 -> btc/11
1.111.111.111:45721

Google 第一条就告诉了我们答案!原来 172.20.100.0/24 作为我们较新的网段,我忘记把 iroute 写到 ccd 文件里面了:
These errors occur because OpenVPN doesn't have an internal route for
192.168.100.249. Consequently, it doesn't know how to route the packet to this
machine, so it drops the packet.
Use client-config-dir and create a ccd file for your client containing the
iroute option to tell OpenVPN that the 192.168.100.0/24 network is available
behind this client.

"MULTI: bad source address from client [192.168.100.249], packet dropped" or
"GET INST BY VIRT: 192.168.100.249 [failed]"?

下面提几个跟 route 关系较大的指令(1, 2) ,以后 end-to-end, sub-to-sub 出现问题,不妨先考虑是不是这些方面造成的。
1. client end 的 route
;push "route 192.168.10.0 255.255.255.0"

2. client subnet 的 route
;client-config-dir ccd
;route 192.168.40.128 255.255.255.248
;route 10.9.0.0 255.255.255.252

最后总结一下,这是一次刺激的发现问题找到根源解决问题的过程,又一次验证了整个 *nix stack 里面最复杂的是 networking 这个问题,因为牵扯到层面太多。正所谓「山穷水尽疑无路,柳暗花明又一村」。不吹牛逼的说,做了这么多年,还真没有我解决不了的工程问题。

openvpn 证书吊销(revoke)的问题

执行的命令很简单:
# ./revoke-full test_1

revoke 之后,index.txt 对应的条目会由 V 变成 R。另外,会在 keys/ 下面生成 crl.pem 的 CRL。
在使用过程中发现,需要将其 ln 到 /etc/openvpn 下面:
# mv /etc/openvpn/keys/crl.pem /etc/openvpn/.
# cd /etc/openvpn/keys
# ln -s /etc/openvpn/crl.pem .

还需要在配置文件里面指明其位置:
crl-verify crl.pem

否则会出现权限错误等问题:
CRL: cannot read: /etc/openvpn/keys/crl.pem: Permission denied (errno=13)
Exiting

重启即可。


在用 build-key 生成证书的过程如,如果后面的 CN(common name) 跟之前的一样,那么或出现下面的问题:
Certificate is to be certified until Jun 10 06:58:00 2023 GMT (3650 days)
Sign the certificate? [y/n]:y
failed to update database
TXT_DB error number 2

自然不推荐使用同样的 CN,如果一定要使用,可以将 index.txt.attr 文件里面的 unique_subject = yes 改成 unique_subject = no。这样在 index.txt 里面看到的就是两个了:
# tail index.txt -n 2
V 230810065719Z   46  unknown /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/CN=test_1/emailAddress=me@myhost.mydomain
V 230810070508Z   47  unknown /C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/CN=test_1/emailAddress=me@myhost.mydomain

这个时候如果需要 revoke,需要单独的对每一个 test_1 进行 revoke,注意不要把两个 CN 的证书搞混淆了。

最后,如果蛋疼的想 unrevoke 证书,可以参考这里

跨机房部署

跨机房的服务部署分两个小问题。第一个是系统(RAID、BIOS、OS) 的部署,另外一个则是 OS 上面的服务的部署。

系统
曾经想通过 dhcp over openvpn 来解决机房的"互联互通"问题,目前暂时仅仅解决了通过 openvpn 进行 lan to lan 的连接,而 over openvpn 的 dhcp(dhcrelay) 到目前为止还不清楚为什么获取不到 IP。

后来看 cobbler 的文档的时候发现可以通过 replicating 实现,这个需要在对端的 IDC 部署一台 cobbler server,由这台机器完成 tftp, dhcp 的功能,而由 central cobbler 实现统一的 system, repo 等配置。
部署起来也很简单。在对端部署一台跟 central cobbler 一样的 cobbler,默认不需要添加任何的 distro 等。该端所有的配置均从 central 拉取,因此需要足够的硬盘空间,尤其要同步 repo:
# cobbler replicate cobbler replicate –master=cobbler.example.org [–distros=pattern] [–profiles=pattern] [–systems=pattern] [–repos-pattern] [–images=pattern] [–prune] [–omit-data]

接下来该建立 RAID 的建立 RAID,该安装系统就安装系统。另外,官方推荐通过 trigger 机制来自动的同步 central 上的数据,这个我后来调研的时候发现并不是很好,如果有多个机房的话,会造成 system 的混乱。因此,暂时还是手动的在对端的 cobbler 上手动的执行一遍 replicating,每次机器上架的时候的才会用到,工作量并不大。

服务
这个相对就比较简单了,如果是通过 tunnel 形成的一个三层结构,那么通过 puppet master 或者类似的 saltstack 就能很轻松的完成,前提是注意 ntp 以及 dns(hosts) 的统一正确。

跨机房的网络通信

这个实现方式有很多种,有条件的可以走光纤专线;没钱的,开 VPN,其中关于 VPN 的实现方式有很多种,这里使用 OpenVPN。由于没有二层的需求,就不考虑使用 TAP(server-bridge) ,这里使用 TUN(server) 设备。
这里,我们把 client/gw 分别部署在 A、B 两 IDC GW 上,即 SNAT 的出口上。

各种复杂情况的组合无外乎下面两种情况的组合叠加:
1. Including multiple machines on the server side when using a routed VPN (dev tun)
这个模式就是我们最普遍使用的模式,科学上网以及一般内网的登录都是使用的该模式

2. Including multiple machines on the client side when using a routed VPN (dev tun) **
这个是让 server 访问 client 后面的机器,需要 client-config-dir 指令来维护路由

这里有个文档,涉及就是上面两种情况的结合,不过是通过普通的家用路由实现的,思路非常的好。而下面,我们会通过更 "纯正" 的方式实现上面的情景。

我们这里有 A、B 两个机房:
A(server): eth0(public), eth1(192.168.1.0/24)
B(client): eth0(public), eth1(10.18.10.0/24)

双方的 vpn 地址在 192.168.4.0/24 里面。

对于 A 的 server.conf 来说,最重要的就是下面几句话:
$ cat /etc/openvpn/server.conf
push "route 192.168.1.0 255.255.255.0"
client-config-dir       /etc/openvpn/ccd

$ cat /etc/openvpn/ccd/B
iroute 10.18.10.0 255.255.255.0

接着就是在 GW 上做 SNAT:
-A INPUT -s 192.168.4.0/24 -j ACCEPT
-A POSTROUTING -s 192.168.4.0/24 -o eth1 -j MASQUERADE

增加到 10.18.10.0/24 的 路由:
# route add -net 10.18.10.0/24 netmask 255.255.255.0 gw tun0

对于 B 来说,client.conf 跟正常的一样,没什么需要特别注意的,接着就是在 netfilter 上添加 SNAT:
-A INPUT -s 192.168.4.0/24 -j ACCEPT
-A POSTROUTING -s 192.168.4.0/24 -o eth1 -j MASQUERADE

最后要注意的是,双方同时开启 ip_forward。理清楚了,就这么简单。