月別アーカイブ: 2013年4月

OpenLDAPでユーザ管理 (12)

amd (am-utils) のマップを LDAP で管理する。これができないと NIS から移行できない。以前は確か NetBSD 付属の am-utils は LDAP に対応していなかったように記憶しているが、NetBSD 5.0 のアナウンス によれば、5.0 から対応していたようだ。

NetBSD のソースツリーにある、src/external/bsd/am-utils/dist/ldap.schema を /usr/pkg/etc/openldap/schema/am-utils.schema にコピーする。

LDAP サーバの /usr/pkg/etc/openldap/slapd.conf に include /usr/pkg/etc/openldap/schema/am-utils.schema を追加して再起動する。

LDAP Admin に am-utils.schema を読ませる方法がわからなかったので、下の LDIF を手書きして ldapadd で登録した。その状態で LDAP Admin で接続すると以後は LDAP Admin で am-utils.schema 内で定義された ObjectClass を扱えるようになった。

dn: cn=amdmap amd.home timestamp,ou=Amd Map,dc=example,dc=co,dc=jp
objectClass: amdmapTimestamp
cn: amdmap timestamp
amdmapTimestamp: 1365580086
amdmapName: amd.home

dn: cn=amdmap amd.home[/defaults],ou=Amd Map,dc=example,dc=co,dc=jp
objectClass: amdmap
cn: amdmap amd.home[/defaults]
amdmapName: amd.home
amdmapKey: /defaults
amdmapValue: type:=nfs;sublink:=${key};opts:=rw,intr,nodev,nosuid,resvport,tcp

“ou=Amd Map,dc=example,dc=co,dc=jp” は事前に作成しておいた organizationUnit。単に整理のために作成しただけで必須ではない。名前も任意。

am-utils では、タイムスタンプ (amdmapTimestamp) とマップ (amdmap) の2種類の Object Class を使用する。

cn の形式は src/external/bsd/am-utils/dist/scripts/amd2ldif.in に合わせた。am-utils で要求されているのかどうかはよくわからない。

amdTimestamp では “cn=amdmap $mapname timestamp” で、amdmap では “cn=amdmap $mapname[$key]” となる。ここで $mapname は、am-utils で指定するマップ名 (map_name) が、$key にはキー名が入る。この例では、amd.home というマップのタイムスタンプと、/defaults の設定をしたことになっている。

タイムスタンプの値は、perl -e ‘print time,”\n”‘ として得られる Epoch を入れた。

ここで LDAP Admin が使用できるようになるので、LDAP Admin を使って個々のエントリを作成する。

LDIF で記述すると以下の通り。

dn: cn=amdmap amd.home[alice]
objectClass: top
objectClass: amdmap
amdmapName: amd.home
amdmapKey: alice
amdmapValue:
  -rhost:=gemini;rfs:=/vol/home
  host==${rhost};type:=link;fs:=${fs}
  host!=${rhost}

amdmapValue の値は am-utils でのそれ。LDAP Admin では1行になるのでやや読みにくい。
ユーザ alice のホームディレクトリが、ファイルサーバ gemini の /vol/home/alice として置かれていて、gemini からは /home がエクスポートされている環境を想定している。gemini が同じマップを使用してマウントしようとした場合には、NFS ではなく、シンボリックリンクを作成する。

次、クライアント。

まず、/etc/amd.conf。

[global]
ldap_proto_version = 3
ldap_base = "ou=Amd Map,dc=example,dc=co,dc=jp"
ldap_hostports = aries.example.co.jp:389
restart_mounts = yes

[/home2]
map_type = ldap
map_name = amd.home

見ればだいたいわかると思う。要注意は ldap_proto_version=3 。デフォルトでは 2 なので忘れると接続できない。

あとは、/etc/rc.conf に NFS と amd に必要な設定をして、起動すれば接続できる。

OpenLDAPでユーザ管理 (11)

LDAP サーバに接続できない場合について。

クライアント側の /usr/pkg/etc/ldap.conf に “bind_policy soft” を設定する。

# Reconnect policy:
#  hard_open: reconnect to DSA with exponential backoff if
#             opening connection failed
#  hard_init: reconnect to DSA with exponential backoff if
#             initializing connection failed
#  hard:      alias for hard_open
#  soft:      return immediately on server failure
bind_policy soft

これで、LDAP サーバが起動していないとかの場合には、接続を待たないですぐにエラーが返ってきて、ローカルユーザではログインできるようになる。

pam_ldap の ignore_unknown_user とか ignore_authinfo_unavail オプションはいらないのかな? なくても大丈夫そうだったけど。

OpenLDAPでユーザ管理 (10)

OpenLDAPでユーザ管理 (7) で管理用のエントリを作成したが、それがどのように使われるかを書いていなかった。

slapd の ACL (Access Control List) によるアクセス制御に用いる。

openldap authentication on netbsd を参考に、/usr/pkg/etc/openldap/slapd.conf に以下のような記述を追加する。

access to attrs=userPassword
  by dn="cn=nss,dc=example,dc=co,dc=jp" write
  by anonymous auth
  by self write
  by * none

access to attrs=uidNumber,gidNumber,uid,homeDirectory
  by dn="cn=nss,dc=example,dc=co,dc=jp" write
  by self read
  by * read

access to *
  by dn="cn=nss,dc=example,dc=co,dc=jp" write
  by self write
  by * read

ここには “access to” で始まる3つのブロックがある。行頭が空白の行は継続行だ。

1つ目のブロックは、属性 (attrs) が “userPassword” に一致するデータに関するアクセス制御を表す。

つまり保存されたパスワード文字列 (またはその暗号化またはハッシュ化された文字列)に対するアクセス許可が記述されており、”attrs=userPassword” で制御の対象がマッチする条件を記述した後に、誰がどのようにアクセスできるかを示した記述が “by” で始まる記述として続く。こちらは先頭から順に検査され、先に一致したものが使用される。

1番目の ‘by dn=”cn=nss,dc=example,dc=co,dc=jp” write’ が、以前定義した管理用の DN “cn=nss,dc=example,dc=co,dc=jp” で接続した場合の制御で、ここでは “write”、つまり、管理者はパスワードの書き込みが許可される。

2番目の ‘by anonymous auth’ は、匿名 (anonymous) で接続した場合。この場合は “auth”、つまり匿名で接続した場合は認証に限って許可される。パスワード文字列を読み出すことはできない。

3番目の ‘by self write’ は、自分が接続した場合。参照している DN と接続に用いる DN が一致している場合だ。この場合は “write”、つまり、自分のパスワードだけは書き込みが許可される。

4番目、最後の ‘by * none’ は、その他の場合。管理者でもなく、自分でもなく、認証の際に使用される匿名でもない、という場合。ようするに、他人が接続した場合だ。この場合は “none”、全てが許可されない。パスワード文字列の読み出しは不要だし、認証の必要もない(通常の認証は匿名で行われる)

2つ目のブロックは、属性が “uidNumber”, “gidNumber”, “uid”, “homeDirectory” の場合。これらは誰でも読める必要がある。また、本人が書き換えてはいけない項目でもある。

3つ目のブロックは、その他の場合。誰でも読める公開情報で、本人が更新できるようにする。

OpenLDAPでユーザ管理 (9)

LDAP に登録されているユーザのパスワードを変更しようとすると、エラーになってしまう。

$ passwd
Changing password for alice.
Enter login(LDAP) password: 
Unable to change auth token: authentication error
$ 

openldap authentication on netbsd によれば、

/etc/pam.d/system
(略)
# password
password sufficient pam_ldap.so
password sufficient pam_krb5.so try_first_pass
password sufficient pam_unix.so try_first_pass
password required pam_deny.so prelim_ignore

The last bit here with pam_deny, is a bit special, it is what enables you to change passwords for both local users and those in the ldap database with the passwd command. pam_deny with the prelim_ignore flag is needed, else pam will will fail in the preliminary phase (it is always run trough twice) and you will not be able to change passwords.

