第二章:点对点模式

起初,使用OpenVPN时,使用预共享密钥(pre-shared keys)的点对点模式是唯一可用的选项。如今,有多种方法可以使用OpenVPN,但点对点模式仍然可用。point-to-point mode using pre-shared keys 通常缩写为 pre-shared keys

在点对点模式下,OpenVPN使用预定义端点的预共享密钥进行配置,一次只能有一个端点连接到服务器实例。服务器一词可能会被认为具有误导性,因为在功能方面,两个端点或多或少是对等的。发起连接的端点被视为客户端,另一个端点则被视为服务器。

我们将从一个非常基本的例子开始演示。之后,我们将讨论OpenVPN提供的更多功能。我们将探讨以下主题:

密钥模式的优缺点

使用预共享密钥模式的主要用例是连接两个远程网络,例如,一家小公司的主办公室和远程办公室。只要需要三个以上的用户或端点,就可以更容易地使用客户端/服务器模式,如第四章 客户端/服务器方式与tun设备 所述。本章稍后将给出一个如何使用预共享密钥将三个站点连接在一起的示例,这将清楚为什么预共享密钥模式不能很好地扩展到三个站点或用户之外。

使用预共享密钥模式的主要优点如下:

使用预共享密钥模式的缺点是:

重要的是要意识到,与使用证书和客户端/服务器设置相比,使用预共享密钥时OpenVPN的实际操作方式不同。OpenVPN中遵循的代码路径实际上非常不同,例如,不需要控制信道协商。普通终端用户不会看到这些差异,但在需要对OpenVPN连接进行故障排除时,了解这些差异非常重要。此外,当读取详细程度设置未高(即高于5)的OpenVPN日志文件时,预共享密钥连接的输出与基于证书的连接的输出看起来会有很大不同。

除非另有说明,否则本章中的示例均基于运行CentOS 6 64bit的端点。安装的OpenVPN软件版本未v2.3.2,取自CentOS-EPEL存储库。

第一个示例

  1. 使用OpenVPN连接两台计算机的最简单和最短的示例是在监听模式下启动第一个端点,使用固定IP地址和tun式网络:

  2. 接下来启动OpenVPN client :

  3. 在另一个终端窗口中,列出网络设备:

    以下屏幕截图显示了如何建立连接:

4.如果防火墙和SELinux规则允许,我们现在可以从任何一端ping OpenVPN端点。

连接日志显示了一些有趣的细节。客户端的OpenVPN版本是2.3.2 x86_64-redhat-linux-gnu。这验证了我们正在64位版本的RedHat Linux衍生产品上运行v2.3.2。

连接日志显示警告:

打印此警告是因为没有指定用于加密连接的密钥,这使得此示例不太安全。

如果打印了以下消息,则连接已成功完成。然而,我们将在后面的示例中看到,这并不一定意味着VPN运行正常。

TCP协议和不同的端口

OpenVPN使用的默认协议是UDP,因为它通常更适合VPN连接。然而,如果需要TCP协议,那么前面的例子只需要稍作修改:

在监听端,使用以下方式启动OpenVPN服务器:

在客户端,使用以下代码:

OpenVPN现在将通过TCP端口1194连接。也可以使用--port参数覆盖端口号,例如 --port 5000

TAP 模式

如果需要通过VPN隧道传递非TCP/IP流量(例如,传统的AppleTalk或IPX流量),则需要一个 tap 设备。tap 设备提供了一个接口,用于通过VPN隧道传递完整的以太网帧。传递完整以太网帧时的开销可以忽略不计。tap 设备的IP分配不同于 tun 设备,因为 tap 设备充当常规(regular)网络适配器,需要为其分配单个IP地址和网络掩码。

前面的例子现在被修改了。在监听端,启动OpenVPN服务器进程:

在客户端,使用以下代码:

然后,我们列出了网络设备配置:

将其与第一个示例中的配置进行比较。

拓扑子网

