RADIUS 第一回

最終目標

RADIUS(Remote Authentication Dial In User Service)サーバを構築し、
何かの認証環境を構築する。
バックエンドにはLDAPサーバ(slapd)を据え、
CHAPなどでセキュリティを確保する。
EAPもできればがんばる。

参考にしたサイト

用意するもの

LDAPにユーザ情報を追加し、apacheへのアクセス時に参照させる

前回までの話

ou=People,dc=shonbori,dc=net
ou=Group,dc=shonbori,dc=net

というディレクトリを作った。

これはdc=netをルートとし、その下位にdc=shonboriディレクトリがあり、
さらにその配下にou=Peopleとou=Groupディレクトリが並列して存在するデータ構造であった。

LDAPに追加登録するデータ

このシリーズの目的は、LDAPのデータツリーに、認証対象となるユーザとパスワードを格納し、
apacheと連携させることである。

これを実現する上でさらに必要なデータは、ユーザとパスワードの対である。

これまでの設定でちょうどPeopleというディレクトリを作ったので、
この配下にユーザIDやパスワードのエントリ(LDAP的にはattribute(属性)らしい)を追加していこう。

具体的には、次のようなldifファイルを新たに用意し、
ldapaddでdn(distinguished name)を追加してやる。

# cat user.hoge.ldif

dn: uid=hoge,ou=People,dc=shonbori,dc=net    # hogeというユーザIDに相当するdnを定義
uid: hoge                                    # 相対dn的な表記
objectclass: account                         # *よくわからないが
objectclass: posixAccount                    # *POSIXに準拠した
#objectclass: shadowAccount                  # *データをここに書くことで
cn: hoge                                     # *既存のschemaを利用できるらしい
uidNumber: 1001                              # *
gidNumber: 1001                              # *
userPassword: {SSHA}smFKBYZoO403X9ay+3wpcg5oLQ8uXPSb # *
#             hogehoge
homeDirectory: /home/hoge                    # *
loginShell: /bin/bash                        # *

POSIXにおけるAccountとは、最低限
cn,uid,uidNumber,gidNumber,homeDirectory
の属性を持っているものらしい。
userPassword属性は、先のエントリで述べた、

# slappasswd -h {SSHA}

このコマンドで暗号化したパスワードである。
ここまでやった後、以下を実行する。

# ldapadd -x -W -D "cn=Manager,dc=shonbori,dc=net" -f user.hoge.ldif

adding new entry "uid=hoge,ou=People,dc=shonbori,dc=net"

これで、uid=hoge,ou=People,dc=shonbori,dc=netというdnについての情報が追加されたはず。
ためしにldapsearchで見てみると・・・

# ldapsearch -x -W -D "cn=Manager,dc=shonbori,dc=net" '(uid=hoge)'

# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (uid=hoge)
# requesting: ALL
#

# hoge, People, shonbori.net
dn: uid=hoge,ou=People,dc=shonbori,dc=net
uid: hoge
objectClass: account
objectClass: posixAccount
cn: hoge
uidNumber: 1001
gidNumber: 1001
userPassword:: e1NTSEF9c21GS0JZWm9PNDAzWDlheSszd3BjZzVvTFE4dVhQU2I=
homeDirectory: /home/hoge
loginShell: /bin/bash

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

このように無事参照できるはずである。

ちなみに余談だが、FreeBSD/Linux等の/etc/passwdもPOSIX準拠のposixAccountのリストであり、
これをそのままLDAPに乗せ換えて、サーバへのログインをLDAP認証に切り替えてしまうこともできるようだ。

apache側の設定

条件

/home/*/public_html配下に.htaccessを置いて、そのディレクトリへのアクセスを、LDAPを使ってBasic認証する。

apacheモジュール
LoadModule ldap_module modules/mod_ldap.so
LoadModule authnz_ldap_module modules/mod_authnz_ldap.so

以上の2つがおそらく必要になるだろう。

.htaccessの記述
        AuthType               Basic
        AuthName               "LDAP Auth"
        AuthBasicProvider      ldap
        AuthzLDAPAuthoritative off    # よくわからなかった
        AuthLDAPURL            ldap://localhost/ou=People,dc=shonbori,dc=net?uid
        Require                 valid-user

.htpasswdファイルを使う場合はAuthBasicProvider,AuthzLDAPAuthoritative,AuthLDAPURLの代わりにAuthFileディレクティブを使っていた。
AuthLDAPURLの書式はapache.orgのマニュアルに詳しく記述されているのでぜひ参照されたい。
ともかくこれで、この.htaccessを設置したディレクトリにhttpでアクセスした場合は、
ldapaddで追加したユーザ(valid-user)のIDとパスワードを入力して認証されなければコンテンツを参照できなくなった。

あとは、LDAPの初期化やユーザの追加を自動化するスクリプトを書いたりすれば、
LDAPサーバのアドミニストレータを名乗れるのではなかろうか。
・・・・だめ?

ldapdelete

コマンド実行例

# ldapdelete -x -W -D 'cn=Manager,dc=shonbori,dc=net' 'dc=shonbori,dc=net'

-xで簡易認証、-Wでパスワードをプロンプト、-D 'hogehoge'でどのdistinguished nameでアクセスするか指定、
そして最後の

'dc=shonbori,dc=net'

↑これで、どのクラスに対してldapdeleteを実施するかを指定している。
前述のldapaddで、dc=shonbori,dc=netより下層のツリーまで追加してしまっているため、
このまま実行すると

ldap_delete: Operation not allowed on non-leaf (66)
        additional info: subordinate objects must be deleted first

というエラーが帰ってくる。
ある階層のクラスから下を全部まとめて消したい場合は、

 -r

をつけて、recursive deleteしてやる必要がある。

# ldapdelete -x -W -r -D 'cn=Manager,dc=shonbori,dc=net' 'dc=shonbori,dc=net'
Enter LDAP Password:

これで、最初のldapaddコマンドで追加したデータが全部消えた。

ldapsearch

# ldapsearch -x -W -D 'cn=Manager,dc=shonbori,dc=net' -H ldap://localhost/389
Enter LDAP Password:
 *******以下、出力結果*******
# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# shonbori.net
dn: dc=shonbori,dc=net
objectClass: dcObject
objectClass: organization
o: VPDCP
dc: shonbori

# Manager, shonbori.net
dn: cn=Manager,dc=shonbori,dc=net
objectClass: organizationalRole
cn: Manager

# People, shonbori.net
dn: ou=People,dc=shonbori,dc=net
objectClass: organizationalUnit
ou: People

# Group, shonbori.net
dn: ou=Group,dc=shonbori,dc=net
objectClass: organizationalUnit
ou: Group

# search result
search: 2
result: 0 Success

# numResponses: 5
# numEntries: 4

オプションなどは先ほどのldapaddとほぼ同じ。
あえて違う使い方をしてみた点は以下のとおり。

# ldapsearch -x -W -D 'cn=Manager,dc=shonbori,dc=net' -H ldap://localhost/389
 -H ldapuri :
    データを読みに行くLDAPサーバを、URI形式で指定する。
      ldap://ホストネーム/ポート番号
      ldaps://ホストネーム/ポート番号
    のように書く。
    比較的新しい記述の仕方のようだ。
    昔から使われていたのは
    -h hostname -p port
    のような書き方らしいが、今ではURI形式の書式が主流になっているらしい。
    -Hや-hを省略するとlocalhostに聞きに行くようだ。
    どこかにデフォルトを設定できそう。

この例では、より下層のディレクトリにあるクラス/オブジェクトの情報も表示されている。
これは、ldapsearchの引数に下記のものがあるからである。

 -b base|one|sub :
         baseがベースオブジェクト検索(ってなんだ?)
         oneは、指定したクラスのみ検索。
         subは、指定したクラスを含め、その配下も検索する。(たぶん)←これがデフォルト

デフォルトがsubとなっているので、dc=shonbori,dc=netだけでなく、
その配下にあるou=People,dc=shonbori,dc=netやou=Group,dc=shonbori,dc=netも検索に引っかかっている。
また、フィルタ条件を書いてやることにより、より厳密な参照が可能になる。

# ldapsearch -x -W -D 'cn=Manager,dc=shonbori,dc=net' '(ou=People)'

こうすると、ouがPeopleとなっているオブジェクトが検索にひっかかる。デフォルトは'(objectClass=*)'。

# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (ou=People)
# requesting: ALL
#

# People, shonbori.net
dn: ou=People,dc=shonbori,dc=net
objectClass: organizationalUnit
ou: People

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

ldapadd

例として、下記のようなldifファイルを用意する。
example.ldif

dn: dc=shonbori,dc=net
objectclass: dcObject
objectclass: organization
o: VPDCP
dc: shonbori

dn: cn=Manager,dc=shonbori,dc=net
objectclass: organizationalRole
cn: Manager

dn: ou=People,dc=shonbori,dc=net
objectclass: organizationalUnit
ou: People

dn: ou=Group,dc=shonbori,dc=net
objectclass: organizationalUnit
ou: Group

ローカルDITのトップレベルをdc=netとし、その1階層下にdc=shonboriがあり、
その下に

  • cn=Manager
  • ou=People
  • ou=Group

の3つのクラスが並んでいる構造をテキストであらわしているようだ。
o=VPDCPはorganizationの名前をVPDCPとしてみたけど、DITのどの位置に来るのかが良くわからない。

いろいろと疑問点は残しつつも、とりあえずldapaddコマンドでデータを追加してみる。

# ldapadd -x -D 'cn=Manager,dc=shonbori,dc=net' -W -f example.ldif
Enter LDAP Password:
adding new entry "dc=shonbori,dc=net"

adding new entry "cn=Manager,dc=shonbori,dc=net"

adding new entry "ou=People,dc=shonbori,dc=net"

adding new entry "ou=Group,dc=shonbori,dc=net"

コマンドと結果を細かく分析すると、

# ldapadd -x -D 'cn=Manager,dc=shonbori,dc=net' -W -f example.ldif
 -x : ldap通信時の認証は、本来はSASLなどの優れた暗号化を通してやるべきだけど、
      今は何の設定もしてないので、このオプションをつけることで簡易な認証で
      いけるようにする。これをつけないとSASL等を勝手に使おうとしてエラーになる。

 -D binddn : ありていに言うとおそらく、どんな権限を持ったユーザとして
      このLDAPデータにアクセスするかの指定だと思う。
      sshでいうところの-l usernameみたいなものとして考えておけばよいのだろうか。

 -W : パスワードをたずねるプロンプトを出す。
      -w ****** というふうにパスワードをコマンドに埋め込むやり方もつかえる。
      どちらを使うべきかはシチュエーションによる。

 -f ldiffile : DITに追加したいデータが書かれたLDIFファイルを指定し、
              そこに書いてあるとおりに情報を追加する。

LDAP Passwordとは、昨日までのエントリでがんばって設定した、
/etc/openldap/slapd.confのグローバル設定部分の管理者パスワード。