2010年02月06日
Debian JP LDAPとGPG署名メールによる自動処理
Debian JP Project で管轄しているサーバのユーザ管理は、LDAPというディレクトリサービスが担っています。 これまでこの保守は鵜飼さんが担当されていて、管理するマシンも鵜飼さんの自宅サーバだったために協業もしにくい状況だったのですが、もともと忙しいところにGoogleに入られてからはますます多忙を極めて手が回らなくなってきていたため、チームによる引き継ぎが必要となっていました。
LDAPの場合はFTPと違ってサービス内容はそれほどおおがかりなものが必要ないことから、仮想化環境に引越そうという話はずっとあったものの、仮想化の親環境の準備で伸び伸びになっていました。しかし、前回のBug Squash Party@Debian勉強会で必要な設定作業を完了して目途が立ったため、移行の手続きをいろいろと進め、一部の作業については自動化できるようにしました。 仮想化環境はG15アソシエーションによるスポンサードです。
LDAPのサーバにはOpenLDAPを使用しています。古いバージョンからの移行の苦労についてはBug Squash Partyの記事に書いたとおりですが、ひとまずこれは移行できました。
LDAPの運用では、LDAPサーバのアカウント情報に直接(あるいはせいぜいキャッシュ程度)アクセスして認証等を行う設計が多いと思われますが、Debian JPの場合はLDAPサーバが単一障害点にならないよう、定期的なアカウント情報伝播を行っています。
具体的にはLDAPサーバdb.debian.or.jp側でアカウント系のファイル(passwd/shadow/group/alias/sshpubkeys)を生成し、これを*.debian.or.jpの各サーバがrsync+sshで取得して使用します。passwd/shadow/groupについてはlibnss-dbを使います。/var/lib/misc内で平文ファイルからBerkeley DBに変換して、nsswitch.confで参照します(「passwd: compat db」のようにする)。aliasについてはnewaliasesを実行するだけですね。sshについてはDebian.orgでは単純に上書きしちゃっているのですが、Debian JPでは鵜飼さんのスクリプトでもうちょっと高度にいじっています。Debian JPの会員はマシンアクセス権限のある正会員と、メール転送のみの賛助会員に分かれており、これはLDAPのフィルタリングで対処します。このあたりはshスクリプト+Makefileで自動化されています。libnss-dbは便利なので、覚えておくとよいでしょう。似た仕組みは私はオフィスネットワークでも流用しています。
LDAP情報の変更については、人手を介するのをできるだけ排除し、ユーザができることはユーザに任せるポリシーを取ることにしました。とはいえ、それなりのユーザ認証は必要です。Debian.org/Debian JPで一番信用のある本人証明といえばGnuPGです。Debian.orgではこれを使って各種の設定をメールベースで行えるようになっているので、Debian JPでも真似てみることにしました。Debian.orgでの処理システムは私もアクセスできない領域にあるので、次に示していくような、私の独自設計です。まぁちゃんと動いてはいるみたいなのでよしとしましょう :-)。
メールベースの前に、とりあえずLDAPサーバにログインしてパスワードを変えるところから始めることにしました。ログインできる時点で認証は済んでいる(というか不正ログインされるようだったらもう「終わってる」のでそこで騒いだところであまり意味がない)ので、単純にldappasswdを動かすだけです。ただ生のldappasswdはDN情報や各種オプションをずらずら書かなければならず面倒すぎる、かといってPAMを使うのもあまり良い思い出がない(サーバが何らかの事情で止まるといろいろ悲しいことになる)、ということでラッパーで包むことにしました。
!/bin/sh
ME=$(whoami)
ldappasswd -x -W -D "uid=${ME},ドメイン" "uid=${ME},ドメイン" -S
単純ですね。Debian JPのアカウントDNはuid名と同じなのでこんな感じで出来上がりです。 これをdebianjp-ldappasswdというコマンドで/usr/local/binに突っ込んでおきました。 ldappasswdの問題としてはpasswdのように旧→新ではなく新→旧とパスワードを尋ねるのがちょっとややこしいのですが、これは目をつむることにします。
さて、メールベースの実装です。Rubyでフィルタスクリプトを作りました。ライブラリとしてはlibldap-rubyと標準net/smtp程度です。
一番簡単なパスワード変更(リセット)から考えます。簡単というのは、リクエストコンテンツを解析しなくてもとにかくランダムパスワードを作って割り当て、それを返せばいいからです。 ランダムパスワードはldappasswdコマンドでもできますが、慣れているmakepasswdコマンド(パッケージ)を使うことにしました。makepasswdはランダムなパスワードとそれをハッシュ化した値の両方を出力してくれるので、こういった処理を作るのに便利です。 管理者DNでバインド済みのldap.modifyを使って指定のuidのuserPasswordエントリをハッシュ値で変更し、パスワードをuid@debian.o.jに返すだけです(リクエストメールのFromではありません)。
さて、「指定のuid」を判断し、確かに変更してよいことを決めるために本人証明が必要になります。 ユーザ側は「echo "Please change password" | gpg --clearsign -a | mail dbadmin+password|email|ssh@db.debian.or.jp」の書式でメールを送ります。
もともと鵜飼さんが作っていた署名認証スクリプトもあった(クリア署名だけでなくMIME署名にも対応)のですが、スクリプトが多段になってわかりにくくなりそうだったので、類似のものを実装しました。
- 送られたファイルはまずgpg --verifyを行い、鍵リングにあるGPG鍵での正しいGPG署名かを検証します。ただ、RubyのネイティブGPGライブラリはいまいちわかりにくかったので、popen呼び出しというちょっとしょぼいコードになっています。
- verifyに成功したら、そのID部分を取り出し、鍵リングに対してそのフィンガープリントを要求します(gpg --fingerprint ID)。
- LDAPサーバのユーザ情報にはGPG IDではなく、フィンガープリントのほうが格納されています。ldap.searchを使って、調べたフィンガープリントを探します。
- マッチしたら、そのuid属性を取得します。
SSH公開鍵と転送設定はコンテンツを解析しなければならないので、パスワードよりは少し面倒です。とはいえ、単純なフォーマット(SSH公開鍵は各鍵を1行ずつ並べたもの、転送設定は転送先アドレスを記述したもの)なので、これについては特に説明するまでもないでしょう。中身を解析し、ldap.modifyで取り込みます。sshrsaauthkeyエントリは複数持てるのですが、ldap.modifyはどっちにしても(単一値でも)配列渡し限定なので特別な処理はあまりありません。ただ、解析の際にSSH2鍵のフォーマットになっているかどうかは確認するようにしています。転送設定も、validなメールアドレスかどうかの確認をしています。
なお、GPGの処理は重いので、実際にはメールが来るたびに即時処理をするということはせず、一旦スプールディレクトリに置きます。これはprocmailのMHフォルダ形式保存指定(「パス/.」)で済みます。スプール内のファイルを定期処理する際、無駄な処理にならないように、適切な宛先か、署名っぽいものが付いているかを調べてから、実際のGPG処理に移るようにしています。メール処理の振り分けも、Postfixのrecipient_delimiter機能(アカウント名+付値 でも届くようにする)と、procmailが活躍します。単純化した.procmailrcはこんな感じ。
SHELL=/bin/sh #LOGFILE=/tmp/procmail.log ←デバッグ用 :0 : * ^To: .*dbadmin\+(password|ssh|email)@db\.debian\.or\.jp スプールパス/. :0 : /dev/null ←ひっかからなければ即ステ
スプールの定期処理はこんな感じ。
#!/bin/sh
find スプールパス -name "[0-9]*" -exec スクリプトファイル {} \;
ということで、最後に全体図を示してまとめとしましょう。procmailのようなMDAの応用方法を覚えておくと、メールベースの自動処理を構築するのに役立ちます。procmail自体は変態言語なので面倒なところもありますが、maildropやrdeliverなどもあるので、試してみるとよいでしょう。
![[hatena]](http://d.hatena.ne.jp/images/b_entry_de.gif)
![[RSS]](/d/rss10.png)
Debian GNU/Linux徹底入門 Sarge対応
Debian辞典