Paranoid Penguin - Linux VPN 与 OpenVPN,第三部分
在前两篇专栏文章中,我开始了一个关于使用 OpenVPN 构建基于 Linux 的虚拟专用网络 (VPN) 解决方案的系列。上次结束时,我已经完成了 OpenVPN 服务器配置过程,包括创建简单的公钥基础设施 (PKI),使用它来生成服务器和客户端证书,以及创建构建 OpenVPN 隧道所需的一些其他“支持”文件。在这样做时,我仅仅完成了 OpenVPN 服务器配置示例文件的前三分之一左右,但这些 PKI/加密相关的配置参数代表了 OpenVPN 配置任务中最复杂的部分。
本月,我将介绍该服务器配置文件的其余部分,并展示一个相应的 OpenVPN 客户端配置文件(我将在下个月剖析它)。我还将展示如何启动服务器和客户端进程,尽管调试、防火墙注意事项和其他更精细的要点也需要等到我的下一篇专栏文章。
请不要害怕——我想您会发现本期内容本身就充满了行动性。让我们开始吧!
通常在多部分系列的这个阶段,我会回顾至少一些上个月专栏文章的细节,但这这次行不通。上个月的文章涵盖了很多内容,而本月需要涵盖更多。我只想说,我开始剖析一个 OpenVPN 服务器配置文件示例,/etc/openvpn/server.ovpn (列表 1)。
我完成了生成文件中引用的文件的步骤ca, cert, key, dh和tls-auth行,使用了 OpenVPN “easy-rsa” 辅助脚本(位于 /usr/share/doc/openvpn/examples/easy-rsa/2.0)以及命令 openvpn 和 openssl。我将继续描述列表 1 的参数,假设前面提到的证书、密钥和其他辅助文件都已就位。
列表 1. 服务器的 server.ovpn 文件
port 1194 proto udp dev tun ca 2.0/keys/ca.crt cert 2.0/keys/server.crt key 2.0/keys/server.key # This file should be kept secret dh 2.0/keys/dh1024.pem tls-auth 2.0/keys/ta.key 0 server 10.31.33.0 255.255.255.0 ifconfig-pool-persist ipp.txt push "redirect-gateway def1 bypass-dhcp" keepalive 10 120 cipher BF-CBC # Blowfish (default) comp-lzo max-clients 2 user nobody group nogroup persist-key persist-tun status openvpn-status.log verb 3 mute 20
因此,在设置了基本的端口/协议/设备设置和加密相关设置后,让我们继续讨论确定一旦客户端成功建立经过身份验证的加密隧道后会发生什么情况的设置。第一个这样的设置是server.
server实际上是一个辅助指令。它扩展到整个其他参数块。与其费力地研究所有这些附加参数,不如这样说,server 指令接受两个参数:网络地址和网络掩码。对于客户端在之前指定的端口上建立的每个隧道,OpenVPN 服务器进程将从指定的 IP 空间中划分出一个小的 30 位子网,将该子范围中的第一个主机 IP 地址分配为本地隧道端点,并将 30 位子网中的另一个主机 IP 分配给连接的客户端作为其远程隧道端点。
在示例中,我指定了网络地址 10.31.33.0,网络掩码为 255.255.255.0,这转换为从 10.31.33.1 到 10.31.33.254 的 IP 地址范围。当第一个隧道建立时,服务器将使用 10.31.33.1 作为其本地隧道端点地址,并将 10.31.33.2 分配给客户端用作远程隧道端点地址。(10.31.33.0 是该子网的网络地址,10.31.33.3 是其广播地址。)
对于下一个要连接的客户端,服务器将使用 10.31.33.5 作为其隧道端点,并将 10.31.33.6 分配为客户端的隧道端点(10.31.33.4 和 10.31.33.7 分别为网络地址和广播地址)。明白了吗?
这不是 IP 范围最有效的使用方式。服务器需要一个不同的本地 IP 地址用于每个它构建的隧道,并且对于每个隧道,服务器基本上浪费了另外两个(用于网络地址和广播地址)。在 server 指令前加上行topology subnet将导致服务器在其server [网络地址] [网络掩码]范围中使用第一个 IP 作为其所有隧道的本地隧道 IP,并且客户端隧道端点 IP 将从该范围中整个剩余的可能 IP 中分配,就像所有远程隧道端点都是同一 LAN 上的 IP 地址一样。
这不是默认行为,因为它对于 OpenVPN 2.1 来说是新的。“subnet”拓扑结构不受早期版本或使用 8.1 或更低版本 TAP-Win32 驱动程序的 Windows 客户端的支持。请注意,如果未声明(如列表 1 中所示),则topology参数的默认值为net30,这会导致服务器指定的 IP 范围被拆分为上述 30 位子网。
继续列表 1,接下来是ifconfig-pool-persist,它指定一个文件,用于存储隧道客户端的公用名称(通常是它们的主机名,如其各自的客户端证书中所指定)与服务器分配给其隧道的 IP 地址之间的关联。虽然这不能保证给定的客户端每次连接时都会收到相同的隧道 IP,但它确实允许客户端在其客户端配置中使用--persist-tun选项,这使隧道会话在服务中断(OpenVPN 服务器守护程序重启、网络问题等)期间保持打开状态。
接下来是语句push "redirect-gateway def1 bypass-dhcp"。push指令使后续的双引号括起来的字符串在客户端上运行,就像它是客户端本地配置文件的一部分一样。在这种情况下,服务器将推送redirect-gateway参数给所有客户端,其效果是,每次客户端连接时,客户端的本地默认网关、DNS 服务器以及通常由 DHCP 提供的其他网络参数将被服务器的这些设置覆盖。
这有效地强制执行了“仅限本地子网拆分隧道”的 VPN 策略。对于那些 VPN 新手来说,拆分隧道配置是指客户端可以使用其 VPN 隧道连接到某些内容,并使用其本地(非隧道)互联网连接连接到其他内容的一种配置。
正如我在之前的专栏文章中所说,强制客户端使用远程网络的基础设施(DNS 服务器、互联网上行链路等)使得连接到客户端本地网络(可能是咖啡店无线热点等不受信任的环境)的攻击者更难执行各种窃听、会话劫持和中间人攻击。
即使使用此设置,客户端仍然能够连接到本地网络上的一些内容。它只是无法将其用作除连接回您的 OpenVPN 服务器之外的任何内容的路由。同样,配置客户端尽可能多地利用您受信任的网络基础设施是一种好的策略。
在push "redirect-gateway..."指令之后是keepalive 10 120。server, keepalive就像一个辅助指令,它扩展到其他参数的列表。再次为了简洁起见,让我总结一下示例行的效果:每十秒钟,服务器将检查以查看每个客户端是否仍然连接,如果在任何 120 秒的时间段内未收到来自给定客户端的回复,它将假定该客户端在其最后已知的 IP 地址处不可达。
例如,如果服务器在 9:00:00 向特定的隧道客户端发送查询并收到回复,但在 9:00:10 向其发送另一个查询但没有收到回复,并且在 9:00:20、9:00:30 以及直到 9:02:00 发送的 11 个或更多查询中也没有收到回复,那么在 9:02:00(在 120 秒没有回复后),服务器将得出结论,客户端系统不可达。
此时,服务器将尝试重新解析远程客户端的名称,假设其 IP 地址可能已更改(例如,由于 DHCP 租约续订),从而重新建立隧道会话。
顺便说一句,上述基础设施设置(例如 DNS 服务器)将由服务器的 openvpn 进程从 /etc/resolv.conf、服务器的运行路由表等读取——除非您希望它们与服务器的不同,否则这些设置不需要 OpenVPN 配置参数。(现在,让我们假设您不想这样做!)
我仅仅在少数几个设置上就花了很多笔墨。但我认为这是有道理的,因为server和keepalive是扩展到更多设置的辅助指令,并且考虑到我们现在已经完成了服务器配置的网络配置部分。
下一个参数很简单cipher BF-CBC,它指定每个隧道的数据负载将使用 Blowfish 密码(在密码块链接模式 (CBC 模式) 下使用 128 位密钥)进行加密。(CBC 模式使攻击者更难以暴力破解给定会话的隔离部分)。BF-CBC 是cipher的默认设置,因此从技术上讲,我不需要指定它,但这是一个重要的设置。您可以使用命令openvpn --show-ciphers来查看所有受支持的密码值及其默认密钥大小的列表。
comp-lzo甚至更简单。它告诉 OpenVPN 使用 LZO 压缩算法压缩所有会话数据,除非给定的数据部分似乎已被压缩(例如,如果正在传输 JPEG 图像或 ZIP 文件),在这种情况下,OpenVPN 将不会压缩,直到它检测到返回到未压缩的会话内容。这种自适应行为有助于最大限度地减少尝试压缩已压缩数据导致的数据填充。由于 LZO 是一种快速算法,因此这是一个很好的设置。它在 CPU 开销方面的成本通常可以通过它节省的网络带宽量(以及由此产生的其他 CPU 周期)来弥补。
下一个设置,max-clients 2,指定一次最多可以有两个隧道处于活动状态。如果您只有一两个用户,则没有充分的理由允许超过一两个并发隧道。但是,在我自己的测试中,我发现即使您只有一个用户,将此设置一直降低到 1 也可能导致问题,这可能是由于 OpenVPN 处理隧道持久性的方式(参见keepalive以上)。
接下来的四个设置是相互关联的。user和group指定非特权用户帐户和组的名称(nobody和nogroup,分别),供 OpenVPN 服务器守护程序在打开必要的 tun/tap 设备、读取其配置文件、证书和密钥以及其他仅限 root 用户的启动操作后,降级为该用户和组。
为了使其正常工作,您还需要设置persist-key和persist-tun. persist-key导致 OpenVPN 在守护程序中断(如隧道断开并重新建立所引起的中断)期间,将密钥文件内容缓存在内存中。persist-tun导致 OpenVPN 在相同的守护程序重启期间,保持启动时打开的任何 tun/tap 设备处于打开状态。
使用user和group设置为非特权用户和组,如果您跳过声明persist-key或persist-tun,则 OpenVPN 守护程序将缺乏重新读取受保护的密钥文件或重新打开 tun 或 tap 设备所需的特权。
当然,您可以跳过用户和组设置。但是,这些设置减轻了一些不可预见的缓冲区溢出漏洞的影响。它可以使攻击者获得非特权 shell 和获得 root shell 之间产生差异。不幸的是,您不能仅仅因为 OpenVPN 在缺乏许多重大安全漏洞方面迄今为止拥有良好的记录,就假设它永远不会有任何漏洞!
列表 1 中的最后三个设置与日志记录有关。status指定一个文件,OpenVPN 将定期向该文件写入守护程序状态更新,而与实际活动无关。与大多数日志文件不同,每次更新此文件时,OpenVPN 都会覆盖之前的消息。这就是我的 OpenVPN 服务器上的文件 /etc/openvpn/openvpn-status.log 现在所说的
OpenVPN CLIENT LIST Updated,Fri Jan 1 21:55:11 2010 Common Name,Real Address,Bytes Received,Bytes Sent,Connected Since minion2,192.168.20.1:36491,125761,103329,Fri Jan 1 17:56:21 2010 ROUTING TABLE Virtual Address,Common Name,Real Address,Last Ref 10.31.33.6,minion2,192.168.20.1:36491,Fri Jan 1 20:54:03 2010 GLOBAL STATS Max bcast/mcast queue length,0 END
如您所见,当前只有一个客户端连接(minion2),以及一个相应的路由表条目。
回到列表 1 中,verb 3将总体日志记录详细程度设置为 3,范围为 0(不记录日志,除非发生重大错误)到 11(可能最详细的调试输出)。默认值为 1,但 3 对于设置和正常工作更有用,而不会带来日志文件增长过快的任何特殊危险。
对于mute 20设置来说尤其如此,它告诉 OpenVPN 永远不要连续记录相同的消息(在给定的事件类别中)超过 20 次。
在我的 Ubuntu 系统上,如果使用openvpn命令执行时带有--daemon标志,这将导致它作为后台(守护程序)进程运行,则 OpenVPN 会将其所有消息写入 /var/log/daemon。如果您运行openvpn不带--daemon,它将在前台运行并将所有消息记录到您启动它的控制台或终端窗口中(在此过程中占用该控制台,但这是在初始设置和测试期间运行 OpenVPN 的一种非常方便的方法)。
既然我已经深入介绍了示例服务器配置文件,那么让我们启动 OpenVPN 守护程序,使其处于服务器模式!正如您将看到的,这是容易的部分。
OpenVPN 使用单个命令openvpn,用于所有操作。任何给定的 OpenVPN 实例的具体作用取决于您如何启动它。正如您已经看到的,一些启动参数,例如--show-ciphers,使 openvpn 命令给出某些信息然后退出。其他参数告诉它保持活动状态,侦听传入的客户端连接(--mode server)或尝试建立和维护到某个服务器的隧道,作为客户端(--mode client).
如果您执行openvpn使用--config参数,后跟配置文件的名称,OpenVPN 将启动自身,并使用该文件中的所有参数进行配置。例如,您可以创建一个配置文件,其中仅包含参数show-ciphers(如果在命令行中指定参数,则参数必须以 -- 开头,但配置文件中的所有参数都省略 --)。
更常见的情况是,与列表 1 一样,我们使用配置文件进行服务器模式和客户端模式启动。我提到过server辅助指令扩展为其他参数的列表;其中第一个是mode server.
因此,要将 OpenVPN 作为持久服务器守护程序启动,并运行列表 1 中显示的配置文件 /etc/openvpn/server.ovpn,请使用以下命令
sudo openvpn --config ./server.ovpn
请注意文件 server.ovpn 的相对路径。如果该文件位于 /etc/openvpn 中,则您需要从该目录中运行上述命令。另请注意 sudo 的使用。在非 Ubuntu 系统上,您可能需要先使用su切换到 root 用户,然后再运行此命令。无论如何,OpenVPN 必须以 root 用户身份运行,才能读取其服务器密钥文件、打开 tun 设备等,即使如列表 1 中配置的那样,它随后也会将自身降级为用户 nobody 和组 ID nogroup。
您是否注意到我在该命令行中省略了--daemon标志?同样,您可以使用该标志告诉 OpenVPN 在后台运行(像一个安静、行为良好的守护程序一样),并将其消息记录到 /var/log/daemon.log 中,但您可能首先需要确保一切正常工作。
在这一点上,我曾希望能够为您详细介绍客户端配置,但现在空间不足,因此这需要等到下次。但是,我不会让您完全悬而未决。列表 2 显示了一个示例客户端配置文件 client.ovpn,它对应于列表 1 的 server.ovpn 文件。
列表 2. 客户端的 iwazaru.ovpn 文件
client dev tun proto udp remote 1.2.3.4 1194 resolv-retry infinite nobind user nobody group nogroup persist-key persist-tun mute-replay-warnings ca ca.crt cert minion.crt key minion.key ns-cert-type server tls-auth ta.key 1 cipher BF-CBC comp-lzo verb 3 mute 20
其中大部分应该很熟悉。其他部分您可以通过 openvpn(8) 手册页来理解。与此同时,请随意尝试。要在客户端计算机上以客户端模式运行 OpenVPN,请使用以下命令
sudo openvpn --config ./iwazaru.ovpn --daemon openvpn-client
给您实验者的一个临别提示:您需要禁用或重新配置您在服务器或客户端系统上运行的任何本地 iptables(防火墙)规则。我将在本系列的下一篇专栏文章中讨论 iptables 的注意事项,并且我将从这次结束的地方继续。在那之前,请注意安全!
Mick Bauer (darth.elmo@wiremonkeys.org) 是美国最大的银行之一的网络安全架构师。他是 O'Reilly 图书 Linux 服务器安全 第二版(以前称为 使用 Linux 构建安全服务器)的作者,信息安全会议的偶尔演讲者和“网络工程波尔卡舞曲”的作曲家。