VMware Tanzu RabbitMQ 4.3のRPMパッケージをRocky Linux 9にインストールするメモ

Note

  • 2026-05-13 Tanzu RabbitMQ 4.3版に更新しました
  • 2026-05-14 TLSの有効化セクションを追加しました

以下の手順では、VMware Tanzu RabbitMQのRPMパッケージをRocky Linux 9にインストールし、3台のクラスタを構成します。

ホスト名はrmq-1rmq-2rmq-3とします。また、以下の手順ではファイアウォールが無効になっている前提としますが、必要であれば以下のTCPポートを開放してください。

ポート 用途
4369 epmd (Erlang Port Mapper Daemon)
5672 AMQP (平文)
5671 AMQPS (TLS)
15672 HTTP (Management UI)
15671 HTTPS (Management UI)
25672 Erlang distribution
35672-35682 CLI tools

目次

VMware Tanzu RabbitMQのダウンロード

Broadcom Supportにログインして、VMware Tanzu RabbitMQのダウンロードページにアクセスします。

最新バージョンを選択します。この手順書では4.3.0を使用します。

image

"I agree to the Terms and Conditions"にチェック(要リンククリック)を入れて、

image

EL9用のインストーラーを~/Downloadsなどにダウンロードします。

image

(必要であれば)rpmに含まれるファイルを確認します。

rpm -qlp tanzu-rabbitmq-server-4.3.0-1.el9.x86_64.rpm

Tanzu RabbitMQのインストール

このセクションでの作業は rmq-1 上で行います。

インストール作業に使用する、あるいは作業に便利なパッケージをインストールします。

sudo dnf install -y wget lsof less vim

RabbitMQに必要なErlangのRPMパッケージをダウンロードします。

wget https://github.com/rabbitmq/erlang-rpm/releases/download/v27.3.4.11/erlang-27.3.4.11-1.el9.x86_64.rpm

Tanzu RabbitMQと依存パッケージをインストールします。

sudo dnf install -y logrotate erlang-27.3.4.11-1.el9.x86_64.rpm tanzu-rabbitmq-server-4.3.0-1.el9.x86_64.rpm

インストールされたパッケージのバージョンを確認します。

$ rpm -qa | grep -E 'erlang|rabbitmq'
erlang-27.3.4.11-1.el9.x86_64
tanzu-rabbitmq-server-4.3.0-1.el9.x86_64

$ sudo rabbitmqctl --version
4.3.0

Tanzu RabbitMQのサービスを有効化し、サービスを起動します。

sudo systemctl enable tanzu-rabbitmq-server
sudo systemctl start tanzu-rabbitmq-server

Tanzu RabbitMQサービスの状態を確認します。Started Tanzu RabbitMQ server.が出力されていることを確認してください。

$ systemctl status tanzu-rabbitmq-server | cat
● tanzu-rabbitmq-server.service - Tanzu RabbitMQ server
     Loaded: loaded (/usr/lib/systemd/system/tanzu-rabbitmq-server.service; enabled; preset: disabled)
    Drop-In: /run/systemd/system/service.d
             └─zzz-lxc-service.conf
     Active: active (running) since Wed 2026-05-13 11:24:34 JST; 5s ago
   Main PID: 1024 (beam.smp)
      Tasks: 52 (limit: 617453)
     Memory: 158.3M (peak: 173.3M)
        CPU: 2.656s
     CGroup: /system.slice/tanzu-rabbitmq-server.service
             ├─1024 /usr/lib64/erlang/erts-15.2.7.8/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 512 -MHlmbcs 512 -MMmcs 30 -pc unicode -P 1048576 -t 5000000 -stbt db -zdbbl 128000 -sbwt none -sbwtdcpu none -sbwtdio none -- -root /usr/lib64/erlang -bindir /usr/lib64/erlang/erts-15.2.7.8/bin -progname erl -- -home /var/lib/rabbitmq -- -pa "" -noshell -noinput -s rabbit boot -boot start_sasl -syslog logger "[]" -syslog syslog_error_logger false -kernel prevent_overlapping_partitions false --
             ├─1037 erl_child_setup 32768
             ├─1084 /usr/lib64/erlang/erts-15.2.7.8/bin/inet_gethost 4
             ├─1085 /usr/lib64/erlang/erts-15.2.7.8/bin/inet_gethost 4
             └─1088 /bin/sh -s rabbit_disk_monitor

May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Doc guides:  https://www.rabbitmq.com/docs
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Support:     https://www.rabbitmq.com/docs/contact
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Tutorials:   https://www.rabbitmq.com/tutorials
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Monitoring:  https://www.rabbitmq.com/docs/monitoring
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Upgrading:   https://www.rabbitmq.com/docs/upgrade
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Logs: /var/log/rabbitmq/rabbit@rmq-1.log
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:         <stdout>
May 13 11:24:33 rmq-1 rabbitmq-server[1024]:   Config file(s): (none)
May 13 11:24:34 rmq-1 rabbitmq-server[1024]:   Starting broker... completed with 0 plugins.
May 13 11:24:34 rmq-1 systemd[1]: Started Tanzu RabbitMQ server.

リッスンしているポートを確認します。

$ sudo lsof -n -i -P | grep -i listen | grep rabbitmq
epmd      881 rabbitmq    3u  IPv4 629997      0t0  TCP *:4369 (LISTEN)
epmd      881 rabbitmq    4u  IPv6 629998      0t0  TCP *:4369 (LISTEN)
beam.smp 1024 rabbitmq   24u  IPv4 627526      0t0  TCP *:25672 (LISTEN)
beam.smp 1024 rabbitmq   39u  IPv6 627537      0t0  TCP *:5672 (LISTEN)

RabbitMQに対してpingが通ることを確認します。

$ sudo rabbitmq-diagnostics ping
Will ping rabbit@rmq-1. This only checks if the OS process is running and registered with epmd. Timeout: 60000 ms.
Ping succeeded

rmq-1をクローンして、rmq-2とrmq-3を作成

ここまで作業した内容を rmq-2rmq-3 にも適用します。 rmq-1 VMをクローンして rmq-2rmq-3 を作ると良いでしょう。もし、 rmq-2rmq-3 も最初から構築する場合は rmq-1/var/lib/rabbitmq/.erlang.cookie の内容をコピーして rmq-2rmq-3 の起動前に配置してください。

$ sudo cat /var/lib/rabbitmq/.erlang.cookie
QQFAJVSPYEVMCQLYCPTH

rmq-1rmq-2rmq-3 のIPアドレスとホスト名を全ノードの /etc/hosts に記入してください。以下は例です。事前に決めた固定IPアドレスを使う場合は rmq-1 の複製時点で作成しておくと良いでしょう。

cat <<EOF | sudo tee -a /etc/hosts
192.168.139.108 rmq-1
192.168.139.201 rmq-2
192.168.139.227 rmq-3
EOF

