RabbitMQ-官方指南-TLS 支持

RabbitMQ-官方指南-TLS 支持
RabbitMQ内置支持TLS。

自RabbitMQ 3.4.0起, 为防止 POODLE attack 攻击,已经自动禁用了SSLv3.

使用TLS时,推荐安装的Erlang/OTP版本为17.5或以上版本. R16版本在某些证书中可以工作,但存在major limitations.

必须安装Erlang加密程序,并且保证它能工作.对于那些从源码进行Erlang编译的Windows用户来说,可能会出现一些问题.

对于将RabbitMQ作为服务来运行的Windows XP用户而言: 要在Windows XP上结合OpenSSL 0.9.8及其之后版本,并使用SSL来将RabbitMQ作为服务运行是不可行的.此bug已在Windows XP SP3和OpenSSL v0.9.8r以及v1.0.0d版本上经过了确认. 如果你想以服务来运行RabbitMQ,建议将其升级为Windows 7或者将OpenSSL降级为更早的版本 (v0.9.7e可以正常工作).

对于那些从源码编译Erlang而言:必须确保配置能找到OpenSSL,并且能构建加密程序.

密钥,证书和CA证书

OpenSSL 是一个庞大而复杂的主题. 如何更全面地了解OpenSSL,以及如何好好使用,我们建议参考其它资源,Network Security with OpenSSL.

OpenSSL可用来简单地建立一个加密通道, 还可以用来在通道的各个端点(end points)之间交换签名证书,并可验证这些证书. 证书验证需要从知名,可信的root证书处建立一条信任链. root certificate 是自签名证书,可提供一个证书颁发机构.也存在一些收费的商业公司,它们可以签名你已经生成的SSL证书.

对于本指南,我们会创建我们自己的证书颁发机构.一旦完成这个步骤,我们就可以为server和clients生成多种格式的签名证书,它们将被用于Java,.Net以及Erlang AMQP 客户端.

注意,Mono对于OpenSSL证书(存在一些bug)有更严格的要求,所以我们将使用更严格的关键约束是必要的 .

# mkdir testca # cd testca # mkdir certs private # chmod 700 private # echo 01 > serial # touch index.txt 

现在将下面的内容放置到openssl.cnf(其位置在我们刚创建的testca目录之下)中:

[ ca ] default_ca = testca  [ testca ] dir = . certificate = $dir/cacert.pem database = $dir/index.txt new_certs_dir = $dir/certs private_key = $dir/private/cakey.pem serial = $dir/serial  default_crl_days = 7 default_days = 365 default_md = sha256  policy = testca_policy x509_extensions = certificate_extensions  [ testca_policy ] commonName = supplied stateOrProvinceName = optional countryName = optional emailAddress = optional organizationName = optional organizationalUnitName = optional  [ certificate_extensions ] basicConstraints = CA:false  [ req ] default_bits = 2048 default_keyfile = ./private/cakey.pem default_md = sha256 prompt = yes distinguished_name = root_ca_distinguished_name x509_extensions = root_ca_extensions  [ root_ca_distinguished_name ] commonName = hostname  [ root_ca_extensions ] basicConstraints = CA:true keyUsage = keyCertSign, cRLSign  [ client_ca_extensions ] basicConstraints = CA:false keyUsage = digitalSignature extendedKeyUsage = 1.3.6.1.5.5.7.3.2  [ server_ca_extensions ] basicConstraints = CA:false keyUsage = keyEncipherment extendedKeyUsage = 1.3.6.1.5.5.7.3.1 

现在我们将使用我们自己的test证书颁发机构来生成密钥和证书,仍然在testca目录中执行:

# openssl req -x509 -config openssl.cnf -newkey rsa:2048 -days 365 
\    -out cacert.pem -outform PEM -subj /CN=MyTestCA/ -nodes # openssl x509 -in cacert.pem -out cacert.cer -outform DER

这就是生成我们自己test证书颁发机构所需的操作.root证书位于testca/cacert.pem,也存在于testca/cacert.cer. 这两个文件包含相同的信息,但格式是不同的. 虽然大多数都使用PEM 格式, Microsoft和Mono则喜欢使用不同的格式DER格式.

在设置了证书颁发机构后,现在我们需要为clients和server生成密钥和证书.Erlang client和RabbitMQ broker都可以直接使用PEM 文件. 它们可通过三个文件来通知: 隐式可信任的root证书, 用于证明公共证书的所有权的私有密钥,以及标识对等的公共密钥自身.

为了方便起见,我们为Java和.Net clients提供了PKCS #12 存储(包含client证书和密钥). PKCS存储通常用密码进行自我保护,因此必须提供密码.

