LDAP認証統合 nss_ldap と pam_ldap を導入

実は UNIX系システムは 認証の仕組みを柔軟に切り替えられます

認証の仕組みというのは アカウントやパスワードを格納するバックエンドです
慣例として アカウントやパスワードの設定方法といえば
  • アカウント情報は /etc/passwd に格納されている
  • アカウント情報は vipw のコマンドで編集する
  • グループ情報は /etc/group に格納されている
  • グループ情報は vigr のコマンドで編集する
  • アカウントのパスワードは passwd コマンドにより修正する
通常は アカウントや グループなどの管理/運用は 名前解決(NS)と呼ばれ
/etc/ 以下のファイルで行われるのが通常です
名前解決は 実は nsswitch という libcの機構があり バックエンドを切り替えることができます

名前解決 nsswitchの構成

名前解決を行うのは getentコマンドなど libcの get〜()関数を利用するツールです
具体的には gethostbyname() getpwuid() getpwnam() getaddrinfo() などの列挙関数です
これらの関数は libnss_〜.soプラグインを切り替えて使えるように設計されています

例えば libnss_files.so は アカウント情報を /etc/passwd から取ってきます
例えば libnss_ldap.so は アカウント情報を LDAPから取ってきます
今回の目的は 通常使う filesのバックエンド以外にも LDAPのバックエンドを追加することで
telnetで LDAPアカウントのログインができることを目標にします

絵でいえば 赤い libnss_ldap.so と pam_ldap.so が追加になります
なぜ pam_ldap.so も追加する必要があるかというと telnetd や login といった
実際のアプリケーションが 認証に PAM(公式でないがここが詳しい)を利用しているためです
$ ldd `which login`
    linux-gate.so.1 =>  (0xb7fc0000)
    libpam_misc.so.0 => /usr/local/lib/libpam_misc.so.0 (0xb7fbc000)
    libpam.so.0 => /usr/local/lib/libpam.so.0 (0xb7fb0000)
    libdl.so.2 => /usr/glibc/2.5/i686/lib/libdl.so.2 (0xb7fab000)
    libc.so.6 => /lib/libc.so.6 (0xb7e7e000)
    libprelude.so.2 => /usr/local/lib/libprelude.so.2 (0xb7e0e000)
    libgnutls.so.13 => /usr/local/lib/libgnutls.so.13 (0xb7d7c000)
    libgcrypt.so.11 => /usr/local/lib/libgcrypt.so.11 (0xb7d2b000)
    libgpg-error.so.0 => /usr/local/lib/libgpg-error.so.0 (0xb7d27000)
    librt.so.1 => /usr/glibc/2.5/i686/lib/librt.so.1 (0xb7d11000)
    /lib/ld-linux.so.2 (0xb7fc1000)
    libpthread.so.0 => /usr/glibc/2.5/i686/lib/libpthread.so.0 (0xb7cfa000)
    libnsl.so.1 => /usr/glibc/2.5/i686/lib/libnsl.so.1 (0xb7ce4000)
    libz.so.1 => /usr/local/lib/libz.so.1 (0xb7cd0000)
で分かるように libpam.so が使われており PAMによる認証が行われていることが分かります
結局 今回の作業の流れとしては
  • LDAPの導入と アカウント情報の登録
  • libnss_ldap.so の導入
  • pam_ldap.so の導入
となります

LDAPの導入と アカウント情報の登録

LDAPとは ディレクトリサービスを提供するいわばデータベースです
Windowsでは Windows2000から ActiveDirectory という LDAPに準じた機能があり
その意味で UNIX Windows 統合バックエンドとしての可能性に脚光が集まりました
ここでは OpenLDAP の導入手順を紹介します
(OpenLDAPのソフトウェア構成)

  • slapd OpenLDAPの常駐サービスです
  • slurp LDAPのレプリケーション構築用常駐ツールです スレーブLDAPに同期情報を転送します
  • ldapsearch ldapadd ldapdelete ldapmodify クライアントツール データ操作を行います
  • slapd.conf slapdの設定ファイルです
  • ldap.conf /etc/ 以下に置かれるクライアント設定ファイル
  • 〜.schema データフォーマットを定義するスキーマファイル 用途に応じていくつかあります

