mod_pythonでRDBMSベースのBasic認証

このエントリは2007/07/21の再掲です

Apacheで任意のURIBasic認証セキュリティをかけるさい、 多くの場合ファイルベースのパスワードソースが使われます。httpd.confや.htaccessに、こんな設定をする例のアレです。

AuthType Basic
AuthName "My Private Secure Zone"
AuthUserFile path-to-passwd-file
Require valid-user

基本的なApache-Basic認証は、臨機応変にリソースを隠蔽するのに便利なのですが、こればかりだと困ってしまう状況も多々あります。

  • htpasswdコマンドが実行できる端末を必要とする
  • パスワードファイルがネットワーク越しに見えてはいけないため、リモート管理が困難
  • 管理担当者に上記のスキルがない場合に、ユーザ対応が後手後手になる
  • ユーザ数に比例してパフォーマンスが低下するので大規模化できない
  • 他のWeb-DBサービスで管理しているユーザのパスワードと同期できない

もしApacheの認証ソースにRDBMSを使うことができれば、Webアプリケーションのフロントエンドからパスワード管理でき、ユーザ数増加時でもオーバーヘッドを一定にすることができます。そこで、mod_pythonでデータベースに接続する認証ハンドラを書けないかを検証してみましょう。

mod_pythonの機能とインストールについては、このページで十分ですね。また、今回の実験には、RDBMSとDBAPIにそれぞれ、PostgreSQLとpsycopg2を使います。psycopg2に限らず、サードパーティ製のドライバ選定には注意が必要です。PostgreSQLについてはメジャーバージョン、 Pythonについては2.xのxの部分が違うと、まったくバイナリ互換性がありません。makeしないかぎりは、バイナリバージョンに十分注意してインストールしましょう。

まず何はともあれ、ユーザ名からパスワードを引き当てるためのテーブルを設けます。

CREATE TABLE auth
(
  username varchar PRIMARY KEY,
  passwd varchar
)

なにか適当なデータを格納しておき、ちゃんと問い合わせることができるかを、Python対話インタプリタで試しましょう。

import psycopg2
conn = psycopg2.connect(
    "host='localhost' dbname='apacheuser' user='postgres' password='postgres'")
curs = conn.cursor()
user = 'myaccount'
curs.execute(
    "select passwd from auth where username=%s", (user,))
r = curs.fetchone()
print r
# password for myaccount shown?
curs.close()
conn.close()

うまくいけば、user変数に格納したユーザのパスワードが表示されるはずです。さて本題はここからで、Apacheがコンテンツを送出するまでに、このユーザ名とパスワードを、HTTPリクエストのヘッダデータと比較することが可能かどうかです。

どうやら、mod_pythonにはPythonAccessHandler / PythonAuthenHandler / PythonAuthzHandlerと、アクセス制限ハンドラを割り込ませるポイントが準備されていて、マニュアルに登場する二つ目のチュートリアルには、もう簡単な認証のデモが掲載されています。これを見れば、答えは「できるにちがいない」ですね。

AuthUserFile ディレクティブを削除し、PythonAuthenHandlerディレクティブに、認証ハンドラを持つモジュール(authenhandler関数を持つスクリプトファイルの.pyを除いたファイル名)を追加しましょう。Pythonのモジュール検索パスを変更できるPythonPathディレクティブ、それと、ブラウザにmod_python自身のエラーを出力してくれるPythonDebugディレクティブも活用できます。

AuthType Basic
AuthName "My Private Secure Zone"
# AuthUserFile path-to-passwd-file -- no needs yet
Require valid-user
PythonAuthenHandler private_access_control
PythonPath sys.path+['/your/script/path']
PythonDebug On

スクリプト実装例 private_access_control.py

private_access_control.py をダウンロードして調整したら、/your/script/pathの部分を、このファイルを置いたディレクトリに変更し、Apacheを再起動します。うまくいけば、データベースにエントリを追加するだけで、この方法で保護されたリソースにアクセスできるユーザを作成できます。

データベースのエントリを管理するのなら、それこそWebフロントエンドを作って、スキルや環境を問わずにユーザ管理できるWebサーバを構築できます。さらに、管理がWebアプリケーションになるのなら、もう一歩進んで、パスワードフレーズの入力にサインアップと承認のモデルが使えます。ユーザ個別にパスワードを持てて、それを自分で入力して決めることができれば、ユーザと管理者がパスワードフレーズを何度もメールでやり取りすることや、ユーザが共用パスワードを付箋メモに残すような、人間的なセキュリティホールをごく自然に防ぐことが可能になります。

課題

  • ユーザごとに認定されたURIにしかアクセスできないようにする
  • PythonOptionを活用してディレクティブでの設定の柔軟性を確保する
  • 接続のオーバーヘッドを減らすためにRDBMSへの接続をプールできるか