OpenVPN 2.1及更高版本支持一种新的拓扑,一种用于在tun式网络中分配IP地址的拓扑子网,与tap式网络使用的IP地址非常相似。使用 --topology subnet 时,将单个IP地址和网络掩码分配给tun接口,而不指定对等地址。

虽然将此拓扑模式用于专用点对点链路没有多大意义,但可以使用此拓扑选项使tun式点对点设置与相应的抽头式设置几乎完全相同。要使用这种新的拓扑模式,请使用下面描述的设置。

在监听端,使用以下命令:

在客户端,使用以下命令:

--ifconfig 行现在与tap示例中的行相同。唯一的另一个变化是在两端添加了 --topology subnet

明文隧道

前面的示例不使用任何加密密码或身份验证密钥;因此,您将收到以下警告:

然而,明文隧道确实有其用途。在安全性在不同级别处理的可信环境中(例如,使用专用光纤电缆),明文隧道比加密隧道提供更好的性能,并且更容易监控隧道上的流量。

此外,如果你事先知道所有通过隧道的流量都是加密的(例如,所有流量都是严格的HTTPS),那么可以使用明文隧道来避免双重加密,这有时会导致性能下降。特别是在小型或嵌入式硬件(例如Raspberry Pi甚至一些Arduino板)上运行OpenVPN时,加密会对性能造成很大损失。

可以使用上一节中给出的示例设置明文隧道。如果没有指定密钥,则自动禁用加密和身份验证(HMAC签名)。也可以明确禁用它们:

在监听端,使用以下命令:

在客户端,使用以下命令:

连接建立后,我们可以使用tcpdump命令(或等效命令,例如Wireshark)验证内容是否确实以明文形式发送:

  1. 启动连接

  2. 启动tcpdump并监听定时的网络接口,不是通道接口本身,然后使用以下命令过滤出OpenVPN包(UDP 端口 1194):

  3. 现在使用nc(netcat)通过隧道发送一些文本: 在服务器端:

    在客户端:

  4. tcpdump输出现在应该显示如下内容:

以文本消息形式显示的字符是OpenVPN数据包封装的产物。

OpenVPN 密钥

为了保护OpenVPN连接,需要一个密钥。首先,我们将生成这样一个密钥。然后,需要使用安全通道将其复制到远程端点(例如SCP):

在FreeBSD14.1中,实际命令为 openvpn --genkey secret secret.key

请注意,没有必要以root身份运行此命令(因此提示$)。生成的密钥文件具有以下格式:

在这里发布密钥后,它就不再是秘密了。

openvpn--genkey命令生成2048位密钥,即256字节的随机数据。这256个字节在secret.key文件中以十六进制格式列出,但目前并非所有256个字节都已使用(我们稍后将看到)。

OpenVPN使用密钥对每个数据包进行加密和身份验证(签名)。默认加密密码是Blowfish密码(BF-CBC),默认HMAC算法是SHA1。Blowfish密码使用128位加密,而SHA1算法使用的密钥是160位。

如果OpenVPN启动时调试输出增加(--verb 7或更高),则启动时会打印使用的密钥:

在监听端,使用以下命令启动OpenVPN守护进程:

在客户端,命令如下:

服务器端日志输出将包含以下格式的行:

BF-CBC 密码密钥是1393 ae68 7606 c1f7 d465 d702 27bf 63e8 ,这正是OpenVPN密钥文件的第一行。

SHA1 HMAC密钥是987d dfa9 d676 6d3b 5e4c 952d c27f 518d 12cc ff6b,也可以在从第五行开始的密钥文件中找到。

请注意,相同的密钥用于加密和解密数据,以及用于验证数据。在下一节中,我们将了解如何使用不同的密钥进行加密、解密和身份验证。

使用多个密钥

OpenVPN支持使用方向(directional)密钥,也就是说,传入和传出数据使用不同的密钥。这进一步增强了安全性。通过在--secret参数中添加方向标志,我们可以指定使用不同的密钥。方向标志需要在一端设置为0,在另一端设置为1:

