RMQ的SSL配置最佳實踐

木棉花開_發表於2019-01-10

網上的各種文章不是很細,配置出了問題很難發現解決,我已經踩了很多坑,結合網上些文章,以及官網網站給出的例子,最終整理出了一套自己的配置模式,參考本章即可不出任何錯誤的完成RabbitMQSSL配置。

Erlang語言的安裝

erlangRabbitMQ的執行環境,為什麼要強調安裝erlang,如果你隨便去下載erlang官網的包,可能會缺少很多依賴,如果你已經安裝好了erlang,執行命令看是否和以下結果一致。

檢查ssl依賴
如果執行後有error,沒有supported輸出,請重新安裝

重灌erlang

RabbitMQ官方網站有一段話,說明了ssl環境需要erlang哪些擴充套件包。原地址:RMQ官方SSL說明

Erlang/OTP Requirements for TLS Support In order to support TLS connections, RabbitMQ needs TLS and crypto-related modules to be available in the Erlang/OTP installation. The recommended Erlang/OTP version to use with TLS is the most recent supported Erlang release. Earlier versions, even if they are supported, may work for most certificates but have known limitations (see below).

The Erlang asn1, crypto, public_key, and ssl libraries (applications) must be installed and functional. On Debian and Ubuntu this is provided by the erlang-asn1, erlang-crypto, erlang-public-key, and erlang-ssl packages, respectively. The zero dependency Erlang RPM for RabbitMQ includes the above modules.

If Erlang/OTP is compiled from source, it is necessary to ensure that configure finds OpenSSL and builds the above libraries.

大概意思是安裝erlang前你的系統中必須已經安裝了openssl,然後erlang開啟ssl需要哪些依賴包,最終他給了一個0依賴的erlang下載地址,這個erlang包他預設包含了所有ssl需要的依賴。然後他給出的erlangRabbitMQ的版本對比,下載你需要的版本.

  1. erlang版本參考地址
  2. 0依賴的erlang下載

rpm安裝過後,將erl命令地址配置到環境變數中,如果在重灌過程中遇到了檔案衝突,使用如下指令。

rpm -Uivh erlang.rpm --replacefiles
複製程式碼

erlang環境裝好後,RabbitMQ安裝就不再介紹,使用rpm安裝就行,沒有什麼坑。

配置RabbitMQ的SSL埠

接下來會簡述證照生成,ssl埠開放

下載SSL證照生成器

git clone https://github.com/Berico-Technologies/CMF-AMQP-Configuration.git
複製程式碼

生成證照

cd CMF-AMQP-Configuration/ssl
複製程式碼

配置當前目錄下的openssl.cnf,基本上不需要改動,證照預設生成後的有效期是一年,如果需要延長可以修改default_days = 365.

生成證照籤發機構

該指令碼是會在當前目錄下生成一個ca目錄,裡面存放著一些證照頒發機構資訊,和已經簽發的證照記錄

sh setup_ca.sh RabbitSSL  
複製程式碼
  1. RabbitSSL: 簽發機構名稱,自定義。

生成服務端公鑰,和私鑰

該指令碼是會在當前目錄下生成一個server目錄,裡面存放著服務端的公鑰,和私鑰檔案。該檔案生成後會在ca目錄檔案中有簽發記錄。

sh make_server_cert.sh rabbit-server rabbit
複製程式碼
  1. rabbit-server: 生成的金鑰字首名,自定義。
  2. rabbit: 訪問該金鑰的密碼,自定義。

生成客戶端公鑰,和私鑰

該指令碼是會在當前目錄下生成一個client目錄,裡面存放著客戶端的公鑰,和私鑰檔案。該檔案生成後會在ca目錄檔案中有簽發記錄。

sh create_client_cert.sh rabbit-client rabbit
複製程式碼
  1. rabbit-client: 生成的金鑰字首名,自定義。
  2. rabbit: 訪問該金鑰的密碼,自定義。

生成客戶端需要的證照