RabbitMQクラスタの作成

rmq-2rmq-3 上で次のコマンドを実行し、 rmq-1 のクラスタに参加します。

sudo rabbitmqctl join_cluster rabbit@rmq-1

Warning

旧バージョンのRabbitMQの場合は、クラスタのセットアップに追加の手順が必要です。

以下のコマンドは任意のノード上で実行してください。

次のコマンドでクラスタの状態を確認します。3台のノードが表示されていることを確認してください。

$ sudo rabbitmqctl cluster_status
Cluster status of node rabbit@rmq-1 ...
Basics

Cluster name: rabbit@rmq-1
Total CPU cores available cluster-wide: 48

Cluster Tags

(none)

Disk Nodes

rabbit@rmq-1
rabbit@rmq-2
rabbit@rmq-3

Running Nodes

rabbit@rmq-1
rabbit@rmq-2
rabbit@rmq-3

Versions

rabbit@rmq-1: Tanzu RabbitMQ 4.3.0 on Erlang 27.3.4.11
rabbit@rmq-2: Tanzu RabbitMQ 4.3.0 on Erlang 27.3.4.11
rabbit@rmq-3: Tanzu RabbitMQ 4.3.0 on Erlang 27.3.4.11

CPU Cores

Node: rabbit@rmq-1, available CPU cores: 16
Node: rabbit@rmq-2, available CPU cores: 16
Node: rabbit@rmq-3, available CPU cores: 16

Maintenance status

Node: rabbit@rmq-1, status: not under maintenance
Node: rabbit@rmq-2, status: not under maintenance
Node: rabbit@rmq-3, status: not under maintenance

Alarms

(none)

Network Partitions

(none)

Listeners

Node: rabbit@rmq-1, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rmq-1, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@rmq-1, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rmq-2, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rmq-2, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Node: rabbit@rmq-2, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rmq-3, interface: [::], port: 15672, protocol: http, purpose: HTTP API
Node: rabbit@rmq-3, interface: [::], port: 25672, protocol: clustering, purpose: inter-node and CLI tool communication
Node: rabbit@rmq-3, interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0

Feature flags

Flag: classic_mirrored_queue_version, state: enabled
Flag: classic_queue_type_delivery_support, state: enabled
Flag: detailed_queues_endpoint, state: enabled
Flag: direct_exchange_routing_v2, state: enabled
Flag: drop_unroutable_metric, state: enabled
Flag: empty_basic_get_metric, state: enabled
Flag: feature_flags_v2, state: enabled
Flag: implicit_default_bindings, state: enabled
Flag: khepri_db, state: enabled
Flag: listener_records_in_ets, state: enabled
Flag: maintenance_mode_status, state: enabled
Flag: message_containers, state: enabled
Flag: message_containers_deaths_v2, state: enabled
Flag: quorum_queue, state: enabled
Flag: quorum_queue_non_voters, state: enabled
Flag: rabbit_exchange_type_local_random, state: enabled
Flag: rabbitmq_4.0.0, state: enabled
Flag: rabbitmq_4.1.0, state: enabled
Flag: rabbitmq_4.2.0, state: enabled
Flag: rabbitmq_4.3.0, state: enabled
Flag: restart_streams, state: enabled
Flag: stream_filtering, state: enabled
Flag: stream_queue, state: enabled
Flag: stream_sac_coordinator_unblock_group, state: enabled
Flag: stream_single_active_consumer, state: enabled
Flag: stream_update_config_command, state: enabled
Flag: tie_binding_to_dest_with_keep_while_cond, state: enabled
Flag: topic_binding_projection_v4, state: enabled
Flag: track_qq_members_uids, state: enabled
Flag: tracking_records_in_ets, state: enabled
Flag: user_limits, state: enabled
Flag: virtual_host_metadata, state: enabled

(任意で) クラスタ名を rabbit@rmq-1 から rmq-cluster に変更します。

sudo rabbitmqctl set_cluster_name rmq-cluster

変更されたことを確認します。

$ sudo rabbitmqctl cluster_status | grep "Cluster name:"
Cluster name: rmq-cluster

admin ユーザーを作成します。初期状態で設定されている guest ユーザーは localhost からのみ利用可能です。

sudo rabbitmqctl add_user admin 'VMware1!'
sudo rabbitmqctl set_user_tags admin administrator
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

ユーザー一覧を確認します。

$ sudo rabbitmqctl list_users
Listing users ...
user	tags
admin	[administrator]
guest	[administrator]

(任意で) guest ユーザーを削除します。

sudo rabbitmqctl delete_user guest

Management Pluginの有効化

次のコマンドを各ノード上で実行し、RabbitMQのManagement Pluginを有効化します。

sudo rabbitmq-plugins enable rabbitmq_management

リッスンしているポートを確認して15672が追加されたことを確認します。

$ sudo lsof -n -i -P | grep -i listen | grep rabbitmq
epmd      881 rabbitmq    3u  IPv4 629997      0t0  TCP *:4369 (LISTEN)
epmd      881 rabbitmq    4u  IPv6 629998      0t0  TCP *:4369 (LISTEN)
beam.smp 1024 rabbitmq   24u  IPv4 627526      0t0  TCP *:25672 (LISTEN)
beam.smp 1024 rabbitmq   39u  IPv6 627537      0t0  TCP *:5672 (LISTEN)
beam.smp 1024 rabbitmq   42u  IPv4 661184      0t0  TCP *:15672 (LISTEN)

いずれかのノードのIPの15672ポートにブラウザでアクセスし、 admin ユーザーでログインします。次のような画面が表示されます。

image

動作確認

簡単なPythonプログラムで動作確認します。

python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install pika

次の rabbitmq_cluster_check.py を作成してください。

#!/usr/bin/env python3
"""
RabbitMQ クラスタ動作確認スクリプト

- クラスタ内の任意のノードに接続 (フェイルオーバー)
- Quorum Queue を作成
- 指定数のメッセージを Publish
- 同じキューから Consume し、件数・内容を検証
- 成功時 exit 0 / 失敗時 exit 1

必要なパッケージ:
    pip install pika

実行例 (TLS あり):
    python3 rabbitmq_cluster_check.py \
        --hosts rabbit01,rabbit02,rabbit03 \
        --port 5671 \
        --user admin --password 'YOUR_PASSWORD' \
        --ca /etc/rabbitmq/certs/ca.crt \
        --count 5 --cleanup

実行例 (mTLS / クライアント証明書あり):
    python3 rabbitmq_cluster_check.py \
        --hosts rabbit01,rabbit02,rabbit03 \
        --port 5671 \
        --user admin --password 'YOUR_PASSWORD' \
        --ca /etc/rabbitmq/certs/ca.crt \
        --cert /etc/rabbitmq/certs/client.crt \
        --key /etc/rabbitmq/certs/client.key \
        --count 5 --cleanup

実行例 (TLS なし):
    python3 rabbitmq_cluster_check.py \
        --hosts rabbit01,rabbit02,rabbit03 \
        --port 5672 --no-tls \
        --user admin --password 'YOUR_PASSWORD' \
        --count 5 --cleanup
"""