在服务器端:

在客户端:

服务器端日志输出现在将包含以下格式的行:

加密CIPHER和HMAC密钥现在与解密CIPHER及HMAC密钥明显不同。此外,每个密钥都可以在OpenVPN secret.key文件中找到:

此外,客户端的日志输出显示密钥颠倒:

这对于VPN隧道的运行是必要的,因为服务器端加密数据所需的密钥需要客户端解密数据,反之亦然。

使用不同的加密和身份验证算法

OpenVPN支持许多不同的加密和身份验证(HMAC签名)算法。每个加密密码和HMAC算法中使用的密钥大小各不相同。密码(如AES256)的当前最大值为256位,HMAC密钥(例如SHA512)的最大值为512位。OpenVPN静态密钥长2048位,对于512位密码和512位HMAC密钥来说足够大。

默认的 BF-CBC 不被 FreeBSD 支持,所以如果未指定 cipher,OpenVPN无法启动

如果我们将AES256指定为加密密码,将SHA512指定为身份验证算法,那么我们会看到使用的密钥的大小在增长:

在服务器端:

在客户端:

服务器端日志输出现在包含以下行:

此日志可以与secret.key文件进行匹配:

它可以类似地匹配解密密钥。

注: VPN隧道的功能与以前一样,但现在有了更强的加密和身份验证。如果将来引入更强大的密码或HMAC算法,OpenVPN静态密钥格式将不得不更新。

路由

如前所述,点对点式网络的主要用例是通过安全隧道连接两个远程网络。在前面的示例中,建立了安全隧道,但没有添加网络路由。

对于下一个示例,考虑以下网络布局:

客户端网络192.168.4.0/24(具有网络掩码255.255.255.0)需要通过VPN隧道路由到服务器。

在服务器端:

在客户端:

在服务器端,添加了一个路由声明,告诉OpenVPN网络192.168.4.0/24位于隧道的另一端。OpenVPN本身对此做得很少,但它会发出适当的/sbin/route或/sbin/ip route命令来配置系统路由表。除了使用OpenVPN的--route语句,我们还可以使用以下命令:

建立VPN连接后,我们也可以使用iproute2命令:

本例中添加的第二行语句指示OpenVPN自行守护(即在后台静默运行),并将所有消息记录到/var/log/movpn-02-server.log文件中。

同样,在客户端添加了相同的daemon+log语句。

注: 每次OpenVPN启动时,--log语句都会截断log文件。如果要附加到上一个日志文件,请使用以下命令:

目前,该示例尚未完全发挥作用。如果我们从VPN服务器ping客户端局域网上的主机,那么我们将不会收到任何响应。这与OpenVPN本身关系不大,但主要与TCP/IP路由有关。OpenVPN用户邮件列表和OpenVPN互联网论坛上提出的大多数问题实际上都是路由问题。

没有从客户端局域网收到响应的原因有两个:

通过发出以下命令可以避免重新启动:

此处,192.168.4.100 是OpenVPN客户端的LAN IP地址。

注意,以这种方式添加的路由不是持久的,将在重启后消失。建议以持久方式添加路由。

现在,该示例正在按预期运行。从OpenVPN服务器,我们可以ping客户端局域网上机器的局域网IP,反之亦然:

配置文件与命令行对比

正如你从前面的示例中看到的,OpenVPN的命令行参数可能会很快变得冗长而复杂。也可以(并且建议)使用配置文件来存储OpenVPN的常用选项。通常,可以使用以下命令在命令行上指定每个选项:

而在配置文件中则使用 <some option> <option-arguments> 格式来指定选项。就是去掉命令行选项前面的两个横杠。

配置文件是在命令行上使用 --config <path> 选项指定的。配置文件中指定的几乎所有选项都被视为命令行上指定的选项。正如我们将在本书后面看到的,可以将证书和私钥文件内联存储在配置文件中。使用命令行参数不容易做到这一点。

