第1回から紹介してきたLDAPによるUNIXアカウント管理ですが、実際に運用する上ではまだまだ問題が残っています。
MigrationToolsによる既存ユーザのインポート
前回までは手動でLDIFファイルを作成し、そのデータをLDAPサーバへインポートしたわけですが、/etc/passwdや/etc/groupをLDAPに移行させるための便利なツールがあります。それがMigrationTools です.
これらは/etc以下にあるパスワード、グループ、ホスト、サービス情報などをLDAPに移行させるためのLDIFを作成してくれるツールで、Perlスクリプトの集合体となっています。試しに既存の/etc/passwd、/etc/group、/etc/hostsの情報をLDIF形式にしてみましょう(図1 )。事前に設定ファイルとなるmigrate_common.ph を編集したり、適切な環境変数を定義することで作成されるLDIFのBASE_DNなどの値を変更することができます。
図1 MigrationToolsを使用したLDIFの作成
% tar zxfv MigrationTools.tgz
% cd MigrationTools-47
# export LDAP_BASEDN="dc=example,dc=com"
# ./migrate_passwd.pl /etc/passwd passwd.ldif
# ./migrate_group.pl /etc/group group.ldif
# ./migrate_hosts.pl /etc/hosts hosts.ldif
カレントディレクトリにpasswd.ldif、group.ldif、hosts.ldif が作成されましたので、それぞれのエントリを追加してみましょう。現在はグループ、ホスト情報用の親エントリがない状態ですので、リスト1 のような親エントリを登録した後それぞれを登録します(図2 )。
リスト1 base.ldif
dn: dc=example,dc=com
objectClass: dcObject
objectClass: organization
dc: example
o: example
dn: ou=People,dc=example,dc=com
objectClass: organizationalUnit
ou: People
dn: ou=Group,dc=example,dc=com
objectClass: organizationalUnit
ou: Group
dn: ou=Hosts,dc=example,dc=com
objectClass: organizationalUnit
ou: Hosts
図2 LDIFファイルのインポート
% ldapadd -x -c -D "cn=manager,dc=example,dc=com" -w secret -f base.ldif
% ldapadd -x -c -D "cn=manager,dc=example,dc=com" -w secret -f passwd.ldif
% ldapadd -x -c -D "cn=manager,dc=example,dc=com" -w secret -f group.ldif
% ldapadd -x -c -D "cn=manager,dc=example,dc=com" -w secret -f hosts.ldif
注意点として、migrate_passwd.plは/etc/shadow中の暗号化されたパスワードエントリの頭に{crypt} という文字をつけてuserPassword属性の値とします。したがって、/etc/shadow中にMD5形式のパスワードが定義されていた場合でも、
userPassword: {crypt}$1$YBwRQlei$Lm.vrBe8AUFoxjcka35wT/
のように、cryptと定義してあるのに実際の値はMD5、と、つじつまの合わない形で出力されてしまうため、必要に応じてuserPassword属性の値を平文、または暗号化された形式に修正すべきです。参考までに、エンコード後の文字列を作成するにはslappasswdコマンド を使用します(表1 参照)。
表1 暗号方式によるコマンドの違い
形式 コマンド例
CRYPT形式 slappasswd -h '{CRYPT}' -s パスワード文字
MD5形式 slappasswd -h '{MD5}' -s パスワード文字
SMD5形式 slappasswd -h '{SMD5}' -s パスワード文字
SSHA形式 slappasswd -h '{SSHA}' -s パスワード文字
SHA形式 slappasswd -h '{SHA}' -s パスワード文字
平文 特になし
詳細はslappasswdコマンドのマニュアルを参照してみてください。
nscdによるキャッシング
少し話は変わりますが、idコマンド を使うと/etc/nsswitch.confに定義されたネームサービス情報を元にユーザ情報を表示させることができます。たとえばリスト2 のようなシェルスクリプトを実行すると、LDAP上に登録されたldapuserの情報を100回表示します。
リスト2 ユーザ情報を100回表示させるスクリプト(count.sh)
#!/bin/sh
i=1
while [ $i -le 100 ]; do
id ldapuser
i=`expr $i + 1`
done
実際にこのスクリプトを実行してみるとわかりますが、LDAP上のユーザ情報を取得するためには、思いの外時間がかかってしまうことに気がつくはずです(図3、4参照) 。
図3 /etc/nsswitch.confにてLDAP検索が有効な場合
# time ./count.sh
real 0m8.450s
user 0m0.911s
sys 0m6.531s
図4 /etc/nsswitch.confにてLDAP検索が無効な場合
# time ./count.sh
real 0m3.586s
user 0m0.382s
sys 0m3.199s
この問題は、ユーザ情報をある程度メモリ上にキャッシュしておけば防止することができます。そのためのデーモンがnscd(Name Service Cache Daemon) です.nscdを使用すると、最初の情報取得時にはLDAPサーバから検索を行い、以降はキャッシュされた値を使用することができます(図5 )。
図5 nscdを有効化した状態でcount.sh(リスト2)を実行
# /etc/init.d/nscd start
# chkconfig nscd on
# time ./count.sh
real 0m3.191s
user 0m0.361s
sys 0m2.578s
このように、キャッシュによって速度的な問題がほぼ解消されたことがわかります.nscdの設定ファイルは/etc/nscd.conf です。一般的な運用であればデフォルトの状態から編集する必要はないかもしれませんが、設定も可能であるということを頭の片隅に置いておきましょう。
インデックス設定
ディレクトリエントリ数が少ない場合にはとくに問題ないのですが、データが多くなるにつれ大きなパフォーマンスダウンにつながるのが、インデックス関連の設定です。リスト3 のスクリプトを図6 のコマンドのように使うと、ダミーエントリを5,000個作成します。
リスト3 add.sh
#!/bin/sh
i=1
while [ $i -le 5000 ]; do
echo "dn: uid=ldapuser$i,ou=People,dc=example,dc=com"
echo "objectClass: account"
echo "objectClass: posixAccount"
echo "uid: ldapuser$i"
echo "cn: ldapuser"
echo "userPassword: ldapuser$i"
echo "loginShell: /bin/bash"
echo "uidNumber: 10$i"
echo "gidNumber: 10$i"
echo "homeDirectory: /home/ldapuser$i"
echo ""
i=`expr $i + 1`
done
図6 ダミーエントリの登録
% ./add.sh | ldapadd -x -c -D "cn=manager,dc=example,dc=com" -w secret
この状態で再びidコマンドを使って適当なエントリの情報を取得してみます(図7 )。
図7 idコマンドの実行
% time id ldapuser996
uid=10996(ldapuser996) gid=10996 groups=10996
real 0m10.339s
user 0m0.002s
sys 0m0.055s
なんと、1ユーザのエントリを取得するのに約10秒もかかってしまいました.nscdが有効であるため、再び同じユーザ情報を得る場合にはそれほど時間がかかりませんが、最初の検索で10秒というのは大きな問題です。
LDAP、RDBMSにせよ、全文検索にしてもそうなのですが、インデックスとは検索を高速化するための効果的な手段です。いくらLDAPが検索重視のプロトコルだと言っても、インデックスを含め、適切な設定が行われていないサーバでは、その恩恵を実感することができません。
ということで、インデックスの設定を行ってみましょう。インデックスとは作成しさえすれば良いものではありません。不必要なインデックスは、ディスクを圧迫したり、かえってパフォーマンスダウンにつながる恐れもあるためです。用途に応じて適切なインデックスを作成する、これが正しい選択です。
今回問題となっているのは、idコマンドを実行した場合です。それではidコマンドを使用した場合にLDAPサーバ側ではどのようなエントリが検索されているのでしょうか?それはサーバ側のログを見ることで把握できます。
さて、ログを見るといっても、CentOSの場合/etc/syslog.confに適切な設定が存在しないため、そのままでは検索時のログを取得することができません.slapdはデフォルトでlocal4というファシリティを使用するため、LDAPサーバ側のsyslog.confをリスト4 のように編集し、デーモンを再起動しておいてください(図8 )。
リスト4 /etc/syslog.confに追加する内容
local4.* /var/log/ldap.log
図8 ログファイルの作成とsyslogdの再起動
# touch /var/log/ldap.log
# chmod 600 /var/log/ldap.log
# /etc/init.d/syslog restart
syslog.confの設定が完了したら、まずは現在の設定のままで再びidコマンドを実行し、サーバ側でどのような検索が行われているのかを把握しておきます.LDAPサーバへ確実に接続できるよう、クライアント側のnscdは一時的に無効にしておくことをお勧めします。
それではidコマンドを実行しつつ、ldap.logの中身を確認してみます(図9、リスト5 )。
図9 idコマンドの実行
% id ldapuser
/euid=1000(ldapuser) gid=1000 groups=1000
リスト5 /var/log/ldap.logの一部
Jul 15 17:19:31 localhost slapd[11014]:
このように、objectClassやuid属性などのインデックス検索に失敗していることがわかりますので、それぞれのインデックス設定を行います。インデックス設定には表2 の項目が用意されています。
表2 インデックスの種類
設定値 意味 検索例
pres 存在 uid=*
eq 等価 uid=ldapuser
approx 近似 uid=ldapusr
sub 部分一致 uid=ldap*
none 無し -
ldap.logからfilterというキーワードを検索することで、今回LDAPサーバでは次のような検索が行われています。
filter="(&(objectClass=posixAccount)(uid=ldapuser))"
filter="(&(objectClass=posixAccount)(uidNumber=1000))"
filter="(&(objectClass=posixGroup)(gidNumber=1000))"
filter="(&(objectClass=posixGroup)(|(memberUid=ldapuser)(uniqueMember=uid=ldapuser,ou=people,dc=example,dc=com)))"
これにより、今回の検索ではobjectClass、uid、uidNumber、gidNumber、memberUid、uniqueMember属性に対して等価検索が行われていることがわかるはずです。ではこれに従ってLDAPサーバ側のslapd.confに設定を追加します(リスト6 )。
リスト6 slapd.confに追加する内容
index objectClass,uid,uidNumber,gidNumber,memberUid,uniqueMember eq
このように、インデックスを適切に設定した上でslapdを停止、slapindexコマンドでインデックスを作成した後各ファイルのパーミッションを適切に設定、slapd起動、という順序で検索を高速化することが出来ます。ただ、今回検証に用いたCentOSでは
equality index of attribute "uniqueMember" disallowed
というエラーが表示され、slapdの起動に失敗してしまいました。どうやらこのバージョンでは文字通りこのインデックスに対する処理がインプリされていないようです。少々気持ちが悪いかもしれませんが、実際にはnss_ldapにおいては、uidやuidNumberなどによる検索でエントリが発見されれば、uniqueMemberを使った検索は当面現れません。したがって、今回は
index objectClass,uid,uidNumber,gidNumber,memberUid eq
というインデックスのみを設定してみました。ちなみに最近の新しいバージョンのOpenLDAPではこのエラーは出ないことを確認しています(図10、11 )。
図10 LDAPサーバ側での作業
# /etc/init.d/ldap stop
# slapindex
# chown -R ldap:ldap /var/lib/ldap
# /etc/init.d/ldap start
図11 クライアントからのidコマンド
# time id ldapuser3008
uid=103008(ldapuser3008) gid=103008 groups=103008
real 0m0.141s
user 0m0.021s
sys 0m0.102s
最終的には、ログ中にインデックス関連の警告が出なくなるまで設定を行ってみてください。
パスワードの暗号化
今までは便宜的にslapd.confやデータベース中になどに平文のパスワードを定義してきました。当然実際の運用では復号不可能なパスワードを定義し、適切なアクセス権限を付与しておくべきです.slapd.confをリスト7 のように書き換えましょう。例のごとく暗号化されたパスワードはslappasswdコマンドを使って作成します。
リスト7 slapd.confを修正
rootpw {SSHA}te0mm46b0qnQr7C/K8cWD56qi/8KhKSK
access to attrs=userPassword
by anonymous auth
by self write
by * none
access to *
by * read
accessからはじまる部分はACL設定 です。1行では長くなってしまいわかりにくいので、通常このように複数行で定義を行います。最初の定義の意味は、userPassword属性に関して匿名バインド、つまり認証無しではアクセス禁止とし、バインドしたユーザ自身であればread/writeを許可という形です.2番目の設定は、それ以外の属性については全員にreadのみ許可、という形です.ACL設定の書式はいくつかあるので、今後の連載でも紹介できていけたらと思います。
チェック項目
LDAPサーバでUNIXアカウントを管理するための簡単なまとめです。これ以外でも冗長化対策や細かい点で改善すべき箇所はあるでしょうが、後々の連載で触れていきたいと思います。
サーバ側で適切なACLは設定されているか
サーバ側でパスワードは暗号化されているか
クライアント側でnscdは動いているか
LDAP上のユーザ名を用いてsshなどによるログインが可能か
suで他ユーザにスイッチできるか
passwdコマンドでLDAP上のパスワードを変更可能か
それでは次回をお楽しみに。