採用情報 お問い合わせ

BLOG

Zabbix Agent 2 で MQTT(TLS)監視設定方法とハマりどころ

本記事では、AlmaLinux 8 環境をベースに、Mosquitto(Broker)と Zabbix Agent 2 を用いた MQTT(TLS)監視の具体的な構築手順を解説します。
Zabbix Agent 2 からは標準で MQTT 監視がサポートされました。そのため、複雑な作り込みをすることなく、設定画面からの簡単なパラメーター入力だけで監視を開始できるという運用の手軽さも大きなメリットです。一方、いざ構築しようとすると、思わぬ落とし穴にハマることもあります。

※ 本記事で使用する用語についてはこちらのページを参照してください。

用語・表記 解説
Zabbix とは サーバ、ネットワーク、アプリケーションを集中監視するためのオープンソースの統合監視ソフトウェアです。
統合監視に必要な監視、障害検知、通知機能を備えています。多数のプラットフォームに対応した Zabbix エージェントと SNMP に対応しているため、システム全体を Zabbix ひとつで監視することが可能です。
MQTT とは IoT 機器の通信に特化した軽量なメッセージプロトコルです。低帯域や不安定なネットワーク環境でも確実に動作するため、センサーデータの収集などに広く使われています。

監視データの流れと構成

  1. ドローン等送信元(今回は mosquitto_pub コマンド)が Mosquitto へデータを送ります。
  2. Zabbix Agent 2 は Mosquitto に接続して待機します。Mosquitto にデータが届いたら Zabbix Agent 2 に push 配信されます。
  3. Zabbix Agent 2 から Zabbix Server に Active Check でデータが送信されます。

前提事項

送信元、Mosquitto、Zabbix Agent 2、Zabbix Server すべてを同じ OS 上に配置して、検証ができる環境を構築します。
実際の IoT 機器を準備することは難しいため、データ送信は mosquitto_pub コマンドを使用します。
予め OS、Zabbix Server、Zabbix Agent 2 がインストールされていて、Zabbix Agent 2 の監視ができている状態を想定します。
Mosquitto の TLS(暗号化通信)ではサーバ証明書のみを検証する片方向の認証方式を設定します。

環境

  • OS : AlmaLinux 8.10
  • Zabbix Server : 7.0.22
  • Zabbix Agent 2 : 7.0.22
  • Mosquitto : 1.6.15-1

構築手順

1. パッケージのインストール

1) EPEL リポジトリのインストール
$ sudo dnf install epel-release -y
2) Mosquitto(Broker)とクライアントツール(pub/sub 用)のインストール
$ sudo dnf install mosquitto mosquitto-clients -y

2. 証明書の作成(SANs 対応版)

Zabbix Agent 2(Go 言語)の仕様に対応するため、IP アドレスや localhost を明記した「SANs 付き証明書」を作成します。

2.1. 作業ディレクトリの準備

Bash

$ sudo mkdir -p /etc/mosquitto/certs
$ cd /etc/mosquitto/certs
2.2. SANs 設定ファイルの作成

Bash

$ sudo bash -c 'cat > san.ext <<EOF
subjectAltName = DNS:localhost,IP:127.0.0.1
EOF'
2.3. CA・サーバ証明書・クライアント証明書の作成
2.3.1. 自作 CA(認証局)の作成
$ sudo openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key -out ca.crt -nodes -subj "/CN=MyRootCA"
2.3.2. サーバ証明書の作成(SANs 付き)

Mosquitto サーバ自身の鍵と発行申込書(CSR)を作り、自作 CA の署名と SANs 設定を使ってサーバ証明書を完成させます。

$ sudo openssl genrsa -out server.key 2048
$ sudo openssl req -new -key server.key -out server.csr -subj "/CN=localhost"
$ sudo openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 3650 -extfile san.ext
2.3.3. Zabbix 用クライアント証明書の作成

本来は不要ですが、後述する問題を回避するために証明書を作成します。

