fluent-plugin-secure-forwardと戯れた話

こちらのエントリーは「GREE Advent Calendar 2015 14日目」の記事です。

こんにちは、データエンジニアリングチームの山田です。6日目の記事の長谷川、田畠とともにグリーの分析基盤を良くする仕事をしています。主にデータ収集周りを担当しており、fluentdとはいつの間にか2年を超える付き合いとなりました。

さて今回は、fluent-plugin-secure-forwardについてつらつらと紹介していきたいと思います。

fluent-plugin-secure-forward(以下secure-forward)は、SSL/TLSを利用しセキュアにデータ転送を行うためのfluentd pluginです。fluentdにデフォルトで含まれているノード間を転送するためのforward plugin(以下forward)は転送時に暗号化などを行っていないため、インターネットを通しての転送には傍受の危険性などがあります。しかし、secure-forwardを使うことでセキュアに転送することが可能です。secure-forwardはいくつかの認証/制限機能を提供しており、今回のエントリでは特にこのあたりの機能と、検証を進めた際にハマったことを中心に紹介していきます。このエントリがこれからsecure-forward、はたまたfluentdを導入しようかな?と考えている方々の手助けになれば幸いです。

それでは、さっそくsecure-forwardの導入から設定ファイルを交えながら認証/制限機能を紹介していきます。

fluent-plugin-secure-forwardのインストール

fluentd自体のインストールは環境によって少し異なりますので、公式のドキュメントを参考にしてインストールしてください。

fluentdのインストールが完了したら、secure-forwardをインストールします。

sudoに関しては適宜追加してください。インストール方法はほかのfluent-pluginと同じです。簡単ですね。: )

設定例と認証方法いろいろ

いきなりですが、まずはイメージしやすいように設定例をお見せします。その後各設定について解説していきます。

設定例

以下では、secure-forwardでデータを受け取る側のノードをアグリゲータ、データを転送する側のノードをクライアントと呼ぶことにします。この設定例を図で表すと以下のようになります。
2482130-593cb851c31fa1fc739c792b14889b5b85f664b2

少しごちゃごちゃとしてしまいましたが、行っている設定と設定例の内容をざっくりまとめると以下のとおりです。

アグリゲータ(受け取る側)

  • 証明書のパスとパスフレーズの設定
  • shared_keyを設定
    • 接続してくるクライアントが提示する共通キーが XXXX な場合のみ許可する
  • <user>によって許可するユーザを指定
    • 接続してくるクライアントが提示するユーザがyukitakuyaでありパスワードが一致してる場合のみ許可する
  • <client> によってIP制限や、クライアント毎に転送を許可するユーザを指定可能
    • xxx.xxx.xxx.0.10xxx.xxx.1.0/24なクライアントからの接続要求だけを許可する

 クライアント(転送する側)

  • 証明書のパスを設定
  • shared_keyを設定
    • アグリゲータへの接続時に提示する共通キーは XXXX
  • <server> によって転送先、ユーザ情報を指定
    • 転送先はAGGREGATOR1_IP_ADDRESS, AGGREGATOR2_IP_ADDRESS
    • 接続時に提示するユーザは yuki

これだけの設定で共通キー、ユーザによる認証と、IP制限が可能です。

なお、secure-forwardの転送側であるout_secure-forwardは、fluentdにデフォルトで含まれているout_forwardと同じObjectBufferedOutputですので、バッファ周りや転送するまでの間隔の設定は同じように行うことができるので環境に合わせて設定してください。

認証方法いろいろ

それでは各設定についてもう少し詳しく紹介していきます。

SSL/TLS通信を行うために

まずはSSL/TLS通信を行うために必要な証明書周りの設定について紹介します。secure-forwardでは信頼できるCAを指定することも可能ですが、今回はプライベートな証明書を作成して利用する方法を紹介します。

プライベートなCA cert/keyの作成

これでKEY_PATH以下にca_cert.pem, ca_key.pemが作成されているはずです。

証明書周りの設定

先ほど作成したファイルのうち必要なものを各サーバに配り、ファイルパスとパスフレーズを指定します。

この設定は必須項目です。
なお、この証明書は5年間で期限が切れます。

shared_key

クライアント、アグリゲータで同じ文字列を指定します。これが一致していない場合転送することができません。ユーザ認証などのほかの認証方法はオプションなのですが、このshared_keyは必須項目なので注意が必要です。また、<client>, <server>ディレクティブにて転送元(先)毎にshared_keyを個別に設定することも可能で、この指定方法をとるとディレクティブ内のshared_keyによる認証が優先されます。

shared_keyが違っていた場合には、アグリゲータ側に以下の様なメッセージが流れます。

ユーザ認証

ユーザ認証を有効にするためには、アグリゲータ側で authentication true にする必要があります(デフォルトではfalse)。ユーザの情報はアグリゲータ側では<user>で、クライアント側では<server>内で指定します。また、クライアント毎に許可するユーザを絞ることも可能です。