创建server和client证书的过程是相似的.唯一的区别是签名证书时添加的keyUsage字段.首先从server开始:

# cd .. # ls testca # mkdir server # cd server # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \     -subj /CN=$(hostname)/O=server/ -nodes # cd ../testca # openssl ca -config openssl.cnf -in ../server/req.pem -out \     ../server/cert.pem -notext -batch -extensions server_ca_extensions # cd ../server # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword 

现在是client:

# cd .. # ls server testca # mkdir client # cd client # openssl genrsa -out key.pem 2048 # openssl req -new -key key.pem -out req.pem -outform PEM \     -subj /CN=$(hostname)/O=client/ -nodes # cd ../testca # openssl ca -config openssl.cnf -in ../client/req.pem -out \     ../client/cert.pem -notext -batch -extensions client_ca_extensions # cd ../client # openssl pkcs12 -export -out keycert.p12 -in cert.pem -inkey key.pem -passout pass:MySecretPassword 

在RabbitMQ中启用SSL支持

要在RabbitMQ中启用SSL/TLS支持,我们必须为RabbitMQ提供root证书, server证书文件,以及server密钥的位置. 我们还需要告诉它监听SSL连接的套接字,我们需要告诉它是否应该要求客户提供证书,如果客户端没有提供一个证书,如果我们不能建立信任链,我们是否应该接受证书. RabbitMQ通过两个参数来配置:

  • -rabbit ssl_listeners

    用于监听SSL连接的端口列表.要在单个网络接口上监听,需要在列表中添加{"127.0.0.1", 5671}这样的配置.

  • -rabbit ssl_options

    这是new_ssl 选项的元组列表. 完整可用的ssl_options信息可通过erl -man new_ssl手册页面查看,但最重要的东西是cacertfilecertfile and keyfile.

设置这些选项最简单的方式是编辑configuration file.配置文件的示例如下,它会在此主机机的所有网络接口上的5671端口上监听SSL连接:

[   {rabbit, [      {ssl_listeners, [5671]},      {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},                     {certfile,"/path/to/server/cert.pem"},                     {keyfile,"/path/to/server/key.pem"},                     {verify,verify_peer},                     {fail_if_no_peer_cert,false}]}    ]} ]. 

Windows用户注意: 配置文件中的反斜杠("\")会被解释为转义序列- 因此如果将CA证书的路径指定为c:\cacert.pem,那么你需要输入{cacertfile, "c:\\cacert.pem"}或{cacertfile, "c:/cacert.pem"}.

当web浏览器连接到一个HTTPSweb服务器时, 服务器会提供它的公共证书,web浏览器会试图在根证书和服务器证书之间建立一个信任链,如果一切进行得很好的话,加密通信通道就建立了.尽管在web浏览器和web服务器不常用, SSL 允许服务器要求客户端提供证书. 通过这种方式,服务器可以验证客户端的身份.

服务器是否要求客户端提供证书以及它们是否相信证书的策略,是由verifyfail_if_no_peer_certarguments 控制的. 通过{fail_if_no_peer_cert,false}选项,我们声明了我们准备接受客户端,它们可以不向我们发送证书,但通过{verify,verify_peer}选项,我们声明了如果客户端没有向我们发送证书, 我们必须能建立信任链. 注意,这些值会随着Erlang/OTP中的ssl版本变化, 因此检查erl -man new_ssl 来确保你有正确值.

注意,如果使用了 {verify, verify_none}, 在客户端和服务器之间将不会发生证书交换,rabbitmqctl list_connections 将输出对等证书信息项的空字符串.

在启动broker后,在rabbit.log中,你会看到下面的输出:

=INFO REPORT==== 9-Aug-2010::15:10:55 === started TCP Listener on 0.0.0.0:5672  =INFO REPORT==== 9-Aug-2010::15:10:55 === started SSL Listener on 0.0.0.0:5671

同时,注意最后一行,它将展示RabbitMQ 服务器正在运行,且监听了ssl连接.

信任Client的Root CA

目前,我们告知RabbitMQ查看testca/cacert.pem 文件. 这包含了我们test认证中心的公共证书. 我们可能存在由多个不同认证中心签发的证书,我们希望RabbitMQ全部信任它们.因此,我们可以简单地将这些证书追加到另一个新文件中,以作为RabbitMQ的cacerts参数:

# cat testca/cacert.pem >> all_cacerts.pem # cat otherca/cacert.pem >> all_cacerts.pem

提供证书密码

可使用password选项来为私有密钥提供密码:

[  {rabbit, [            {ssl_listeners, [5671]},            {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},                           {certfile,  "/path/to/server_certificate.pem"},                           {keyfile,   "/path/to/server_key.pem"},                           {password,  "t0p$3kRe7"}                          ]}           ]} ]. 