import argparse
import ssl
import sys
import time
import uuid

import pika


def build_params(hosts, port, vhost, user, password,
                 use_tls, ca_file, skip_verify,
                 client_cert=None, client_key=None):
    credentials = pika.PlainCredentials(user, password)

    ssl_context = None
    if use_tls:
        ssl_context = ssl.create_default_context(cafile=ca_file)
        if client_cert and client_key:
            ssl_context.load_cert_chain(certfile=client_cert, keyfile=client_key)
        if skip_verify:
            ssl_context.check_hostname = False
            ssl_context.verify_mode = ssl.CERT_NONE

    params_list = []
    for host in hosts:
        kwargs = dict(
            host=host,
            port=port,
            virtual_host=vhost,
            credentials=credentials,
            connection_attempts=2,
            retry_delay=1,
            socket_timeout=10,
            heartbeat=30,
        )
        if use_tls:
            kwargs["ssl_options"] = pika.SSLOptions(ssl_context, server_hostname=host)
        params_list.append(pika.ConnectionParameters(**kwargs))
    return params_list


def connect_with_failover(params_list):
    last_err = None
    for params in params_list:
        try:
            conn = pika.BlockingConnection(params)
            print(f"[OK]   Connected to {params.host}:{params.port}")
            return conn
        except Exception as e:
            print(f"[WARN] Failed to connect to {params.host}: {e}")
            last_err = e
    raise RuntimeError(f"All nodes unreachable. Last error: {last_err}")


def main():
    p = argparse.ArgumentParser(description="RabbitMQ cluster sanity check")
    p.add_argument("--hosts", default="rabbit01,rabbit02,rabbit03",
                   help="comma-separated hostnames (default: rabbit01,rabbit02,rabbit03)")
    p.add_argument("--port", type=int, default=5671,
                   help="AMQP port (default: 5671 for TLS, use 5672 for plain)")
    p.add_argument("--vhost", default="/", help="vhost (default: /)")
    p.add_argument("--user", default="admin", help="username (default: admin)")
    p.add_argument("--password", required=True, help="password")
    p.add_argument("--queue", default="test.quorum.check",
                   help="queue name (default: test.quorum.check)")
    p.add_argument("--count", type=int, default=5, help="number of messages (default: 5)")
    p.add_argument("--no-tls", action="store_true", help="disable TLS")
    p.add_argument("--ca", default="/etc/rabbitmq/certs/ca.crt",
                   help="CA certificate path for TLS")
    p.add_argument("--cert", default=None,
                   help="client certificate path for mutual TLS (mTLS)")
    p.add_argument("--key", default=None,
                   help="client private key path for mutual TLS (mTLS)")
    p.add_argument("--skip-verify", action="store_true",
                   help="skip TLS hostname/cert verification (NOT for production)")
    p.add_argument("--cleanup", action="store_true",
                   help="delete queue after test")
    args = p.parse_args()

    hosts = [h.strip() for h in args.hosts.split(",") if h.strip()]
    use_tls = not args.no_tls

    print("=" * 60)
    print(f" Target:     {hosts} port={args.port} vhost={args.vhost}")
    print(f" User:       {args.user}")
    print(f" Queue:      {args.queue} (quorum)")
    print(f" Messages:   {args.count}")
    print(f" TLS:        {'enabled (CA=' + args.ca + ')' if use_tls else 'disabled'}")
    print("=" * 60)

    params_list = build_params(
        hosts=hosts, port=args.port, vhost=args.vhost,
        user=args.user, password=args.password,
        use_tls=use_tls, ca_file=args.ca, skip_verify=args.skip_verify,
        client_cert=args.cert, client_key=args.key,
    )

    # ---- Connect (with failover) ----
    connection = connect_with_failover(params_list)
    channel = connection.channel()
    channel.confirm_delivery()  # Publisher Confirms 有効化

    # ---- Declare Quorum Queue ----
    print(f"[INFO] Declaring quorum queue: {args.queue}")
    channel.queue_declare(
        queue=args.queue,
        durable=True,
        arguments={"x-queue-type": "quorum"},
    )

    # ---- Publish ----
    run_id = uuid.uuid4().hex[:8]
    print(f"[INFO] Publishing {args.count} messages (run_id={run_id})")
    sent = []
    for i in range(1, args.count + 1):
        body = f"[{run_id}] msg-{i:03d} ts={int(time.time())}"
        channel.basic_publish(
            exchange="",
            routing_key=args.queue,
            body=body.encode("utf-8"),
            properties=pika.BasicProperties(
                delivery_mode=2,          # persistent
                content_type="text/plain",
            ),
            mandatory=True,
        )
        sent.append(body)
        print(f"  -> SENT {body}")

    # ---- Consume ----
    print(f"[INFO] Consuming up to {args.count} messages")
    received = []
    # 今回送ったメッセージだけを対象にする (run_id でフィルタ)
    for _ in range(args.count * 2):  # 余裕をもってループ
        method, _, body = channel.basic_get(queue=args.queue, auto_ack=False)
        if method is None:
            break
        msg = body.decode("utf-8")
        if run_id in msg:
            received.append(msg)
            channel.basic_ack(delivery_tag=method.delivery_tag)
            print(f"  <- RECV {msg}")
        else:
            # 別 run_id のメッセージは requeue
            channel.basic_nack(delivery_tag=method.delivery_tag, requeue=True)
        if len(received) == args.count:
            break

    # ---- Verify ----
    missing = set(sent) - set(received)
    success = (len(sent) == len(received)) and not missing

    print("-" * 60)
    print(f" Sent:     {len(sent)}")
    print(f" Received: {len(received)}")
    if missing:
        print(f" Missing:  {len(missing)}")
        for m in sorted(missing):
            print(f"   - {m}")
    print("-" * 60)

    # ---- Cleanup ----
    if args.cleanup:
        print(f"[INFO] Deleting queue: {args.queue}")
        channel.queue_delete(queue=args.queue)

    connection.close()

    if success:
        print("[PASS] RabbitMQ cluster check succeeded.")
        sys.exit(0)
    else:
        print("[FAIL] RabbitMQ cluster check failed.")
        sys.exit(1)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("[ABORT] Interrupted by user.")
        sys.exit(130)
    except Exception as e:
        print(f"[ERROR] {e}")
        sys.exit(1)

スクリプトを実行します。

python rabbitmq_cluster_check.py --hosts rmq-1,rmq-2,rmq-3 --port 5672 --user admin --password 'VMware1!' --count 5 --no-tls --cleanup

次のような出力になればOKです。