なお、アグリゲータ側で<user><client>内にユーザ情報を書いても、 authentication false の場合は無視されます。

また、ユーザ認証が失敗した場合にはアグリゲータにて以下の様なメッセージが流れます。

IP制限

IPによる制限をおこなうためには、アグリゲータ側で allow_anonymous_source false にする必要があります(デフォルトではtrue)。ホワイトリスト型の制限なので、許可するサーバ情報をアグリゲータ側に<client>で指定します。

これらのサーバ以外からのアクセスが有った場合、

といったメッセージがアグリゲータに流れます。かなり簡単にIPアドレスによる制限ができます。

なお、アグリゲータ側でホワイトリストを設定しても、 allow_anonymous_source true の場合は無視されます。ただし、IP制限は無効かつ、ユーザ認証が有効な場合には<client>内の設定が一部効力を発揮するようなので注意が必要です。例えば以下の様な設定の場合、<client>は無効化されておりIPアドレス xxx.xxx.xxx.xxx のサーバ以外からのデータ転送を許可している状態にあります。しかし、IPアドレス xxx.xxx.xxx.xxx なサーバからの転送に限り、<client>内の users shared_key を使用してユーザ認証を行おうとしてしまうため注意が必要です。

以上で各認証方法の紹介は終わりです。

これらの認証に失敗した場合、fluentd自身がエラーメッセージをfluent.warnタグをつけて流してくれるので、それをメールやチャットなどに投げるといろいろと捗るかもしれません。

ハマった点

さてここからは、検証を進めた際にハマったことをいくつか紹介していきます。

td-agentのバージョンによって利用できるSSL/TLSのバージョンが異なる

secure-forwardは ssl_version オプションにて利用するSSL/TLSのバージョンを指定することができます。このオプションのデフォルトは TLSv1_2 なのですが、ruby1.9系を同梱しているtd-agent version1系ではこのデフォルトのバージョンを利用することはできません。

ssl_versionオプションでtd-agent version1が利用可能なSSL/TLSのバージョンを指定すればよいのですが、新しい機能もどんどん追加されていますしこの際、td-agentをversion2にあげてしまうのはどうでしょうか?

output pluginがスレッドセーフかどうか

通常のforwardは、インプットを担うスレッドは1つだけで、そのスレッドでバッファへの書き込みまでを行います。一方でsecure-forwardは、クライアントからのコネクション毎にスレッドが作成され、そのコネクションがスレッドとともに維持されます。つまりアグリゲータ側には、各クライアントに対して専用のインプットスレッドが立っている状態となります。これらのインプットスレッドは受け取ったデータをバッファへ書き込みます。バッファへ書き込む部分は排他制御されているのですが、受け取ったログを加工してバッファへ書き込むまでの部分に関しては各プラグインに委ねている部分が多く、弊社で独自に手を加えたプラグインがまさにこの部分で問題を起こしてしまいました。アウトプットプラグイン内でインスタンス変数をキャッシュのように利用していたため、複数のインプットスレッドによってレースコンディションが起きてしまいました。

今のところほかの公開されているプラグインでこうした問題が発生したプラグインを見たことはありませんが、独自に作成、拡張したプラグインを利用している場合には、念のためアグリゲータのアウトプットプラグインのemitメソッドからformatメソッドあたりまでがスレッドセーフかどうか確認してみてください。こうした振る舞いのテストを行う際に、fluentdの結合テスト的なことが気軽にできるツールが欲しいですね。

また、ハマったことではないのですがsecure-forwardが動いているアグリゲータに接続しているクライアント数には気をつけたほうが良いかもしれません。先述したように、secure-forwardのアグリゲータには接続してきているクライアント数だけスレッドが立つので、クライアント数がそのまま負荷に繋がる恐れがあります。そのため弊社では現在以下の図のように、webサーバなどから直接secure-forwardを使うのではなく、forwardで一度ログを集約してからsecure-forwardで転送するようにして、secure-forwardのアグリゲータに接続しているクライアント数をなるべく少なくなるようにしています。

2482141-7333603129813a4877513150c12a1990e9e30215

こうした対策は、以前通常のforwardを利用していた際にアウトプット側のスレッドを多くしすぎたために「can't create thread」なるwarnが出てfluentdが急に落ちたりとおかしな状態に陥ったことがあったため行っており、クライアント数の増加とともに今後も経過観察を続けていく予定です。

まとめ

fluent-plugin-secure-forwardの導入から各認証方法の設定を交えて紹介しました。どの設定も難しくないですし、ホワイトリスト型式でIP制限を行えるのは嬉しい機能です。

また、セキュリティが関わる部分なので、pluginのアップデートには追従したいところです。今年もfluentdにはたくさんの機能追加が行われていますし、td-agent/fluentdも積極的にバージョンアップしていきたいですね。 : )

明日は菊池さんによる記事です。菊池さんは、先月18日にオライリー社から書籍『OpenStack Swift』をリリースしています。ぜひショッピングカートに入れてあげてくださいね。お楽しみに!