2006年07月14日

アクセス制御モジュールを作ってみる (apache module 開発事初め その2)

はてなブックマークに登録


前回の記事では,apxs が生成したテンプレートをそのまま動かしてみましたが,今度は少しコードを書いてみましょう.同じ handler を作っても面白くないので,アクセス制御をするモジュールにしてみます.Apache のアクセス制御は2種類あって,一つはユーザ認証を目的としたもので,mod_auth の眷属がそれです.もう一つはリクエストの別の側面,例えばクライアントのアドレスによってアクセスを許可したり拒否したりするもので,標準モジュールでは mod_access がそれに当たります.あまり複雑なことをしても話が見えにくくなるので,今回作るモジュールではランダムにアクセスを許可したり拒否したりすることにします.


まず,今回のモジュールのソースコードを次に示します(apxs -g -n denyrandomとして出力したものをベースにしています).

  1  #include "httpd.h"
2 #include "http_config.h"
3 #include "http_protocol.h"
4 #include "ap_config.h"
5
6 #include "http_log.h"
7 #include "apr_time.h"
8
9 #define RATE 10
10
11 static int denyrandom(request_rec *r){
12 int msec = apr_time_msec(r->request_time);
13 if(msec % RATE){
14 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "deny: msec = %d", msec);
15 return HTTP_FORBIDDEN;
16 }
17
18 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "allow: msec = %d", msec);
19 return OK;
20 }
21
22 static void denyrandom_register_hooks(apr_pool_t *p)
23 {
24 ap_hook_access_checker(denyrandom, NULL, NULL, APR_HOOK_MIDDLE);
25 }
26
27 /* Dispatch list for API hooks */
28 module AP_MODULE_DECLARE_DATA denyrandom_module = {
29 STANDARD20_MODULE_STUFF,
30 NULL, /* create per-dir config structures */
31 NULL, /* merge per-dir config structures */
32 NULL, /* create per-server config structures */
33 NULL, /* merge per-server config structures */
34 NULL, /* table of config file commands */
35 denyrandom_register_hooks /* register hooks */
36 };

今回も関数が二つ,アクセスの許可/拒否を判断する本体の関数と,それを hook 登録する関数だけです.hook 登録のための関数は今回は denyrandom_register_hooks() という名前になってますが,これは apxs が自動で生成してくれた名前そのままです.この名前は実際のところ何でもかまいませんが,特に拘るものではないので流儀に従っておきます.


さて,前回のモジュールで hook 登録するのに使った関数は ap_hook_handler() でしたが,今回のは ap_hook_access_checker() になっています.これは mod_access が使うのと同じ hook 登録関数です.hook 登録関数は前回の記事の最後に示した参考文献にもあるように何種類かありますが,引数の数と種類は全て同じです(実は,ap_hook_ という名前で始まるこれらの関数定義は,全てマクロで生成されています).これら hook登録関数の引数の意味は順に

 ap_hook_***(func, pre, suc, pos);


func
登録する hook 関数

pre
ここで登録する hook 関数より前に実行されているべきモジュールのリスト

suc
ここで登録する hook 関数より後に実行されているべきモジュールのリスト

pos
ここで登録する hook 関数を実行する大体の位置.始めの方か後の方か真ん中か


です.presucpos は全て,他のモジュールが登録する同じ種類の hook 関数との相対的な実行順序を指定するための引数になります.presuc は文字列の配列で,実際には例えば次のような値を入れた配列を指定します.
 static const char * const pre[] = {
"mod_access.c"
NULL
};

修飾子が一杯付いていますが,つまるところ NULL ターミネートされた,前(後)に実行しておくべき hook関数を定義したモジュールのソースファイルの名前のリストです.presuc には特段指定する必要がなければ NULL を渡しておきます.他のモジュールに依存したモジュールでなければ,特に指定する必要はありません.
pos は整数値,実際にはマクロ定義された値を指定します.指定できる値は

  • APR_HOOK_REALLY_FIRST

  • APR_HOOK_FIRST

  • APR_HOOK_MIDDLE

  • APR_HOOK_LAST

  • APR_HOOK_REALLY_LAST


