tail my trail

作るのも使うのも、結局は、人なのだ

Socket.io with Websocket の SSL/TLS 対応

※ (2016/9/19 追記) Nginx 使った対応方法も記載しているので、あわせて参考にして下さい。


昨今のサービスにおいて、暗号化はもはや必須の流れである。 GoogleFacebookなど主要なサービスはずいぶん前からHTTPS通信を標準としているし、HTTPS化対応しているサイトはSEO的にも優遇されるようになる という方針が出ていたりする。

前記事でSocket.IO + Redis PubSubを用いたリアルタイムメッセージ配信の仕組みをまとめたが、このままWebSocketを利用すると当然インターネット上を平文のテキストが流れてしまう。 また、チャット機能を呼び出す親元のWebページがHTTPSで提供されているものであれば、Mixed Content でブラウザによっては暗号化されていないWebSocket通信をブロックされることもあるだろう。 後からSSL/TLS対応を入れると往々にしてハマるので、ハナから対応しておくに越したことはない。

ということで、Socket.IOを用いたWebSocket通信をSSL/TLS対応させる。 以下、プログラム側での対応方法を記したが、NginxをWebSocket Proxyとして利用 できるので、NginxでSSL Terminationさせる手もある。

ポイント

f:id:uorat:20150830185442p:plain

  • ブラウザによってはWebSocket通信が利用できないことがあるため、Socket.IOでは、クライアントに適した通信プロトコルを自動選択する仕組みが実装されている。
  • セッションを張り続けて通信を行うWebSocketを使う以上、ボトルネックになりがちなロードバランサーを介することは推奨できない。
    • ロードバランサは最初の接続確立のみ利用し、WebSocket通信はクライアントからサーバに対して直刺しさせる

例えばAWSで運用している場合、SSL TerminationをELBに任せることが多いと思うが、WebSocket通信をELBを介さず行うため、サーバサイド (Socket.js on Node.js) で暗号化させる必要がある。

実装

サーバサイド

サーバに事前に配置したサーバ証明書、中間CA証明書、秘密鍵のセットをロードさせて起動することで、SSL対応が可能。 この対応で、Listenしたポートでhttps, wss通信が可能となる。

var fs = reqquire('fs');
var io = require('socket.io').listen(3000, {
    key : fs.readFileSync('/etc/pki/tls/private/your.domain.com.key').toString(),
    cert: fs.readFileSync('/etc/pki/tls/certs/your.domain.com.crt').toString(),
    ca: fs.readFileSync('/etc/pki/tls/certs/your.domain.com.cer').toString(),
    'log level':1
});

当然、証明書のドメインとWebSocket接続時にクライアントから指定するドメインは揃える必要がある。 ワイルドカードで証明書を作成していれば、例えば hostname.domain.com で接続させれば良い。

DNSにホスト名で名前解決できるようにA recordを登録しておく必要あり

クライアントサイド

以下のとおり、https で指定する。 "wss"ではない。 前に述べたとおり、handshake時にどのプロトコル (WebSocket / xhr-polling) で対話するかを採択するが、その通信は httpないしはhttpsで行われる。 handshakeが成功すると、wss または xhr-poolling over TLS で接続確立される。

<script src="https://hostname.domain.com:3000/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('https://hostname.domain.com:3000');

    ... your logic ...
</script>

ソース (※ 2016/5/15 追)

※ローカルのメモを転記しておく。

Socket.IOのdocumentaionにはSSL/TLS対応に関する記載が見当たらなかったが、ソース読んでみると明解。

Socket.IO

Socket.IO のコードを読んでみると、 `require('socket.io').listen した時に options.key があれば https#CreateServer 呼びだすロジックになっている。 ちょっと古いが、socket.io (v0.9.13) /lib/socket.io.js l63 - l66 抜粋

if (options && options.key)
  server = require('https').createServer(options);
else
  server = require('http').createServer();

Node.js - https.createServer

Official Documentationにあるとおり、 https.createServer の options は tls.createServer と同様で、以下のフィールドでSSL/TLS通信に必要なフィールドを指定できる。

https://nodejs.org/api/https.html#https_https_createserver_options_requestlistener https://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener

終わりに

以上、Socket.IOでのWebSocket通信のSSL対応の一例としてプログラム側での対応方法を記したが、NginxをWebSocket Proxyとして利用 できるので、NginxでSSL Terminationさせるという方法もある。 手っ取り早く試すなら上記でも良いが、既にNginxを投入済みのサイトであればNginx WebSocket Proxyを用いても良いかも。

※ (2016/9/19 追記) Nginx 使った対応方法も記載しているので、あわせて参考にして下さい。

実践Node.js プログラミング (Programmer's SELECTION)

実践Node.js プログラミング (Programmer's SELECTION)