了解 TLS 漏洞: POODLE, BEAST, etc

POODLE

POODLE 是一个已知的能破坏SSLv3的SSL/TLS攻击.从3.4.0版本开始, RabbitMQ服务器拒绝接受SSLv3连接. 在2014年12月,一个经过改良的能影响TLSv1.0的POODLE攻击被 宣布. 因此,建议使用Erlang 18.0+的版本( 消除了TLS 1.0 POODLE攻击漏洞)或者禁用TLSv1.0支持 (参考下面的章节).

BEAST

BEAST attack 是一个能攻击TLSv1.0的漏洞. 为了减轻它的攻击,禁用TLSv1.0支持(参考下面的章节).

通过配置来禁用 SSL/TLS 版本

为了限制SSL/TLS协议版本,使用版本选项:

%% Disable SSLv3.0 support, leaves TLSv1.0 enabled. [  {ssl, [{versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}]},  {rabbit, [            {ssl_listeners, [5671]},            {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},                           {certfile,  "/path/to/server_certificate.pem"},                           {keyfile,   "/path/to/server_key.pem"},                           {versions, ['tlsv1.2', 'tlsv1.1', tlsv1]}                          ]}           ]} ]. 
%% Disable SSLv3.0 and TLSv1.0 support. [  {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},  {rabbit, [            {ssl_listeners, [5671]},            {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},                           {certfile,  "/path/to/server_certificate.pem"},                           {keyfile,   "/path/to/server_key.pem"},                           {versions, ['tlsv1.2', 'tlsv1.1']}                          ]}           ]} ]. 
要验证,可使用 openssl s_client:
# connect using SSLv3 openssl s_client -connect 127.0.0.1:5671 -ssl3 
# connect using TLSv1.0 through v1.2 openssl s_client -connect 127.0.0.1:5671 -tls1 
可以看到下面的输出:
SSL-Session:   Protocol  : TLSv1 

JDK 和 .NET支持的TLS版本

禁用tlsv1.0限制客户端平台的支持。下面是一个表,说明JDK和 .NET支持的TLS版本 
TLS version Minimum JDK version Minimum .NET version
TLS 1.0 JDK 5 (RabbitMQ Java client requires 6) .NET 2.0 (RabbitMQ .NET client requires 4.5)
TLS 1.1 JDK 7 (see Protocols, JDK 8 recommended) .NET 4.5
TLS 1.2 JDK 7 (see Protocols, JDK 8 recommended) .NET 4.5
  • .NET versions source.
  • JDK versions source.

配置密码套件

可配置RabbitMQ使用的密码套件.注意现在所有的套件都可以在所有系统上使用. 例如,使用椭圆曲线密码,请运行最新的Erlang发布。下面的例子演示了如何使用TLS的密码选项。

%% List allowed ciphers [  {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},  {rabbit, [            {ssl_listeners, [5671]},            {ssl_options, [{cacertfile,"/path/to/ca_certificate.pem"},                           {certfile,  "/path/to/server_certificate.pem"},                           {keyfile,   "/path/to/server_key.pem"},                           {versions, ['tlsv1.2', 'tlsv1.1']},                           {ciphers,  [{ecdhe_ecdsa,aes_128_cbc,sha256},                                       {ecdhe_ecdsa,aes_256_cbc,sha}]}                          ]}           ]} ]. 

要列出Erlang 运行时安装的所有密码套件,可使用:

rabbitmqctl eval 'ssl:cipher_suites(openssl).' 

信任级别

设置SSL连接时,协议中有两个重要的阶段.第一个阶段发生对等端点交换证书(可选)的时候.当证书交换后,对等端点可选择在根证书和其它存在的证书之间建立信任链. 这用来验证对等端点的身份(提供的私有密钥不会被偷!).

第二阶段是协商对等节点的加密密钥,该密钥将用于通信的其余部分。如果交换证书,公钥/私钥将在密钥协商中使用。
因此,您可以创建一个加密的SSL连接,而无需验证证书。java客户端支持两种操作模式。

密钥管理器,信任管理器,以及密钥库

在Java安全框架中,有三个需要注意的组件: 密钥管理, 信任管理以及密钥库.

密钥管理器用于对待节点管理其证书.也就是说,在会话设置时,密钥管理会控制发送哪些证书给远端对待节点.

信任管理器用于对等节点管理远程证书.也就是说,在会话设置时,信任管理远端节点的那些证书是可信任的.

密钥库是证书的Java封装. Java会将证书转换为Java特定的二进制格式或PKCS#12格式. 这些格式是使用Key Store类来管理的.对于服务端证书,我们会使用Java二进制格式,但对于客户端 密钥/证书对,我们将使用 PKCS#12格式.