============================================================
 Target:     ['rmq-1', 'rmq-2', 'rmq-3'] port=5672 vhost=/
 User:       admin
 Queue:      test.quorum.check (quorum)
 Messages:   5
 TLS:        disabled
============================================================
[OK]   Connected to rmq-1:5672
[INFO] Declaring quorum queue: test.quorum.check
[INFO] Publishing 5 messages (run_id=0fb619ec)
  -> SENT [0fb619ec] msg-001 ts=1776328534
  -> SENT [0fb619ec] msg-002 ts=1776328534
  -> SENT [0fb619ec] msg-003 ts=1776328534
  -> SENT [0fb619ec] msg-004 ts=1776328534
  -> SENT [0fb619ec] msg-005 ts=1776328534
[INFO] Consuming up to 5 messages
  <- RECV [0fb619ec] msg-001 ts=1776328534
  <- RECV [0fb619ec] msg-002 ts=1776328534
  <- RECV [0fb619ec] msg-003 ts=1776328534
  <- RECV [0fb619ec] msg-004 ts=1776328534
  <- RECV [0fb619ec] msg-005 ts=1776328534
------------------------------------------------------------
 Sent:     5
 Received: 5
------------------------------------------------------------
[PASS] RabbitMQ cluster check succeeded.

TLSの有効化

ここまでは平文(AMQP 5672、Management UI 15672)で構成しました。このセクションでは、クライアント向け通信に続けてノード間通信も段階的にTLS化します。

  1. opensslで証明書(CA・サーバ・クライアント)を生成
  2. 全ノードに証明書を配置
  3. AMQPS (5671) を有効化
  4. Management UIをHTTPS (15671) 化
  5. ノード間通信(Erlang distribution)のTLS化
  6. mTLS(クライアント証明書の検証)を有効化

Note

rabbitmqctlrabbitmq-diagnostics はAMQPではなくErlang distribution経由で通信します。そのため、AMQP (5671) のTLS化はCLIツールに影響しませんが、ノード間通信(25672ポート)をTLS化した後はCLIツール側にもTLS設定が必要になります(後述)。

TLS証明書の生成

rmq-1 上で openssl コマンドを使い、1つのCAと、3ノードで共用する1枚のサーバ証明書(SANに rmq-1, rmq-2, rmq-3 を持つ)、ノード間通信の client ロールおよび mTLS テスト用のクライアント証明書をまとめて生成します。鍵はRSA 4096、有効期限は3650日です。記事中で必要なすべてのケース(AMQPS / Management HTTPS / ノード間 distribution の server・client ロール / AMQP mTLS)をこの1回の発行作業でカバーします。

作業ディレクトリを作成します。

mkdir -p ~/certs && cd ~/certs

CAの秘密鍵と自己署名証明書を作成します。

# CA private key
openssl genrsa -out ca_key.pem 4096
chmod 600 ca_key.pem

# Self-signed CA certificate (10 years)
openssl req -x509 -new -key ca_key.pem -sha256 -days 3650 \
  -subj "/O=RabbitMQ Lab/CN=RabbitMQ Lab CA" \
  -out ca_certificate.pem

サーバ証明書用の設定ファイル server.cnf を作成します。subjectAltName (SAN) に3ノードのホスト名を列挙することで、1枚の証明書を全ノードで共用できるようにします。

cat <<'EOF' > server.cnf
[req]
distinguished_name = dn
prompt             = no

[dn]
O  = server
CN = rmq-cluster

[v3_server]
basicConstraints = CA:FALSE
keyUsage         = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName   = @alt_names

[alt_names]
DNS.1 = rmq-1
DNS.2 = rmq-2
DNS.3 = rmq-3
EOF

サーバ証明書(SAN付き、全ノード共用)を生成します。

# Server private key
openssl genrsa -out server_key.pem 4096
chmod 600 server_key.pem

# CSR
openssl req -new -key server_key.pem -config server.cnf -out server.csr

# Sign with CA (SAN/EKU are taken from the v3_server section of server.cnf)
openssl x509 -req -in server.csr -sha256 -days 3650 \
  -CA ca_certificate.pem -CAkey ca_key.pem -CAcreateserial \
  -extfile server.cnf -extensions v3_server \
  -out server_certificate.pem

クライアント証明書用の設定ファイル client.cnf を作成します。ノード間通信の client ロールでは extendedKeyUsage = clientAuth が必要なため、サーバ証明書とは別の証明書として発行します。

cat <<'EOF' > client.cnf
[req]
distinguished_name = dn
prompt             = no

[dn]
O  = client
CN = rmq-client

[v3_client]
basicConstraints = CA:FALSE
keyUsage         = critical, digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth
EOF

クライアント証明書を生成します。

# Client private key
openssl genrsa -out client_key.pem 4096
chmod 600 client_key.pem

# CSR
openssl req -new -key client_key.pem -config client.cnf -out client.csr

# Sign with CA
openssl x509 -req -in client.csr -sha256 -days 3650 \
  -CA ca_certificate.pem -CAkey ca_key.pem -CAcreateserial \
  -extfile client.cnf -extensions v3_client \
  -out client_certificate.pem

生成された証明書のSANとEKUを確認します。

$ openssl x509 -in server_certificate.pem -noout -ext subjectAltName,extendedKeyUsage
X509v3 Extended Key Usage: 
    TLS Web Server Authentication
X509v3 Subject Alternative Name: 
    DNS:rmq-1, DNS:rmq-2, DNS:rmq-3

$ openssl x509 -in client_certificate.pem -noout -ext extendedKeyUsage
X509v3 Extended Key Usage: 
    TLS Web Client Authentication

CAとの連鎖検証を行います。両方とも OK であれば成功です。

$ openssl verify -CAfile ca_certificate.pem server_certificate.pem client_certificate.pem
server_certificate.pem: OK
client_certificate.pem: OK

~/certs ディレクトリに以下のファイルが揃います。以降のセクションではこれらを各ノードに配布します。

$ ls -lh ~/certs
-rw-r--r-- 1 toshiaki toshiaki 1.9K May 19 13:33 ca_certificate.pem
-rw-r--r-- 1 toshiaki toshiaki   41 May 19 13:33 ca_certificate.srl
-rw------- 1 toshiaki toshiaki 3.2K May 19 13:33 ca_key.pem
-rw-r--r-- 1 toshiaki toshiaki  222 May 19 13:33 client.cnf
-rw-r--r-- 1 toshiaki toshiaki 1.6K May 19 13:33 client.csr
-rw-r--r-- 1 toshiaki toshiaki 1.9K May 19 13:33 client_certificate.pem
-rw------- 1 toshiaki toshiaki 3.2K May 19 13:33 client_key.pem
-rw-r--r-- 1 toshiaki toshiaki  308 May 19 13:33 server.cnf
-rw-r--r-- 1 toshiaki toshiaki 1.6K May 19 13:33 server.csr
-rw-r--r-- 1 toshiaki toshiaki 2.0K May 19 13:33 server_certificate.pem
-rw------- 1 toshiaki toshiaki 3.2K May 19 13:33 server_key.pem