$ sudo openssl genrsa -out agent.key 2048
$ sudo openssl req -new -key agent.key -out agent.csr -subj "/CN=agent"
$ sudo openssl x509 -req -in agent.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out agent.crt -days 3650

3. 権限の設定

作成した鍵や証明書を、Mosquitto と Zabbix の両方のシステムがエラーなく読み込めるようにファイルの権限(パーミッション)を調整します。

$ sudo chown mosquitto:mosquitto /etc/mosquitto/certs/*
$ sudo chmod 644 /etc/mosquitto/certs/*.crt
$ sudo chmod 644 /etc/mosquitto/certs/*.key

4. Mosquitto (Broker)の設定

4.1. TLS 設定ファイルの作成

/etc/mosquitto/conf.d/tls.conf を作成します。
今回は TLS(暗号化通信)でサーバ証明書のみを使用する設定にします。
Mosquitto に接続する URL は、「tls://localhost:8883」となります。

# ポート 8883 で TLS 待ち受け
listener 8883

# 証明書の指定
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key

# サーバ証明書だけを使う設定
require_certificate false
allow_anonymous true
4.2. 設定反映

Bash

$ sudo systemctl restart mosquitto
$ sudo systemctl enable mosquitto

5. Zabbix Agent 2 の設定

5.1. 設定ファイルの編集

/etc/zabbix/zabbix_agent2.d/plugins.d/mqtt.conf を以下の内容にします。
セッション名は Zabbix の監視で接続を区別するための名前です。アイテム設定と合わせる必要があります。ここでは「DroneTLS」とします。

# セッション名 : DroneTLS
Plugins.MQTT.Sessions.DroneTLS.Url=tls://localhost:8883

# CA 証明書の指定
Plugins.MQTT.Sessions.DroneTLS.TLSCAFile=/etc/mosquitto/certs/ca.crt

# バグ回避のためクライアント証明書を指定する
Plugins.MQTT.Sessions.DroneTLS.TLSCertFile=/etc/mosquitto/certs/agent.crt
Plugins.MQTT.Sessions.DroneTLS.TLSKeyFile=/etc/mosquitto/certs/agent.key
5.2. 設定反映
$ sudo systemctl stop zabbix-agent2
$ sudo systemctl start zabbix-agent2

6. Zabbix Server (Web 画面)の設定

アイテムの作成

監視対象のホストに以下内容のアイテムを作成します。
セッション名は Zabbix Agent 2 の設定(5.1)と合わせる必要があります。
トピック名は MQTT の Topic を指定します。今回は「test/tls」とします。

  • 名前 : dronecheck(任意)
  • タイプ : Zabbix エージェント (アクティブ)
  • キー : mqtt.get[DroneTLS,test/tls]
    • 第 1 引数 : セッション名 (DroneTLS)
    • 第 2 引数 : トピック名 (test/tls)
  • データ型 : テキスト

7. 動作確認

7.1. データの送信 (Publish)

Mosquitto クライアントから、TLS 経由でデータを送信します。 -r (Retain) オプションを付け、Zabbix Agent 2 にデータが渡せるまで保持させます。

# mosquitto_pub 
  --cafile /etc/mosquitto/certs/ca.crt 
  -h localhost -p 8883 
  -t "test/tls" -m "Construction_Complete" -r

--cafile : 作成した自作 CA(ルート証明書)を指定して暗号化通信を行います。
-h / -p : 接続先のホスト(localhost)と、TLS 用のポート番号(8883)を指定します。
-t : 宛先となるトピック名です。Zabbix のアイテム設定(第 2 引数)で指定した名前(test/tls)と完全に一致させる必要があります。
-m : 送信するメッセージ(データ本体)です。今回はテストとして Construction_Complete という文字列を送ります。
-r : Zabbix Agent 2 がデータを受け取る準備ができるまで、Mosquitto 側に最新のデータを保持させておきます。

7.2. データ受信確認

Zabbix Web 画面の[ 監視データ ] → [ 最新データ ]で、アイテム dronecheck の最新の値に Construction_Complete が表示されれば構築完了です。

今回のハマリどころ

Mosquitto の監視において、TLS の片方向認証(サーバ証明書のみ検証)を使用した場合に、エラーが発生して監視に失敗する事象を確認しました。調査の結果、意図した挙動ではなくバグである可能性が高いと考えています。

TLS の片方向認証(サーバ証明書のみ検証)を使用する場合、サーバ証明書の検証のみ行われるため、クライアント証明書の設定は不要なはずです。
したがって、Zabbix Agent 2 の MQTT プラグインの設定(/etc/zabbix/zabbix_agent2.d/plugins.d/mqtt.conf)は以下となります。

# セッション名 : DroneTLS
Plugins.MQTT.Sessions.DroneTLS.Url=tls://localhost:8883

# CA 証明書の指定
Plugins.MQTT.Sessions.DroneTLS.TLSCAFile=/etc/mosquitto/certs/ca.crt

しかし、この設定で監視を行うと「Cannot create tls config: failed to load key pair: open: no such file or directory.」というログと共にステータスが取得不可(Not supported)となります。

更に、事象が発生するタイミングを狙って strace で Zabbix Agent 2 の PID を追ってみた結果、空文字 "" を open しようとしてエラーになっていることが分かりました。

[pid 14943] openat(..., "/etc/mosquitto/certs/ca.crt", ...) = 9
[pid 14943] openat(..., "", ...) = -1 ENOENT
  1. 1 行目 : /etc/mosquitto/certs/ca.crt を開くことに 成功しています(= 9)。
  2. 2 行目 : その直後に、空文字 "" を開こうとして失敗しています(= -1)。

ソースコードと突き合わせて調べたところ疑わしい箇所がありました。

src/go/vendor/golang.zabbix.com/sdk/tlsconfig/tlsconfig.go

82 func CreateConfig(details Details, skipVerify bool) (*tls.Config, error) { 83 rootCertPool := x509.NewCertPool() 84 85 pem, err := os.ReadFile(details.TLSCaFile) 86 if err != nil { 87 return nil, errs.Wrap(err, "failed to read TLS CA file") 88 } 89 90 ok := rootCertPool.AppendCertsFromPEM(pem) 91 if !ok { 92 return nil, errs.New("failed to append PEM") 93 } 94 95 clientCerts := make([]tls.Certificate, 0, 1) 96 97 certs, err := tls.LoadX509KeyPair(details.TLSCertFile, details.TLSKeyFile) 98 if err != nil { 99 return nil, errs.Wrap(err, "failed to load key pair") 100 } ...

まず、85 行目は strace の結果の 1 行目に該当する部分だと考えられます。
TLSCaFile(/etc/mosquitto/certs/ca.crt)を読み込む処理です。ここでは正常にファイルが開かれています。
次に 97 行目はクライアント証明書を読み込む処理です。
設定ファイルの TLSCertFile を空欄にしている場合、空文字のファイルを読み込もうとして
OS から -1 ENOENT(No such file or directory)というエラーが返ります。これが、strace 結果の 2 行目に該当する部分だと考えられます。
最終的に Zabbix としては、「failed to load key pair: open: no such file for directory.」というエラーが出力されます。
問題は、97 行目の処理が設定の有無に関わらず無条件に実行される実装となっている点です。
コードをみる限り、現在の Zabbix Agent 2(MQTT プラグイン)は、サーバ証明書のみを検証する、片方向認証(クライアント証明書なし)を想定した実装になっていないと考えられます。
これを回避するため、本来の構成では不要であるクライアント証明書をわざわざダミーとして作成し、設定ファイルに指定するという対策をとっています。
この事象は Zabbix に報告を行っています。https://support.zabbix.com/browse/ZBX-27403

※Zabbix は、ラトビア共和国およびその他の国における Zabbix SIA の登録商標です。

本記事に関連するリンク