ということで、/etc/pam.d/system の password の pam_unix.so を sufficient に変更し、最後に pam_deny.so を required で追加、オプションを prelim_ignore にするらしい。

prelim_ignore?

続きを読むと、pam_deny.c にパッチをあてて pam_deny.so を作り直せと書いてある。ドキュメントは少し古いのだが、今でもその対応しかないのだろうか。

Google で検索したが最近の事情はわからずじまい。

OpenLDAPでユーザ管理 (8)

slapd のログにエラー?が出ている。

slapd[29164]: conn=1013 op=1 SRCH base="dc=example,dc=co,dc=jp" scope=2 deref=0 filter="(&(objectClass=posixAccount)(uidNumber=6400))" 
slapd[29164]: conn=1013 op=1 SRCH attr=uid userPassword uidNumber gidNumber cn homeDirectory loginShell gecos description objectClass shadowLastChange shadowMax shadowExpire
slapd[29164]: <= bdb_equality_candidates: (uidNumber) not indexed 

検索した属性にインデックスがないとで出るらしい。なくても検索はできるが遅くなる。インデックスを作りすぎるとHDD容量を圧迫する、ってとこかな?

よく出てくるものに対しては設定するのがよさそう。

/usr/pkg/etc/openldap/slapd.conf の index の設定を変更

 # Indices to maintain
-index   objectClass     eq
+index   objectClass,uid,uidNumber,gidNumber     eq

変更したら、slapindex でインデックスを作り直す必要があるらしい。

$ sudo /etc/rc.d/slapd stop
$ sudo slapindex
$ sudo /etc/rc.d/slapd start

OpenLDAPでユーザ管理 (7)

LDAP に登録した認証情報を使ってログインはできるようになったが、このままではセキュリティ上の問題がある。

LDAP でユーザ情報を管理用するための DN を作成して、その他はアクセス権を制限する。

管理用 DN の作成。openldap authentication on netbsd を参考に、次の DN を作成する。

dn: cn=nss,dc=example,dc=co,dc=jp
objectClass: top
objectClass: inetOrgPerson
cn: nss
sn: manager

システムにログインするユーザではないので、objectClass に posixAccount は含まれていない。エントリが置かれる場所も ou=People,dc=exacple,dc=co,dc=jp の下ではなく、dc=example,dc=co,dc=jp の直下だ。

今回は LDAP Admin で追加する。

まずは、ツリーのトップエントリ dc=example,dc=co,dc=jp を選択した状態で、メニューバーの [Edit] – [New] – [Entry…] を選ぶ。

左下ペインの Objectclass に top と inetOrgPerson を追加。inetOrgPerson を追加すると右下のペインに Attribute が現れるので、cn に nss、sn に manager を入力する。入力すると、中段の Rdn で cn=nss と sn=manager が選べるようになるので cn=nss を選ぶ。これが今作成しているエントリの DN の一部になる。

ここまで入力したら、New Entry ウィンドウのメニューバーの [File] – [Save and close] を選択してエントリを登録する。

下のようにトップの直下に cn=nss が作成される。

パスワードも設定しておく。

OpenLDAPでユーザ管理 (6)

次はクライアント側の設定。

ldap の設定ファイルは /usr/pkg/etc/ldap.conf になるようだ。 nss_ldap も pam-ldap も共通のファイルを用いる。デフォルトのファイルは /usr/pkg/share/examples/nss_ldap/ldap.conf と /usr/pkg/share/examples/pam-ldap/ldap.conf に置かれている。両者の diff を取ると、バージョンが異なる。あとコメントアウトされた箇所がそれなりに違う。

-# @(#)$Id: ldap.conf,v 2.49 2009/04/25 01:53:15 lukeh Exp $
+# @(#)$Id: ldap.conf,v 1.38 2006/05/15 08:13:31 lukeh Exp $

/usr/pkg/etc/ldap.conf の base を書き換え、uri を追加した。

(略)
# The distinguished name of the search base.
base dc=example,dc=co,dc=jp
(略)
uri ldap://aries.example.co.jp/
(略)

