查看源代码 通过 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。
以下各节将描述这些步骤。
构建包含 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 应用程序。
为 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 参数(请参阅下一节)。
指定 TLS 选项
TLS 分布选项可以写入在节点启动时查阅的文件中。然后,此文件名使用命令行参数 -ssl_dist_optfile
指定。
可以在选项文件中指定任何可用的 TLS 选项。
注意
采用
fun()
的选项必须使用语法fun Mod:Func/Arity
,因为在查阅文件时无法编译函数体。还可以指定文件的编码,如模块epp
定义的那样。
警告
不要篡改套接字选项
list
、binary
、active
、packet
、nodelay
和deliver
,因为它们由分布协议处理程序本身使用。其他原始套接字选项(如packet_size
)可能会严重干扰,因此请注意!
为了使 TLS 工作,至少必须为服务器端指定公钥和证书,并且客户端需要指定它信任的 CA(客户端证书是可选的,需要更多配置)。
在以下示例中(为了简单起见),PEM 文件 "/home/me/ssl/erlserver.pem"
同时包含服务器证书及其私钥。
创建一个名为 "/home/me/ssl/[email protected]"
的文件,例如
[{server,
[{certfile, "/home/me/ssl/erlserver.pem"}]},
{client,
[{cacertfile, "/home/me/ssl/client_trusted.pem"}]}].
然后像这样启动节点(命令中的换行符是为了提高可读性,键入时不能存在)
$ 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 作为分布协议。
通过 IPv6 使用 TLS 分布
可以使用通过 IPv6 而不是 IPv4 的 TLS 分布。为此,在启动 Erlang 时,在命令行或 ERL_FLAGS
环境变量中传递选项 -proto_dist inet6_tls
而不是 -proto_dist inet_tls
。
使用此选项的命令行示例如下所示
$ erl -boot /home/me/ssl/start_ssl -proto_dist inet6_tls
-ssl_dist_optfile "/home/me/ssl/[email protected]"
-sname ssl_test
以这种方式启动的节点将只能与使用通过 IPv6 的 TLS 分布的其他节点通信。
指定 TLS 选项(旧版)
注意
以下部分描述了 OTP 20.2 之前的 TLS 选项处理,并且只能处理实际可用选项的一小部分。此处仅为了向后兼容。
与上一节一样,PEM 文件 "/home/me/ssl/erlserver.pem"
同时包含服务器证书及其私钥。
在 erl
命令行上,您可以指定 TLS 分布在创建套接字时添加的选项。
可以通过在选项名称前添加前缀 server_
或 client_
来指定以下列表中最简单的 TLS 选项
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 选项的形式编写,因为 funs 在命令行上不被接受。
服务器还可以采用选项 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>
设置环境以始终使用 TLS
指定 Erlang 参数的便捷方法是使用环境变量 ERL_FLAGS
。可以使用该变量指定使用 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()
调用验证是否为模拟器提供了正确的参数。