Kenichi Maehashi's Blog

脳内コアダンプ

RSS
Category: Computer
Windows 7 / Vista に付属の付箋アプリで、付箋の削除時に「今後、このメッセージを表示しない」に間違えてチェックした場合の修正方法。

コマンドプロンプトで以下のコマンドを実行します。
reg add HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Applets\StickyNotes /v PROMPT_ON_DELETE /t REG_DWORD /d 1 /f
余談ですが、付箋アプリのファイル名は StikyNot.exe なんですね。Vista からの新アプリなのに 8.3 な理由が謎。
Category: Mobile
iPhone (Softbank) の回線がひどいと評判になっていたので、実験をしてみました。

実験内容

  • 日時: 2011/6/20 22:34-22:39 JST (5 分間)
  • 都内某所にて iPhone 4 を静止状態、Wi-Fi を無効に設定して実施。アンテナは常時安定して 5 本立った状態
  • iPhone 4 (iOS 4.3.3) から 3G 経由で、自宅の au ひかり (1 Gbps) 回線に接続された SSH サーバ (Linux) にアクセスし、yes コマンドを実施 (iPhone アプリの iSSH を使用)
  • サーバ側にて、tcpdump コマンドでパケットを採取。同時に ping を実施
コマンドは次の通り。
# tcpdump -i br0 -n host (iPhone の IP アドレス) -w iphone.pcap & ping (iPhone の IP アドレス)

結果

Wireshark による集計結果は次の通り。縦軸はパケット、横軸は時間。画像はクリックで拡大します。

TCP

黒い線がサーバの送出したパケット (tcp) 、赤い線が再送したパケット (tcp.analysis.retransmission) ≒ packet lossです。


遅延があるのは置いておくとして、ロスはあまり目立たず、モバイル回線としてはまずまずという感じではないでしょうか。

ICMP Echo (ping)

黒い線がサーバの送出したエコー要求パケット (icmp.type == 8) 、赤い線が iPhone からのエコー応答パケット (icmp.type == 0) です。(packet loss が無い場合、赤い線が黒い線に重なります)


赤い線が地を這っています。81% packet loss でした。

まとめ

  • Softbank 回線の ICMP パケット転送には、何らかのフィルタが掛かっている、ようだ。
  • 回線の品質は、言われているほど悪くはないのではないか。
  • ネットワークの専門家ではないので、これ以上のことは聞かれても困ります :-)
ある 5 分間を切り出したものでしかないので過信はできませんが、少なくとも ICMP と TCP の packet loss 率に相関が無いことは示せたかと。

余談ですが、ICMP パケットもパケット課金の対象になるので、あんまりジャンジャカ転送されるよりは多少フィルタが掛かってた方がパケ放題じゃない人にとっては嬉しいはず。ping flood でパケ死とか、笑えない。

# あと、TCP の RTT のいい感じな可視化方法があれば、知りたいです。

追記 (2011/06/21)

TCP RTT の可視化について、コメントをいただいています。本 blog 史上最大のコアっぷりに焦りと勉強不足を感じつつ(笑)
そうか、こういうときのための Jailbreak だったのか。

追記 (2011/06/24)

はてブコメントを頂いたので。
id:totttte 反証記事だからきっちり対等に比較しなきゃ意味ないのに、 どうして、TCP側は網内→インターネット方向で張ったパケットなのに、pingはインターネット→網内のネットワークになっているのか?
往路 (インターネット → SB 網) と復路 (SB 網 → インターネット) のどちらで packet loss が生じたのかは、確かにこの検証からは不明ですね。ただ、「TCP 接続をどちらが開始したのか」というのは意味のない議論だと考えます。往路で loss しても、復路 (ACK) で loss しても、サーバ側では再送を行うはずです。ping に関しても、いずれかで loss が起きれば request timeout になります。
Category: Linux
ちょいと使いたくなった。

プロトコルと設定

プロトコルは TCP / UDP のポート 11211 が標準。テキストベースなので、telnet でお話しすることもできます。
$ telnet localhost 11211
Trying 127.0.0.1...
Connected to localhost.localdomain (127.0.0.1).
Escape character is '^]'.
version
VERSION 1.4.5
quit
Connection closed by foreign host.
詳しくは memcachedプロトコルについて などが参考になりました。

UDP で繋いだときのプロトコルは謎 (nc -u localhost 11211 してみたけどよく分からない)。オプション -U 0 で UDP で待ち受けなくなるようです。あと、必要であれば SASL 認証ができるらしい... ですが lo のみバインドさせておくのが無難ですね。

設定変更。稀に /etc/rc.d/init.d/memcached を直接編集することを推奨する記事がありますが、RHEL 系では /etc/sysconfig/memcached を編集しましょう。
PORT="11211"
USER="nobody"
MAXCONN="1024"
CACHESIZE="64"
OPTIONS="-l 127.0.0.1 -U 0"

Perl から使う