OpenLDAP 自身のインストールについては省略します
パッケージからインストールするか ソースファイルをビルドしてインストールすることもできます

slapdの基本設定

slapd.confの設定です

# (1) スキーマインクルード
include         /usr/local/etc/openldap/schema/core.schema
include         /usr/local/etc/openldap/schema/cosine.schema
include         /usr/local/etc/openldap/schema/ppolicy.schema
include         /usr/local/etc/openldap/schema/nis.schema

# (2) ディレクトリ情報の基本設定
suffix          "dc=local"
rootdn          "cn=Manager,dc=local"
rootpw          testpass

# (3) バックエンドの基本設定
database        bdb
directory       /usr/local/var/openldap-data
pidfile         /var/run/slapd.pid
argsfile        /var/run/slapd.args

# (4) データへのアクセス権
access to attrs=userPassword
    by self write
    by dn="cn=Manager,dc=local" write
    by anonymous auth
    by * none

access to *
    by self write
    by dn="cn=Manager,dc=local" write
    by * read
 

(1)のインクルードファイルは OpenLDAPインストール時は core.schema のみですが
今回の アカウント管理用のスキーム定義 nis.schema を入れるための必要なスキーマを追加しています

(2)の設定においては
LDAPが 分散ディレクトリ管理(ネットワーク経由で 各LDAPサーバが管理情報を分散して管理)であること
に由来します つまりグローバルなネットワークにおける 自分自身の DN(ドメインみたいなもの) を決める必要があります
今回の LDAPの使いかたはグローバルにさらさない ローカル用途のみですので
DNを 「dc=local」 としました 気を使って「dc=ldaproot,dc=xxx-coopation,dc=co,dc=jp」みたいな
混み合った名称にする必要ありません

また rootdn とは LDAPの管理者アカウントです ここでは 「cn=Manager,dc=local」を設定しています
もっとセンスのよい名前でもよいでしょう

(3)はデータベースファイルの指定や プロセスのPID情報の書きだし先を指定します
特にインストール時の設定のままで修正する必要はありません

(4)はデータに対する 読み書きアクセス権限の設定です
今回導入する アカウント情報の管理において userPassword属性だけは参照もできないようにします
文法としては
「access to」 で データエントリ条件毎に設定します
「by アクセス元指定 write(読書可)|read(読のみ)|auth(認証)|none(アクセス不可)」
で アクセス元毎にアクセス可否を設定します
アクセス元には 以下のいずれかを指定します
「dn=”指定”」 (LDAPアカウント指定)
「self」 (アクセスアカウント)
「users」 (認証済)
「anonymous」 (認証前)
「*」 (その他)
「access to 〜 by 〜」は複数指定でき 最初に条件が一致したルールが適用されます

slapd.confの設定が終わったら ためしに slapdを起動してみます

$ su
# /usr/local/libexec/slapd
# ps ax | grep slapd
/usr/local/libexec/slapd
 

登録するデータは レコード単位で エントリと呼ばれます

  • エントリの IDキーにあたるものは DN(Distinguished Name)と呼ばれ ‘ou=People,dc=local’ のような名前を与えます
  • エントリは DNの名付けかたにより階層構造(親子の関係)をとることができます
  • エントリには dn以外に属性を与えることができます
  • 属性名や属性フォーマット は スキーマと呼ばれる定義ファイルで規定されます
  • エントリは (どのスキーマに属するかの)クラス情報を(複数)持てます objectClass と呼ばれる属性で設定します

エントリを 1つ追加してみます ldapaddコマンドを使います

