fluentdを触ってみたのでメモ
ログ収集・配送ツールfluentdをしばらくぶりに触ってみた感じをメモ。
組み込みのバッファが便利
fluentdコア側で各プラグイン毎にスレッドを起動し、バッファ管理とスレッド間のログの受け渡し処理を担うことで、プラグインは逐次的に書くことが出来る。
スレッドより細かい並列化・非同期化はプラグインが各自で行う。主なinputプラグインはcool.ioを使ったサーバーとして動作し、outputプラグインは単にスレッド毎にコネクションを持つか独自のコネクションプールを使っているようだ。
後段のoutputプラグインで部分的な失敗やACK相当の部分が失われたりしてもロールバックのような機構はない(かプラグインが自前で実装する)。トランザクションIDのようなものも無く、リトライによる重複が困るならログにUUIDを含めるなどユーザー側で対応する。
通常のBufferedOutputプラグインならバッファ管理は全てfluentd側がよきに計らってくれるので、基本プラグインは逐次処理するだけで十分実用になる。単にIOでブロックしているならスレッドを増やして対応できる。
ただ(UnBufferedな)Outputプラグインの場合inputプラグインから直接呼ばれているようで、inputプラグインのスレッドがブロックしてしまうとのこと。 out_stdoutのようなデバッグ向けか、filterプラグイン導入以前にバッファのオーバーヘッドなしに多段output出力をするためだったみたい。今は軽い処理はfilterプラグインに書けばいいのでほぼ不要。
設定はDSLで
標準の設定ファイルのパーサは独自書式。以前クオート中の"#"がコメント開始と認識されててハマったりした。 tagを変えないfilterが追加されたので記述順序も意味をもつようになった。
その後rubyのDSLで書けるようになっていた http://qiita.com/toyama0919/items/10616cb9025cb9cecd69 ので結構楽に。 これで正規表現のエスケープに悩む必要も無くなった。
外部ライブラリを使いにくい
内部エンコードEncoding.default_internal
をなぜかASCII-8BITに強制していて、文字列としてASCII互換エンコードを期待するライブラリが壊れてしまう。
いちいちfluentd用に書き直す必要があるので、複雑な処理をしようとすると苦行になりそう。
サーバなのでdefault_externalはいいとして、なんでinternalを設定しているのかは導入したコミットをみてもよく分からない。rubyの経験不足。
セキュアな転送が面倒
APIは平文。セキュアなログ転送手法は(ベンダのAPIへの送信オンリーを除いて)提供されていない。
ほかにもインストール手段がセキュアで無かったり、ちょっとセキュリティ意識が弱く見える。 そもそも外部に持ち出せるログなのだからセキュアである必要はあまり無いと考えているのかも?
素直にローカルバインドしたin_forwardにSSHなりstunnelなりでトンネルするのがよさそう。 out_forwardの再接続時にトンネルも再起動できるようにしたい。TODO
secure_forwardは使うな。あれは罠だ。
追記: 5/22に確認した所、インストールスクリプトと鍵はHTTPS化されている。以前は全部httpだったはず
雑感
面倒なログ管理が簡単に書け、要るのがrubyだけというのがいい。(というかlogstashはなんでjavaまで要求するんだ……)
私企業が開発主導なので仕方ないけど、ロックインさせたい感じがちらほら見えてちょっと不安。
http://qiita.com/tatsu-yam/items/bd7006e483f3b3c64309 https://gist.github.com/sonots/c54882f73e3e747f4b20
BINDのRRL実装のアルゴリズムについて注意点
この記事は古い9.9系当時のメモを引っ張り出してきたものなので注意
(追記・bind-9.10.2で確認。 lib/dns/rrl.c 660行目あたり。)
TL;DR
BINDのRRLはトークンバケットではない。設定レートは十分上げてslip 0
は使わない。
アルゴリズムとドキュメントの問題
BIND9.9系に追加されたRRL (Response Rate Limiting)のマニュアルには、レート制限にトークンバケットを利用していると書かれている。
これは(少なくとも導入時点では)間違っていて、実際には負のバケツを導入したペナルティ付きのアルゴリズムになっている。 バケツに定期的にトークンが補充されるのは通常のトークンバケットと同じなのだが、 トークンが枯渇してもクエリが来るとドロップ(またはTC)されるだけでなく、バケツの値はゼロを超えて負の値になる。
トークンが枯渇した後、通常のトークンバケットでは次のトークンが投入された時点で次のリクエストが許可される。 ところが、BINDの実装ではドロップされたクエリまでカウントしてバケツを負にしてしまうため、クライアントが諦めるかトークンの補充レートが追いつかない限り、バケツが正にもどることがなく、RRLによるリミットがかかりっぱなしになってしまう。
大雑把に言うと、一度設定レートより多くのクエリを送ってRRLに引っかかってしまうと超過分はペナルティにカウントされ、超過クエリ数/設定レート
の冷却期間クエリを止めないと通常の動作に戻らない。
バーストにペナルティを課すこの挙動はバーストを許容しかつ最低レートを保証するトークンバケットの性質とはほぼ正反対になっている。
TCPフォールバックしないリゾルバの問題
slip
が1以上でTruncatedなレスポンスが返る場合、リゾルバはTCPでリトライするためRRL対象外となり問題ないはずだが、攻撃目的クエリの規模が大きい時などにslip 0
を指定していたりすると、UDPのドロップ時にTCPでリトライしないリゾルバ実装では名前解決ができなくなる問題を引き起こす。
また、そもそもTCPフォールバックできないアホな実装やネットワークでは、UDPでリトライし続けるため長時間RRLが解除されず名前解決できない。
BINDのRRLをそのまま使い、かつ上手くTCPフォールバックできないクライアントを考慮するなら、設定レートは十分に上げておくべきと思われる。 もっとも、DNS Amp対策としてはRRLの設定レートを上げると効果が無くなってしまうのだけど。
SSD VPSのDigitalOceanがシンガポールリージョンを追加
VPSプロバイダのDigitalOceanがシンガポールリージョンをリリースした。
DigitalOceanは標準でSSD、snapshotに対応、時間課金なのでAnsibleのテスト環境として利用している。ストレージがSSDだとテスト時間が大部分ネットワークレイテンシなので確認してみた。
レイテンシ
国内からのtracerouteは良い時に85msくらい。 AS133165
NTT経由のときはいいけれど、telia.netに行ってしまうと西海岸経由で悲惨なことになる。経路依存なので、必要な場所から ping speedtest-sgp1.digitalocean.com
してチェックしたほうが良さそう。
7 xe-7-4.a16.tokyjp01.jp.ra.gin.ntt.net (61.213.145.93) 12.994 ms 13.205 ms 13.757 ms 8 ae-6.r24.tokyjp05.jp.bb.gin.ntt.net (61.213.169.177) 14.429 ms 13.953 ms 13.817 ms 9 as-0.r20.sngpsi02.sg.bb.gin.ntt.net (129.250.4.91) 76.973 ms 87.418 ms 87.652 ms 10 ae-1.r00.sngpsi02.sg.bb.gin.ntt.net (129.250.4.143) 76.893 ms 87.259 ms 76.356 ms 11 116.51.27.150 (116.51.27.150) 101.476 ms 101.557 ms 97.790 ms 12 103.253.144.242 (103.253.144.242) 85.614 ms 94.697 ms 94.266 ms 13 *** (***) 100.764 ms 89.258 ms 88.088 ms
5 ae-12.r24.tokyjp05.jp.bb.gin.ntt.net (129.250.5.92) 1.547 ms 1.414 ms 1.474 ms 6 as-0.r20.sngpsi02.sg.bb.gin.ntt.net (129.250.4.91) 79.530 ms 75.088 ms 75.270 ms 7 ae-1.r00.sngpsi02.sg.bb.gin.ntt.net (129.250.4.143) 75.647 ms 75.388 ms 75.380 ms 8 116.51.27.150 (116.51.27.150) 229.987 ms 229.558 ms 230.153 ms 9 103.253.144.242 (103.253.144.242) 201.196 ms 201.171 ms 201.127 ms 13 *** (***) 249.515 ms 238.729 ms 238.970 ms
逆向きを見るとteliaの西海岸経由している
3 103.253.144.250 (103.253.144.250) 10.348 ms 10.335 ms 10.336 ms 4 10ge-v107-mad-prov-ixn.airenetworks.es (62.115.40.169) 6.781 ms 6.716 ms 6.683 ms 5 hnk-b2-link.telia.net (213.155.136.118) 39.762 ms hnk-b2-link.telia.net (80.91.245.149) 39.921 ms 38.904 ms 6 las-bb1-link.telia.net (213.155.132.215) 205.099 ms 200.264 ms las-bb1-link.telia.net (213.155.136.44) 211.212 ms
帯域
今のところシンガポール国外宛が詰まっているようで、シンガポール内のサーバー相手だと十分出ている。
またサービスイン直後なせいか直近の各種リポジトリからのパッケージダウンロードが重くなっている模様。
Hosted by World's Fastest Indian (Tokyo) [5320.20 km]: 21.617 ms Testing download speed........................................ Download: 19.77 Mbit/s Testing upload speed.................................................. Upload: 9.12 Mbit/s
Hosted by Viewqwest Pte Ltd (Singapore) [7.42 km]: 26.385 ms Testing download speed........................................ Download: 151.81 Mbit/s Testing upload speed.................................................. Upload: 27.08 Mbit/s
cpuinfo
メモリ512MBのドロップレット
processor : 0 vendor_id : GenuineIntel cpu family : 6 model : 2 model name : QEMU Virtual CPU version 1.5.0 stepping : 3 cpu MHz : 1999.999 cache size : 4096 KB fpu : yes fpu_exception : yes cpuid level : 4 wp : yes flags : fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pse36 clflush mmx fxsr sse sse2 syscall nx lm up rep_good unfair_spinlock pni vmx cx16 popcnt hypervisor lahf_lm bogomips : 3999.99 clflush size : 64 cache_alignment : 64 address sizes : 40 bits physical, 48 bits virtual power management:
リバースブルートフォース攻撃対策手法
一言で言えば、リバースブルートフォース対策=脆弱なパスワードの禁止。
ブルートフォースとの相違点
『リバースブルートフォース』と呼ばれる攻撃は、多くのユーザーが使っていそうな少数のパスワード辞書と、多数のアカウント名を用いてログイン試行を繰り返す手法。
少数のアカウントに対して多数のパスワードを試す、通常のブルートフォース攻撃であれば、アカウント毎にログイン失敗回数の上限を設けてアカウントロックすることで防げる。 ところが、リバースブルートフォースは攻撃対象アカウントが変化するため、この単純な方法では防げない。 また、ボットネットを使えばリバースブルートフォースはいくらでも並列に行うことができるため、単純なIPアドレス当りのアクセス頻度制限でも防げない。[JALの不正ログイン事件について徳丸さんに聞いてみた]
リバースブルートフォースを防ぐにはアカウント名(ID)かパスワードを、予測不能なもの、つまり十分長くかつ攻撃者の辞書に載っていないものにする必要がある。
アカウント名は内部・外部で利用されうるので、アカウント名を予測不能にするのは難しい。後者を保証するためには、よく利用されるパスワードの利用を禁止することになる。
「よく使われるパスワード」というものは一定ではなく、実際のユーザーによって変化していく(qwertyが禁止されれば、qwerty123456の頻度が増えるだろう)。攻撃側も、随時パスワード辞書を更新して、実態に近いより効率的な攻撃を考えている。
理想的にはどのユーザーも同じパスワードを使うことがないように出来ればいいのだが、これを実装してしまうと、生パスワードかソルト(nonce)が同じハッシュを永久的に保管することになり、漏洩時のリスクから見て好ましくなく、他のユーザーが使っているパスワードを調べる方法を攻撃者に与えてしまう。
パスワードあたりログイン失敗回数
リバースブルートフォース対策としてはアカウントロックの反対、つまりパスワード(のハッシュ)毎のログイン失敗回数を記録して、失敗回数が閾値に達したら、そのパスワードは攻撃者の持っている辞書に載った脆弱なパスワードになったと判定し、無効なパスワード辞書に追加して使用不能にする、という方法がある。
例えば、ある18文字のパスワード"NagaiNagaiPassword"は登録時のパスワードチェックでは、安全な長いパスワードと判定されてしまうかもしれない。 もしこのパスワードでのログイン失敗が何度もあったら、なんらかの攻撃の徴候と考えられる。"NagaiNagaiPassword"は攻撃者が使う「よくあるパスワードリスト」に載ってしまった可能性が高い。
以降"NagaiNagaiPassword"はそのサイトでパスワードとして登録出来なくなり、"NagaiNagaiPassword"をパスワードにしているアカウントはロックされてパスワード以外の認証、たとえばメール認証を要求される。
新しいパスワードを攻撃者がブルートフォースに使い始める理由は
- ユーザーがパスワードを他の場所でも使い回していて、それが漏洩した
- 攻撃者が独自に統計をとったり、偶然に思いついて辞書に追加した
のいずれかとなる。 アカウントを忘れた本当のユーザーが単に何度も間違ったアカウント名で試し続けている、という誤検出はありえるが、パスワードが失効するのは本人のアカウントだけなので問題はないだろう。
この方法は、パスワードリスト保持のためにメモリが必要という欠点を除けば単純で、脆弱なパスワード辞書をアップグレードするためのデータも与えてくれる。
なお、ログイン失敗したパスワードは本物のパスワードのtypoである可能性があるので、生パスワードでは保存せず、定期的に消去したりソルトを変更することが望ましい。
問題点
この手法があまり使われないのは、『危険なパスワードを使うことが出来ない』という問題があるためだろう。
本当に安全な(乱数で生成した長いパスワードで漏洩経験がない)パスワードを使っているなら、ブルートフォースに使われることはよほど運が悪くない限り突然パスワードが使えなくなることはない。
しかし実際にパスワードを暗記しているほとんどの人が脆弱なパスワードを使っているがために、脆弱なパスワードの禁止はそれ自体不便なシステムと見られてしまう。
この手の手法を導入していても、ユーザーへのリスク通知にとどめているウェブサービスは多いのではないかと思う。
ツイッターのお気に入りリンク共有fav2markを公開
Twitterのお気に入り(ふぁぼったツイートのリスト)からリンクを抽出して、はてなブックマークで共有するOAuthアプリを作ってみた。
conohaが開催しているこのこんというのに応募しようと思って約3日で作ったもの。基本機能はテストしているものの、実アカウントが少ないので絶賛人柱募集中。
とにかくPythonでアプリ、プロセス管理、サーバー管理まで揃えている。 応募用にSlideShareでまとめ(?)たもの↓
時間がかかったのはだいたいOAuthとMongoEngineのせい。 MongoEngineのシリアライズが変なのでモンキーパッチして性能を稼いでいたりと、内部的には余り美しくないけれど、そこそこのアカウントをopenvzなVPSでも裁けるようにgevent、celery、redis で頑張ってみた。
uWSGIで複数バージョンのPythonを動かす
今のところuWSGIはvirtualenvで作ったアプリ別のモジュールをロードすることは出来るものの、システムのlibpython.soを直接使っているため、そのままではビルド時のPythonしか動かない。
異なるバージョンのpythonを含むvirutalenvに入ろうとすると、PYTHONPATHが違うためimport site
の時点でモジュールのロードに失敗する。
Python version: 2.6.6 (r266:84292, Nov 22 2013, 12:16:22) [GCC 4.4.7 20120313 (Red Hat 4.4.7-4)] ... Set PythonHome to /path/to/py27env 'import site' failed; use -v for traceback
他のバージョンのpythonを使うには、対応するpythonとpythonプラグイン対応のuWSGIをビルドし直す必要がある。
ビルド手順
デフォルトのままpipなどでインストールすると、python他プラグイン全部入りのuwsgiバイナリが出来ているはず。
(pipで入れていれば/tmp/pip-build-root/uwsgi
に必要なファイルが展開されているので、これを元にビルド)
uwsgiソースのbuildconfディレクトリには、uwsgi本体ビルド用の設定ファイルが含まれている。
pythonを含まないバイナリを作るには、buildconf/nolang.ini
を使ってビルドするか、main_plugin=
からpythonとgeventを抜いておく必要がある。(geventもpythonに依存しているためビルド出来ない)
python uwsgiconfig.py --build nolang
次にプラグインをビルドするのだが、uwsgiconfig.pyはリンクするlibpython.soをビルド時のPython環境から探しているらしく、ほしいバージョンのsharedなpythonとlibpythonがセットで必要。 さらに面倒なことにpythonビルドツールのpythonzはデフォルトではsharedなビルドを作ってくれない。(OSXは別?)
一応configureオプションを渡すことでビルドは可能で、
pythonz install 2.7.6 --verbose --configure='--enable-shared LDFLAGS="-Wl,-rpath -Wl,\\$$ORIGIN/../lib"'
あるいは手動でインストール
./configure --prefix=/usr/local/python27 --enable-shared LDFLAGS="-Wl,-rpath /usr/local/python27/lib" make sudo make altinstall
必要なバージョンのsharedなpythonバイナリでプラグインをビルド。pluginsディレクトリにプラグインのテンプレートが入っている。
python2.7 uwsgiconfig.py --plugin plugins/python nolang python27 python2.7 uwsgiconfig.py --plugin plugins/gevent nolang gevent27
これでuwsgiバイナリと*_plugin.so
が出来るのでバイナリを置き換え。
sudo cp uwsgi /usr/bin/uwsgi sudo mkdir -p /usr/lib/uwsgi sudo cp *.so /usr/lib/uwsgi
アプリの設定ファイルから
[uwsgi] plugins-dir=/usr/lib/uwsgi plugin=python27_plugin.so virtualenv=/path/to/py27env chdir=/path/to/py27app
とロードさせればpython2.7を利用できる。
同様にphp-embeddedの入った環境で
python uwsgiconfig.py --plugin plugins/php nolang php
でphpプラグインもビルドできた。
ものすごく面倒なのでuwsgi側でvirtualenvから自動ロードするか、libpython.soをplugins-dirに持ってくるようにパッチを当てたい……
OpenVZゲストでNative IPv6とIPv6トンネルのデュアルルートを作る
DTIのserversman VPSをx86_64化してIPv6がいくらか動くようになったのでOpenVPNサーバーにしてみた。
前提
- OpenVZのVPSゲスト、CentOS6.x + OpenVPN 2.3.x
- dev venet0:0 にNative IPv6が1つ(/128)だけ割り当て済み。可能ならこのルートを使う。
- He.netのIPv6トンネルから/64の割り当てを受けてOpenVPNのクライアントへ割り当てる。
- dev tun0 をOpenVPN tun-ipv6 で作成
- dev tb をユーザースペーストンネル tb_tun で作成。
トンネルとルートの作成
まずOpenVZ上ではカーネルもジュールを導入できないので、sitトンネルを使えない。 トンネルパケットをユーザースペースで処理するtb_tunがあるのでサービスとして起動する。 http://code.google.com/p/tb-tun/
setsid tb_userspace tb {{tunnel_server}} any sit > /var/log/tunnel.log 2>&1 &
tbにIPv6アドレスを割り当て。
ifconfig tb up ifconfig tb inet6 add {{tunnel_client_ipv6}}
venetをデフォルトにするためmetricを増やして追加しておく。
ip -f inet6 route add default metric 10 dev tb
他のOpenVZマネージャ(SolusVM)ではIPv6割り当てが無いのにデフォルトルートが設定されている。その場合削除しておく。
ip -f inet6 route del default metric 1 dev venet0
iptablesで-p 41のINPUT/OUTPUTを通しておく事を忘れずに。
iptables -A INPUT -p 41 -s {{tunnel_server}} -j ACCEPT
ここまででトンネルIPv6は到達可能になった。
トンネル-VPN間のルーティング
IPv6ルーティングを有効化
net.ipv6.conf.all.forwarding = 1
He.netを経由させるRoutedなソース範囲に対してルール付きのテーブルを作成、デフォルトルートに指定
ip -f inet6 rule add src {{tunnel_routed}} table 10 ip -f inet6 route add default metric 1 dev tb table 10
ip6tablesのFORWARDを許可
ip6tables -A FORWARD -i tun0 -s {{tunnel_routed}} -j ACCEPT ip6tables -A FORWARD -i tb -d {{tunnel_routed}} -j ACCEPT
ServersMan@VPSはopenvzカーネルが古すぎてip6tablesのconntrackが機能していない。 常用するならもっとまともなフィルタルールが必要。
OpenVPN側
routedなIPv6を割り当てる。クライアントにはデフォルトルートとしてpushする。
server-ipv6 {{tunnel_routed}} push "route-ipv6 ::/0"
残念ながら、openvpn 2.3.6ではまだIPv6 DNSサーバーアドレスをpushすることができない。 そのためAAAA filteringされていないHe.net提供のIPv4 DNSサーバーを指定するか、クライアント側で上書きしてもらう必要がある。