没有验证证书的连接

我们的第一个示例将展示一个简单的client,通过不验证证书以及不存在任何客户端证书的SSL来连接RabbitMQ服务器.

import java.io.*; import java.security.*;   import com.rabbitmq.client.*;  publicclass Example1 {     public static void main(String[] args) throws Exception     {          ConnectionFactory factory = new ConnectionFactory();         factory.setHost("localhost");         factory.setPort(5671);          factory.useSslProtocol();         // Tells the library to setup the default Key and Trust managers for you
  // which do not do any form of remote server trust verification          Connection conn = factory.newConnection();         Channel channel = conn.createChannel();          //non-durable, exclusive, auto-delete queue         channel.queueDeclare("rabbitmq-java-test", false, true, true, null);         channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());           GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);         if(chResponse == null) {             System.out.println("No message retrieved");         } else {             byte[] body = chResponse.getBody();             System.out.println("Recieved: " + new String(body));         }           channel.close();         conn.close();     } }

这个简单的示例只是一个echo test.它会创建一个rabbitmq-java-test队列,并向默认direct交换器中发送消息,然后读回发布的消息,并进行回显. 注意,我们使用的是专用的,非持久化,自动删除的队列,因此我们不需要担心后期的手动清除.

存在和验证证书

首先,我们需要设置密钥库.我们假设有要连接的服务器的证书,所以我们现在需要将它添加到我们的密钥库中(信任管理器会使用).

# keytool -import -alias server1 -file /path/to/server/cert.pem -keystore /path/to/rabbitstore

上面的命令会将cert.pem导入到rabbitstore中,并在内部称其为server1. alias参数用于有多个证书或密钥的时候用于指定别名,因为在内部必须有不同的名称.

在关于是否相信证书的问题上,必须回答yes, 并要选择一个密码. 在这个例子中,我的密码为rabbitstore。

然后我们要用PKCS#12文件中的客户端证书和密钥,已经在上面展示过了.

下面的例子会修改上面的代码, 以使用我们的密钥库,密钥管理器,以及信任管理器:

import java.io.*;   import java.security.*;   import javax.net.ssl.*;    import com.rabbitmq.client.*;     publicclass Example2   {       publicstaticvoid main(String[] args) throws Exception       {          char[] keyPassphrase = "MySecretPassword".toCharArray();         KeyStore ks = KeyStore.getInstance("PKCS12");         ks.load(new FileInputStream("/path/to/client/keycert.p12"), keyPassphrase);          KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");         kmf.init(ks, passphrase);          char[] trustPassphrase = "rabbitstore".toCharArray();         KeyStore tks = KeyStore.getInstance("JKS");         tks.load(new FileInputStream("/path/to/trustStore"), trustPassphrase);          TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");         tmf.init(tks);          SSLContext c = SSLContext.getInstance("TLSv1.1");         c.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);          ConnectionFactory factory = new ConnectionFactory();         factory.setHost("localhost");         factory.setPort(5671);         factory.useSslProtocol(c);          Connection conn = factory.newConnection();         Channel channel = conn.createChannel();          channel.queueDeclare("rabbitmq-java-test", false, true, true, null);         channel.basicPublish("", "rabbitmq-java-test", null, "Hello, World".getBytes());           GetResponse chResponse = channel.basicGet("rabbitmq-java-test", false);         if(chResponse == null) {             System.out.println("No message retrieved");         } else {             byte[] body = chResponse.getBody();             System.out.println("Recieved: " + new String(body));         }           channel.close();         conn.close();     } }

为了确保上述代码能在其它情况下工作,可使用未导入到密钥库中的安全证书来尝试,这时你可能会看到屏幕上的难异常信息.

配置 .Net 客户端

为了能让服务器安全证书能在.Net平台上使用, 它们可以是多种格式,包括DER 和PKCS #12, 但不能是PEM. 对于DER格式, .Net希望它们能存储在.cer扩展名的文件中.在上面的步骤中,当创建test认证机构时,我们会把PEM 转换成DER格式,命令如下:

# openssl x509 -in /path/to/testca/cacert.pem -out /path/to/testca/cacert.cer -outform DER 

PEM是base64编码的DER格式, 使用分隔符进行封闭。此编码通常是使它更容易在7位有限协议传输数据,如电子邮件(SMTP)。

RFC 5280, Certificate Key Usage and Mono

正如上面所提到的,Mono是相当严格的,强制证书只能应用于它声明的特定目的.

SSL 证书和密钥可应用于各种各样的用途, 例如, 电子邮件签名,代码签名, 通信加密等等. (我们这里的目的是TCP 通信加密). RFC 5280 指定了许多不同的目的, 并允许一个证书为特定的一组目的进行签名.

