4 使用 TLS 进行 Erlang 分布
本节介绍 Erlang 分布如何使用 TLS 获得额外的验证和安全性。
理论上,Erlang 分布可以使用几乎任何基于连接的协议作为承载协议。但是,需要一个模块来实现连接设置的协议特定部分。默认的分布模块是 Kernel 应用程序中的 inet_tcp_dist。在启动分布式 Erlang 节点时,net_kernel 使用此模块来设置监听端口和连接。
在 SSL 应用程序中,可以使用额外的分布模块 inet_tls_dist 作为替代。所有分布式连接都将使用 TLS,并且分布式系统中的所有参与 Erlang 节点都必须使用此分布式模块。
安全级别取决于为 TLS 连接设置提供的参数。但是,始终使用 Erlang 节点 Cookie,因为它们可以用来区分两个不同的 Erlang 网络。
要通过 TLS 设置 Erlang 分布,请执行以下操作:
- 步骤 1:构建包括 SSL 应用程序的引导脚本。
- 步骤 2:为 net_kernel 指定分布模块。
- 步骤 3:指定安全选项和其他 SSL 选项。
- 步骤 4:设置始终使用 TLS 的环境。
以下部分介绍这些步骤。
4.1 构建包括 SSL 应用程序的引导脚本
引导脚本使用 SASL 应用程序中的 systools 实用程序构建。有关 systools 的更多信息,请参阅 SASL 文档。这只是一个示例。
最简单的引导脚本仅包含 Kernel 和 STDLIB 应用程序。此类脚本位于 Erlang 分布的 bin 目录中。脚本的源代码位于 Erlang 安装的顶层目录下 releases/<OTP version>/start_clean.rel 中。
执行以下操作:
将该脚本复制到另一个位置(最好是另一个名称)。
在 STDLIB 应用程序之后添加 Crypto、Public Key 和 SSL 应用程序及其当前版本号。
以下显示了一个包含 TLS 的示例 .rel 文件:
{release, {"OTP APN 181 01","R15A"}, {erts, "5.9"}, [{kernel,"2.15"}, {stdlib,"1.18"}, {crypto, "2.0.3"}, {public_key, "0.12"}, {asn1, "4.0"}, {ssl, "5.0"} ]}.
您的系统中的版本号可能不同。每当包含在脚本中的应用程序之一升级时,请更改脚本。
执行以下操作:
-
构建引导脚本。
假设 .rel 文件 存储在当前目录中的 start_ssl.rel 文件中,则可以按如下方式构建引导脚本:
1> systools:make_script("start_ssl",[]).
现在,当前目录中有一个 start_ssl.boot 文件。
执行以下操作:
测试引导脚本。为此,使用 -boot 命令行参数启动 Erlang,并指定此引导脚本(带完整路径,但不带 .boot 后缀)。在 UNIX 中,它可能如下所示:
$ erl -boot /home/me/ssl/start_ssl Erlang (BEAM) emulator version 5.0 Eshell V5.0 (abort with ^G) 1> whereis(ssl_manager). <0.41.0>
whereis 函数调用验证 SSL 应用程序是否已启动。
作为构建引导脚本的替代方法,您可以显式地在命令行上添加指向 SSL ebin 目录的路径。这通过命令行选项 -pa 完成。这是因为 SSL 应用程序不需要启动才能使分布启动,因为 SSL 应用程序的克隆已挂接到 Kernel 应用程序中。因此,只要能够访问 SSL 应用程序代码,分布就会启动。-pa 方法仅建议用于测试目的。
SSL 应用程序的克隆必须启用 SSL 代码的使用,以满足在如此早期的引导阶段设置分布所需的条件。但是,这使得无法对 SSL 应用程序进行软升级。
4.2 为 net_kernel 指定分布模块
TLS 的分布模块名为 inet_tls_dist,并且在命令行上使用选项 -proto_dist 指定。-proto_dist 的参数是模块名称,不带 _dist 后缀。因此,此分布模块在命令行上使用 -proto_dist inet_tls 指定。
扩展命令行,得到以下结果:
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls
为了启动分布,请也为模拟器指定一个名称:
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1>
但是,以这种方式启动的节点拒绝与其他节点通信,因为没有提供 TLS 参数(请参阅下一节)。
4.3 指定 TLS 选项
TLS 分布选项可以写入一个文件,该文件在节点启动时进行查询。此文件名然后使用命令行参数 -ssl_dist_optfile 指定。
任何可用的 TLS 选项都可以在选项文件中指定,但请注意,采用 fun() 的选项必须使用语法 fun Mod:Func/Arity,因为在查询文件时无法编译函数体。
请勿篡改套接字选项 list、binary、active、packet、nodelay 和 deliver,因为它们由分布协议处理程序本身使用。其他原始套接字选项(例如 packet_size)可能会严重干扰,因此请注意!
为了使 TLS 正常工作,必须至少为服务器端指定一个公钥和一个证书。在以下示例中,PEM 文件 "/home/me/ssl/erlserver.pem" 包含服务器证书及其私钥。
创建一个名为 "/home/me/ssl/[email protected]" 的文件:
[{server, [{certfile, "/home/me/ssl/erlserver.pem"}, {secure_renegotiate, true}]}, {client, [{secure_renegotiate, true}]}].
然后像这样启动节点(命令中的换行符是为了可读性,在键入时不应存在):
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_optfile "/home/me/ssl/[email protected]" -sname ssl_test
{server, Opts} 元组中的选项在调用 ssl:handshake/3 时使用,{client, Opts} 元组中的选项在调用 ssl:connect/4 时使用。
对于客户端,在连接时添加选项 {server_name_indication, atom_to_list(TargetNode)}。这使得可以使用客户端选项 {verify, verify_peer},并且客户端将验证证书是否与您要连接到的节点名称匹配。这仅在服务器证书颁发给名称 atom_to_list(TargetNode) 时才有效。
对于服务器,也可以使用选项 {verify, verify_peer},服务器将只接受由服务器知道的根证书信任的证书的客户端连接。提供不受信任证书的客户端将被拒绝。此选项最好与 {fail_if_no_peer_cert, true} 结合使用,否则即使客户端没有提供任何证书,也会被接受。
以这种方式启动的节点是完全功能的,使用 TLS 作为分布协议。
4.4 指定 TLS 选项(旧版)
与上一节一样,PEM 文件 "/home/me/ssl/erlserver.pem" 包含服务器证书及其私钥。
在 erl 命令行上,您可以指定 TLS 分布在创建套接字时添加的选项。
以下列表中最简单的 TLS 选项可以通过在选项名称前添加前缀 server_ 或 client_ 来指定:
- certfile
- keyfile
- password
- cacertfile
- verify
- verify_fun(写为 {Module, Function, InitialUserState})
- crl_check
- crl_cache(写为 Erlang 项)
- reuse_sessions
- secure_renegotiate
- depth
- hibernate_after
- ciphers(使用旧的字符串格式)
请注意,verify_fun 需要以与相应的 TLS 选项不同的形式编写,因为命令行上不接受函数。
服务器还可以使用选项 dhfile 和 fail_if_no_peer_cert(也带前缀)。
client_ 前缀的选项在分布启动与另一个节点的连接时使用。 server_ 前缀的选项在接受来自远程节点的连接时使用。
原始套接字选项(例如 packet 和 size)不能在命令行上指定。
用于指定 TLS 选项的命令行参数名为 -ssl_dist_opt,后面跟着 SSL 选项及其值的配对。参数 -ssl_dist_opt 可以重复任意次数。
以下示例命令行执行与上一节示例相同的操作(命令中的换行符是为了可读性,在键入时不应存在):
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_opt server_certfile "/home/me/ssl/erlserver.pem" -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1>
4.5 设置始终使用 TLS 的环境(旧版)
使用环境变量 ERL_FLAGS 是指定 Erlang 参数的一种便捷方法。所有用于使用 TLS 分布的标志都可以在该变量中指定,然后作为所有后续 Erlang 调用的命令行参数进行解释。
在 Unix(Bourne) shell 中,它可能如下所示(换行符是为了可读性,在键入时不应存在):
$ ERL_FLAGS="-boot /home/me/ssl/start_ssl -proto_dist inet_tls -ssl_dist_opt server_certfile /home/me/ssl/erlserver.pem -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true" $ export ERL_FLAGS $ erl -sname ssl_test Erlang (BEAM) emulator version 5.0 [source] Eshell V5.0 (abort with ^G) (ssl_test@myhost)1> init:get_arguments(). [{root,["/usr/local/erlang"]}, {progname,["erl "]}, {sname,["ssl_test"]}, {boot,["/home/me/ssl/start_ssl"]}, {proto_dist,["inet_tls"]}, {ssl_dist_opt,["server_certfile","/home/me/ssl/erlserver.pem"]}, {ssl_dist_opt,["server_secure_renegotiate","true", "client_secure_renegotiate","true"] {home,["/home/me"]}]
init:get_arguments() 调用验证是否向模拟器提供正确的参数。
4.6 在 IPv6 上使用 TLS 分布
可以在 IPv6 上使用 TLS 分布,而不是 IPv4。为此,在启动 Erlang 时,将选项 -proto_dist inet6_tls 传递给 -proto_dist inet_tls,无论是通过命令行还是通过 ERL_FLAGS 环境变量。
以下示例命令行包含此选项:
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet6_tls -ssl_dist_optfile "/home/me/ssl/[email protected]" -sname ssl_test
以这种方式启动的节点只能与使用 IPv6 上的 TLS 分布的其他节点通信。