接続の暗号化

普通にMQTTを応用しようとすると、避けては通れない暗号化です。あまり素人が手出しするべき所ではないかもしれませんが、勉強のため考えて見ました。

mosquitto.confを眺めてみると、mosquittoでは2つのやり方が準備されているようです。

  • 公開鍵認証・暗号化

    公開鍵をお互いにやり取りして、お互いを認証・暗号化する普通のやり方ですね。

  • 証明書ベース認証・暗号化

    HTTPで一般的に使われている証明書による認証ですね。https で始まるwebサイトでは暗号化されるように、この仕組みを使えばMQTTでも通信が暗号化されます。
    サーバーがオープンで誰でもが接続できるような状況にするためには、必須です。

mosquittoではユーザ(サブスクライバ、パブリッシャ)認証のためにユーザ名、パスワード方式が使えますが、認証時に平文でこれらがやり取りされるという事ですので、通信経路そのものの暗号化が欠かせません。

MQTTブローカに頼らない暗号化という意味では ssh しかないでしょうか。
ブローカがオープンでない場合であれば、ポートフォワードと合わせ技で安全性が高められるかもしれません。
ただ、この場合、ネットワーク環境によってはsshのトンネリングができない(ポリシー的に)という場合もあるかと思うので、環境の調査が必要かと思います。

最近はwebsocketという接続方法もあるので、MQTTブローカをweb applicationでラップしてセキュアにするというやり方もあるかもしれません。

今回はsshのポートフォワードで暗号化してみましたので報告です。

まず、何はなくとも検索。ssh mosquittoあたりでgoogleさんに聞いてみたところ、このサイトがヒット。正しくやろうとしていたことです。

このページの最終目標としては、簡易的なVPNとしてのsshによるトンネリングを使ってMQTTプロトコルをセキュアにしようという事のようです。

手順としては、

  1. トンネリングの設定

  2. トンネルを使った通信の設定

  3. パスフレーズなしでの接続方法とセキュリティ設定

順を追ってやってみます。

トンネリングの設定

まずクライアント側の設定です。

1
ssh -f -L 22883:127.0.0.1:1883 SSHusername@MQTT -N

とすることで、クライアント側のポート22883 に対する通信が暗号化された上でサーバ”MQTT”の1883に接続されます。ユーザ名は”SSHusername”です。
各オプションの意味は以下のとおりです。

-f : バックグラウンドで動作

-L : ローカル側でのポートフォワード(クライアント側のポート変換が設定されます)

-N : サーバ側でコマンドを実行しないように指定

この例ではポートは標準の22となっています。必要に応じて-pオプションでssh通信そのもののポート番号を指定してください。 sshのポート番号も専用のものにしておくことで、更にセキュリティを確保できるかもしれません。
ここらへんはsshのポートフォワード設定を参照してもらったほうが正しい理解が得られるかと。

この接続をするためには、クライアント、サーバ共にsshが動く環境であることが必要です。適宜設定してください。

このコマンドを実行するとパスワードを聞かれるはずです。実際の無人運用ではこれが問題になりますが、これは後ほど解決してもらえます。

トンネルを使った通信の設定

上記の操作でセキュアな通信経路が22883ポートに設定されましたので、これを使って実際にMQTTプロトコルのデータを流してみます。

1
mosquitto_sub -h 127.0.0.1 -p 22883 -u dan -P password -t 'test/#'

-P, -uでユーザ名とパスワードを指定してブローカにサブスクライバとしてログインしています。ホストはローカルホスト、ポートは先程トンネルを設定した22883に指定します。
-P, -uは必須ではないです。私はこれらナシでつかってます。

トピックを適切に設定してあれば、なにかメッセージを受け取れるはずです。

パスフレーズなしでの接続方法とセキュリティ設定

参考にしたページでは、”Key-Based authentification”と説明されていますが、公開キーを事前にやり取りしている間柄なら、十分信用できるだろうという事で、鍵を持っている相手はパスフレーズなしでログインさせましょう、という設定です。
この設定では、ssh接続に際しパスフレーズなしで接続できるようになってしまうので、鍵を他の用途で使うとセキュリティに問題が出る可能性があります。そのため、「このトンネリングだけに使う専用の鍵を用意するように」と注意書きがあります。

パスフレーズ無しでキーを作り、それをサーバに登録します。

1
2
ssh-keygen
ssh-copy-id SSHusername@MQTT

サーバ側のセキュリティ確保のため、クライアントのキー記述の最初の部分に以下のような設定を追記します。

1
from="192.168.1.*",no-pty,permitopen="127.0.0.1:1883",command="/bin/false" ssh-rsa........

この例では

  • 接続先のIP制限 from=
  • ターミナルのアサインをしない no-pty
  • ローカルホストの指定ポートのみオープン可能にする permitopen=
  • ログイン時に実行するコマンドを /bin/falseに設定

ということを指定しています。
具体的には、192.168.1.*のネットワークからの接続に限定、ターミナルをアサインしない、ローカルホストの1883への接続のみを許可する、リモートから接続するときに指定されたコマンドを実行しない。
という事になりそうです。
くわしくは、ここらへんを参照してください。

この設定を書くときの注意ですが、設定同士の区切りはコンマですが、設定と鍵本体の区切りは、コンマでなくスペースです。ここを間違えて、2時間ほどハマりました。
ここをコンマにすると鍵が正しく認識できないのでパスワードを要求されます。

公開鍵をやり取りしていて有効であれば、パスフレーズを聞かれるか何もなしでログインできるかのどちらかです。

私の実験では、IPアドレスの制限をしない設定、で実行してうまく動いています。

本来的には、証明書ベースの認証システムを組むのがいいのでしょうけれど、証明書の発行など、よくわかっていないと事故に繋がりそうなので、今回は初学者向けということでトンネリングを実験して見ました。
小規模なシステムでネットワーク環境もある程度わかっているところにデバイスを配置するには十分な感じかと思います。

また、MQTTプロトコル上からデバイスがつながっているシステムの操作をするときに、重要なパラメタの制御は1箇所でできないようにするとか、あるデバイスの許可とペアで設定して初めてアクチュエートするなど、システム上でのセキュリティ構築も必要かもしれません。

2015/1/14 追記

authorized_keysに記入する設定で、permitopenというパラメタがありますが、これを設定すると”コマンドからのsshでは接続出来なくて、configを用いた接続は可能”という変な状態になりました。

具体的には、

ssh -f -L 22883:localhost:1883 user@broker -i rsa_key

と指定すると、一応sshは動いてトンネリングはできますが、接続しようとするとエラーになります。

一方全く同じパラメタをconfigファイルに書きこんで、configに設定したhost名でsshを起動すると普通に接続できます。

謎です。