Note

ここでは秘密鍵をパスフレーズなしで生成しています。パスフレーズ付きの鍵を使う場合は、RabbitMQ側に ssl_options.password の設定が必要です。

証明書の配置

各ノードに /etc/rabbitmq/certs ディレクトリを作成し、以下のファイルを共通の名前で配置します。証明書は rabbitmq ユーザーから読める必要があります。

  • ca_certificate.pem — CA証明書(全ノード共通)
  • server_certificate.pem / server_key.pem — サーバ証明書(SANに rmq-1, rmq-2, rmq-3 を含む1枚を全ノード共通で使う)
  • client_certificate.pem / client_key.pem — ノード間通信の client ロールおよび mTLS テスト用のクライアント証明書(全ノード共通)

3ノードとも完全に同じ証明書セットを配置するため、すべてのノードで同じ rabbitmq.conf / inter_node_tls.config を使えます。

Warning

sudo cp/etc/rabbitmq/certs/ 配下に作成したファイルは root 所有・mode 600 になるため、rabbitmq ユーザーから読めません。必ず以下の chownchmod まで通して実行してください。証明書ファイルが rabbitmq ユーザーで読めないと、RabbitMQ サーバ自体は起動するものの、CLI ツール (rabbitmqctl) や他ノードからの inter-node TLS dist が証明書をロードできずに失敗し、サーバログに TLS エラーすら残らない状態でハングします(後段の Khepri が timeout_waiting_for_leader を出すなど、原因が分かりにくい症状になります)。後から証明書を追加コピーした場合も chown / chmod を再実行してください。

rmq-1 での作業:

sudo mkdir -p /etc/rabbitmq/certs
sudo cp ~/certs/ca_certificate.pem     /etc/rabbitmq/certs/ca_certificate.pem
sudo cp ~/certs/server_certificate.pem /etc/rabbitmq/certs/server_certificate.pem
sudo cp ~/certs/server_key.pem         /etc/rabbitmq/certs/server_key.pem
sudo cp ~/certs/client_certificate.pem /etc/rabbitmq/certs/client_certificate.pem
sudo cp ~/certs/client_key.pem         /etc/rabbitmq/certs/client_key.pem
sudo chown -R rabbitmq:rabbitmq /etc/rabbitmq/certs
sudo chmod 755 /etc/rabbitmq/certs
sudo chmod 640 /etc/rabbitmq/certs/ca_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/server_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/server_key.pem
sudo chmod 640 /etc/rabbitmq/certs/client_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/client_key.pem

rmq-2rmq-3 にも同じファイルを転送して配置します。サーバ証明書もクライアント証明書も全ノード共通なので、3ノードとも完全に同じファイルセットになります。以下は rmq-1 から rmq-2 へ転送する例です(rmq-3 も同様)。

# rmq-1上
scp ~/certs/ca_certificate.pem \
    ~/certs/server_certificate.pem \
    ~/certs/server_key.pem \
    ~/certs/client_certificate.pem \
    ~/certs/client_key.pem \
    rmq-2:/tmp/

# rmq-2上
sudo mkdir -p /etc/rabbitmq/certs
sudo cp /tmp/ca_certificate.pem     /etc/rabbitmq/certs/ca_certificate.pem
sudo cp /tmp/server_certificate.pem /etc/rabbitmq/certs/server_certificate.pem
sudo cp /tmp/server_key.pem         /etc/rabbitmq/certs/server_key.pem
sudo cp /tmp/client_certificate.pem /etc/rabbitmq/certs/client_certificate.pem
sudo cp /tmp/client_key.pem         /etc/rabbitmq/certs/client_key.pem
sudo chown -R rabbitmq:rabbitmq /etc/rabbitmq/certs
sudo chmod 755 /etc/rabbitmq/certs
sudo chmod 640 /etc/rabbitmq/certs/ca_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/server_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/server_key.pem
sudo chmod 640 /etc/rabbitmq/certs/client_certificate.pem
sudo chmod 640 /etc/rabbitmq/certs/client_key.pem
rm /tmp/ca_certificate.pem /tmp/server_certificate.pem /tmp/server_key.pem \
   /tmp/client_certificate.pem /tmp/client_key.pem