$ ldapadd -D 'cn=Manager,dc=local' -w testpass
dn: dc=local
objectClass: dcObject
objectClass: organization
dc: local
o: local
description: LDAP Root Directory
(ここで 空行を入れる)
adding new entry "dc=local"
(ここで Ctrl+D を入れる)
$
 

ldapadd の オプションは以下の通りです ldapsearch ldapdelete ldapmodify でも共通です

-D ‘DN指定’ slapdにバインドするアカウントを指定します
-w パスワード 上記アカウントに対するパスワードです

登録したのは 「dc=local」つまりルートエントリです
ldapadd を実行すると 標準入力が開いて テキスト文章を直接入力することができます
複数行を入力していますが dn: が DN名 残りの行が属性設定となります
エントリに必要な情報を入力終えたら 空行 を1行入れることで 実際に LDAPへの登録が行われます

この後 続けて 別エントリのデータを入力することができます
このように 空白行がエントリ区切りとなるフォーマットを LDIF (LDAP Data Interchange Format) と言います

登録されたことを ldapsearchで確認します

$ ldapsearch
# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# local
dn: dc=local
objectClass: dcObject
objectClass: organization
dc: local
o: local
description: LDAP Root Directory

# numResponses: 2
# numEntries: 1
 

slapd.confで設定したとおり データの参照は 管理者権限は必要ありませんが
結果コメント行に ちらっと情報があるように 「ldapsearch」は実は
「ldapsearch -s subtree -b ‘dc=local’ ‘(objectClass=*)’ ‘*’」 の省略形です
-s subtree は 検索スコープで 検索範囲を指定します 以下のようなものがあります
base (ベースエントリ検索)
one (1レベル検索)
subtree (サブツリー検索)
children (子エントリ検索)
-b ‘dc=local’ は 検索の起点となるベースDN を指定します 省略時 slapd.conf の suffixが入ったため dc=local となってます
次の ‘(objectClass=*)’ は 検索フィルタ条件の指定です
objcectClass属性は全てのエントリがなんらかの値を持つので つまり 全てのエントリが該当することになります
(dn を検索フィルタ条件に指定できないので objectClass属性となっているのかもしれません)
最後の ‘*’ は 検索に引っかかったエントリのどの属性情報を表示するかの指定です
dn以外のどの属性を表示するかを 空白区切りで指定できますが ‘*’で全項目を指定しています

ひととおりデータの 挿入 と 検索 ができることが確認できました
実際に アカウント管理を行うためのエントリを定義します nis.schema で定義される管理方法です

$ ldapadd -D 'cn=Manager,dc=local' -w testpass
dn: ou=Groups,dc=local
objectClass: organizationalUnit
ou: Groups

dn: ou=Users,dc=local
objectClass: organizationalUnit
ou: Users

dn: cn=ldapuser,ou=Groups,dc=local
objectClass: posixGroup
cn: ldapuser
gidNumber: 500

dn: uid=test,ou=Users,dc=local
uid: test
cn: test
objectClass: account
objectClass: posixAccount
userPassword: testpass2
uidNumber: 550
gidNumber: 500
homeDirectory: /home/test
loginShell: /usr/local/bin/bash
gecos: test account

(ここで Ctrl+D)

Groupsという グループ管理ディレクトリ と Usersという アカウント管理ディレクトリを作り
test というアカウント情報を登録しました
ldapsearch で 登録どおりの情報が返ることを確認できますが もう 1点 重要な確認事項があります
「userPassword」の属性が見えないことを確認してください
先の slapd.confで設定したとおり anonymous状態では参照できないようになっているはずです
(もちろん 「cn=Manager,dc=local」でバインドすれば見えます)

libnss_ldap.so の導入

LDAPへは testアカウントを登録しましたが
「getent passwd」のコマンドで testアカウントの情報はまだ見えません
nsswitch に nss_ldap の機能を追加する必要があります

ここ から libnss_ldap のパッケージを取得して