次に /etc/nsswitch.conf の group と passwd のエントリを ldap を参照するように修正。

(略)
group:          files ldap
(略)
passwd:         files ldap
(略)

id(1) で LDAP に登録されたユーザが引けるかどうか確認。

$ id allice
uid=6400(allice) gid=6525(ldapusers) groups=6525(ldapusers)
$

正しく引けている。

次は PAM の設定。/etc/pam.d/system に pam_ldap.so を記述する。以下は抜粋。

# auth
auth            sufficient      /usr/pkg/lib/security/pam_ldap.so
auth            sufficient      pam_skey.so             no_warn try_first_pass
auth            sufficient      pam_krb5.so             no_warn try_first_pass
auth            optional        pam_afslog.so           no_warn try_first_pass
auth            required        pam_unix.so             no_warn try_first_pass nullok

# account
account         sufficient      /usr/pkg/lib/security/pam_ldap.so
account         required        pam_krb5.so
account         required        pam_unix.so

# session
session         sufficient      /usr/pkg/lib/security/pam_ldap.so
session         required        pam_lastlog.so          no_fail no_nested

# password
password        sufficient      /usr/pkg/lib/security/pam_ldap.so
password        sufficient      pam_krb5.so             no_warn try_first_pass
password        required        pam_unix.so             no_warn try_first_pass

LDAP Admin でユーザ allice alice にパスワードを設定しておく。

$ login alice
Password: (LDAP Admin で設定した alice のパスワード)
(略)
NetBSD 6.0.1 (GENERIC)

Welcome to NetBSD!

$

OpenLDAPでユーザ管理 (5)

前回でトップレベルの DN と、管理者の DN エントリを手作業で登録したので、GUI な LDAP 管理ツールが利用できるようになった。

ここからは LDAP Admin を使用する。Windows で動作する LDAP 管理ツールだ。使用したバージョンは 1.5.1。

LDAP サーバへの接続設定。新規登録、変更するなら Account に管理者の Username と Password を設定する。Username はもちろん管理者の DN で。

トップレベルに Organizational unit を追加。Organizational Unit の名前に People、Group をそれぞれ指定する。

メニューバーの [Tools] – [Session preferences] で [ID Settings] タブ内の設定を変更する。ここでは、User ID、Group ID の範囲をともに 6000-6999 にしている。

ou=Group の下に Group エントリを作成、Group name は ldapusers にする。

ou=People の下に User エントリを作成、ここではユーザ名を allice alice としている。

[Create User] の [Membership] タブのところで、Primary group として、先に作成した ldapusers を指定する。[Set…] ボタンを押すと、LDAP に登録されているグループが表示され、選択できる。

これで、ユーザとグループの登録ができた。登録後はこんな感じ。ou=Group と ou=People の下にそれぞれ各グループとユーザのエントリがぶら下がる木構造になっている。

OpenLDAPでユーザ管理 (4)

LDAP サーバは起動したが、この段階ではデータベースは空の状態。

最初にいくつかのエントリを登録しないといけないらしい。

一つはデータベースのトップを表すエントリ、もう一つは管理者のエントリだ。ユーザアカウント管理のためにはまだ登録しないといけないエントリがあるが、まずはこの2つのエントリを登録する必要がある。

$ cat example.ldif
dn: dc=example,dc=co,dc=jp
objectclass: dcObject
objectclass: organization
o: Example Company 
dc: example

dn: cn=Manager,dc=example,dc=co,dc=jp
objectclass: organizationalRole
cn: Manager
$

ここには空行を挟んで2つのエントリが記述されている。前半がトップのエントリ、後半が管理者のエントリだ。dn は識別名 (Distinguished Name) のこと。他の属性で短すぎて意味を推測しにくいものをメモしておく。これらは core.schema で定義されている。

  • o (organizarionName) 組織名
  • cn (commonName) 一般名
  • dc (domainComponent) ドメインの構成要素

管理者の dn は、slapd.conf の rootdn で指定したものと一致させる。

