今回はOpenLDAPにおけるアクセス管理についてお話ししたいと思います。コンピュータの世界でファイルやDBにアクセス権という概念が存在するように、LDAPにおいても同様の考え方が存在します。たとえばLDAP上にUNIXアカウント情報を格納する場合、それぞれのユーザのパスワード情報はツリー上のuserPassword属性 に格納します。
/etc/shadowと同様、パスワードは一般的に一方向関数で暗号化した状態で保存しますが、この情報が万が一他人の手に渡ってしまうと、John The Ripperなどのツールによって元のパスワードが漏洩してしまう可能性もあります。そのため、次のような形で適切なアクセスコントロールを行う必要があります。
あるユーザは他のユーザのuserPassword属性を得ることはできない。
他ユーザのエントリを変更することもできない。
ただし自分のエントリであれば自由に参照、変更することができる
これ以外の例として、OpenLDAPでは次のようなアクセスコントロールを実現することもできます。
特定のIPアドレスからであれば読み書き可能、それ以外からはリードオンリー
mail属性が登録されているユーザからであれば読み取り可能、それ以外からは参照不可能
slapd.confとACL
アクセスコントロールはおなじみのslapd.conf 中で設定します。フォーマットは次の通りですが、設定が長くなりがちなので、通常は複数行にまたいでACL設定を行います。
リスト1 ACLの書式
access to by
まずは単純な設定を見てみましょう。
リスト2 全エントリに参照を許可
access to dn.subtree="ou=People,dc=example,dc=com"
by * read
この設定は、ou=People,dc=example,dc=com
以下のサブツリーに対して、アスタリスク、つまり全アクセスにおける読み込み許可を設定しています。もちろん改行を行わず、
リスト3
access to dn.subtree="ou=People,dc=example,dc=com" by * read
と設定することも可能です。
先ほどのwhatフィールド に設定できる項目は、DN、フィルタ、属性です。つまり「~というDNに対して」「 ~という検索フィルタにマッチする場合に」「 特定の属性に対して」といった設定を行うことができます。
whoフィールドについては、「 特定のDNである場合」「 特定のフィルタにマッチする場合」「 特定のIPアドレスの場合」などさまざまな設定を行うことができます。すべてを紹介することはできませんので、マニュアルであるslapd.access(5)を参照してください。
accessフィールドでは読み込みや書き込みなどの実際の挙動を設定します。具体的にはnone, disclose, auth, compare, search, read, writeという設定を行うことができますので、たとえば、特定のDNからであれば認証のみ許可するがエントリは参照させない、といった設定も可能です。それぞれの意味は表1の通りです。
表1 それぞれの意味
none
なし
disclose
なし(エラー情報は開示)
auth
認証の許可
compare
比較の許可
search
検索の許可
read
読み込みの許可
write
読み書きの許可
ちなみに、OpenLDAP-2.4系には管理DITを編集するために、writeよりも強いmanageという設定項目があるようですが、時間の都合により詳しく検証することができませんでした。
最後に、CONTROLフィールドではstop, continue, breakを設定することができます。たとえば
リスト4
access to dn.subtree="dc=example,dc=com"
by * none
by peername.ip=127.0.0.1 write
という設定の場合、2行目で全アクセスに対してnoneを設定しているため、通常であれば3行目は評価されず、仮にローカルホストからの接続であっても検索を行うことができません。一方、
リスト5
access to dn.subtree="dc=example,dc=com"
by * none continue
by peername.ip=127.0.0.1 write
という設定を行っておけば、2行目にマッチした場合でもcontinueにより3行目の評価を行うことが可能で、その結果ローカルホストからの接続であれば読み書きが可能となります。
ちなみに、これらの例は良くない例なので、実は最初から
リスト6
access to dn.subtree="dc=example,dc=com"
by peername.ip=127.0.0.1 write
by * none
という設定を行っておけば、この要件を満たすことができます。では次はbreakに関する例です。
リスト7
access to dn.subtree="dc=example,dc=com"
by * none
access to dn.subtree="dc=example,dc=com"
by * read
という設定が存在した場合、全てのアクセスが2行目にマッチしてしまうため、全ての検索などができません。一方、
リスト8
access to dn.subtree="dc=example,dc=com"
by * none break
access to dn.subtree="dc=example,dc=com"
by * read
という設定の場合、2行目の評価が終わっても、継続して以降の評価を行うことができます。この結果、まずは2行目にマッチしてしまうのですが、その後4行目にマッチすることで、読み込み操作が可能となります。
実践: ACL設定
再びUNIXアカウントの例ですが、一般ユーザからの要求であればリードオンリー、ユーザ管理者(ディレクトリサーバ管理者とは異なる)からのアクセスではエントリの編集が可能、といった機能を実現するための設定は次のようになります。
リスト9 slapd.confの一部
access to dn.subtree="ou=People,dc=example,dc=com"
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by * read
by anonymous auth
ACLとuserPassword属性
冒頭で説明しましたように、このままでは一般ユーザが別ユーザの暗号化されたパスワード情報を取得することができてしまいますので、設定をもう少し厳しくします。さらに、パスワードはユーザ自身が変更できるように修正してみます。
リスト10 slapd.confの一部
access to dn.subtree="ou=People,dc=example,dc=com" attrs=userPassword
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by self write
by anonymous auth
access to dn.subtree="ou=People,dc=example,dc=com"
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by * read
by anonymous auth
さて、これまでの設定ではou=People,dc=example,dc=com
というツリー以下で設定を行ってきましたが、UNIXアカウントをLDAPで管理する上で、グループ情報は通常ou=Group,dc=example,dc=com
というツリーの下に保存されるはずです。従ってこのツリーにもACL設定を行わなければなりません。
リスト11 グループを考慮したACL設定
access to dn.subtree="ou=People,dc=example,dc=com" attrs=userPassword
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by self write
by anonymous auth
access to dn.subtree="ou=People,dc=example,dc=com"
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by * read
by anonymous auth
access to dn.subtree="ou=Group,dc=example,dc=com" attrs=userPassword
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by self write
by anonymous auth
access to dn.subtree="ou=Group,dc=example,dc=com"
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by * read
by anonymous auth
このように、ACLの設定自体は同じなのですが設定が長くなってしまいました。このような場合は正規表現ルールを用いて設定をまとめることができます。
リスト12 正規表現を使ったACL設定
access to dn.regex="ou=(People|Group),dc=example,dc=com$" attrs=userPassword
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by self write
by anonymous auth
access to dn.regex="ou=(People|Group),dc=example,dc=com$"
by dn.exact="cn=useradmin,ou=People,dc=example,dc=com" write
by * read
by anonymous auth
このように、正規表現を使うと柔軟なACLを作成することができます。
もう一歩進んだ正規表現ルール
次のように、部署毎にツリーが分かれた状態でアカウントが存在するとします。営業部に所属する鈴木さんは当然サポート部の鈴木さんのエントリを変更してはなりません。
リスト13
cn=suzuki, ou=People, o=Sales, dc=example, dc=com
cn=useradmin, ou=People, o=Sales, dc=example, dc=com
cn=tanaka, ou=People, o=Support, dc=example, dc=com
cn=useradmin, ou=People, o=Support, dc=example, dc=com
この場合の正規表現を用いたACLは次のようになります。$1を使って正規表現の後方参照を使っているところがミソです。つまり、
o=任意の文字列A,dc=example,dc=com
以下のツリーを変更可能なのは
cn=useradmin,ou=People,o=任意の文字列A,dc=example,dc=com
という設定を実現することができるのです。
リスト14 後方参照を使ったACL
access to dn.regex="o=([^,]+),dc=example,dc=com$" attrs=userPassword
by dn.regex="cn=useradmin,ou=People,o=$1,dc=example,dc=com" write
by self write
by anonymous auth
access to dn.regex="o=([^,]+),dc=example,dc=com$"
by dn.regex="cn=useradmin,ou=People,o=$1,dc=example,dc=com" write
by * read
by anonymous auth
まとめ
単にディレクトリサービスを提供するのは簡単ですが、適切なACLを設定するのは少し面倒な作業になるかもしれません。
例えば、きちんと設定したつもりであっても穴があった、その逆で設定を厳しくしすぎてソフトウェアから参照できない、ということも考えられます。実際にACLを設定したら、ダミーアカウントなどを作成し、正しく動作するか事前に確認しておくことが大切です。