鍵のパーミッションは 640(owner rabbitmq / group rabbitmq のみ読み取り可)にしているため、後の動作確認で作業ユーザーから /etc/rabbitmq/certs/*.pem を直接読みたい場合は、作業ユーザーを rabbitmq グループに追加しておきます。後述の mTLS の動作確認で作業ユーザーから秘密鍵を参照するため、テストを実行するノード(記事中では rmq-1)で実行しておきます。

sudo usermod -aG rabbitmq $(whoami)

グループの変更を現在のシェルに反映するため、いったんログアウト・ログインし直すか、newgrp rabbitmq を実行します。

$ id
uid=1000(toshiaki) gid=1000(toshiaki) groups=1000(toshiaki),10(wheel),989(rabbitmq)

Note

公開証明書 (ca_certificate.pem / server_certificate.pem / client_certificate.pem) は他者に渡しても問題ない情報のため 644 にしても構いません。秘密鍵 (server_key.pem / client_key.pem) は同一ホスト上の任意ユーザーにTLS資格情報を盗まれないよう、本記事では 640 にしています。

AMQPS (5671) の有効化

記事冒頭の systemctl status の出力で Config file(s): (none) だったとおり、初期状態では設定ファイルがありません。全ノードで /etc/rabbitmq/rabbitmq.conf を作成します。

cat <<'EOF' | sudo tee /etc/rabbitmq/rabbitmq.conf
listeners.ssl.default = 5671

ssl_options.cacertfile           = /etc/rabbitmq/certs/ca_certificate.pem
ssl_options.certfile             = /etc/rabbitmq/certs/server_certificate.pem
ssl_options.keyfile              = /etc/rabbitmq/certs/server_key.pem
ssl_options.verify               = verify_none
ssl_options.fail_if_no_peer_cert = false
EOF

Note

平文のAMQP (5672) も引き続きリッスンされます。平文を無効化したい場合は listeners.tcp = none を追加してください。本記事では後の動作確認で平文も使うため残しています。

全ノードでサービスを再起動します。

sudo systemctl restart tanzu-rabbitmq-server

リッスンしているポートを確認し、5671が追加されたことを確認します。

$ sudo lsof -n -i -P | grep -i listen | grep rabbitmq
epmd      881 rabbitmq    3u  IPv4 629997      0t0  TCP *:4369 (LISTEN)
epmd      881 rabbitmq    4u  IPv6 629998      0t0  TCP *:4369 (LISTEN)
beam.smp 3518 rabbitmq   24u  IPv4 801487      0t0  TCP *:25672 (LISTEN)
beam.smp 3518 rabbitmq   42u  IPv4 807112      0t0  TCP *:15672 (LISTEN)
beam.smp 3518 rabbitmq   43u  IPv6 801517      0t0  TCP *:5672 (LISTEN)
beam.smp 3518 rabbitmq   44u  IPv6 801523      0t0  TCP *:5671 (LISTEN)

openssl s_client でTLSハンドシェイクを確認します。Verify return code: 0 (ok) であればCA検証に成功しています。

$ openssl s_client -connect rmq-1:5671 -CAfile /etc/rabbitmq/certs/ca_certificate.pem </dev/null 2>/dev/null | grep -E 'subject=|Verify return code'
subject=O=server, CN=rmq-cluster
Verify return code: 0 (ok)

$ openssl s_client -connect rmq-2:5671 -CAfile /etc/rabbitmq/certs/ca_certificate.pem </dev/null 2>/dev/null | grep -E 'subject=|Verify return code'
subject=O=server, CN=rmq-cluster
Verify return code: 0 (ok)

$ openssl s_client -connect rmq-3:5671 -CAfile /etc/rabbitmq/certs/ca_certificate.pem </dev/null 2>/dev/null | grep -E 'subject=|Verify return code'
subject=O=server, CN=rmq-cluster
Verify return code: 0 (ok)

「動作確認」セクションで作成した rabbitmq_cluster_check.py を、TLS経由で実行します。--ca を指定するとTLSが有効になります(ポートは5671を指定)。

python rabbitmq_cluster_check.py \
  --hosts rmq-1,rmq-2,rmq-3 --port 5671 \
  --user admin --password 'VMware1!' \
  --ca /etc/rabbitmq/certs/ca_certificate.pem \
  --count 5 --cleanup

次のように TLS: enabled と表示され、[PASS] で終わればOKです。

============================================================
 Target:     ['rmq-1', 'rmq-2', 'rmq-3'] port=5671 vhost=/
 User:       admin
 Queue:      test.quorum.check (quorum)
 Messages:   5
 TLS:        enabled (CA=/etc/rabbitmq/certs/ca_certificate.pem)
============================================================
[OK]   Connected to rmq-1:5671
[INFO] Declaring quorum queue: test.quorum.check
[INFO] Publishing 5 messages (run_id=c834a3ea)
  -> SENT [c834a3ea] msg-001 ts=1779166061
  -> SENT [c834a3ea] msg-002 ts=1779166061
  -> SENT [c834a3ea] msg-003 ts=1779166061
  -> SENT [c834a3ea] msg-004 ts=1779166061
  -> SENT [c834a3ea] msg-005 ts=1779166061
[INFO] Consuming up to 5 messages
  <- RECV [c834a3ea] msg-001 ts=1779166061
  <- RECV [c834a3ea] msg-002 ts=1779166061
  <- RECV [c834a3ea] msg-003 ts=1779166061
  <- RECV [c834a3ea] msg-004 ts=1779166061
  <- RECV [c834a3ea] msg-005 ts=1779166061
------------------------------------------------------------
 Sent:     5
 Received: 5
------------------------------------------------------------
[INFO] Deleting queue: test.quorum.check
[PASS] RabbitMQ cluster check succeeded.

Management UIのHTTPS (15671) 化

全ノードの /etc/rabbitmq/rabbitmq.conf に以下を追記します。

cat <<'EOF' | sudo tee -a /etc/rabbitmq/rabbitmq.conf

management.ssl.port       = 15671
management.ssl.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
management.ssl.certfile   = /etc/rabbitmq/certs/server_certificate.pem
management.ssl.keyfile    = /etc/rabbitmq/certs/server_key.pem
EOF

全ノードでサービスを再起動し、15671が追加されたことを確認します。

$ sudo systemctl restart tanzu-rabbitmq-server
$ sudo lsof -n -i -P | grep -i listen | grep rabbitmq | grep 15671
beam.smp 3757 rabbitmq   42u  IPv4 822635      0t0  TCP *:15671 (LISTEN)

Note

平文のHTTP (15672) はそのまま残ります。閉じたい場合は management.tcp.ip = 127.0.0.1 でローカルからのみアクセス可能にするなどの対応を検討してください。

ブラウザで https://rmq-1:15671/ にアクセスし、admin ユーザーでログインします。(端末側の/etc/hostsも更新されている前提です。) プライベートCAで署名した証明書なので、ブラウザは証明書の警告を表示します。

image

"rmq-1 にアクセスする(安全ではありません)"をクリックすれば、HTTPS対応されたManagement UIにアクセスできます。

image

なお、ca_certificate.pem をOSやブラウザの信頼済みCAに登録すると警告は出なくなります。

ノード間通信(Erlang distribution)のTLS (mTLS) 化

ここまではクライアント向け通信(AMQPS / Management UI)をTLS化しました。次に、ノード間通信(Erlang distribution、25672ポート)と、その上で通信するCLIツール(rabbitmqctl など)の通信もTLS化します。証明書は「証明書の配置」セクションで配置済みのもの (server_certificate.pem / server_key.pem および client_certificate.pem / client_key.pem) を再利用します。

Note

distribution では自ノードが他ノードに接続するときの クライアントロール にも証明書が必要(mTLS)で、そこには id-kp-clientAuth を持つ client 証明書を指定する必要があります。「TLS証明書の生成」セクションで発行したサーバ証明書は Extended Key Usage が id-kp-serverAuth のみで id-kp-clientAuth を持たないため、サーバ証明書をクライアントロールに流用すると相手ノードに invalid_ext_keyusage で拒否されます。そのため client_certificate.pem / client_key.pem を別途用意しています。

Warning

ノード間通信のTLS化は、クラスタ全体を一括で切り替える必要があります。TLS設定済みのノードと未設定(平文)のノードは通信できないため、ローリング再起動(1台ずつの再起動)はできません。全ノードの設定を変更してから、クラスタ全体を停止・起動します。

inter_node_tls.config の作成

全ノードで /etc/rabbitmq/inter_node_tls.config を作成します。これはErlangのtermファイルで、server(自ノードがサーバとして振る舞うとき)と client(自ノードがクライアントとして他ノードに接続するとき)の双方のTLSオプションを記述します。

cat <<'EOF' | sudo tee /etc/rabbitmq/inter_node_tls.config
%% coding: utf-8
[
  {server, [
    {cacertfile, "/etc/rabbitmq/certs/ca_certificate.pem"},
    {certfile,   "/etc/rabbitmq/certs/server_certificate.pem"},
    {keyfile,    "/etc/rabbitmq/certs/server_key.pem"},
    {secure_renegotiate, true},
    {verify, verify_peer},
    {fail_if_no_peer_cert, true}
  ]},
  {client, [
    {cacertfile, "/etc/rabbitmq/certs/ca_certificate.pem"},
    {certfile,   "/etc/rabbitmq/certs/client_certificate.pem"},
    {keyfile,    "/etc/rabbitmq/certs/client_key.pem"},
    {secure_renegotiate, true},
    {verify, verify_peer}
  ]}
].
EOF
sudo chown rabbitmq:rabbitmq /etc/rabbitmq/inter_node_tls.config
sudo chmod 644 /etc/rabbitmq/inter_node_tls.config

Note

「TLS証明書の生成」セクションでは秘密鍵をパスフレーズなしで生成しているため password の指定は不要です。パスフレーズ付きの鍵を使う場合は server / client の両方に {password, "..."} を追加してください。

rabbitmq-env.conf の設定

全ノードで /etc/rabbitmq/rabbitmq-env.conf に以下を追記します。-proto_dist inet_tls でErlang distributionをTLS化し、-ssl_dist_optfile で先ほどの設定ファイルを指定します。-pa でssl applicationのパスをコードパスに追加します(distributionの起動時に必要になるため)。

Erlangのssl applicationのパスは環境によってバージョンが異なるので、まず確認します。

$ ls -d /usr/lib64/erlang/lib/ssl-*/ebin
/usr/lib64/erlang/lib/ssl-11.2.12.7/ebin