也可以混合配置文件和命令行参数。这使得在配置文件中存储常用选项变得容易,可以使用命令行参数覆盖配置文件——即,命令行参数优先级高于配置文件。

但是,不是所有配置选项都可以被覆盖。某些选项可以多次指定(特别是 remote <remote-host>)。在这些情况下,通常先尝试第一次出现的。

前面示例中的服务器命令行可以转换为以下配置文件:

如果将以上内容保存在名为 movpn-02-01-server.conf的文件中,则启用侦听的命令如下:

注意,命令行参数的顺序很重要。在 --config <path> 选项之前指定的所有选项都将被配置文件中指定的选项覆盖;在 --config 选项后面指定的所有选项都会推翻配置文件中的选项(如前所述,有少数例外)。

完整的设置

基于前面的示例,我们现在可以使用配置文件构建完整的生产级(production-level)配置,包括路由、日志、IPv6支持以及OpenVPN提供的其他一些生产功能。

示例网络拓扑:

左侧为客户端,Site A LAN:

右侧为服务器端,Site B LAN:

对于服务器,我们创建配置文件 movpn-02-02-server.conf,内容如下:

在客户端,我们创建配置文件 movpn-02-02-client.conf,内容如下:

客户端和服务器端配置文件非常相似,除了镜像地址和镜像密钥方向。

这些配置文件中引入了一些新选项:

我们现在可以使用以下命令启动隧道两端,而不是指定一个非常长的命令行来启动隧道两端:

在两端检查openvpn.log文件,应该有以下行:

最后,我们验证了我们可以使用ping和ping6到达隧道的另一端:

注意,为了使路由正常工作,我们需要在两端进行IP转发,以及局域网段上计算机的返回路由。在客户端局域网上,我们需要类似以下的路由:

此处,192.168.4.100是OpenVPN客户端的LAN地址。

在服务器端LAN,我们需要做以下设置:

此处,192.168.122.1是OpenVPN服务器端的LAN地址。

注意,目前即使隧道仅支持IPv6,也需要始终使用ifconfig指定IPv4地址。这个缺点将在OpenVPN2.4+无IP设置中得到解决。

高级无IP设置

OpenVPN允许在启动VPN连接时运行用户定义的脚本,这允许进行一些高级设置。

在这个例子中,我们将使用自定义的up脚本来创建OpenVPN隧道,而不向隧道的端点分配IP地址。在路由网络设置中,这确保了隧道端点本身永远无法到达,这增加了一些安全性,也可以使路由表更短一些。

此脚本仅在Linux系统上进行了测试,因为它需要一些在其他平台上不可用的网络接口配置。我们使用与上例相同的网络布局,但没有IPv6地址:

左侧为客户端,Site A LAN:

右侧为服务器端,Site B LAN:

在服务器端,我们创建一个配置文件 movpn-02-03-server.conf:

伴随up.sh脚本:

此处,网络192.168.4.0/24是我们希望从服务器端局域网到达的客户端局域网。对于客户端,我们创建文件 movpn-02-03-client.conf :

同样伴随up.sh脚本:

在进行VPN连接之前,应确定up.sh脚本为可执行(chmod a+x up.sh)。

在两端分别启动隧道:

检查两端的openvpn.log文件,应该有以下内容:

检查分配给tun0接口的地址:

或者,可以使用更现代的iproute2命令:

接口已启动,但没有IP地址。接下来,我们要验证路由表:

正确的路由已经存在,所以,最后我们验证了我们可以ping客户端局域网上的主机:

三路路由

正如引言中所述,当连接少量端点时,点对点网络是一个很好的选择。在这个例子中,我们将展示如何使用点对点隧道将三个站点连接在一起,它还将显示这种设置有多快会变得非常复杂。

网络架构如下:

Site A LAN——192.168.4.0/24

Site B LAN——192.168.5.0/24