の 5種類になります.実際のところ,大抵のモジュールでは APR_HOOK_MIDDLE を指定すれば良いと思います.pos に同じ値が指定された同種の hook 関数の呼び出し順序は,presuc が指定されていなければ,モジュールが初期化される順番に依存します.
func で指定する関数の型は,hook の種類毎に異なります.今回の ap_hook_access_checker() や前回の ap_hook_handler() で登録する hook 関数の場合,引数として request_rec 構造体のポインタを取り,int 型の値を返します.


さて今回のモジュールの本体である denyrandom() ですが,この関数の目的はランダムにアクセスを拒否する,です.はじめは rand_r() 関数(srand()rand() をいっぺんにやってくれる関数です.srand()rand() の組み合わせはスレッドセーフじゃないので,マルチスレッドで動作する Apache のモジュールでは使えません)を使おうかと思い,そのための seed に使えるものを探してみました.rand_r() は疑似ランダム関数ですので,seed が固定では意味がありませんからリクエスト毎に異なる整数値を… と request_rec 構造体の中を見てました.

この request_rec 構造体は httpd.h で定義されている,現在のリクエスト情報とそれを処理するための情報,そしてレスポンスの情報を保持する構造体で,リクエスト毎に作成されます.この構造体のメンバに関しては,今後の記事の中で出てくる度に説明を書いていきますが,気になる方は中身を見てみてください.Web 上でしたらこちらに置いてありました.(蛇足ですが,このサイトで Apache の中身を解説してる blog が公開されていました.filter や handler を書く上で重要な bucket brigade や,メモリ管理機構などに関して,その実装も参照しつつ詳細に解説されていますので,module を書こうという方は一読されることをおすすめします.)
さて肝心の seed ですが,丁度良さそうな request_time という変数を見つけました.コメントによれば,これは「リクエスト開始時の時間」だそうです.中身は UNIX 時間の開始からのマイクロ秒になっています.これを seed にして疑似乱数を得よう,と思いましたが,良くよく考えてみたら別にわざわざ疑似乱数を計算するまでもなく,この値をそのまま使えば良いことに気づきました.

ということで,request_time から整数値を得る必要があります.request_time の型は apr_time_t になっていますが,実際のところは 64bit の整数値のようです.このマイクロ秒をそのまま使っても良いのですが,もうちょっと人間がコントロールしやすそうな単位のミリ秒に変換することにしました.手で変換しても良いのですが,何か用意されていないか探してみたところ,apr_time.h の中にミリ秒を返してくれる apr_time_msec() マクロがありました.
今回のモジュールでは,このリクエスト(の処理)が開始された時間のミリ秒を 10(RATE マクロ)で割った余りが 0 の場合アクセスを許可し,それ以外の場合は拒否することにします(13行目の if文です).アクセスを拒否する場合は,返値として HTTP_FORBIDDEN返します(15行目).これは httpd.h定義されたマクロです.この他に,HTTP の各ステータスコードに相当する値が一通り定義されています.許可する場合は HTTP_OK ではなく OK返しますHTTP_OK を返さないのは,他のモジュールでの判断が入る可能性があるので,このモジュールの判断は OK だよ,と返すわけです.


アクセスを許可する場合も拒否する場合も,ap_log_rerror() を呼び出しています.これはその名前の通り,エラーログを出力する関数です.ErrorLog ディレクティブで指定した先にログが出力されます.この関数は http_log.h で定義されていて,次のような形で呼び出します.

   ap_log_rerror(APLOG_MARK, level, status, r, fmt, args...); 

引数の意味は,

APLOG_MARK
マクロです.実際にはファイル名と行番号に展開されます.何も考えずにこれを指定してください

level
ログのレベルです.こでは APLOG_DEBUG を指定しています.低い方から順に,_DEBUG, _INFO, _NOTICE, _WARNING, _ERR, _CRIT, _ALERT, _EMERG が定義されてます

status
他の関数呼び出しで報告されたエラー情報を指定します.特になければ 0 を指定しておきます

r
request_rec 構造体のポインタを渡します

fmt
printf 形式で,出力フォーマットを指定します

args...
fmt に対する引数です


