そろそろLDAPにしてみないか?

第7回ApacheのBasic認証をLDAPで

Basic認証の危険性とLDAP化の概要

多くの読者の皆さんがご存じのように、パスワード認証を要求するWebページをApache上で作成するためには、通常は.htaccessと.htpasswdなどを用いたBasic認証を使用します。

たとえば、http://www.example.com/secret/以下にてパスワード認証を実現するには次のような設定を行うのが一般的でしょう。

リスト1 .htaccessやhttpd.confの設定(部分)
AuthUserFile /home/passwd/.htpasswd
AuthGroupFile /dev/null
AuthName "Secret Area"
AuthType Basic
require valid-user
リスト2 /home/passwd/.htpasswdの例
tanaka:vDVcobip.AMqE
suzuki:T8QexllcwF2Jc

この方式ではhtpasswdコマンドなどを用いて暗号化されたパスワードを作成し、ファイルシステム上のどこかで管理しておく必要があります。数年前、.htpasswdファイルをDocumentRoot内に保存しているサイトが多数存在していたため、検索エンジン経由で容易にパスワードファイルを取得できる状態となっていました。

それから数年を経て、⁠DocumentRoot以下に.htpasswdファイルを保存するのは危険」という認識は広まってきましたが、共有サーバの場合、攻撃者が自らのCGIを悪用することで、共有サーバ上に存在する.htpasswdファイルを検索、内容を解析するという攻撃も現れ始めました。環境次第では、どこに.htpasswdファイルを保存しても危険な状態になっていたのです。

近年では管理者のセキュリティ意識も高まってきたため、このように危険な状態のサーバはほとんど無いでしょうが(思い当たる方はすぐに対処してくださいね!⁠⁠、パスワード情報がLDAP上に保存されているとすればどうでしょうか?

もうパスワードの置き場所について悩むことはありません。当然検索エンジンでパスワードファイルを拾われることはありませんし、悪意のあるCGI経由でパスワードが奪われることもありません。なぜなら攻撃者はLDAPバインド用のDNやパスワードを知らないためです。当然WebサーバやLDAPサーバの設定が適切でなかったり、サーバ自体がクラックされてしまえばその限りではありませんので100%安全と言えるわけではありませんが、設定次第では非常にセキュアで便利な環境を提供することができます。

Apacheのインストール

ApacheでLDAPに対応したBasic認証を実現するためには、LDAP関連のApacheモジュールをインストールしておく必要があります。今回検証に用いたApacheは執筆時の最新バージョンである2.2.6です。本連載で毎回使用しているCentOS-4.xにおけるApacheのバージョンは2.0.52であり、LDAP認証モジュールの設定方法なども最新版と異なるため、今回はApacheバージョン2.2.6を/usr/local以下にインストールしてみました。もちろんOS標準の2.0.52でもLDAP認証を実現することはできますので、詳細は2.0.52用のドキュメントを参照してみてください。

Apache-2.2系でLDAP認証を実現するためのモジュールは、mod_authnz_ldapです。同じようにLDAPを参照するためのmod_ldapというモジュールも存在するため注意してください。こちらは認証ではなくLDAPサーバとの接続プールやキャッシュ用に使用されるモジュールです。mod_authnz_ldapはmod_ldapを利用することで、効率的にLDAPサーバの資源を使用することができます。

なお、mod_authnz_ldapを使用するためにはmod_ldapが必要ですし、mod_ldapはLDAPに対応したapr-utilに依存します。すでにLDAPに対応したapr-utilが導入されている場合はそれほど気にしなくて良いのですが、Apache付属のapr-utilを導入する場合には図1のように--with-ldapオプションを忘れないでください。

混乱しがちなのでまとめておきますと、それぞれのオプションの意味は次表の通りです。

--with-ldapLDAP対応apr-util作成のため
--enable-authnz-ldap=sharedmod_authnz_ldap.soを作成
--enable-ldap=sharedmod_ldap.soを作成
図1 LDAP認証に対応したApacheインストール
% ./configure --with-ldap --enable-authnz-ldap=shared --enable-ldap=shared
% make
# make install

設定

今回は、レンタルサーバのような環境で、それぞれのユーザにアクセス解析サービスを提供させてみたい状況を想定します。アクセス解析画面ではパスワード認証を要求し、それ以外のユーザはページにアクセスすることができません。また、ユーザは普段FTP経由でコンテンツをアップロードするため、ユーザ名、パスワードはLDAPサーバに存在するものとします。FTPとLDAPの連携については過去の記事を参考にしてみてください。まずは簡単な仕様を確認しておきましょう。

アクセスログ解析データは

/home/support/ユーザ名/analog

以下に存在するとして、

http://www.example.com/logs/ユーザ名/

にて認証を要求した上で参照できるものとします。suzukiというユーザはhttp://www.example.com/logs/suzuki/にアクセスし、ユーザ名、パスワード、ともに正しい情報を入力すればページが表示されるという仕組みです。今回の方式では、suzukiという認証済みのユーザがhttp://www.example.com/logs/tanaka/にアクセスしてしまうと、別ユーザの画面が見えてしまいますが、これについては手抜きで気にしないものとします。

LDAP URL

今回の設定ではLDAP URLというLDAP特有のURLが登場します。たとえば本稿も掲載されている技術評論社のポータルサイトのアドレスは次のように表すことができます。

http://gihyo.jp:80/admin/serial/01/ldap

つまり、gihyo.jpの80番ポートの中で、コンテンツは/admin/serial/01/ldapにある、という意味です。LDAPの場合、次のようなLDAP URLを使って検索条件などを表すことができます。

リスト3 LDAP URLの用例
scheme "://" [hostport] ["/"
[dn ["?" [attributes] ["?" [scope]
["?" [filter] ["?" extensions]]]]]]

例を挙げた方がわかりやすいですね。

ldap://10.0.100.10/ou=People,dc=example,dc=com?cn?sub?(serviceType=1)

というURLであれば、次のコマンドと等価です。条件などを?の後につづっていく形となります。

ldapsearch -x -s sub -b "ou=People,dc=example,dc=com" "(serviceType=1)" cn

LDAPに対応したソフトウェアの設定では、このようなLDAP URLを用いるものと検索ベースや検索条件などを別々に定義するものがあります。両方の形式に対応できるよう表示に慣れておきましょう。

LDAPデータ登録

今回Basic認証に必要なユーザ名やパスワードはFTPログインに使用するものをそのまま流用するものとします。検証に際してリスト4のようなエントリを使用しました。特殊な設定はなく、/etc/passwdや/etc/shadowと同等の意味を持ちます。

リスト4 user.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: cn=users,ou=Group,dc=example,dc=com
objectClass: posixGroup
objectClass: top
cn: users
userPassword: {crypt}x
gidNumber: 1000

dn: uid=suzuki,ou=People,dc=example,dc=com
objectClass: account
objectClass: posixAccount
uid: suzuki
cn: suzuki
userPassword: suzuki
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1000
homeDirectory: /home/suzuki

dn: uid=tanaka,ou=People,dc=example,dc=com
objectClass: account
objectClass: posixAccount
uid: tanaka
cn: tanaka 
userPassword: tanaka
loginShell: /bin/bash
uidNumber: 1002
gidNumber: 1000
homeDirectory: /home/tanaka

このような環境下で、たとえば認証画面でUser: tanaka、パスワード: tanakaと入力すれば認証済みになるとします。Apache側の設定としてはリスト5のようになります。いつもと違うオプションはAuthBasicProvider、AuthzLDAPAuthoritative、AuthLDAPURLです。

リスト5 httpd.conf(一部)
# 認証モジュールとLDAP接続モジュールをロード
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
LoadModule ldap_module modules/mod_ldap.so
Alias /logs/ /home/support/logs/
<Directory "/home/support/logs/*/">
    AuthName "User/Password"
    AuthType Basic
    AuthBasicProvider ldap
    AuthzLDAPAuthoritative  off
    AuthLDAPURL  ldap://10.0.100.10/ou=People,dc=example,dc=com?cn?sub?(objectClass=account)
    Require valid-user
</Directory>

ここでmod_authnz_ldapの各オプションについて解説しておきます。それ以外のオプションに関しては、Apacheのドキュメントで確認してください。ちなみに筆者が普段使用するオプションはAuthLDAPBindDN、AuthLDAPBindPassword、AuthLDAPUrl、AuthzLDAPAuthoritativeくらいです。Require valid-userを使用しているため、それに伴いAuthzLDAPAuthoritative offを定義しています。

そのほか、認証時に入力させるユーザ名ではない値を環境変数REMOTE_USERに保存するためのAuthLDAPRemoteUserAttributeというオプションなどもありますが、使用する機会は少ないように思います。

AuthLDAPBindDNLDAPサーバへバインドするためのバインドDN
AuthLDAPBindPassword上記パスワード
AuthLDAPUrlLDAP URLの指定
AuthzLDAPAuthoritative認証が失敗したときに他の認証モジュールが認証を行うのを防ぐ

認証と課金

場合によっては、今回のようなアクセスログ解析サービスを有料オプションとして提供したい場合もあるでしょう。その場合、FTP用にIDやパスワードを参照させますが、WebのBasic認証をパスさせない、という対策が必要になります。今までの連載でも触れてきましたように、LDAPでは検索フィルタを用いることでこのような機能を簡単に実装することができます。

たとえば検索フィルタが(&(objectClass=account)(servicetype=1))となっていれば、利用者の中でもservicetypeという属性が1であるユーザのみが検索対象となります。言い換えれば、有料オプション利用者のエントリではserviceType=1と定義しておき、それ以外のユーザではserviceType=0と定義しておけば良いのです。

もちろんserviceTypeという属性は筆者が勝手に決めたものであり、標準スキーマに含まれているわけではありませんので、次のようにして組み込んでおく必要があります。

リスト6 /etc/openldap/schema/webaccount.schema
attributetype (1.3.6.1.4.1.12461.1.1.1 NAME 'serviceType'
        DESC 'serviceType'
        EQUALITY integerMatch
        ORDERING integerOrderingMatch
        SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 SINGLE-VALUE)

objectclass (1.3.6.1.4.1.12461.1.2.1 NAME 'webAccount'
        DESC 'web Account'
        SUP  account
        MAY ( serviceType ) )
リスト7 /etc/openldap/slapd.confの一部
include  /etc/openldap/schema/webaccount.schema

これに合わせたユーザ用のLDIFはリスト8のようになります。

リスト8 ユーザデータ
# このユーザは有料オプションを契約しているのでserviceType=1としておく
dn: uid=suzuki,ou=People,dc=example,dc=com
objectClass: account
objectClass: posixAccount
objectClass: webAccount
uid: suzuki
cn: suzuki
userPassword: suzuki
loginShell: /bin/bash
uidNumber: 1001
gidNumber: 1000
homeDirectory: /home/suzuki
serviceType: 1

# このユーザは有料オプションを契約していないのでserviceType=0としておく
dn: uid=tanaka,ou=People,dc=example,dc=com
objectClass: account
objectClass: posixAccount
objectClass: webAccount
uid: tanaka
cn: tanaka 
userPassword: tanaka
loginShell: /bin/bash
uidNumber: 1002
gidNumber: 1000
homeDirectory: /home/tanaka
serviceType: 0

あとは検索フィルタを有効にしたhttpd.confを準備するだけですリスト9⁠。AuthLDAPURLオプションをうまく設定します。

リスト9 httpd.confの一部
<Directory "/home/support/logs/*/">
    AuthName "User/Password"
    AuthType Basic
    AuthBasicProvider ldap
    AuthzLDAPAuthoritative  off
    AuthLDAPURL  ldap://10.0.100.10/ou=People,dc=example,dc=com?cn?sub?(&(objectClass=account)(serviceType=1))
    Require valid-user
</Directory>

まとめ

今回はBasic認証用にLDAPを活用することで、冒頭に述べたとおり

  • パスワードファイルの置き場所に悩まない
  • パスワードを一元管理
  • 検索フィルタを活用してサービス制限

といったことを実現することができました。また、LDAP URLを用いて検索フィルタや条件などを追加することができることもわかりました。2007年最後の記事となりましたが、来年も少しずつLDAPの使い道を紹介していく予定です。それではまた。

おすすめ記事

記事・ニュース一覧