$ tar -xzf nss_ldap.tgz
$ cd nss_ldap-260
$ ./configure
〜
configure: creating ./config.status
config.status: creating Makefile
config.status: creating config.h
config.status: executing depfiles commands
$ make
make  all-am
make[1]: Entering directory `/home/admin/nss_ldap-260'
〜
$ su
# cp nss_ldap.so /usr/glibc/2.5/i686/lib/libnss_ldap.so.2
# ln -s libnss_ldap.so.2 /usr/glibc/2.5/i686/lib/libnss_ldap.so
 

libnss_ldap.so.2 は GLIBCの lib/ ディレクトリに入れます
手元の環境は /usr/glibc/2.5/i686 ですが大抵の人は /usr でしょう
最後に /etc/nsswitch.conf で libnss_ldap.so を使う設定を入れます

passwd:      files
shadow:      files
group:       files
 

のそれぞれのエントリに “nss_ldap”の設定を追加します

passwd:      files nss_ldap
shadow:      files nss_ldap
group:       files nss_ldap
 

次のように testアカウントの存在を確認します

$ getent passwd
〜
vpopmail:x:89:89::/var/vpopmail:/bin/false
alias:x:200:200::/var/qmail/alias:/bin/false
postfix:x:207:207:postfix:/var/spool/postfix:/bin/false
smmsp:x:209:209:smmsp:/var/spool/mqueue:/bin/false
portage:x:250:250:portage:/home/portage:/bin/false
nobody:x:65534:65534:nobody:/:/bin/false
test::550:500:test account:/home/test:/usr/local/bin/bash
 

testアカウントが見えていれば OKです

pam_ldap.so の導入

telnet ftp などパスワードを要求する 多くのプログラムは PAM の機構を導入してます
PAMに pam_ldap の機能を追加します

ここ から pam_ldap のパッケージを取得して

$ tar -xzf pam_ldap.tgz
$ cd pam_ldap-184
$ ./configure
〜
updating cache ./config.cache
creating ./config.status
creating Makefile
creating config.h
$ make
gcc -DHAVE_CONFIG_H   -DLDAP_REFERRALS -DLDAP_DEPRECATED -D_REENTRANT  -g -O2 -Wall -fPIC -c pam_ldap.c
〜
gcc  -g -O2 -Wall -fPIC   -o pam_ldap.so -shared -Wl,-Bdynamic -Wl,--version-script,./exports.linux pam_ldap.o md5.o  -lldap -llber -lnsl -lcrypt -lresolv -lpam -ldl
$ su
# cp pam_ldap.so /usr/local/lib/security/
# cp pam_ldap.5 /usr/local/man/man5/
 

本来 make install でインストールすべきところですが
ldap.conf を不意に上書きするおそれがあったので あえて手動インストールしています

PAMの /etc/pam.d/system-auth に ldapの設定を追加します

#%PAM-1.0

auth       required     pam_env.so
auth       sufficient   pam_unix.so likeauth nullok
auth       sufficient   pam_ldap.so use_first_pass
auth       required     pam_deny.so

account    sufficient   pam_unix.so
account    sufficient   pam_ldap.so
account    required     pam_deny.so

password   required     pam_cracklib.so retry=3
password   sufficient   pam_ldap.so try_first_pass echo_pass
password   sufficient   pam_unix.so nullok md5 shadow use_authtok
password   required     pam_deny.so

session    required     pam_limits.so
session    required     pam_unix.so
 

pam_ldap.so を使う 3行を追記します
これで testアカウントで認証ができるはずです

$ telnet localhost
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.

Linux 2.6.24 (localhost) (pts/5)


turion.local login: test
Password:
 

これで 認証されれば OKです

OpenLDAPのアカウント管理は さらに Sambaの認証バックエンドとしても使えるらしいのですが
手元の環境では 動作確認まで至りませんでした
(「NT_STATUS_LOGON_FAILURE」と出て終了)