となります.ap_log_rerror() の眷属はいくつかあります.それぞれ r の部分の引数が異なっていて,

ap_log_cerror()
request_req* の代わりに conn_rec* を取る.コネクション単位の処理時に使用する.

ap_log_error()
request_req* の代わりに server_rec* を取る.ディレクティブの設定時に使用する.

ap_log_perror()
request_req* の代わりに apr_pool_t* を取る.それ以外の処理時に使用する.


の3つがあります.


中身が分かったところで,実際に動かしてみましょう.ただし,後で理由を述べますが,くれぐれも何かに実用していて自分以外の人も使っているサーバには組み込まないでください.今回のモジュールのソースはこちらに置いてます.前回の記事で説明したように,apxs コマンドを使って

 apxs -c -i -a mod_denyrandom.c

としてコンパイルとインストールと設定の追加をして,Apache を(再)起動してください.再起動したら,適当な URL をリクエストしてみてください.ちゃんと動作していればたまにアクセスできるだけで,ほとんどのリクエストは Forbidden と言われるはずです.本当にこのモジュールが動いているのか,ログを確認してみましょう.変更していなければエラーログは logs/error_log に出力されます.モジュールが動作していれば
 [Fri Jul 14 13:49:40 2006] [debug] mod_denyrandom.c(18): [client 127.0.0.1] allow: msec = 120
[Fri Jul 14 13:49:41 2006] [debug] mod_denyrandom.c(14): [client 127.0.0.1] deny: msec = 498

のようなログが出力されているはずです.


さて今回のモジュールでは,httpd.conf にはモジュールを読み込む設定(LoadModule)は(apxs コマンドで)追加しましたが,前回の SetHandler のような,このモジュールと実際のリクエストとの結びつけを指定していません.実は,このモジュールを組み込んだ Apache が処理する全てのリクエストがこのモジュールのアクセス制御判断を受けます = denyrandom()が起動されます.VirutalServer を設定している場合でも,全 VirtualServer に適用されます(なので,実用している Apache にこのモジュールを組み込むと簡単に障害になります (^^;).つまり,どのリクエストに対して動作するべきか,というのは Apache 側が判断するのではなくて,モジュール側が判断する必要があるのです.では何を基準にその判断をするのか,というのは次の稿のテーマとしたいと思います.

klab_gijutsu2 at 18:31│Comments(4)TrackBack(1)apache | 開発

トラックバックURL

この記事へのトラックバック

1. 日記/2008-02-19  [ Naruのメモサイト (PukiWiki/TrackBack 0.4) ]   2008年02月19日 04:24
Apacheのモジュール作成 ひな形の作成 http://d.hatena.ne.jp/s-masatarou/20070502/1178130657 上記サイトでの修正 LoadModule testworld_module xxx/mod_testmodule.so <Location /hello> SetHandler testworld </Location> 参考リンク http...

この記事へのコメント

1. Posted by perisy   2007年08月23日 10:28
5 参考にさせていただいております。

サンプルソースのコンパイルは、
「#include "http_request.h"」を追加しないと通らないようなので報告させていただきます。
2. Posted by かつみ   2007年08月23日 12:00
手元の環境(2.0.59)で確認したところ,apxs -c や,apxs -g で生成された Makefile を使ってのコンパイルでは,このままでコンパイルできました.が,確かに ap_hook_access_checker() が定義されているのは http_request.h の中なので,include しないといけないはずですね….
perisy さんがコンパイルエラーに遭遇されたときは,どのような環境/形でコンパイルされてましたでしょうか?
3. Posted by perisy   2007年08月24日 14:12
5 すばやい回答ありがとうございます。

> どのような環境/形でコンパイルされてましたでしょうか?

Windows環境でVC++ 6.0を使ってコンパイルをしました。
apxsを使わない場合は、必要になるのだと思います。

ちなみに、コンパイルしたモジュールはWin32版 Apache 2.0.59にて、動作も確認しました。
4. Posted by かつみ   2007年08月24日 15:01
ご報告ありがとうございます.
なるほど,VC++ でしたか.手元に環境がないもので,こういう情報は助かります(^^

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント