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が追加されたので記述順序も意味をもつようになった。

その後rubyDSLで書けるようになっていた 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アプリを作ってみた。

https://fav2mark.usb0.net/

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を使うには、対応するpythonpythonプラグイン対応の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に持ってくるようにパッチを当てたい……

参考:http://projects.unbit.it/uwsgi/wiki/MultiPython

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サーバーを指定するか、クライアント側で上書きしてもらう必要がある。

ひとまずこれでnativeのIPv6を生かしたままIPv6 over OpenVPNが出来た。