既に説明した通り、LDAPをユーザ認証に使うことができます。簡単にいえばLDAPを/etc/passwdやNISのかわりに使うことができるわけです。
LDAPをユーザ認証に使う場合、LDAPにユーザのアカウント情報を格納しておくことになります。LDAPではアカウント情報はユーザごとにディレクトリエントリを作ることで表現されます。
まずは簡単な例を説明しましょう。リスト1のようなpasswdエントリをもつアカウント情報をLDAPに格納しようとしているとします。これはリスト2のような情報をあらわしています。
foo:x:1001:1001:foo,,,:/home/foo:/bin/bash
ユーザ名 foo ユーザID 1001 グループID 1001 GECOSフィールド foo,,, ホームディレクトリ /home/foo ログインシェル /bin/bash
これをLDAPに格納する時はLDIF形式を使ってあらわすとリスト3になります。ドメインはこれまでに説明したようにexample.jpを使っており、人に関する情報はou=Peopleという相対識別名(RDN)でつくられるディレクトリツリーに格納することとします。ouというのはorganizationalUnitの略で組織における部局などを表現するための属性です。
dn: uid=foo,ou=People,dc=example,dc=jp uid: foo objectclass: posixAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar
どのようなディレクトリエントリであっても識別名(DN)が決まっていなければいけません。識別名(DN)を構成する相対識別名(RDN)はどの属性を使ってもかまいませんが、その相対識別名(RDN)により識別名(DN)が一意になるような属性を使う必要があります。ここではユーザ名で一意に決まるものとしてユーザ名をあらわすuidという属性を相対識別名(RDN)としています。uidのかわりにユーザID(uidNumber)などを使う場合もありえます。
属性に関してはリスト2とリスト3を見比べるとわかると思いますが、ほぼ一対一に対応しています。違うところはobjectclassとnがあるところです。objectclassというのは既に説明したとおり、このディレクトリエントリがどのような情報であるかをあらわすための属性です。ここではこのディレクトリエントリはアカウント情報をあらわしているのでposixAccountというobjectclassを使っています。
OpenLDAP 2.0.xでは、objectclassはschemaファイルで定義されています。定義されていないobjectclassは使うことができません。objectclass posixAccountはnis.schemaに定義されています(リスト4)。prefix=/usr/localでインストールしているとこのファイルは/usr/local/etc/openldap/schema/nis.schemaにインストールされているはずです。この定義によるとobjectclass posixAccountはcn属性(common name; 一般名)を持たないといけない(MUST)ため、このディレクトリエントリにはcn属性を登録しています。
objectclass ( 1.3.6.1.1.1.2.0 NAME 'posixAccount' SUP top AUXILIARY DESC 'Abstraction of an account with POSIX attributes' MUST ( cn $ uid $ uidNumber $ gidNumber $ homeDirectory ) MAY ( userPassword $ loginShell $ gecos $ description ) )
passwdの情報とLDIFの対応をまとめると図1のようになります。
| foo:x:1001:1001:foo,,,:/home/foo:/bin/bash | | | | | | | | | | | | | | | loginShell | | | | | | homeDirectory | | | | | gecos | | | | gidNumber | | | uidNumber | | (userPasswordだがshadowを使っている場合は/etc/shadowの方になる) | uid objectclass: posixAccount cn (common name; 一般名) ユーザ名 uid ユーザID uidNumber グループID gidNumber GECOSフィールド gecos ホームディレクトリ homeDirectory ログインシェル loginShell
最近のUNIXではパスワードに関してはshadowファイルに格納されています。これもLDAPで管理することができます。
shadowファイルではパスワードはリスト5のように格納されています。これはリスト6のような意味をもっています。
foo:SAUcuDpGYyyD2:10953:0:99999:7: : :
ユーザ名 foo 暗号化されたパスワード (crypt) Rlth8/fbmfjD2 最終パスワード変更日時 10953 パスワード変更不能日数 0 パスワード変更要求迄の日数 99999 パスワード期限満了警告日数 7 アカウント無効までの日数 (なし) アカウント期限満了の日付 (なし) フラグ(将来の使用に予約) (なし)
これをLDAPに格納する時はLDIF形式を使ってあらわすとリスト7になります。普通はアカウント情報とあわせてパスワード情報を管理するのでリスト3の情報とあわせてあります。
dn: uid=foo,ou=people,dc=example,dc=jp uid: foo objectclass: posixAccount objectclass: shadowAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar userPassword: {CRYPT}SAUcuDpGYyyD2 shadowLastChange: 10953 shadowMin: 0 shadowMax: 99999 shadowWarning: 7
passwdはobjectclass posixAccountを使いましたがshadowに関してはobjectclass shadowAccountを使います。objectclass shadowAccountもobjectclass posixAccountと同様nis.schemaに定義されています(リスト8)。
objectclass ( 1.3.6.1.1.1.2.1 NAME 'shadowAccount' SUP top AUXILIARY DESC 'Additional attributes for shadow passwords' MUST uid MAY ( userPassword $ shadowLastChange $ shadowMin $ shadowMax $ shadowWarning $ shadowInactive $ shadowExpire $ shadowFlag $ description ) )
shadowの情報とLDIFの対応をまとめると図2のようになります。
| foo:SAUcuDpGYyyD2:10953:0:99999:7: : : | | | | | | | | | | | | | | | | | | | shadowFlag | | | | | | | | shadowExpire | | | | | | | shadowInactive | | | | | | shadowWarning | | | | | shadowMax | | | | shadowMin | | | shadowLastChange | | userPassword | uid ユーザ名 uid 暗号化されたパスワード (crypt) userPassword 最終パスワード変更日時 shadowLastChange パスワード変更不能日数 shadowMin パスワード変更要求迄の日数 shadowMax パスワード期限満了警告日数 shadowWarning アカウント無効までの日数 shadowInactive アカウント期限満了の日付 shadowExpire フラグ(将来の使用に予約) shadowFlag
userPassword属性だけが特殊な記述方法になります。userPassword属性には様々な暗号化方法を使って暗号化された文字列を格納できるようになっています。どの暗号化方法を使ったかをあらわすために文字列の最初に{}の中に暗号化方法を書くようになっています。UNIXのpasswd,shadowでつかっている暗号化方法は{CRYPT}になります。他に{MD5}、{SMD5}、{SSHA}、{SHA}が使えます。このような{}による暗号化方法の指定がないと平文がつかわれることになります。ちなみに、この表記はslapd.confのrootpw行でも使うことができます。
このuserPasswordの値を平文のパスワードから生成するためのツールとしてslappasswdがあります。これを使えばOpenLDAPで使える様々な暗号方法をつかった暗号化されたパスワード文字列を生成することができます。cryptで暗号化されたパスワードは次のようにして得ることができます。なおcryptでパスワードを暗号化する時はsalt文字が必要となるのでそれを-cオプションであたえます。
% /usr/local/sbin/slappasswd -h '{CRYPT}' -c "%s" New password: Re-enter new password: {CRYPT}SAUcuDpGYyyD2
passwd(1)などと同じように二度同じパスワード文字列を入力すると、それを暗号化したパスワード文字列を最後に出力します。これはuserPassword attributeの値に使うことができます。二度タイプするかわりに-sオプションで平文のパスワードを渡すこともできます。
% /usr/local/sbin/slappasswd -h '{CRYPT}' -s secret -c "%s" {CRYPT}2AHpdR1TnKiLc
ここまでで設定したslapd.confではcore.schemaしかincludeしていなかったのでそのままではobjectclass: posixAccountなディレクトリエントリを追加することはできません。まず、slapd.confでnis.schemaをincludeする必要があります。nis.schemaの中ではcosine.schemaで定義している属性なども参照しているためにcosine.schemaもincludeするようにしないといけません(リスト9)。
include /usr/local/etc/openldap/schema/core.schema include /usr/local/etc/openldap/schema/cosine.schema <- 追加 include /usr/local/etc/openldap/schema/nis.schema <- 追加
さらにLDAPでパスワードを変更する時も{CRYPT}を利用するようにするためにslapd.confにリスト10のようにpassword-hashを定義しておくといいでしょう。
password-hash {CRYPT}
cosine.schema、nis.schemaをslapd.confに追加したらslapdを再起動します。
# kill `cat /usr/local/var/slapd.pid` # /usr/local/libexec/slapd
もしcosine.schemaなどをincludeしわすれていると次のようのエラーがでてslapdは起動しません。
# /usr/local/libexec/slapd /usr/local/etc/openldap/schema/nis.schema: line 193: AttributeType not found: "manager"
それではリスト7のアカウント情報、パスワード情報をLDAPに登録してみましょう。LDAPに登録したからといっていきなりそれがUNIXのアカウントとして利用できるようにはなりません。そのようにするためにはさらに設定が必要となりますが、それに関しては後述することにします。ここではまずLDAPに登録することからはじめます。
この例ではou=People,dc=example,dc=jpにアカウント情報を登録することにしていました。しかし、dc=example,dc=jpは既に登録してありますが、ou=People,dc=example,dc=jpはまだ登録していあせんでした。従ってまずou=People,dc=example,dc=jpを登録しておく必要があります。
もし、ou=People,dc=example,dc=jpがない状態で、uid=foo,ou=People,dc=example,dc=jpを登録しようとしても次のようなエラーがでて登録はできません。
% ldapadd -x -D 'cn=Manager,dc=example,dc=jp' -W -f /tmp/foo.ldif Enter LDAP Password: [cn=Manager,dc=example,dc=jpのパスワード; rootpwの値] adding new entry "uid=foo,ou=people,dc=example,dc=jp" ldap_add: No such object matched DN: "dc=example,dc=jp" additional info: parent does not exist ldif_record() = 32
ou=People,dc=example,dc=jpのLDIFはリスト11のような感じにしておきます。
dn: ou=People,dc=example,dc=jp ou: people objectclass: organizationalUnit
これをldapaddで追加しておきます。
% ldapadd -x -D 'cn=Manager,dc=example,dc=jp' -W -f people.ldif Enter LDAP Password: [cn=Manager,dc=example,dc=jpのパスワード; rootpwの値] adding new entry "ou=People,dc=example,dc=jp"
このようにou=People,dc=example,dc=jpを追加しておいてから、uid=foo,ou=People,dc=example,dc=jpを登録します。
% ldapadd -x -D 'cn=Manager,dc=example,dc=jp' -W -f foo.ldif Enter LDAP Password: [cn=Manager,dc=example,dc=jpのパスワード; rootpwの値] adding new entry "uid=foo,ou=People,dc=example,dc=jp"
このように登録するとldapsearchで検索できるようになります。
% ldapsearch -x -b 'dc=example,dc=jp' 'uid=foo' version: 2 # # filter: uid=foo # requesting: ALL # # foo, people, example, jp dn: uid=foo,ou=people,dc=example,dc=jp uid: foo objectClass: posixAccount objectClass: shadowAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar userPassword:: e0NSWVBUfVNBVWN1RHBHWXl5RDI= shadowLastChange: 10953 shadowMin: 0 shadowMax: 99999 shadowWarning: 7 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1
このように検索ベースとしてdc=example,dc=jpを指定すれば、その直接の子供のエントリ(今の場合はou=People,dc=example,dc=jpとcn=Manager,dc=example,dc=jpだけが直接の子供のエントリ)だけでなくdc=example,dc=jpの子孫すべての中からuid属性がfooであるエントリを検索することができます。
uid属性だけでなく他の属性でも検索できます。例えばuidNumber属性で検索する場合は次のようになります。
% ldapsearch -x -b 'dc=example,dc=jp' 'uidNumber=1001' version: 2 # # filter: uidNumber=1001 # requesting: ALL # # foo, people, example, jp dn: uid=foo,ou=people,dc=example,dc=jp uid: foo objectClass: posixAccount objectClass: shadowAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar userPassword:: e0NSWVBUfVNBVWN1RHBHWXl5RDI= shadowLastChange: 10953 shadowMin: 0 shadowMax: 99999 shadowWarning: 7 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1
userPassword属性はuserPassword: {CRYPT}SAUcuDpGYyyD2で登録したはずですが、ここでは userPassword:: e0NSWVBUfVNBVWN1RHBHWXl5RDI= となっています。このようにuserPassword属性の値はエンコードされて表示されます。LDIFではこのように属性名の後に:が二つつく場合は後ろはbase64でエンコードされるという約束になっています。これはバイナリデータなどのための属性値などでも使われます。
base64されているだけなので、このままでは userPassword属性の値を誰でも見ることができてしまうという問題があります。これを防ぐためにアクセス制御を設定しておくべきです。
defaultのslapd.confではアクセス制御はなにも設定されていません。これはrootdnは読み書きができ、その他のユーザ(bindしてない状態のanonymousも含めて)すべてのディレクトリエントリが読めるということをことになっています。少なくともuserPassword属性が誰でも見られるという状態は問題があります。
簡単な制御としてuserPassword属性はそのユーザだけが読み書きでき、他の人はなにもできないようにしておくのがいいでしょう。ただしbindする前はanonymous状態ですからその時には認証だけはできるようにしてしておかないとそもそもそのユーザとして認識されないためにuserPasswordを使うことができないことに注意する必要があります。(リスト12)
access to attribute=userPassword by self write
by anonymous auth
by * none
これによりuserPassword属性は、そのディレクトリエントリの識別名(DN)でbindしていれば書きこみ(write)権限をもちます("by self write")。書きこみ(write)権限が一番強くて書きこみ(write)権限があれば読み込み(read)権限ももつことになっています。UNIXのファイルのパーミッションなどとは違って書けるけれでも読めないという設定はできません。anonymous状態の場合、つまりbindする前の状態は認証目的のためだけにuserPassword属性を使うことができます("by anonymous auth")。読んだり書いたりすることはできません。それ以外の状態、このuserPassword属性をもつディレクトリエントリの識別名(DN)と違う識別名(DN)にbindしている場合などはuserPassword属性に対してなにもすることができません("by * none")。
他の属性に関しては自分自身では書きこみができて誰でも読めるようにするためのアクセス制御はリスト13のようになります。
access to * by self write by * read
なお、rootdnで設定した識別名(DN)にbindすればアクセス制御にかかわらずあらゆる操作をおこなうことができます。rootdnを設定するかわりにアクセス制御である識別名(DN)であらゆる操作ができるようにする場合はアクセス制御はリスト14のように書きます。userPassword属性に関してもrootdn的に権限をもてるようにするのならaccess to attribute=userPasswordのところに by dn="cn=Manager,dc=example,dc=jp" write を追加しておくといいでしょう。
access to * by dn="cn=Manager,dc=example,dc=jp" write by self write by * read
アクセス制御はこのように「ある対象(to なんとか)」に対して「誰(by なんとか)」が「どういう操作までできるか(write/read/auth/noneなど)」を記述していきます。
今回の例ではlapd.confにリスト15のアクセス制御を追加することにします。slapd.confにリスト15を追加してからslapdを再起動します。
access to attribute=userPassword by self write by dn="cn=Manager,dc=example,dc=jp" write by anonymous auth by * none access to * by dn="cn=Manager,dc=example,dc=jp" write by self write by * read # kill `cat /usr/local/var/slapd.pid` # /usr/local/libexec/slapd
このようにしてから検索してみると今度はuserPassword属性が見えないことがわかります。
% ldapsearch -x -b 'dc=example,dc=jp' uid=foo version: 2 # # filter: uid=foo # requesting: ALL # # foo, people, example, jp dn: uid=foo,ou=people,dc=example,dc=jp uid: foo objectClass: posixAccount objectClass: shadowAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar shadowLastChange: 10953 shadowMin: 0 shadowMax: 99999 shadowWarning: 7 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1
uid=foo,ou=People,dc=example,dc=jpでbindするとuserPassword属性が見られるかどうか試してみましょう。
% ldapsearch -x -D 'uid=foo,ou=People,dc=example,dc=jp' -W -b 'dc=example,dc=jp' uid=foo Enter LDAP Password: [uid=foo,ou=People,dc=example,dc=jpのuserPassword属性に設定したパスワード] version: 2 # # filter: uid=foo # requesting: ALL # # foo, people, example, jp dn: uid=foo,ou=people,dc=example,dc=jp uid: foo objectClass: posixAccount objectClass: shadowAccount uidNumber: 1001 gidNumber: 1001 gecos: foo,,, homeDirectory: /home/foo loginShell: /bin/bash cn: foo bar userPassword:: e0NSWVBUfVNBVWN1RHBHWXl5RDI= shadowLastChange: 10953 shadowMin: 0 shadowMax: 99999 shadowWarning: 7 # search result search: 2 result: 0 Success # numResponses: 2 # numEntries: 1
パスワードを間違えると次のようなエラーになります。
% ldapsearch -x -D 'uid=foo,ou=People,dc=example,dc=jp' -W -b 'dc=example,dc=jp' uid=foo Enter LDAP Password: [間違ったパスワード] ldap_bind: Invalid credentials
LDAPに登録されているディレクトリエントリのパスワードつまりuserPassword属性を変更するにはldappasswdコマンドを使います。uid=foo,ou=People,dc=example,dc=jpがuid=foo,ou=People,dc=example,dc=jp自体のパスワードを変更するには次のようにします。
% ldappasswd -x -D 'uid=foo,ou=People,dc=example,dc=jp' -W 'uid=foo,ou=People,dc=example,dc=jp' -S New password: [新規パスワード] Re-enter new password: [新規パスワードをもう一度] Enter bind password: [古いパスワード(bindするため)] Result: Success (0)
-xオプションは既に説明した通りSASLを使わないことを意味します。 -D 'uid=foo,ou=People,dc=example,dc=jp'でuid=foo,ou=People,dc=example,dc=jpでLDAPにbindすること、つまりこのユーザ権限でLDAPに接続することを意味します。 -Wオプションでbindする時のパスワードを聞いてくるようになります。 次の'uid=foo,ou=People,dc=example,dc=jp'がパスワードを変更する対象です。 -Sで新規パスワードを尋ねてきます。
New password:に新規パスワードを入力します。Re-enter new password:でもう一度尋ねてくるので同じ新規パスワードを入力します。その後-WオプションによりEnter bind password:と-Dオプションで指定した識別名(DN)でbindするためのパスワードを入力します。2回入力した新規パスワードが一致して、bindパスワードが正しければ、この操作は成功しResult: Success (0)で終了します。
-Sオプションを指定しないと次のように新規パスワードを自動的に生成します。
% ldappasswd -x -D 'uid=foo,ou=People,dc=example,dc=jp' -W 'uid=foo,ou=People,dc=example,dc=jp' Enter bind password: [古いパスワード(bindするため)] New password: Cy7nCCms Result: Success (0)
このようにNew password:の行に自動的に新規生成されたパスワードが表示されます。この例ではuid=foo,ou=People,dc=example,dc=jpのパスワードは今後Cy7nCCmsとなります。 ldappasswordではパスワードを変更する人(-Dオプションで指定した識別名)と、パスワードを変更する対象は同じである必要はありません。パスワードを変更する人が、パスワードを変更する対象のuserPassword属性に対する書きこみ権限を持っていれば変更することができます。例えばcn=Manager,dc=example,dc=jpがrootdnとして設定されているとすると、cn=Manager,dc=example,dc=jpは全てのディレクトリエントリのあらゆる属性に対してあらゆる操作ができますから、当然のことながらパスワード(userPassword属性)も変更することができます。cn=Manager,dc=example,dc=jpがuid=foo,ou=People,dc=example,dc=jpのパスワードを変更する時は次のようにします。
% ldappasswd -x -D 'cn=Manager,dc=example,dc=jp' -W 'uid=foo,ou=People,dc=example,dc=jp' -S New password: [uid=foo,ou=People,dc=example,dc=jpの新規パスワード] Re-enter new password: [uid=foo,ou=People,dc=example,dc=jpの新規パスワードをもういちど] Enter bind password: [cn=Manager,dc=example,dc=jpのパスワード] Result: Success (0)
このように-Dオプションで変更操作をするユーザの識別名(DN)を指定するわけです。New password:、Re-enter new password:にはuid=foo,ou=People,dc=example,dc=jp(変更対処うの識別名(DN))の新規パスワードを指定し、Enter bind password:にはcn=Manager,dc=example,dc=jp(変更操作をおこなう識別名(DN))のパスワードを入力します。
slapdには一度のリクエストで返答することができる最大のディレクトリエントリ数の制限があります。それを設定するのがsizelimitです。defaultは500に設定されています。それを越えるディレクトリエントリ数が検索にマッチすると検索結果を返答しません。したがって返答しうる最大のディレクトリエントリ数以上をslapd.confファイルの中のsizelimitに設定しておく必要があります(リスト16)。
sizelimit 8192
何も設定しないとslapdの検索はそれほど速くはありません。少しでも検索を速くするためにはインデックスを作成する必要があります。インデックスを作成しなくても検索はできますが、その場合はすべてのディレクトリエントリを読んで比較することになるのでディレクトリエントリの数が増えるについれて時間がかかるようになってしまいます。よく検索するような属性に関してはインデックスを作成しておけば高速な検索が可能になります。ただしインデックスを作成すればその分ディスク容量などが必要になるので必要なものだけ作るのがいいでしょう。
objectclass posixAccountやshadowAccountのディレクトリエントリを使う場合は、検索はuid属性やuidNumber属性、gidNumber属性を見ることがほとんどです。gecos属性やhomeDirectory属性、loginShell属性などで検索することは滅多にありません。従ってインデックスはuid属性、uidNumber属性、gidNumber属性にたいして作成しておくことにします。またcn属性で検索することも多々あるのでこれに関してもインデックスを作成します。その他の属性は、これらの属性で検索してからそのディレクトリエントリの属性を見るという使い方になるのでインデックスは作成しません。インデックスを作成する時はどのような検索条件のためのインデックスを作成するかを指定します。一致についてのインデックスはeq、部分文字列ならsub、類似ならapprox、存在ならpresを指定します(リスト17)。
index uid,uidNumber,gidNumber pres,eq index cn pres,eq,sub
slapd.confにindexの設定の行を書くだけでは、既にLDBMに登録されている情報に関してのインデックスは作成されていません。この状態でslapdを動かしてもインデックスがあるものとして検索しにいくために、実際にはLDBMに登録されている情報でもインデックスがないために検索にはひっかからないということになってしまいます。既にLDBMに登録してあるディレクトリエントリからインデックスを作成するためにslapindexコマンドを使います。
slapindexでインデックスを作るためには、まずslapdを止めます。
# kill `cat /usr/local/var/slapd.pid`
slapd.confにsizelimit,index行を追加してから、slapindexを実行します。
# /usr/local/sbin/slapindex
slapindexはslapd.confを読んで必要なインデックスファイルを作成します。slapindexを実行した後のLDBMのディレクトリ/usr/local/var/openldap-ldbmを見るとindexで指定した属性のためのインデックスファイルが作成されているのを見ることができます。
# ls /usr/local/var/openldap-ldbm cn.dbb gidNumber.dbb nextid.dbb uid.dbb dn2id.dbb id2entry.dbb objectClass.dbb uidNumber.dbb
ちゃんとインデックスファイルができていたらslapdを起動します。ldapsearchを使って問題なく検索できるかどうかを確認しておきましょう。
# /usr/local/sbin/slapindex # /usr/local/libexec/slapd % ldapsearch -x -b 'dc=example,dc=jp' '(uid=foo)'
ここまでで説明したやり方で登録したアカウント情報をシステムで参照できるようにしてみましょう。
slapdにはuid=foo,ou=People,dc=example,dc=jpが登録されているとします。また/etc/passwdなど通常使うパスワードデータベースにはfooとユーザはまだ登録されてないとします。
% id foo id: foo: そのようなユーザは存在しません
slapdに登録されているuid=foo,ou=People,dc=example,dc=jpをfooというユーザのアカウント情報として利用できるようにする方法を説明します。
従来のUNIXではpasswdの情報は/etc/passwdからか、NISを使って検索するようになっていました。最近のLinuxやSolarisではName Service Switch(NSS)という仕組をつかってpasswdなどの情報をどこからとってくるかを制御することができるようになっています(図3)。NSSの設定ファイルが/etc/nsswitch.confです。Linuxのglibc2.2ではdefaultはリスト18のようになっています。このようにそれぞれのネームサービスごとにどこからどの順番で検索するかを指定できるようになっています。passwdなどはcompatになっていますが、これは+によるNIS検索などに対応しています。glibcのNSSでは検索方法に応じた共有オブジェクトライブラリで実装されています。たとえばcompatに対応したものは/lib/libnss_compat.so.2です。
| Application | | | ---------- getpw*() ----------- libc API | | nss_file nss_nis nss_ldap | | | | | -------------------------------- | | | <LDAP> | /etc/passwd NIS | | ldap server
# /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glibc-doc' and `info' packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: compat group: compat shadow: compat hosts: files dns networks: files protocols: files services: files ethers: files rpc: files netgroup: nis
NSSのバックエンドモジュールとしてLDAPを利用したnss_ldapというものがあります。これを使うと、LDAPを使ったアカウント情報の管理ができるようになります。nss_ldapはftp://ftp.padl.com/pub/nss_ldap.tar.gzに最新版があります。
Debian potatoではlibnss-ldapパッケージになっているのでこれをインストールします。なお、このパッケージではOpenLDAP 1.2.xのライブラリを利用していますが、OpenLDAP 2.0.xから通信することはできます。また暗号化に関してはcryptパスワードのサポートのみのようです。
apt-get install libnss-ldap Reading Package Lists... Done Building Dependency Tree... Done The following extra packages will be installed: libopenldap-runtime libopenldap1 The following NEW packages will be installed: libnss-ldap libopenldap-runtime libopenldap1 0 packages upgraded, 3 newly installed, 0 to remove and 0 not upgraded. Need to get 124kB of archives. After unpacking 365kB will be used. Do you want to continue? [Y/n] y Get:1 http://http.debian.or.jp stable/main libopenldap-runtime 1:1.2.12-1 [26.3kB] Get:2 http://http.debian.or.jp stable/main libopenldap1 1:1.2.12-1 [63.5kB] Get:3 http://http.debian.or.jp stable/main libnss-ldap 122-1 [34.2kB] Fetched 124kB in 0s (277kB/s) Selecting previously deselected package libopenldap-runtime. (Reading database ... 7923 files and directories currently installed.) Unpacking libopenldap-runtime (from .../libopenldap-runtime_1%3a1.2.12-1_all.deb) ... Selecting previously deselected package libopenldap1. Unpacking libopenldap1 (from .../libopenldap1_1%3a1.2.12-1_i386.deb) ... Selecting previously deselected package libnss-ldap. Unpacking libnss-ldap (from .../libnss-ldap_122-1_i386.deb) ... Setting up libopenldap-runtime (1.2.12-1) ... Setting up libopenldap1 (1.2.12-1) ... Setting up libnss-ldap (122-1) ...
これで/lib/libnss_ldap.so.2およびその設定ファイル/etc/libnss-ldap.confがインストールされます(リスト19)。
# # $Id: ldap.conf,v 2.10 2000/01/14 23:29:47 lukeh Exp $ # # This is the configuration file for the LDAP nameservice # switch library and the LDAP PAM module. # # PADL Software # http://www.padl.com # # If the host and base aren't here, then the DNS RR # _ldap._tcp.<defaultdomain>. will be resolved. <defaultdomain> # will be mapped to a distinguished name and the target host # will be used as the server. # Your LDAP server. Must be resolvable without using LDAP. host 127.0.0.1 # The distinguished name of the search base. base dc=padl,dc=com # The LDAP version to use (defaults to 2) #ldap_version 3 # The distinguished name to bind to the server with. # Optional: default is to bind anonymously. #binddn cn=manager,dc=padl,dc=com # The credentials to bind with. # Optional: default is no credential. #bindpw secret # The port. # Optional: default is 389. #port 389 # The search scope. #scope sub #scope one #scope base # The following options are specific to nss_ldap. # The hashing algorith your libc uses. # Optional: default is des #crypt md5 #crypt sha #crypt des
この/etc/libnss-ldap.confではbaseがdc=padl,dc=comになっているので、ここだけは必ず直す必要があります。今の例ではdc=example,dc=jpを使っているのでそのように変更します。また、パスワードの変更やユーザ認証時にはuserPassword属性を読むことが必要となるのでrootbinddnにuserPassword属性が読み書きできる識別名(DN)を指定しておく必要があります。ここではrootdnであるcn=Manager,dc=example,dc=jpを指定することにします(リスト20)。rootbinddnでLDAPにbindするためのパスワードは/etc/ldap.secretに平文で書きます。当然のことながら、この/etc/ldap.secretファイルはroot所有のファイルにしてパーミッションは0600(rootのみ読み書き可能)としておく必要があります。
# Your LDAP server. Must be resolvable without using LDAP. host 127.0.0.1 # The distinguished name of the search base. base dc=example,dc=jp # The distinguished name to bind to the server with # if the effective user ID is root. Password is # stored in /etc/ldap.secret (mode 600) rootbinddn cn=Manager,dc=example,dc=jp # echo パスワード > /etc/ldap.secret # chown root /etc/ldap.secret # chmod 0600 /etc/ldap.secret
ここまでで説明したアクセス制御ではanonymousでもuserPassword属性以外必要な属性は読むことができるようになっているのでbinddnやbindpwを設定する必要はありません。
/etc/libnss-ldap.confを設定できたら、次は/etc/nsswitch.confでnss_ldapを使ってLDAPを参照するように変更する必要があります。LDAPに登録してあるのはpasswdとshadowの情報だけなのでpasswdおよびshadowのcompatの後ろにldapを追加します(リスト21)。このようにするとcompatで指定したところ(つまり/etc/passwdやNIS)になければldapを参照するようになります。まだgroupをLDAPで管理するようにしていませんがついでにgroupもldapを見るように設定しています。
# /etc/nsswitch.conf # # Example configuration of GNU Name Service Switch functionality. # If you have the `glibc-doc' and `info' packages installed, try: # `info libc "Name Service Switch"' for information about this file. passwd: compat ldap group: compat ldap shadow: compat ldap hosts: files dns networks: files protocols: files services: files ethers: files rpc: files netgroup: nis
すべてがうまく設定できていればこれでユーザfooが見えるようになっています。
% id foo uid=1001(foo) gid=1001(foo) groups=1001(foo)
もしユーザfooが見えなければ設定が正しいかどうか確認しましょう。
nss_ldapではobjectclassがposixAccountでuid属性やuidNumber属性でユーザ情報を検索しています。したがってobjectclassがposixAccountになっていないとそれをユーザアカウント情報と認識しません。次のようにしてLDAPで検索できるかどうか試してみましょう。
% ldapsearch -x -b 'dc=example,dc=jp' '(objectclass=posixAccount)' % ldapsearch -x -b 'dc=example,dc=jp' '(&(objectclass=posixAccount)(uid=foo))'
さまざまなユーザプロセスからNSSを介してLDAPへ検索がかけられます。アクセス制御が厳しいと場合によっては検索できない場合もありえるかもしれません。一番簡単なのは誰でもobjectclass posixAccountの情報を読めるようにしておくことです。
当然のことながらlibnss-ldap.confで正しいLDAPサーバを指定している必要があります。hostがLDAPサーバが動いているサーバになっているか確認しましょう。また、baseが正しい検索ベースでないとこれも検索できません。
libnss-ldapだけではパスワードの変更をpasswd(1)からすることができません。passwd(1)からパスワードを変更するのにLDAPを使うPAMモジュールが必要になります。これはftp://ftp.padl.com/pub/pam_ldap.tar.gzにあります。Debianではlibpam-ldapというパッケージになっているのでそれをインストールします。
# apt-get install libpam-ldap Reading Package Lists... Done Building Dependency Tree... Done The following NEW packages will be installed: libpam-ldap 0 packages upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 15.6kB of archives. After unpacking 47.1kB will be used. Get:1 http://http.debian.or.jp stable/main libpam-ldap 43-2 [15.6kB] Fetched 15.6kB in 0s (284kB/s) Selecting previously deselected package libpam-ldap. (Reading database ... 7957 files and directories currently installed.) Unpacking libpam-ldap (from .../libpam-ldap_43-2_i386.deb) ... Setting up libpam-ldap (43-2) ...
基本的にlibnss-ldapと同じような設定で問題ありません。リスト20のような/etc/libnss-ldap.confを使っているのなら、これをほぼ同じ内容の/etc/pam_ldap.confで問題ありません。ただしpam_password cryptという行が必要になるので、これを追加しておく必要があります(リスト22)。rootbinddnを設定しているので/etc/ldap.secretが当然必要になりますが、これは/etc/libnss-ldap.confを設定した時に使ったのがそのまま使えるはずです。(ちなみにsidにあるDebianパッケージではdebconfを使って他のLDAP関連のパッケージの設定情報を使って/etc/pam_ldap.confが作成されるのでほとんど設定を変更しなくてもすみます。)
# Your LDAP server. Must be resolvable without using LDAP. host 127.0.0.1 # The distinguished name of the search base. base dc=example,dc=jp # The distinguished name to bind to the server with # if the effective user ID is root. Password is # stored in /etc/ldap.secret (mode 600) rootbinddn cn=Manager,dc=example,dc=jp pam_password crypt
このLDAP PAMモジュールを有効にするためには/etc/pam_ldap.confを設定するだけでは足りません。PAMを必要としているアプリケーション毎にどのPAMモジュールを使うかを設定していく必要があります。passwd(1)のPAMは/etc/pam.d/passwdで設定します。デフォルトではリスト23のようになっています。LDAPを使うようにするにはリスト24のように変更します。このように「password sufficient pam_ldap.so」の行を他の行よりも上に書いておきます。LDAP PAMモジュールがうまく動いていれば次のようにEnter login(LDAP) passwordのように尋ねてくるようになります。
# # The PAM configuration file for the Shadow `passwd' service # # The standard Unix authentication modules, used with NIS (man nsswitch) as # well as normal /etc/passwd and /etc/shadow entries. For the login service, # this is only used when the password expires and must be changed, so make # sure this one and the one in /etc/pam.d/login are the same. The "nullok" # option allows users to change an empty password, else empty passwords are # treated as locked accounts. # # (Add `md5' after the module name to enable MD5 passwords the same way that # `MD5_CRYPT_ENAB' would do under login.defs). # # The "obscure" option replaces the old `OBSCURE_CHECKS_ENAB' option in # login.defs. Also the "min" and "max" options enforce the length of the # new password. password required pam_unix.so nullok obscure min=4 max=8 # Alternate strength checking for password. Note that this # requires the libpam-cracklib package to be installed. # You will need to comment out the password line above and # uncomment the next two in order to use this. # (Replaces the `OBSCURE_CHECKS_ENAB', `CRACKLIB_DICTPATH') # # password required pam_cracklib.so retry=3 minlen=6 difok=3 # password required pam_unix.so use_authtok nullok md5
password sufficient pam_ldap.so password required pam_unix.so nullok obscure min=4 max=8 % passwd Enter login(LDAP) password: New password: Re-enter new password: LDAP password information changed for foo
LDAPにないユーザは以下のように UNIX passwordを聞いてきます。
% passwd Changing password for baz (current) UNIX password:
正しく設定できてない場合、/etc/pam_ldap.confでbaseやrootbinddnが間違っているような場合はLDAPの方にアカウントがあってもUNIX passwordを聞いてきますが正しく動かないので注意が必要です。
通常はgecosの変更やシェルの変更はchfn(1)、chsh(1)でおこなっています。しかし、LDAPにしかアカウント情報がないとこれらのコマンドは/etc/passwdを変更するために使うことができません。実際にやってみると次のようになります。
% chsh Password: Changing the login shell for foo Enter the new value, or press return for the default Login Shell [/bin/bash]: /usr/bin/zsh chsh: foo not found in /etc/passwd
このように/etc/passwdにエントリがあることを必要とします。chfnの場合も同様です。
このように/etc/passwdにエントリがあることを必要とします。chfnの場合も同様です。
これらの情報はchsh(1)やchfn(1)を使うかわりに直接LDAPの情報を変更することで同様のことができます。LDAPの情報を変更するためのコマンドはldapmodifyです。
例えばこの例のようにfooのユーザのログインシェルを/bin/bashから/usr/bin/zshに変更することをやってみると次のようになります。
% echo 'dn: uid=foo,ou=People,dc=example,dc=jp changetype: modify replace: loginShell loginShell: /usr/bin/zsh ' | ldapmodify -x -D 'uid=foo,ou=People,dc=example,dc=jp' -W Enter LDAP Password: modifying entry "uid=foo,ou=People,dc=example,dc=jp"
このように変更に対する要件もLDIFにより記述します。まず最初の行は対象とする識別名(DN)を書きます。
dn: uid=foo,ou=People,dc=example,dc=jp
次の行はどのように変更するかを示すchangetypeです。追加や削除ではないのでここはodify(修正)を使います。
changetype: modify
その次の2行でどのように変更するかを指示しています。replaceによりloginShell属性を以前のものから置きかえることを意味し、次のloginShellの行で実際にどの値に置きかえるかを意味しています。
replace: loginShell loginShell: /usr/bin/zsh
以上で識別名(DN)がuid=foo,ou=People,dc=example,dc=jpのloginShell属性をloginShell: /usr/bin/zshにおきかえるという意味になります。
これをldapmodifyに入力します。既に何度も使っているように-xオプションはSASLを使わないことを意味しています。-D 'uid=foo,ou=People,dc=example,dc=jp'はこの識別名(DN)でbindすることを意味し、-Wはパスワードを尋ねてくることを意味しています。
chfnと同等のことをする時はgecosを変更することになります。ちなみに同時に変更するような場合は次のような記述になります。
dn: uid=foo,ou=People,dc=example,dc=jp changetype: modify replace: loginShell loginShell: /usr/bin/zsh - replace: gecos gecos: foo,0000,1111,2222
このようにloginShell属性の変更の指示とgecos属性の変更の指示は`-'だけの行で区切ります。
アカウントを削除する時は、LDAPから対応するエントリを削除することになります。削除するにはldapdeleteを使います。
% ldapdelete -x -D 'cn=Manager,dc=example,dc=jp' -W 'uid=foo,ou=People,dc=example,dc=jp'
なお、当然のことながらLDAPからアカウント情報を消してもホームディレクトリが削除されることはありません。またホームディレクトリを作成する時のようにPAM moduleを使って自動的にすることもできません。
既存のアカウント情報/etc/passwdなどからLDAPへ一括して登録するには、/etc/passwdファイルからユーザ一人毎にobjectclassがposixAccountおよびshadowAccountであるディレクトリエントリを生成すればいいわけです。したがってそのような処理をするスクリプトを書けば既存のアカウント情報を一括してLDAP側に登録することができます。
PADL Software では、従来のアカウント情報からLDAPへ登録するためのLDIFを生成するようなmigrate_passwd.plをはじめとする様々な移行のためのツールをまとめたMigrationTools.tar.gzを配布しています。これは <URL:ftp://ftp.padl.com/pub/MigrationTools.tar.gz> から入手できます。これらのツールを使えば簡単に既存の情報をLDAPにもっていくことができます。ただし、最新のMigrationTools-39.tar.gzでもMD5パスワードの対応はないようです。
例えば、passwdをLDIFに変換する場合はMigrationTools.tar.gzを展開して得られるmigration_passwd.plを使います。設定はmigration_common.phに書かれていますが、必要な設定は環境変数を設定することでかえることができます。basednは設定する必要があります。basednは環境変数LDAP_BASEDNで変更できます。また、migrate_passwd.plではパスワード情報を得るために/etc/shadowも参照するのでroot権限で実行しなければなりません。
% tar zxf MigrationTools.tar.gz % ls MigrationTools-39 MigrationTools.tar.gz % cd MigrationTools-39/ % ls CVSVersionInfo.txt migrate_base.pl Make.rules migrate_common.ph MigrationTools.spec migrate_fstab.pl README migrate_group.pl ads migrate_hosts.pl migrate_aliases.pl migrate_netgroup.pl migrate_all_netinfo_offline.sh migrate_netgroup_byhost.pl migrate_all_netinfo_online.sh migrate_netgroup_byuser.pl migrate_all_nis_offline.sh migrate_networks.pl migrate_all_nis_online.sh migrate_passwd.pl migrate_all_nisplus_offline.sh migrate_profile.pl migrate_all_nisplus_online.sh migrate_protocols.pl migrate_all_offline.sh migrate_rpc.pl migrate_all_online.sh migrate_services.pl migrate_automount.pl migrate_slapd_conf.pl % cp /etc/passwd passwd % vi passwd [LDAPに登録するユーザだけ残す。システムユーザなどは消してしまえばいい] % sudo -s # LDAP_BASEDN='dc=example,dc=jp' ./migrate_passwd.pl passwd > /tmp/account.ldif
これでpasswdにあるアカウント情報をaccount.ldifにLDIF形式に書きだすことができます。普通はrootのアカウント情報など/etc/passwdにあるアカウントを全部LDAPに登録する必要がないので、必要なエントリだけ別のファイルにかきだしてそれをmigrate_passwd.plの入力ファイルに指定してLDIF情報に変更させます。一旦LDIFにできればあとはそれをldapaddでLDAPに登録するだけです。
% ldapadd -D 'cn=Manager,dc=example,dc=jp' -W -f /tmp/account.ldif Enter LDAP Password:
/etc/passwd,/etc/shadowからLDIFへの変換はそれほど複雑ではないので自分でperlやrubyなどで簡単なスクリプトを書いて済ますことも可能です。(リスト25)(リスト26)
このスクリプトを応用すれば、元が/etc/passwdなどでなくても例えばCSVなどからLDAPへ登録するためのLDIFを作成することができます。LDIFを作成する時に注意しないといけないのは次の通りです。
各ディレクトリエントリはdn: 識別名(DN)ではじまっています。この識別名(DN)にはslapd.confでsuffixに指定した識別名(DN)のサブツリーに含まれるものを使うようにします。そのサブツリーの中ならばどのような階層構造でも構いません。例えば、部署ごとにou(organizationalUnit)をきって、その中にユーザ情報などを含めるようにすることもできます。PADL Softwareのmigrate_passwd.plではユーザ情報は ou=People にまとめていれるようにしています。また識別名(DN)にuid属性をいれていますが、これも必ずuid属性を入れる必要はありません。例えば識別名(DN)ではcn属性を使うようにしても問題ありません。識別名(DN)で使うものはディレクトリエントリの中で、その属性名と属性値をいれるようにしておきます。たとえば識別名(DN)が「cn=foo,ou=People,dc=example,dc=jp」の場合、「cn: foo」という属性をディレクトリエントリの中にいれることになります。
それぞれのディレクトリエントリは他と重ならない識別名(DN)をもちます。同じ識別名(DN)をもつ二つのディレクトリエントリというのはありえません。従って識別名(DN)の最初で使う属性名と属性値のペアは同じレベルにあるディレクトリエントリの中では重複できないことになります。例えば識別名(DN)が「cn=foo,ou=People,dc=example,dc=jp」というものだとすると、ou=People,dc=example,dc=jpには他の「cn=foo,ou=People,dc=example,dc=jp」というディレクトリエントリは存在しえないことになります。
各ディレクトリエントリ間は空行で区切ることになっています。これを忘れると二つのディレクトリエントリがつながってひとつになってしまいます。
uid属性の値がfooである時、識別名(DN)では「dn: uid=foo,ou=People,dc=example,dc=jp」のようにuid=fooと記述しますが、ディレクトリエントリの中では「uid: foo」と記述します。dnでの表記につられて「uid=foo」とすると正しいLDIFになりません。
正しいobjectclass属性をいれておかないと正しく処理がおこなわれません。LDAPをNISのかわりに使おうと思っていてNSSで参照するのならばobjectclass: posixAccount、objectclass: shadowAccountになっている必要があります。
objectclassは、schemaファイルに記述されているようにそれぞれ必要とする属性や含むことができる属性が決まっています。これにあうように記述していく必要があります。objectclass posixAccountは既に述べたようにcn, uid, uidNumber, gidNumber, homeDirectory属性が必要で、userPassword,loginShell,gecos,description属性をもつことができます。objectclass shadowAccountも既にのべたようにshadow関係の属性をもつことができます。またNSSでLDAPを使う場合は結局/etc/passwdや/etc/shadowに書かれている情報が必要とされるので、objectclass posixAccountで必要とされている属性に加えてuserPasswordやloginShell,gecos属性なども書いておくことになります。
userPassword属性での値は/etc/shadowのパスワード欄の文字列をそのまま使えるわけではありません。userPassword属性にはどのような暗号化がほどこされているかについてを文字列の最初に{}でかこって記述することになっています。cryptの場合は{CRYPT}となります。/etc/shadowでは$1$で始まっている場合はmd5ですが、これも{CRYPT}の一種として扱われます。なお、userPasswordに含む値を得るためのツールとしては既に述べた通りslappasswdがあります。
adduserなどを使ってアカウントを作成する場合には、/etc/passwdや/etc/shadowのエントリにしたがってホームディレクトリなども作成してくれますが、LDAPにアカウント情報を登録するだけではホームディレクトリの作成などはおこなわれないので何らかの手段で作成する必要があります。大量のユーザを登録する必要がある場合にはそれぞれのユーザのホームディレクトリを作成することも大変です。
それらを楽にするための仕組としてmkhomedirというPAM moduleがあります。PAMというのはPluggable Authentication Moduleのことで、認証処理などにplug-inできるモジュールを提供する仕組です。Linux-PAMには多くのPAM moduleが含まれていますが、その中にmkhomedirというPAMのmoduleがあります。これを使うとユーザが最初にログインした時に、ホームディレクトリがないと自動的にホームディレクトリを作成するようにすることができます。ドットファイルなどもskelパラメータを指定しておけば、ホームディレクトリを作成する時にskelパラメータで指定したディレクトリからコピーされるようになります。またデフォルトのパーミッションもumaskパラメータで設定が可能です。最近のLinuxディストリビューションではPAMはほぼ最初からはいっているのであらためてインストールする必要はないでしょう。mkhomedir moduleを有効にするには/etc/pam.d/loginや/etc/pam.d/sshなどにリスト27のような行を追加します。
session required pam_mkhomedir.so skel=/etc/skel/ umask=0022
これによりログインに成功してセッションを開始する時にsessionのPAM moduleがよびだされます。つまりsessionの必須(required)であるpam_mkhomedir.soがパラメータskelが/etc/skel/、umaskが0022として呼びだされます。/etc/skel/にあるファイルがホームディレクトリ作成時にコピーされるのでデフォルトのドットファイルなどはここに置いておくとよいでしょう。
アカウントと同様グループもLDAPで管理することができます。アカウントはobjectclass posixAccountとobjectclass shadowAccountを使いましたが、グループではobjectclass posixGroupを使うことになります。これはposixAccountなどと同様にnis.schemaに定義されています(リスト28)。
objectclass ( 1.3.6.1.1.1.2.2 NAME 'posixGroup' SUP top STRUCTURAL DESC 'Abstraction of a group of accounts' MUST ( cn $ gidNumber ) MAY ( userPassword $ memberUid $ description ) )
このposixGroupと/etc/groupの対応は図4の通りになります。
| foo:x:1001:foo,bar | | | | | | | | | | | memberUid | | | | memberUid | | | gidNumber | | userPassword | cn
従ってこの/etc/groupの行をLDIFで表現するとリスト29のようになります。
dn: cn=foo,ou=Group,dc=example,dc=jp cn: foo objectclass: posixGroup gidNumber: 1001 memberUid: foo memberUid: bar
今までと同様example.jpなのでdc=example,dc=jpになります。またアカウントはou=Peopleを使っていましたが、グループに関してはou=Groupを使います。ou=Peopleの時と同じようにまずou=Group,dc=example,dc=jpというエントリをあらかじめ追加しておく必要があります。
% echo 'dn: ou=Group,dc=example,dc=jp ou: Group objectclass: organizationalUnit ' | ldapadd -x -D 'cn=Manager,dc=example,dc=jp' -W Enter LDAP Password: adding new entry "ou=Group,dc=example,dc=jp"
このようにou=Group,dc=example,dc=jpを追加してからグループの情報を追加します。
% echo 'dn: cn=foo,ou=Group,dc=example,dc=jp cn: foo objectclass: posixGroup gidNumber: 1001 memberUid: foo memberUid: bar ' | ldapadd -x -D 'cn=Manager,dc=example,dc=jp' -W Enter LDAP Password: adding new entry "cn=foo,ou=Group,dc=example,dc=jp"
既存の/etc/groupからLDIFに変更するためのツールもMigrationTools.tar.gzの中にmigrate_group.plがあります。
# LDAP_BASEDN='dc=example,dc=jp' perl ./migration_group.pl /etc/group > /tmp/group.ldif
グループ名はcnなので、cnのインデックスもつくっておく方が検索のパフォーマンスはよくなるでしょう。
グループへユーザを追加したり削除したりするのも、gecosやshellの変更と同じくldapmodifyを使っておこなうことになります。
グループfooにユーザbazを新たに追加する場合は次のような内容をldapmodifyに入力します。
% echo 'dn: cn=foo,ou=Group,dc=example,dc=jp changetype: modify add: memberUid memberUid: baz ' | ldapmodify -x -D 'cn=Manager,dc=example,dc=jp' -W Enter LDAP Password: modifying entry "cn=foo,ou=Group,dc=example,dc=jp"
「add: memberUid」が次のmemberUid属性を新たに追加することを意味します。以前からあるmemberUid属性はそのまま残ります。一度に複数追加したい場合は次のようにします。
dn: cn=foo,ou=Group,dc=example,dc=jp changetype: modify add: memberUid memberUid: user1 memberUid: user2 memberUid: user3
逆にグループfooからユーザbarを削除したい場合は次のような内容をldapmodifyに入力します。
dn: cn=foo,ou=Group,dc=example,dc=jp changetype: modify delete: memberUid memberUid: bar
「delete: memberUid」によりmemberUid属性を削除することを意味します。どのmemberUid属性を削除するかは次の行で指示します。複数を削除する時も追加する時と同様にすることができます。
#!/usr/bin/perl # # $0 > account.ldif # $BASEDN = 'dc=example, dc=jp'; if (defined($ENV{'LDAP_BASEDN'})) { $BASEDN = $ENV{'LDAP_BASEDN'}; } open(PW, "/etc/passwd") or die "cannot open passwd, $!"; while (<PW>) { chomp; next if /^#/; ($uid,$x,$uidNumber,$gidNumber,$gecos,$homedir,$shell) = split(/:/); if ($uidNumber < 1000) { # ignore, system users next; } $users{$uid} = { 'uid' => $uid, 'uidNumber' => $uidNumber, 'gidNumber' => $gidNumber, 'gecos' => $gecos, 'homeDirectory' => $homedir, 'loginshell' => $shell, }; } open(SHADOW, "/etc/shadow") or die "cannot open shadow, $!"; while (<SHADOW>) { chomp; ($uid,$pw,$shadowlastchange,$shadowmin,$shadowmax,$shadowwarning,@_) = split(/:/); if ($pw =~ s/^\$1\$//) { $pw = "{md5}" . $pw; } else { $pw = "{crypt}" . $pw; } if (defined($users{$uid})) { $users{$uid}->{userpassword} = $pw; $users{$uid}->{shadowlastchange} = $shadowlastchange; $users{$uid}->{shadowmin} = $shadowmin; $users{$uid}->{shadowmax} = $shadowmax; $users{$uid}->{shadowwarning} = $shadowwarning; } } close(SHADOW); foreach $uid (keys %users) { $u = $users{$uid}; print <<END; dn: uid=$u->{uid}, ou=People, $BASEDN uid: $u->{uid} cn: $u->{uid} objectclass: account objectclass: posixAccount objectclass: shadowAccount objectclass: top uidNumber: $u->{uidNumber} gidNumber: $u->{gidNumber} gecos: $u->{gecos} homeDirectory: $u->{homeDirectory} loginShell: $u->{loginshell} END print <<PW if $u->{userpassword} ne ""; userpassword: $u->{userpassword} PW print <<SHADOW if $u->{shadowlastchange} != 0; shadowlastchange: $u->{shadowlastchange} shadowmin: $u->{shadowmin} shadowmax: $u->{shadowmax} shadowwarning: $u->{shadowwarning} SHADOW print "\n"; }
BASEDN = ENV['LDAP_BASEDN'] || 'dc=example,dc=jp'
class Account
SHADOW = ['shadowLastChange', 'shadowMin', 'shadowMax', 'shadowWarning', 'shadowInactive', 'shadowExpire', 'shadowFlag'] def initialize(uid, uidNumber, gidNumber, gecos, homeDirectory, loginshell) @uid = uid @uidNumber = uidNumber @gidNumber = gidNumber @gecos = gecos @homeDirectory = homeDirectory @loginshell = loginshell @userPassword = '' @shadow = {} SHADOW.each {|k| @shadow[k] = '' } end def uid return @uid end def userPassword= (pw) setUserPassword(pw) end def setUserPassword(pw) if pw.gsub!(/\$1\$/,"") @userPassword = "{MD5}#{pw}" else @userPassword = "{CRYPT}#{pw}" end end def setShadowLastChange(lc) @shadow['shadowLastChange'] = lc end def setShadowMin(min) @shadow['shadowMin'] = min end def setShadowMax(max) @shadow['shadowMax'] = max end def setShadowWarning(warning) @shadow['shadowWarning'] = warning end def setShadowInactive(inactive) @shadow['shadowInactive'] = inactive end def setShadowExpire(expire) @shadow['shadowExpire'] = expire end def setShadowFlag(flag) @shadow['shadowFlag'] = flag end def toldif r = "dn: uid=#{@uid}, ou=People, #{BASEDN}
objectclass: posixAccount objectclass: shadowAccoutn objectclass: top uid: #{@uid} uidNumber: #{@uidNumber} gidNumber: #{@gidNumber} gecos: #{@gecos} homeDirectory: #{@homeDirectory} loginshell: #{@loginshell} " if @userPassword != '' r += "userPassword: #{@userPassword}\n" end SHADOW.each {|k| if /^\s+$/ =~ @shadow[k] r += "#{k}: #{@shadow[k]}\n" end } return r end end accounts = {} File.open("/etc/passwd") {|fp| fp.each {|line| line.chomp! uid,x,uidNumber,gidNumber,gecos,homedir,shell = line.split(/:/) if uidNumber.to_i > 100 accounts[uid] = Account.new(uid,uidNumber,gidNumber,gecos,homedir,shell) end } } File.open("/etc/shadow") {|fp| fp.each {|line| line.chomp! uid,pw,shadowlastchange,shadowmin,shadowmax,shadowwarning,shadowinactive,shadowexpire,shadowflag = line.split(/:/) if accounts[uid] accounts[uid].userPassword = pw accounts[uid].setShadowLastChange(shadowlastchange) accounts[uid].setShadowMin(shadowmin) accounts[uid].setShadowMax(shadowmax) accounts[uid].setShadowWarning(shadowwarning) accounts[uid].setShadowInactive(shadowinactive) accounts[uid].setShadowExpire(shadowexpire) accounts[uid].setShadowFlag(shadowflag) end } } accounts.each_value {|acc| print acc.toldif print "\n" }