というわけで、超ナイーブに使ってみます。単語帳から単語を中間一致検索します。
#!/usr/bin/perl

use strict;
use warnings;
use utf8;

use Fcntl;
use Time::HiRes qw/gettimeofday tv_interval/;
use Cache::Memcached;

my $memd = new Cache::Memcached {
'servers' => ["127.0.0.1:11211"]
};

sub main() {
my (@words, @results);
my ($begin, $cost);
my $query = $ARGV[0] || die "No query given!\n";

sysopen(my $fh, '/usr/share/dict/words', O_RDONLY);
@words = <$fh>;
close($fh);

$begin = [gettimeofday];

my $results_ref = search_word(\@words, $query);
@results = @$results_ref;

$cost = tv_interval($begin);

foreach my $result (@results) {
print "Result: $result\n";
}
print STDERR "Query: $query ($cost sec.)\n";
}

sub search_word(@$) {
my $words_ref = shift;
my $query = shift;
my $result_ref;
my @result;

$result_ref = $memd->get($query);
if (! defined $result_ref) {
$result_ref = linear_search($words_ref, $query);
}
@result = @$result_ref;
return \@result;
}

sub linear_search(@$) {
my $words_ref = shift;
my $query = shift;
my @words = @$words_ref;
my @result;
foreach my $word (@words) {
chomp $word;
if ($word =~ /$query/) {
push(@result, $word);
}
}
$memd->set($query, \@result);
return \@result;
}

main();
モジュールが勝手にシリアライズ・デシリアライズをやってくれるので、難しいことを考えなくていいのが嬉しい。

実行してみると、
$ perl search.pl ab > /dev/null
Query: ab (0.291347 sec.)
$ perl search.pl ab > /dev/null
Query: ab (0.046976 sec.)
# service memcached restart
Shutting down Distributed memory caching (memcached): [ OK ]
Starting Distributed memory caching (memcached): [ OK ]
$ perl search.pl ab > /dev/null
Query: ab (0.2867 sec.)
$ perl search.pl ab > /dev/null
Query: ab (0.045132 sec.)
ということで、2 回目からはすごく速くなります (memcached を再起動するとリセットされます)。いい感じ。
Category: Linux
Google Apps で利用可能になっている Google Authenticator による 2 要素認証ですが、実は PAM モジュールがオープンソースで公開されています。このモジュールを利用して、SSH サーバを Google Authenticator による 2 要素認証にしてみましょう。

メリット

  • 単一のパスワードではなくパスワード + ワンタイムパスワード(確認コード) の 2 要素で認証するため、パスワード漏洩時やブルートフォース攻撃に対するセキュリティ侵害リスクが低くなります。
  • iPhone や Android などのアプリケーションでワンタイムパスワードを生成できるためお手軽です。

注意点

PAM を使用して認証するため、公開鍵 + 確認コードの 2 要素認証にはできません (公開鍵のみの認証は引き続き可能です)。

導入手順

以下はすべて root を想定した作業です。環境に応じて su - や sudo などを使用してください。また、Development Tools や Development Libraries が前提となっています。

Mercurial の導入

Google Code からソースコードをチェックアウトするため、Mercurial (VCS) をインストールします。
事前に EPEL または RPMforge のセットアップが必要です。
yum -y install mercurial

ソースコードの取得

リポジトリをクローンします。
cd /usr/local/src
hg clone https://google-authenticator.googlecode.com/hg/ google-authenticator

Makefile の修正 (64-bit 環境の場合のみ)

Makefile を開きます。
vi /usr/local/src/google-authenticator/libpam/Makefile
3 箇所ある /usr/lib/libdl.so/usr/lib64/libdl.so に書き換えます。

コンパイルとインストール

cd /usr/local/src/google-authenticator/libpam
make
make install
/lib(64)/security/pam_google_authenticator.so と /usr/local/bin/google-authenticator がインストールされます。

PAM の設定

/etc/pam.d/sshd の設定は、デフォルトで以下のようになっています。
#%PAM-1.0
auth include system-auth
(account 以降は省略)
ここでは system-auth をインクルードしていますが、これでは不都合なので /etc/pam.d/system-auth から auth タイプの行をコピーしてきて展開します。特に認証連携を行っていない (UNIX 認証のみ) の場合は、以下のようになっているはずです。
auth        required      pam_env.so
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny.so
UNIX 認証に引き続いて Google Authenticator による認証を実行するため、pam_unix.so モジュールの control を sufficient から requisite に変更します。加えて、pam_google_authenticator.so モジュールを差し込みます。最終的に、/etc/pam.d/sshd は次のようになります。
#%PAM-1.0
auth required pam_env.so
auth requisite pam_unix.so nullok try_first_pass
auth sufficient pam_google_authenticator.so
auth requisite pam_succeed_if.so uid >= 500 quiet
auth required pam_deny.so
(account 以降は省略)

時間の設定