rabbitmq-env.conf に追記します。SERVER_ADDITIONAL_ERL_ARGS はRabbitMQサーバ本体に、RABBITMQ_CTL_ERL_ARGSrabbitmqctl などのCLIツールに渡る引数です。

cat <<'EOF' | sudo tee -a /etc/rabbitmq/rabbitmq-env.conf

ERL_SSL_PATH=$(ls -d /usr/lib64/erlang/lib/ssl-*/ebin | tail -n1)
SERVER_ADDITIONAL_ERL_ARGS="-pa $ERL_SSL_PATH -proto_dist inet_tls -ssl_dist_optfile /etc/rabbitmq/inter_node_tls.config"
RABBITMQ_CTL_ERL_ARGS="-pa $ERL_SSL_PATH -proto_dist inet_tls -ssl_dist_optfile /etc/rabbitmq/inter_node_tls.config"
EOF

このように動的な設定にすることでErlangのマイナーアップデートで ssl のバージョンが上がってもパスが自動追随できます。

クラスタの停止・起動

全ノードで証明書と設定ファイル(inter_node_tls.config および rabbitmq-env.conf)を配置したら、全ノードでサービスを停止し、その後に 3ノードを並行起動 します。

# 全ノードで停止
sudo systemctl stop tanzu-rabbitmq-server

# 全ノードで起動(並行に。各ノードで実行)
sudo systemctl start --no-block tanzu-rabbitmq-server

Warning

RabbitMQ のメタデータストア (Khepri) は Raft で過半数(3ノードなら2ノード)の合意を必要とします。1ノードずつ順番に起動すると、先発ノードが quorum を待ってタイムアウト (timeout_waiting_for_leader) し、ブートに失敗します。3ノードを並行起動してください(例: ターミナルを3つ開いて同時に実行する、または各ノードで systemctl start --no-block を実行して起動コマンドが quorum 待ちでブロックしないようにする)。また前述のとおり、平文設定のままのノードと TLS 設定済みのノードは通信できないため、systemctl restart を1台ずつ実行することもできません。

確認

クラスタが3ノードで再形成されていることを確認します。rabbitmq-env.confRABBITMQ_CTL_ERL_ARGS を設定済みなので、rabbitmqctl はそのままTLS経由で通信します。このコマンドが成功すること自体が、CLIツール↔ノード間とノード↔ノード間のTLSハンドシェイクが成立している証拠です。

$ sudo rabbitmqctl cluster_status | grep -A4 "Running Nodes"
Running Nodes

rabbit@rmq-1
rabbit@rmq-2
rabbit@rmq-3

rabbitmq-diagnostics listeners の出力で、25672 ポートのプロトコルが clustering/ssl (TLSあり)になっていることを確認します。平文のままだとここが clustering になります。

$ sudo rabbitmq-diagnostics listeners
Asking node rabbit@rmq-1 to report its protocol listeners ...
Interface: [::], port: 15671, protocol: https, purpose: HTTP API over TLS (HTTPS)
Interface: [::], port: 25672, protocol: clustering/ssl, purpose: inter-node and CLI tool communication over TLS
Interface: [::], port: 5672, protocol: amqp, purpose: AMQP 0-9-1 and AMQP 1.0
Interface: [::], port: 5671, protocol: amqp/ssl, purpose: AMQP 0-9-1 and AMQP 1.0 over TLS

openssl s_client で25672ポートを確認します。fail_if_no_peer_cert を有効にしているため、クライアント証明書なしの接続はハンドシェイクに失敗します。

$ openssl s_client -connect rmq-1:25672 -CAfile /etc/rabbitmq/certs/ca_certificate.pem </dev/null; echo "exit=$?"
...
00FEEDFEFF7F0000:error:0A000410:SSL routines:ssl3_read_bytes:ssl/tls alert handshake failure:ssl/record/rec_layer_s3.c:916:SSL alert number 40
...
exit=1

Note

出力の途中に Verify return code: 0 (ok) という行が表示されますが、これは「openssl が受け取ったサーバ証明書のCA検証は成功した」という意味であり、TLSハンドシェイク全体が成功したという意味ではありません。TLS 1.3 ではサーバが先に証明書を送り、後から CertificateRequest を送るため、クライアントが証明書を返せない場合でも openssl は Verify return code: 0 (ok) を表示します。SSL alert number 40 (handshake failure) と非ゼロの終了コード (exit=1) でハンドシェイク全体が失敗したことを判断してください。

証明書を指定すると、TLSハンドシェイクが成功します(distributionのクライアントロール用には、配置済みの client_certificate.pem / client_key.pem を使います)。

$ openssl s_client -connect rmq-1:25672 \
    -CAfile /etc/rabbitmq/certs/ca_certificate.pem \
    -cert /etc/rabbitmq/certs/client_certificate.pem \
    -key  /etc/rabbitmq/certs/client_key.pem </dev/null 2>/dev/null | grep -E 'subject=|Verify return code'; echo "exit=$?"
subject=O=server, CN=rmq-cluster
    Verify return code: 0 (ok)
exit=0

この時点ではAMQP (5671) はまだmTLSではないので、クライアント向けの動作確認は前述の「AMQPS (5671) の有効化」と同じく rabbitmq_cluster_check.py--port 5671 --ca ...)で行えます。

Note

epmd(4369ポート、Erlang Port Mapper Daemon)はノード名とdistributionポートのマッピングのみを扱い、TLS化されません。機密データは流れませんが、必要であればファイアウォールでアクセス元を制限してください。また、/var/lib/rabbitmq/.erlang.cookie によるErlang cookie認証はTLSとは別に引き続き必要です(記事前半で配置済み)。