SSL v3 证书可包含许多不同的扩展.处理证书如何使用的扩展被称为Key Usage Extension. 不同使用一般来说,都支持得很好,即使是定义良好,它们的使用也是广泛地解释. 一些关键的用法已经废弃,大部分已经完全忽视。

这里有一个更进一步的扩展, 它也指定了使用用途, 但它选择O.I.Ns来进行, 如"1.3.6.1.5.5.7.3.1".显然,英语缺乏一些明显的用于添加的随机数字。这是一个扩展密钥使用扩展,序列的对象标识符,进一步定义了那些证书的使用是允许的。

而Mono,看上去认为这些扩展都是重要的, 需要有待进一步的观察. 如果证书遗漏了Key Usage Extension,Mono会让证书无效.默认情况下, OpenSSL 对于自签名证书省略了Key Usage Extension ,因为它希望如果没有找到Key Usage Extension该证书是有效的,可用于任何目的。

这就是为什么在样例openssl.cnf文件的列举中,root_ca_extensionsclient_ca_extensions 和 server_ca_extensions 都指定了keyUsage,并且最后两个还有extendedKeyUsage定义.因此上面生成的证书对于Mono的使用是有效的; keyEncipherment 指定了证书可通过SSL服务器来使用, digitalSignature指定了证书可由SSL客户端使用. extendedKeyUsage 字段中的值都说了同样的事情.

你可以使用this small tool 来检查Mono是否接受RabbitMQ提供的证书. 注意,你需要使用合适的OpenSSL命令来转换server/cert.pem 到tserver/cert.cer:

# openssl x509 -in /path/to/server/cert.pem -out /path/to/server/cert.cer -outform DER # mono certcheck.exe /path/to/server/cert. Checking if certificate is SSLv3... Ok Checking for KeyUsageExtension (2.5.29.15)... Ok Checking if KeyEncipherment flag is set... Ok This certificate CAN be used by Mono for Server validation 

TLS对等验证:你说谁呢你?

在.NET平台上,默认情况下,你连接的服务器的hostname需要匹配服务器安全证书上的CN (Common Name) 字段, 否则证书将会被拒绝.这就是为什么在指南开头的命令中指定了 ...-subj /CN=$(hostname)/... 它会动态地查找你的主机名.如果你在一台机器上生成,要在其它机器上使用那么必须交换$(hostname)部分,并使用你服务器正确的hostname来代替.

为了抑制匹配检查,一个应用程序可以在SslOptions.AcceptablePolicyErrors中设置 System.Net.Security.SslPolicyErrors.RemoteCertificateNameMismatch 标志.

SslOptions.CertificateValidationCallback 可用来提供一个RemoteCertificateValidationCallback 代表. 代表会用适合应用程序的任何逻辑来验证对等节点(RabbitMQ节点)的身份.如果没有指定, 默认的回调将会联合AcceptablePolicyErrors属性来确定远程服务器证书是否有效.

SslOption.CertificateSelectionCallback 可用来提供一个LocalCertificateSelectionCallback ,它将在对等节点验证中选择要用的本地安全证书.

信任 .Net

在 .NET 平台上, 远程证书是通过任意数量的存储库来管理的.这些存储库的管理是通过 'certmgr'(Microsoft .Net 实现和Mono都可用)工具来完成的.

NB: 在某些Windows平台上,有两种版本的命令-一个是随着操作系统自带的,只提供了图形界面,另一个Windows SDK自带的,提供了图形界面和命令行界面. 两者都可以很好的完成工作,但示例将以后者为基础.

对于我们的情况,因为我们提供的客户端certificate/key对都在单独的PKCS #12 文件中,我们要做的只是导入根证书机构的证书到Root (Windows) / Trust(Mono) 存储库中.在存储库中所有签名的证书都是自动可信的.

对比java客户端,会乐于使用SSL连接而不验证服务器的证书,该.Net客户端需要验证成功。为阻止验证,应用程序可在SslOptions.AcceptablePolicyErrors中设置System.Net.Security.SslPolicyErrors.RemoteCertificateNotAvailable 以及System.Net.Security.SslPolicyErrors.RemoteCertificateChainErrors 标志.

Certmgr证书管理

certmgr 允许Add, Delete, List 以及在特定的Store上执行其它操作.这些stores 可以是用户stores, 或机器范围的.只有管理员用户有机器范围stores的写访问权限.

为了将证书加入users Root (Windows) / Trust (Mono) store,我们可以运行:

(Windows) > certmgr -add -all \path\to\cacert.cer -s Root (Mono)    $ certmgr -add -c Trust /path/to/cacert.cer

将证书加入机器证书存储库,我们要运行