Google Authenticator を使用する場合、SSH サーバと Authenticator クライアント (スマートフォン) の時刻を合わせる必要があります (タイムゾーンは異なっても問題ないはずです)。ntpdate や date などで時間合わせを行っておいてください。

SSH サーバの設定

OpenSSH の設定 (/etc/ssh/sshd_config) を、少なくとも以下のように設定します。
  • PasswordAuthentication no (この設定が yes だと、1 要素認証ができてしまうため)
  • ChallengeResponseAuthentication yes (PAM による keyboard-interactive 認証を使用するため)
  • UsePAM yes (同上)
また、PermitRootLogin without-password の設定がされている場合、これは無視されます (root も 2 要素認証でログインできるようになる) ので、必要であれば PAM のルールで制限しましょう。ただし、root ユーザで Google Authenticator の設定 (下記) を行わない限り 2 要素認証はできませんので、あまり問題はないと思います。また、全てのユーザで、引き続き公開鍵認証を行うこともできます (公開鍵認証が先に行われた場合は Google Authenticator は無視されます)。

上記設定後、SSH サーバを再起動してください。
service sshd restart

ユーザごとの設定

Google Authenticator を使用してログインしたいユーザで /usr/local/bin/google-authenticator コマンドを実行すると、以下のように URL と、いくつかの質問が表示されます。
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/kenichi@centos5%3Fsecret%3DXXXXXXXXXXXXXXXX
↑ この URL にアクセスすると、QR コードが表示されます。iPhone / Android の Google Authenticator アプリで読み込みます。
Your new secret key is: XXXXXXXXXXXXXXXX
Your verification code is 123456
Your emergency scratch codes are:
12345678
12345678
12345678
12345678
12345678
↑「バックアップコード」です。

Do you want me to update your "~/.google_authenticator" file (y/n) y
↑ ~/.google_authenticator ファイルを作成または更新します。更新した場合、QR コードの読み込みを再度行う必要があります。

Do you want to disallow multiple uses of the same authentication
token? This restricts you to one login about every 30s, but it increases
your chances to notice or even prevent man-in-the-middle attacks (y/n) y
↑ 同じ認証コードを 2 回以上使えないようにします (中間者攻撃やリプレイ攻撃を防ぐため)。認証コードは 30 秒間同じものを使うため、30 秒以内に再度ログインすることはできなくなります。推奨値は y。

By default, tokens are good for 30 seconds and in order to compensate for
possible time-skew between the client and the server, we allow an extra
token before and after the current time. If you experience problems with poor
time synchronization, you can increase the window from its default
size of 1:30min to about 4min. Do you want to do so (y/n) n
↑ サーバとクライアントの時間のズレの許容値を、90 秒から 240 秒に増やします。時間がズレやすいホストの場合は y。通常の推奨値は n。

If the computer that you are logging into isn't hardened against brute-force
login attempts, you can enable rate-limiting for the authentication module.
By default, this limits attackers to no more than 3 login attempts every 30s.
Do you want to enable rate-limiting (y/n) y
↑ 30 秒間に試行できるログインを 3 回までに制限します。推奨値は y。
ここまでで、設定は完了です。上記の URL を表示し、QR コードを iPhone や Android の Google Authenticator アプリで読み込みます。詳しくは Installing Google Authenticator (日本語) を参照してください。
また、バックアップコードについては Signing in using backup codes (日本語) を参照してください (Google Authenticator アプリが使用できない、緊急の場合にのみ使用するコードです。メモしておきましょう)。

ログイン!

お手近の SSH クライアントから:
$ ssh kenichi@10.0.123.123
Password: <= アカウントの UNIX パスワードを入力
Verification code: <= Google Authenticator アプリから入手したワンタイムパスワードを入力
Last login: Thu Jan 1 12:34:56 2011 from 10.0.123.100
[kenichi@centos5 ~]$
というわけで、ログインできました。Password を間違えた場合は、Verification code のプロンプトが出ないはずです (Google Apps でも、このようになっています)。Verification code を間違えた場合は、Password からやり直しです。

公開鍵が使われてしまって実験できない場合は、~/.ssh/authorized_keys を退避するか、
ssh -o PubkeyAuthentication=no kenichi@10.0.123.123
とすれば良いでしょう。

備考

PAM の設定次第で、コンソールログインに Google Authenticator を使うこともできます (どちらかというと、こちらの方が SSH の設定よりも簡単ですね)。ただ、system-auth はログインだけでなく su - などにも影響を与えるので、設定内容には注意が必要です。
なお、authconfig / authconfig-tui を実行すると system-auth-ac が書き換えられてしまうので、注意してください (心配であれば、system-auth から system-auth-ac のシンボリックリンクを外してコピーしてしまうと良いでしょう。ただし、推奨はしません)。

また、今回は UNIX 認証を requisite としましたが、サーバにブルートフォース攻撃への対策を全く行っていない場合は required が良いかもしれません (この場合、Password の適否に関わらず Verification code プロンプトが表示されます)。
Password と Verification code の順序を逆にするのもオモシロイですね!