LDIF を手書きするなど随分とめんどくさいし、間違えたらどうなるのだろうと不安であるが、このエントリは最初に登録するだけなので我慢して記述する。ファイル名はなんでもよいが、example.ldif とした。

ldapadd での登録の前に、オプション ‘-n’ をつけて実行する。

$ ldapadd -x -D "cn=Manager,dc=example,dc=co,dc=jp" -W -f example.ldif -n
!adding new entry "dc=example,dc=co,dc=jp"

!adding new entry "cn=Manager,dc=example,dc=co,dc=jp"

$

エラーはなさそうだが、これでチェックになっているのかどうかはよくわからない。

‘-n’ を外して ldapadd を実行すると、パスワードを入力するプロンプトが現れた。パスワードを設定した覚えはないので、空のまま enter を押す。

$ ldapadd -x -D "cn=Manager,dc=example,dc=co,dc=jp" -W -f init.ldif 
Enter LDAP Password: (何も入力せずにenter)
ldap_bind: Server is unwilling to perform (53)
        additional info: unauthenticated bind (DN with no password) disallowed
$

何かが失敗しているようだ。試しにでたらめな文字列をパスワードとして入力してみる。

$ ldapadd -x -D "cn=Manager,dc=example,dc=co,dc=jp" -W -f init.ldif 
Enter LDAP Password: (でたらめな文字列を入力してenter)
ldap_bind: Invalid credentials (49)
$

当然失敗するがエラーメッセージは異なっている。

もう一度 slapd.conf を確認すると、

(略)
rootdn          "cn=Manager,dc=example,dc=co,dc=jp"
# Cleartext passwords, especially for the rootdn, should
# be avoid.  See slappasswd(8) and slapd.conf(5) for details.
# Use of strong authentication encouraged.
rootpw          secret
# The database directory MUST exist prior to running slapd AND 
(略)

うむ、この rootpw の ‘secret’ は何かのキーワードじゃなくて、パスワードそのものだったようだ。

$ ldapadd -x -D "cn=Manager,dc=example,dc=co,dc=jp" -W -f init.ldif   
Enter LDAP Password: (slapd.conf の rootpw に設定したパスワード)
adding new entry "dc=example,dc=co,dc=jp"

adding new entry "cn=Manager,dc=example,dc=co,dc=jp"

$

今度は無事成功である。

確認してみる。

$ ldapsearch -x -b 'dc=example,dc=co,dc=jp' '(objectclass=*)'
# extended LDIF
#
# LDAPv3
# base  with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# example.co.jp
dn: dc=example,dc=co,dc=jp
objectClass: dcObject
objectClass: organization
o: Example Company
dc: example

# Manager, example.co.jp
dn: cn=Manager,dc=example,dc=co,dc=jp
objectClass: organizationalRole
cn: Manager

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2
$ 

こちらも問題なし。

OpenLDAPでユーザ管理 (3)

/usr/pkg/etc/openldap/slapd.conf の編集。

システムにログインするユーザの認証には、最初から slapd.conf に記述されている core.schema に加え、3つのスキーマ cosine.schema、inetorgperson.schema、nis.schema が必要となる。

include         /usr/pkg/etc/openldap/schema/core.schema
include         /usr/pkg/etc/openldap/schema/cosine.schema
include         /usr/pkg/etc/openldap/schema/inetorgperson.schema
include         /usr/pkg/etc/openldap/schema/nis.schema

suffix を編集する。ここではドメイン名が example.co.jp であるとして

suffix          "dc=example,dc=co,dc=jp"
rootdn          "cn=Manager,dc=example,dc=co,dc=jp"

/etc/rc.conf に slapd の起動オプションを追加して、

slapd=YES

slapd を起動。

$ sudo /etc/rc.d/slapd start

動作確認。

$ ldapsearch -x -b '' -s base '(objectclass=*)' namingContexts
# extended LDIF
#
# LDAPv3
# base <> with scope baseObject
# filter: (objectclass=*)
# requesting: namingContexts 
#

#
dn:
namingContexts: dc=example,dc=co,dc=jp

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1
$ 

あたりまえだがドキュメント通り、確かに動いている。