(Windows) > certmgr -add -all \path\to\cacert.cer -s -r localMachine Root (Mono)    $ certmgr -add -c -m Trust /path/to/cacert.cer

在将证书添加到store后,我们可以用-list命令来展示证书内容:

(Windows) > certmgr -all -s Root (Mono)    $ certmgr -list -c Trust  Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.  Self-signed X.509 v3 Certificate   Serial Number: AC3F2B74ECDD9EEA00   Issuer Name:   CN=MyTestCA   Subject Name:  CN=MyTestCA   Valid From:    25/08/2009 14:03:01   Valid Until:   24/09/2009 14:03:01   Unique Hash:   1F04D1D2C20B97BDD5DB70B9EB2013550697A05E

正如我们看到的,在信任库里有一个自签名的X.509 v3 证书. Unique Hash在store里是唯一的标识符. 

要删除这个证书,可使用unique hash:

(Windows) > certmgr -del -c -sha1 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E -s Root (Mono)    $ certmgr -del -c Trust 1F04D1D2C20B97BDD5DB70B9EB2013550697A05E  Mono Certificate Manager - version 2.4.0.0 Manage X.509 certificates and CRL from stores. Copyright 2002, 2003 Motus Technologies. Copyright 2004-2008 Novell. BSD licensed.  Certificate removed from store.

使用相同的步骤,我们可以在客户端上也可以执行add/delete/list 我们根证书的操作.

创建连接

要创建RabbitMQ的SSL连接,我们需要在ConnectionFactory参数字段中设置一些新字段.为了让事情更简单,这里有一新字段Parameters.Ssl,它可作为需要设置所有其它字段的命名空间 . 这些字段是:

  • Ssl.CertPath: 如果你的服务器希望验证客户端的话,这是PKCS#12格式的客户端证书的路径.这是可选的.
  • Ssl.CertPassphrase:如果你正在使用PKCS#12 格式的客户端证书,那么可能会需要一个密码,你可以在这个字段中进行指定.
  • Ssl.Enabled: 这是一个boolean字段,用以打开或关闭SSL支持.默认是off.
  • Ssl.ServerName: 记住 .Net希望这匹配发送证书的CN.

例子

这与Java部分的例子是一样的.它创建了一个channel, rabbitmq-dotnet-test队列,并使用默认direct交换器来发布,然后再读回发送的消息并进行回显.注意,我们使用的是exclusive, non-durable, auto-delete队列,因此我们不必担忧事后的手动清除.

using System; using System.IO; using System.Text;  using RabbitMQ.Client; using RabbitMQ.Util;  namespace RabbitMQ.Client.Examples {   public class TestSSL {     public static int Main(string[] args) {       ConnectionFactory cf = new ConnectionFactory();        cf.Ssl.ServerName = System.Net.Dns.GetHostName();       cf.Ssl.CertPath = "/path/to/client/keycert.p12";       cf.Ssl.CertPassphrase = "MySecretPassword";       cf.Ssl.Enabled = true;        using (IConnection conn = cf.CreateConnection()) {         using (IModel ch = conn.CreateModel()) {           ch.QueueDeclare("rabbitmq-dotnet-test", false, false, false, null);           ch.BasicPublish("", "rabbitmq-dotnet-test", null,                           Encoding.UTF8.GetBytes("Hello, World"));           BasicGetResult result = ch.BasicGet("rabbitmq-dotnet-test", true);           if (result == null) {             Console.WriteLine("No message received.");           } else {             Console.WriteLine("Received:");             DebugUtil.DumpProperties(result, Console.Out, 0);           }           ch.QueueDelete("rabbitmq-dotnet-test");         }       }       return 0;     }   } } 

注意,在Windows XP上,运行此例,可能会出现失败

  CryptographicException: Key not valid for use in specified state.
在这种情况下,你需要从证书存储库加载证书并直接设置 ConnectionFactory的 Ssl.Certs参数才能成功运行.

R16B01之前的Erlang版本

有可能在老版本的Erlang中使用SSL.这些老版本可以与某些证书工作,其它则很困难.同时,它们也包含OTP-10905 bug,这使得它不能禁用掉任何协议版本.实际上,由于它不能禁用SSLv3, 使用SSL的老版本Erlang 对于POODLE attack (PDF link)是不安全的.

如果探测到老版本的Erlang,RabbitMQ 3.4.0会自动禁用SSL监听器.如果这不是你所希望的,你可以设置ssl_allow_poodle_attack rabbit 配项为true.

ssl_allow_poodle_attack 是一个全局设置;在rabbit程序中的设置会控制所有SSL监听器的行为(AMQP, management, STOMP, etc).

下面的例子进行了说明:

[   {rabbit, [      {ssl_listeners, [5671]},      {ssl_allow_poodle_attack, true},      {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},                     {certfile,"/path/to/server/cert.pem"},                     {keyfile,"/path/to/server/key.pem"},                     {verify,verify_peer},                     {fail_if_no_peer_cert,false}]}    ]} ]. 

证书链和验证深度

使用由中间人CA签名的客户端证书时,可能需要配置RabbitMQ服务器使用更高的验证深度。该深度是非自颁发的中间证书的最大数量,可以按照有效的证书路径中的对等证书。因,如果depth为0,对等节点(如.client)证书 必须由CA直接签发,如果为1,路径可以是"对等节点, CA, 信任的CA",如果设置为2,则路径可以是 "peer, CA, CA, trusted CA"等等.下面的例子演示了如何来配置RabbitMQ server的证书验证深度:

[   {rabbit, [      {ssl_listeners, [5671]},      {ssl_options, [{cacertfile,"/path/to/testca/cacert.pem"},                     {certfile,"/path/to/server/cert.pem"},                     {keyfile,"/path/to/server/key.pem"},                     {depth, 2},                     {verify,verify_peer},                     {fail_if_no_peer_cert,false}]}    ]} ]. 

当在RabbitMQ 插件中使用SSL时,如federation或 shovel,有可能需要为Erlang客户端配置证书的验证深度,正如下面所描述的.

配置Erlang client

在RabbitMQ Erlang client 中启用SSL是相当直接的. 在#amqp_params_network记录,我们只需要在ssl_options字段中提供值你会认识到我们指定RabbitMQ的选项。

Erlang SSL 选项

必须提供三个重要选项:

  • cacertfile 选项用于指定明确信任的根证书机构的证书.
  • certfile是client拥有的PEM格式的证书
  • keyfile是客户端PEM格式的私有密钥文件

作为RabbitMQ自身, verify 和fail_if_no_peer_cert 选项可用来指定当服务器未提供证书或我们不能根据服务器证书建立信任链时下的动作. depth配置了证书验证的深度(参考上面部分).

代码

Params = #amqp_params_network { port = 5671,                                 ssl_options = [{cacertfile, "/path/to/testca/cacert.pem"},                                                {certfile, "/path/to/client/cert.pem"},                                                {keyfile, "/path/to/client/key.pem"},                                                %% only necessary with intermediate CAs                                                %% {depth, 2},                                                {verify, verify_peer},                                                {fail_if_no_peer_cert, true}] }, {ok, Conn} = amqp_connection:start(Params), 

现在你就可以像普通连接一样来使用Conn了.


TLS/SSL故障排除

介绍

本页收集一些技巧来帮助诊断SSL错误。策略是使用其它的SSL实现来测试必要的组件,在故障排除的过程中识别故障.

请记住,如果两个特定的组件之间有相互作用的话,这个过程不保证识别问题.

我们也解释了在日志中可能出现的一些常见错误消息.

Erlang中检测SSL支持

建立与broker的SSL连接的第一个要求是在broker中要有SSL支持. 可通过运行erl(或Windows上的werl.exe)来确认Erlang VM是否支持SSL,命令为:

ssl:versions().
其输出看起来像这样(版本号可能有所不同):
[{ssl_app,"5.3.6"},  {supported,['tlsv1.2','tlsv1.1',tlsv1]},  {available,['tlsv1.2','tlsv1.1',tlsv1]}] 

相反,如果你收到了错误信息,那么就要确认Erlang是否使用了OpenSSL来构建.在基于Debian的系统上,你需要安装erlang-ssl包.

使用OpenSSL来检查密钥和证书

我们现在用其它的SSL实现来验证配置文件中指定的证书和密钥.这个例子使用 OpenSSL s_client 和 s_server. 我们将确认用于连接两端的证书和密钥. 

针对下面的例子,我们假设你有下面的信息

Item Location
CA certificate testca/cacert.pem
Server certificate server/cert.pem
Server key server/key.pem
Client certificate client/cert.pem
Client key client/key.pem

在一端执行下面的命令:

openssl s_server -accept 8443 -cert server/cert.pem -key server/key.pem \   -CAfile testca/cacert.pem
在另一端执行
openssl s_client -connect localhost:8443 -cert client/cert.pem -key client/key.pem \   -CAfile testca/cacert.pem
如果证书和密钥创建正确,SSL连接建立序列将出现,并且终端会连接.一端的输入会出现在另一端.如果建立了信任链,第二个终端将显示下面的信息:
Verify return code: 0 (ok)

如果收到错误,需要确认证书和密钥是否正确创建.

检查broker正在监听