不同的語言操作方式不一樣,這裡我們使用的是java語言,使用javakeytool工具生成,首先確保已經安裝java並且在環境變數中已經配置

keytool -import -alias rabbit-server -file server/rabbit-server.cert.pem -keystore rabbitStore -storepass rabbit
複製程式碼

用服務端的公鑰生成證照,這個步驟很關鍵,該證照用於客戶端和服務端通訊。

server/rabbit-server.cert.pem: 上個步驟已經生成好的服務端公鑰

如果需要刪除已經生成的證照,可執行以下命令

keytool -delete -alias rabbit-server -keystore rabbitStore -storepass rabbit
複製程式碼

證照生成結束步驟檢查

如果你跟著文章一步一步做到這,說明你離成功就只差最後一步了,接下來檢查我們前幾個步驟的結果,經過幾個步驟我們在CMF-AMQP-Configuration/ssl/目錄下生成了:

  • ca
  • server
  • client
  • rabbitStore 證照

如果以上的幾個目錄和這個證照都存在,說明該大步驟已經完美結束。接下來進入最關鍵的一步了。

修改RabbitMQ的SSL配置

接下來的步驟就比較關鍵了,需要用到我們上面所有生成的檔案,將它們配置到RabbitMQconfig檔案中.

  1. ca,server,client,rabbitStore拷貝到/etc/rabbitmq目錄下
cp -r ca server client rabbitStore /etc/rabbitmq/ssl
複製程式碼
  1. 如果/etc/rabbitmq目錄下沒有rabbitmq.config,建立該檔案。
vim /etc/rabbitmq/rabbitmq.config
複製程式碼
  1. 將以下配置複製到rabbitmq.config
%%Disable SSLv3.0 and TLSv1.0 support.
[
    {ssl, [{versions, ['tlsv1.2', 'tlsv1.1']}]},
    {rabbit, [
        {tcp_listeners, [5672]},
        {ssl_listeners, [5671]},
        {ssl_options, [{cacertfile,"/etc/rabbitmq/ssl/ca/cacert.pem"},
            {certfile,"/etc/rabbitmq/ssl/server/rabbit-server.cert.pem"},
            {keyfile,"/etc/rabbitmq/ssl/server/rabbit-server.key.pem"},
            {verify, verify_peer},
            {ciphers, ["ECDHE-ECDSA-AES256-GCM-SHA384","ECDHE-RSA-AES256-GCM-SHA384",
                        "ECDHE-ECDSA-AES256-SHA384","ECDHE-RSA-AES256-SHA384", "ECDHE-ECDSA-DES-CBC3-SHA",
                        "ECDH-ECDSA-AES256-GCM-SHA384","ECDH-RSA-AES256-GCM-SHA384","ECDH-ECDSA-AES256-SHA384",
                        "ECDH-RSA-AES256-SHA384","DHE-DSS-AES256-GCM-SHA384","DHE-DSS-AES256-SHA256",
                        "AES256-GCM-SHA384","AES256-SHA256","ECDHE-ECDSA-AES128-GCM-SHA256",
                        "ECDHE-RSA-AES128-GCM-SHA256","ECDHE-ECDSA-AES128-SHA256","ECDHE-RSA-AES128-SHA256",
                        "ECDH-ECDSA-AES128-GCM-SHA256","ECDH-RSA-AES128-GCM-SHA256","ECDH-ECDSA-AES128-SHA256",
                        "ECDH-RSA-AES128-SHA256","DHE-DSS-AES128-GCM-SHA256","DHE-DSS-AES128-SHA256",
                        "AES128-GCM-SHA256","AES128-SHA256","ECDHE-ECDSA-AES256-SHA",
                        "ECDHE-RSA-AES256-SHA","DHE-DSS-AES256-SHA","ECDH-ECDSA-AES256-SHA",
                        "ECDH-RSA-AES256-SHA","AES256-SHA","ECDHE-ECDSA-AES128-SHA",
                        "ECDHE-RSA-AES128-SHA","DHE-DSS-AES128-SHA","ECDH-ECDSA-AES128-SHA",
                                                "ECDH-RSA-AES128-SHA","AES128-SHA"]},
            {honor_cipher_order, true},
            {fail_if_no_peer_cert, true},
            {versions, ['tlsv1.2', 'tlsv1.1']}
        ]},
        {auth_mechanisms,['PLAIN', 'AMQPLAIN', 'EXTERNAL']}
    ]}
].
複製程式碼

