---
title: VMware Tanzu RabbitMQ 4.3のRPMパッケージをRocky Linux 9にインストールするメモ
summary: この記事では、Rocky Linux 9 に VMware Tanzu RabbitMQ 4.3をインストールし、3 ノードのクラスタを構成する方法について説明します。
tags: ["RabbitMQ", "Tanzu", "Rocky"]
categories: ["Middleware", "Messaging", "RabbitMQ"]
date: 2026-04-26T10:47:16.666Z
updated: 2026-05-19T05:07:08.587Z
---

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

以下の手順では、[VMware Tanzu RabbitMQ](https://techdocs.broadcom.com/us/en/vmware-tanzu/data-solutions/tanzu-rabbitmq-rpm/4-3/tanzu-rabbitmq-rpm-offering/site-overview.html)のRPMパッケージをRocky Linux 9にインストールし、3台のクラスタを構成します。

ホスト名は`rmq-1`、`rmq-2`、`rmq-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 |

**目次**
<!-- toc -->

### VMware Tanzu RabbitMQのダウンロード


[Broadcom Support](https://support.broadcom.com)にログインして、[VMware Tanzu RabbitMQのダウンロードページ](https://support.broadcom.com/group/ecx/productdownloads?subfamily=VMware%20Tanzu%20RabbitMQ)にアクセスします。

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

![image](https://s3.ik.am/ikam/_/1778636893171_pasted-image.png)



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

![image](https://s3.ik.am/ikam/_/1777200804879_64875884-dcfb-4513-a64e-843e4b9026d4.png)


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

![image](https://s3.ik.am/ikam/_/1778636962289_pasted-image.png)


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

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


### Tanzu RabbitMQのインストール


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

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

```bash
sudo dnf install -y wget lsof less vim
```

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

```bash
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と依存パッケージをインストールします。

```bash
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
```


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

```bash
$ 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のサービスを有効化し、サービスを起動します。

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

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

```bash
$ 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.
```


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

```bash
$ 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が通ることを確認します。

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


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


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

```bash
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-2` と `rmq-3` 上で次のコマンドを実行し、 `rmq-1` のクラスタに参加します。

```bash
sudo rabbitmqctl join_cluster rabbit@rmq-1
```



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


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

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

```bash
$ 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` に変更します。

```bash
sudo rabbitmqctl set_cluster_name rmq-cluster
```


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

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


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

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

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

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

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

```bash
sudo rabbitmqctl delete_user guest
```


### Management Pluginの有効化


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

```bash
sudo rabbitmq-plugins enable rabbitmq_management
```

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

```bash
$ 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](https://s3.ik.am/ikam/_/1778640395737_pasted-image.png)

### 動作確認


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

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


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


```python
#!/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)
```


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

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


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

```bash
============================================================
 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]
> `rabbitmqctl` や `rabbitmq-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回の発行作業でカバーします。

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

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

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

```bash
# 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枚の証明書を全ノードで共用できるようにします。

```bash
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付き、全ノード共用）を生成します。

```bash
# 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` が必要なため、サーバ証明書とは別の証明書として発行します。

```bash
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
```


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

```bash
# 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を確認します。

```bash
$ 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` であれば成功です。

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


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

```bash
$ 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` ユーザーから読めません。**必ず以下の `chown` と `chmod` まで通して実行してください**。証明書ファイルが `rabbitmq` ユーザーで読めないと、RabbitMQ サーバ自体は起動するものの、CLI ツール (`rabbitmqctl`) や他ノードからの inter-node TLS dist が証明書をロードできずに失敗し、サーバログに TLS エラーすら残らない状態でハングします（後段の Khepri が `timeout_waiting_for_leader` を出すなど、原因が分かりにくい症状になります）。後から証明書を追加コピーした場合も `chown` / `chmod` を再実行してください。

`rmq-1` での作業:

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

```bash
# 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`）で実行しておきます。

```bash
sudo usermod -aG rabbitmq $(whoami)
```


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

```bash
$ 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` を作成します。

```bash
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` を追加してください。本記事では後の動作確認で平文も使うため残しています。


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

```bash
sudo systemctl restart tanzu-rabbitmq-server
```

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

```bash
$ 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検証に成功しています。

```bash
$ 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を指定）。

```bash
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です。

```bash
============================================================
 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` に以下を追記します。

```bash
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が追加されたことを確認します。

```bash
$ 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](https://s3.ik.am/ikam/_/1778649034797_pasted-image.png)

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

![image](https://s3.ik.am/ikam/_/1778649087707_pasted-image.png)


なお、`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オプションを記述します。

```bash
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のパスは環境によってバージョンが異なるので、まず確認します。

```bash
$ 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_ARGS` は `rabbitmqctl` などのCLIツールに渡る引数です。

```bash
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ノードを並行起動** します。

```bash
# 全ノードで停止
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.conf` で `RABBITMQ_CTL_ERL_ARGS` を設定済みなので、`rabbitmqctl` はそのままTLS経由で通信します。このコマンドが成功すること自体が、CLIツール↔ノード間とノード↔ノード間のTLSハンドシェイクが成立している証拠です。

```bash
$ 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` になります。

```bash
$ 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` を有効にしているため、クライアント証明書なしの接続はハンドシェイクに失敗します。

```bash
$ 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` を使います）。

```bash
$ 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.conf` の `ssl_options.verify` と `ssl_options.fail_if_no_peer_cert` を次のように変更します。

```ini
ssl_options.verify               = verify_peer
ssl_options.fail_if_no_peer_cert = true
```
次のコマンド

```bash
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設定も含む）。

```ini
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_peer` と `management.ssl.fail_if_no_peer_cert = true` を追加します。ただしブラウザにクライアント証明書をインポートする必要があるため、本記事ではAMQPのみmTLSを有効化します。


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

```bash
sudo systemctl restart tanzu-rabbitmq-server
```

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

```bash
$ 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` のみ）で実行しても接続に失敗します。

```bash
$ 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` を渡します。

```bash
$ 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` でクライアント証明書を指定できます。これを使って実行します。

```bash
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の構成は完了です。

```bash
============================================================
 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）には影響しません。

```bash
$ 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化が完了しました。
