PHPからPAMを使っていいのか!?

最近、このサーバーに少し手を入れていて、

その中でユーザー認証としてSSH同様のTOTP+パスワードを使いたいと思った。

ワンタイムパスワードとの2要素認証

しかし、これをやる上ではいくつか問題があって、まず1つがroot権限が必要なことである。


PHPにPAMってパッケージがあるんですよね。

Package :: PAM (PECL)

で、以前これを使っていた時期があるんですが、大問題があって、

それはこれを動かすユーザーは/etc/shadowにアクセス出来なければならないということである。

/etc/shadowとは暗号化されたパスワードが記録されているファイルで、通常はrootユーザーしかアクセスできない。

まだうちの環境はphp-cgiだから、apacheユーザーに/etc/shadowを見せられるようにしろとか、そこまでではないが。

これと同じ理屈で言えばTOTPはさらに問題である。

各ユーザーディレクトリ上の.google_authenticatorファイルを詠まないといけないからだ。


ここが1つの問題だと思っていたが、sudoを使えば打開できることがわかった。

sudoとかsu -の実行ぐらいしか使っていないが、もっと多様な使い方ができて、

特定のユーザーに特定のコマンドを利用させるということもできる。

visudoで/etc/sudoerを編集して、

hoge ALL=NOPASSWD:/home/hoge/bin/pamtest

みたいにすれば、このコマンドはhogeユーザーからパスワードなしでroot権限で実行できる。

このコマンドにパスワードを入力して照合結果を伝達する形ならば、安全でしょう。


あとはPAMを使ってパスワード照合するプログラムですが、これはサンプルを見ながらCで書いた。

あんまりだとは思うのですが、他にあまり例がないのでアップロードしておく。

構造がよく理解できなかったのだが、pam_start関数には、

サービス名(/etc/pam.d/[サービス名]に従って認証をする)・ユーザー名とpam_convという構造体を渡す。

このpam_convという構造体には関数ポインタとappdata_ptrというポインタを設定する。

関数ポインタの意味がわからなかったのだが、これはパスワード入力要求の関数である。

この関数にはメッセージの個数を表す変数と、pam_message構造体とpam_response構造体の二重ポインタと、さっき指定したappdata_ptrを受け取るポインタが引数として入力される。

二重ポインタってなんぞと思ったら、pam_responseには自分でcallocとかで確保したポインタを入れるんですって。

それで、その構造体のrespの文字列ポインタにも自分でcallocで確保したポインタを入れるんですって。

freeするのはPAMの方でやってくれるらしい。最初意味がわからなかった。

結果的には標準入力から受け取ったものを確保したメモリ上に投げ込んでるだけである。

最後に判定結果を RESULT:SUCCESS: のように出力して、これを見て判定結果とすると。

コンパイルはこんな感じでやる。-lpamでPAMとリンクされると。

$ gcc pamtest.c -o pamtest -lpam

あとはPHPからこのコマンドをsudo付きで実行すればOKと。

proc_open (PHP)

標準入出力でパスワード・認証結果のやりとりをすることにしたのでこれで。

結果を見てSUCCESSなら処理を進めて、違えば処理を止めると。

TOTPとパスワードが一致すると通り、どちらか合わないとNGとなるということでやりたいことは実現できた。


あえてLinuxのログイン情報をWebの認証手段に使う理由はあまりないと思うが、

とある事情によりこういう形にしたかったところがある。

(一般の人がアクセスするところではないところで使っている)

root権限が必要なのは事実だが、これなら比較的抵抗感は低いかな。

SSH鍵の持ち合わせが無いときのSSHアクセス手段という、

非常に微妙な用途でしか使ってなかったTOTPに新しい用途ができたのはよかったかな。

そのためにこんなことをやるのか? という話はあるが。