在以上配置中我們將證照頒發機構以及服務端的公鑰和私鑰配置進去了。client目錄和rabbitStore是給客戶端使用的,我們使用5671埠作為我們ssl通訊埠,5672保持不變,繼續為內網tcp提供服務

  1. 重啟rabbitmq服務

以下命令是參考,每個人服務安裝方式不一樣,總之將它重啟就可以

systemctl restart rabbit-server.service
複製程式碼
  1. 檢視rabbitmq日誌輸出
less /var/log/rabbitmq/xxx.log
複製程式碼

log顯示成這樣,代表ssl開啟成功

啟動日誌

或者訪問網頁檢視5671是否開啟ssl

rabbit頁面

如上,ssl服務已經開啟.最後一步程式碼測試

編寫Java程式碼測試證照授權

將前面還沒有用到的client目錄和rabbitStore證照拷貝的專案中,放入到resource目錄下,執行以下程式碼做測試;

public class SslReceiver {

    public static void main(String[] args) throws TimeoutException {
        String classpath = SslReceiver.class.getResource("/").getPath();
        //證照密碼
        char[] sslPwd = "rabbit".toCharArray();
        //讀取client金鑰,和rabbitStore證照
        try (InputStream sslCardStream = new FileInputStream(classpath + "keyStore/client/rabbit-client.keycert.p12");
             InputStream rabbitStoreStream = new FileInputStream(classpath + "keyStore/rabbitStore")) {

            //載入祕鑰
            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(sslCardStream, sslPwd);
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("SunX509");
            keyManagerFactory.init(ks, sslPwd);

            //讀取授權證照,只含有服務端的公鑰
            KeyStore jks = KeyStore.getInstance("JKS");
            jks.load(rabbitStoreStream, sslPwd);
            TrustManagerFactory keyStoreManager = TrustManagerFactory.getInstance("SunX509");
            keyStoreManager.init(jks);
            SSLContext context = SSLContext.getInstance("TLSv1.2");
            context.init(keyManagerFactory.getKeyManagers(), keyStoreManager.getTrustManagers(), null);
            ConnectionFactory factory = new ConnectionFactory();
            factory.setUsername("rabbitTest");
            factory.setPassword("123456");
            factory.setHost("127.0.0.1");
            factory.setPort(5671);
            factory.setAutomaticRecoveryEnabled(true);

            //設定sslContext
            factory.useSslProtocol(context);
            Connection connection = factory.newConnection();
            Channel channel = connection.createChannel();
            channel.queueDeclare("rabbitmq-queue", false, true, true, null); //rabbitmq-queue是rabbitmq佇列
            channel.basicPublish("", "rabbitmq-queue", null, "Test,Test".getBytes());
            GetResponse chResponse = channel.basicGet("rabbitmq-queue", false);
            if (chResponse == null){
                System.out.println("No message retrieved");
            }else {
                byte[] body = chResponse.getBody();
                System.out.println("Recieved: " + new String(body));
            }
            channel.close();
            connection.close();
        } catch (KeyStoreException | UnrecoverableKeyException | KeyManagementException
                | CertificateException | NoSuchAlgorithmException | IOException e) {
            log.error("SSL證照解析失敗", e);
        }
    }
}
複製程式碼

如果收到了那條訊息,到此ssl結束,如果有異常資訊,請在啟動jvm中傳遞引數 -Djavax.net.debug=all,檢視連線過程,在結合服務端/var/log/rabbitmq下的log一起分析,或者聯絡我!通常來講,如果你將我的每一步複製,不可能出現問題。 完結!

聯絡我