Site C LAN——192.168.6.0/24

以上三个网络通过VPN两两互通。

我们将在两个地点之间修建三条隧道,并设置冗余路线。这样,如果其中一条隧道坍塌,所有地点都会彼此可见。然而,这是以性能损失为代价的。假设A与B之间的链接断开,备份路由从A到C再到B,因此,现在从站点A到站点B的流量必须进行额外的跳转。

首先,我们创建三个密钥:

然后,我们通过安全通道(比如scp)将这些密钥传输到所有端点。

接下来,我们创建六个配置文件:三个侦听器或服务器配置、三个客户端配置。

第一个,创建服务器配置文件 BtoA.conf :

接下来,创建CtoA.conf :

然后,创建最后一个服务器配置文件 BtoC.conf:

现在,我们创建客户端(连接器)配置文件 AtoB.conf :

接下来,我们创建客户端配置文件 AtoC.conf :

最后,我们创建客户端配置文件 CtoB.conf:

启动每台服务器并连接其相应的客户端:

检查隧道两侧的日志文件,并在继续下一个站点之前验证路由是否(部分)正常工作:

最后:

此时,所有路线都应该存在,包括冗余路线。例如,从路由表可以看出,站点A有两条到站点B的路由(LAN 192.168.5.0/24):

从这个表中我们可以观察到以下内容:

这种设置的优点是,如果一个隧道发生故障,那么在60秒后,连接及其相应的路由就会中断。然后,到另一个网络的备份路由会自动接管,所有三个站点都可以再次相互连接。原始隧道恢复后,具有较高度量的路线再次优先,并恢复到原始状态。

这种配置的缺点是,在这60秒内,所有流量都会丢失。RIPv2或OSPF等路由协议可能有助于更快地发现故障路由,从而减少网络停机时间。

Route, net_gateway, vpn_gateway, 和 metrics

以下配置语句在此设置中至关重要。

vpn_gateway 是一个特殊的OpenVPN关键字,它指定了VPN远程端点地址。通常,不必指定此关键字,除非还需要为此路由指定度量(metric)。

route 指令的语法和选项为:

此处,gateway 可以显式设置为IPv4地址,也可以使用特殊关键字 vpn_gatewaynet_gateway 。如果没有指定网关和度量,则使用 vpn_gateway

关键字 net_gateway 用于指定不应通过VPN明确路由的子网。在第四章 客户端/服务器模式与tun设备 中,将对路由选项进行更详细的解释。

该指标有一个默认指标,可以使用以下命令设置:

这适用于所有路线。如果希望推翻特定路由的度量(正如我们在这个例子中所做的那样),那么需要指定网关(在我们的例子中是vpn_gateway),然后指定该特定路由的指标。

这里需要配置声明route-delay ,以确保在所有连接可用后添加路由。如果没有它,可能会过早添加路由,导致无法将路由添加到远程子网之一。

两端的桥接tap适配器

专用点对点VPN的另一个高级用例是将两个远程网段桥接在一起。OpenVPN允许你将具有相同IP地址范围的两个网络桥接在一起,形成一个透明的网段。通常不建议这样做,因为这种桥接网络的性能不是最佳的。在某些情况下,这是不可避免的。通常,最好为两端分配不同的子网,但有时特殊软件会绑定到特定的IP地址,除了在两端拥有相同的子网别无选择。

考虑以下网络布局:

Site A LAN——192.168.4.0/24,Client br0:192.168.4.128

Site B LAN——192.168.4.0/24,Server br0:192.168.4.65

在客户端,网络192.168.4.0/24正在使用中,OpenVPN客户端位于192.168.4.128。在服务器端,使用相同的子网——OpenVPN服务器位于192.168.4.65。目标是将两个网络连接在一起,这样两端的所有机器都可以透明地看到彼此。