mTLS(クライアント証明書の検証)の有効化

最後に、AMQPクライアントの証明書をサーバ側で検証するようにします(mTLS)。全ノードの /etc/rabbitmq/rabbitmq.confssl_options.verifyssl_options.fail_if_no_peer_cert を次のように変更します。

ssl_options.verify               = verify_peer
ssl_options.fail_if_no_peer_cert = true

次のコマンド

sudo sed -i \
  -e 's/^\(ssl_options\.verify[[:space:]]*=[[:space:]]*\)verify_none/\1verify_peer/' \
  -e 's/^\(ssl_options\.fail_if_no_peer_cert[[:space:]]*=[[:space:]]*\)false/\1true/' \
  /etc/rabbitmq/rabbitmq.conf

変更後の /etc/rabbitmq/rabbitmq.conf 全体は次のようになります(Management UIのHTTPS設定も含む)。

listeners.ssl.default = 5671

ssl_options.cacertfile           = /etc/rabbitmq/certs/ca_certificate.pem
ssl_options.certfile             = /etc/rabbitmq/certs/server_certificate.pem
ssl_options.keyfile              = /etc/rabbitmq/certs/server_key.pem
ssl_options.verify               = verify_peer
ssl_options.fail_if_no_peer_cert = true

management.ssl.port       = 15671
management.ssl.cacertfile = /etc/rabbitmq/certs/ca_certificate.pem
management.ssl.certfile   = /etc/rabbitmq/certs/server_certificate.pem
management.ssl.keyfile    = /etc/rabbitmq/certs/server_key.pem

Note

Management UI (HTTPS) もクライアント証明書を必須にする場合は management.ssl.verify = verify_peermanagement.ssl.fail_if_no_peer_cert = true を追加します。ただしブラウザにクライアント証明書をインポートする必要があるため、本記事ではAMQPのみmTLSを有効化します。

全ノードでサービスを再起動します。

sudo systemctl restart tanzu-rabbitmq-server

クライアント証明書なしの接続が拒否されることを確認します。openssl s_client ではハンドシェイクに失敗します。

$ openssl s_client -connect rmq-1:5671 -CAfile /etc/rabbitmq/certs/ca_certificate.pem </dev/null; echo "exit=$?"
...
40A7XXXXXX:error:0A000412:SSL routines:ssl3_read_bytes:ssl/tls alert certificate required:...:SSL alert number 116
exit=1

rabbitmq_cluster_check.py をクライアント証明書なし(--ca のみ)で実行しても接続に失敗します。

$ python rabbitmq_cluster_check.py --hosts rmq-1,rmq-2,rmq-3 --port 5671 --user admin --password 'VMware1!' --ca /etc/rabbitmq/certs/ca_certificate.pem --count 5 --cleanup
============================================================
 Target:     ['rmq-1', 'rmq-2', 'rmq-3'] port=5671 vhost=/
 User:       admin
 Queue:      test.quorum.check (quorum)
 Messages:   5
 TLS:        enabled (CA=/etc/rabbitmq/certs/ca_certificate.pem)
============================================================
[WARN] Failed to connect to rmq-1: StreamLostError: ("Stream connection lost: SSLError(1, '[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2651)')",)
[WARN] Failed to connect to rmq-2: StreamLostError: ("Stream connection lost: SSLError(1, '[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2651)')",)
[WARN] Failed to connect to rmq-3: StreamLostError: ("Stream connection lost: SSLError(1, '[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2651)')",)
[ERROR] All nodes unreachable. Last error: StreamLostError: ("Stream connection lost: SSLError(1, '[SSL: TLSV13_ALERT_CERTIFICATE_REQUIRED] tlsv13 alert certificate required (_ssl.c:2651)')",)

次に、クライアント証明書を指定すると接続できることを確認します。openssl s_client には -cert-key を渡します。

$ openssl s_client -connect rmq-1:5671 \
    -CAfile /etc/rabbitmq/certs/ca_certificate.pem \
    -cert /etc/rabbitmq/certs/client_certificate.pem \
    -key  /etc/rabbitmq/certs/client_key.pem </dev/null 2>/dev/null | grep 'Verify return code'; echo "exit=$?"
Verify return code: 0 (ok)
exit=0

「動作確認」セクションで作成した rabbitmq_cluster_check.py--cert--key でクライアント証明書を指定できます。これを使って実行します。

python rabbitmq_cluster_check.py \
  --hosts rmq-1,rmq-2,rmq-3 --port 5671 \
  --user admin --password 'VMware1!' \
  --ca   /etc/rabbitmq/certs/ca_certificate.pem \
  --cert /etc/rabbitmq/certs/client_certificate.pem \
  --key  /etc/rabbitmq/certs/client_key.pem \
  --count 5 --cleanup

[PASS] ... で終わればmTLSの構成は完了です。

============================================================
 Target:     ['rmq-1', 'rmq-2', 'rmq-3'] port=5671 vhost=/
 User:       admin
 Queue:      test.quorum.check (quorum)
 Messages:   5
 TLS:        enabled (CA=/etc/rabbitmq/certs/ca_certificate.pem)
============================================================
[OK]   Connected to rmq-1:5671
[INFO] Declaring quorum queue: test.quorum.check
[INFO] Publishing 5 messages (run_id=50d0b2e9)
  -> SENT [50d0b2e9] msg-001 ts=1779167063
  -> SENT [50d0b2e9] msg-002 ts=1779167064
  -> SENT [50d0b2e9] msg-003 ts=1779167064
  -> SENT [50d0b2e9] msg-004 ts=1779167064
  -> SENT [50d0b2e9] msg-005 ts=1779167064
[INFO] Consuming up to 5 messages
  <- RECV [50d0b2e9] msg-001 ts=1779167063
  <- RECV [50d0b2e9] msg-002 ts=1779167064
  <- RECV [50d0b2e9] msg-003 ts=1779167064
  <- RECV [50d0b2e9] msg-004 ts=1779167064
  <- RECV [50d0b2e9] msg-005 ts=1779167064
------------------------------------------------------------
 Sent:     5
 Received: 5
------------------------------------------------------------
[INFO] Deleting queue: test.quorum.check
[PASS] RabbitMQ cluster check succeeded.

クラスタが3ノードのまま動作していることを確認します。今回のmTLS化はAMQP (5671) に対する設定なので、ノード間通信(25672)には影響しません。

$ sudo rabbitmqctl cluster_status | grep -A4 "Running Nodes"
Running Nodes

rabbit@rmq-1
rabbit@rmq-2
rabbit@rmq-3

以上で、クライアント向け通信(AMQPS / Management UI HTTPS)とノード間通信(Erlang distribution)のTLS化が完了しました。