此步骤可检查broker是否正在期望的AMQPS端口上进行监听.当你使用有效的SSL配置文件进行启动时, broker会在日志文件中报告SSL监听的地址.你可以类似于下面的东西:

=INFO REPORT==== 8-Aug-2011::11:51:47 === started SSL Listener on 0.0.0.0:5671
如果你设置了"ssl_listeners"配置指令但没有看到这样的信息,那么可能是你的配置文件没有被broker读取. 确认配置文件引用的broker日志中包含SSL配置选项. 
查看 configuration page来了解细节.

尝试与broker的SSL连接

一旦RabbitMQ broker监听于SSL端口,你可使用OpenSSL s_client来验证SSL 连接, 这次针对的是broker. 这可以检测broker是否配置正确,不必配置AMQPS client.此例假设broker使用"ssl_listeners"指令配置监听SSL连接的端口5671:

openssl s_client -connect localhost:5671 -cert client/cert.pem -key client/key.pem \   -CAfile testca/cacert.pem
输出类似于端口8443的情况. 当连接建立的时候,broker日志文件应该包含一个新的条目:
=INFO REPORT==== 8-Aug-2011::11:55:13 === accepting AMQP connection <0.223.0> (127.0.0.1:58954 -> 127.0.0.1:5671)
如果你现在为broker提供8个随机字节,broker将会使用字符串"AMQP"紧跟编码的版本号数字进行回复. 如果你认出了"AMQP"字符串,那么你能确定你已经连接上了AMQP broker.

使用stunnel来验证客户端连接

最后的检查是验证AMQPS clients. 我们将使用stunnel 来提供SSL能力.要这个配置中,AMQPS clients会使用stunnel来作安全连接,它将传递解密后的数据给broker的AMQP端口. 这提供了一些信心,客户端的SSL配置是独立于broker的SSL配置的正确配置.

stunnel会以守护进程的方式运行在与broker的同一台机器上.在下面的讨论中,假定只会暂时使用stunnel

在下面的讨论中,假定只会暂时使用stunnel。当然可能使用Stunnel提供SSL能力更永久,但是随着broker的集成缺乏,意味着管理报告功能和认证的插件(使用SSL信息)将无法这样做。

在这个例子中,stunne会连接到未加密的AMQP端口(5672) ,并接受具有SSL能力的客户端5679端口上的连接:

cat client/key.pem client/cert.pem > client/key-cert.pem stunnel -r localhost:5672 -d 5679 -f -p client/key-cert.pem -D 7
stunnel 需要证书和相应的密钥. 生成的客户端证书和相应的密钥应该使用和如上面所示的cat命令连接。Stunnel要求的密钥不需要密码保护。

SSL能力客户端现在可以连接端口5679,任何SSL错误会在stunnel启动时出现在控制台.

连接client和broker

如果上面的步骤没有出错,那么你可以测试AMQPS客户端已经连接到broker的 AMQPS端口, 首先须确保停止任何运行的OpenSSL或stunnel会话.

证书链和验证深度

当使用第三方CA签发的客户端证书时,有必要配置RabbitMQ server使用更高的验证深度. 该depth是非自颁发的中间证书的最大数量,可以按照有效的证书路径中的对等证书。

参考TLS/SSL guide来了解如何配置验证深度.

理解 SSL logs

上述步骤会产生新broker日志文件条目.这些条目与诊断输出一起可以帮助确认SSL错误的原因. 

下面的是一些常见的错误条目:

Entries containing  {ssl_upgrade_error, ekeyfile} or  {ssl_upgrade_error, ecertfile}

这意味着broker的 keyfile 或certificate文件是无效的.须确认keyfile匹配证书,且两者都是PEM 格式的. PEM格式是一种可打印的编码与识别的分隔符. 证书以 -----BEGIN CERTIFICATE----- 并以-----END CERTIFICATE----- 结束. keyfile 会以-----BEGIN RSA PRIVATE KEY----- 开始,并以-----END RSA PRIVATE KEY----- 结束.

Entries containing  {ssl_upgrade_failure, ... certify ...}

此错误与客户端验证有关.客户端提供了一个无效的证书或无证书. 如果ssl_options设置了 verify  选项为 verify_peer,那么将使用临时使用 verify_none .必须确保客户端证书已经正确生成了, 并且客户端提交正确的证书。
Entries containing  {ssl_upgrade_error, ...}
这是一个通用的错误,可能有许多原因。确保你使用的是Erlang的推荐版本。
Entries containing  {tls_alert,"bad record mac"}

服务器已尝试验证它所接收的数据的完整性和检查失败。这可能是由于有问题的网络设备, 无意的客户端socket共享(如.因为使用了fork(2))或者客户端实现的TLS的bug.

你可能感兴趣的:(RabbitMQ-官方指南-TLS 支持)