dev tap 模式下,OpenVPN将创建一个新的或打开一个现有的tap适配器。在大多数现代操作系统上,tap适配器的行为就像常规网络适配器一样,如果操作系统支持适配器桥接,则tap适配器可以与系统中的另一个网络适配器桥接。众所周知,这适用于Linux、BSD和MS Windows。在Linux上,需要安装bridge-utils包才能使此实例工作。

在这个例子中,tap适配器与OpenVPN客户端和服务器的LAN接口桥接。为了能够做到这一点,在初始化VPN连接之前,在持久状态下创建tap适配器:

然后,创建并初始化网桥。在客户端执行以下命令:

在继续之前,请检查网桥与其相关适配器的状态。此外,请确保仍然可以访问局域网:

服务器端的输出应该相似;网桥IP地址为192.168.4.65。

接下来,我们创建配置文件 movpn-02-05.conf,此配置文件在两端相同:

注:在配置文件中,使用设备tap0的全名。如果省略0,则OpenVPN将打开一个新的tap适配器,网桥将无法运行。

然后,我们开始服务器和客户端:

初始化序列完成后,网段被桥接。通过ping远程端的主机来验证这一点。

观察隧道上的流量也很有用。桥接网络的缺点是,一端产生的所有(广播)流量都会复制到另一端。这可能会导致网络性能不佳。如果有很多网络背景噪音,那么这将显示在tap0接口上的tcpdump中:

此tcpdump捕获的输出显示,通过发出以下命令,生成树协议已在服务器端的网络上启用:

流量停止了,导致网桥上的噪音大大降低。

注意:只有当你知道自己在做什么时,才关闭网桥上的STP。在这种情况下,没有环路的风险,因为只有一个网桥,只有两个设备连接。

在tcpdump捕获中看到的ARP请求不容易被抑制。然而,这些请求非常小,不应导致性能大幅下降。然而,在高延迟线路上,这些请求将成为瓶颈。

移除网桥

如果不再需要网桥,最好删除网桥和持久tap0设备:

记得让eth0网络接口重新联机。

将点对点模式与证书相结合

对于下一个例子,我们借用了第3章“PKI和证书”中的一些内容。在客户端/服务器模式下,OpenVPN使用公钥基础设施(Public Key Infrastructure——PKI)配置,具有X.509证书和私钥。还可以使用X.509证书和私钥来建立点对点隧道。与预共享密钥相比,使用X.509证书的优点是它提供了完美转发保密性(Perfect Forwarding Secrecy——PFS),大大提高了VPN数据的安全性。如果没有PFS,如果攻击者在某个时候设法破解了加密,那么所有之前记录的VPN流量都可以被解密。使用PFS,无法解密旧数据。

为了使用证书建立点对点隧道,我们必须首先复制CA证书和两个端点的证书/私钥对:

和:

在服务器端,我们还需要生成VPN会话密钥所需的Diffle-Hellman(密钥交换)参数文件。会话密钥是临时密钥,在首次建立客户端和服务器之间的连接时生成。

Diffie-Hellman——简称DH或dh,是一种确保共享key安全穿越不安全网络的方法,它是OAKLEY的一个组成部分。仅用于密钥的交换,不能进行消息的加密和解密。

要生成DH参数文件,请执行以下命令:

我们现在已准备好设置OpenVPN配置文件。在服务器端,创建以下配置文件,并将其另存为movpn-02-06-server.conf:

在客户端,创建配置文件 movpn-02-06-client.conf:

然后,我们启动服务器和客户端:

初始化序列完成后,我们将看到创建的隧道与使用预共享密钥创建的隧道具有相同的属性。

总结

点对点是OpenVPN初始版本中唯一支持的配置。在本章中,我们从一个非常基本的点对点示例开始。我们介绍了OpenVPN的更多功能,并发现有充分的理由在生产环境中使用这种模式。在最后一个用例中,桥接TAP适配器在客户端和服务器端都使用。

这是解释点对点模式的唯一一章。在下一章中,我们将正确设置使用OpenVPN的另一种模式(客户端/服务器模式)所需的证书。