LDAPツリーと検索フィルタ、そして組織図の罠
一言でいうと
LDAPはツリーであり、このツリーでグループが人を指すのか、人がグループを指すのかが照会の方向を決めます。この方向を誤ると、ログインがユーザー数に比例して遅くなります。
なぜ必要なのか
「社内アカウントを付けてください」は、結局LDAP照会で終わります。ところが、フィルターをいい加減に書くと、無効なアカウントやサービスアカウントまでユーザー一覧に入ってきて、返す属性を指定しないと、10万人のディレクトリで全属性を取得してサーバーを苦しめます。
もっと静かな問題は方向です。このディレクトリはグループがmemberで人を指すため、ユーザーのエントリをどれだけ見ても、所属グループは出てきません。これを知らずに、ログインのたびに全グループを走査するコードが、実際に作られます。ユーザーが100人のときは誰も気づかず、1万人になるとログインが3秒になります。
DIT: ディレクトリはツリーです
LDAPのデータは、DIT(Directory Information Tree)というツリーに格納されます。各ノード(エントリ)は、DN(Distinguished Name)で一意に特定されます。
dc=labhub,dc=co,dc=kr ← 루트 (Base DN)
├── ou=People
│ ├── uid=hong,ou=People,dc=labhub,dc=co,dc=kr
│ └── uid=kim,ou=People,dc=labhub,dc=co,dc=kr
├── ou=Groups
│ ├── cn=dev-team,ou=Groups,...
│ └── cn=role-admin,ou=Groups,...
└── ou=Disabled ← 퇴사자 격리 구역
このコードブロックの韓国語コメントは、順に、ルート(Base DN)と、退職者の隔離区域を意味します。
DNは下から上へ読みます。uid=hong,ou=People,dc=labhub,dc=co,dc=krは、「labhub.co.krの組織のPeopleという部署のhong」です。ファイルパスと逆の方向なので、最初は混乱します。
主な属性の略語は、これくらいを知っていれば始められます。
| 略語 | 意味 | 例 |
|---|---|---|
dc |
domainComponent | dc=labhub,dc=co,dc=kr |
ou |
organizationalUnit | ou=People |
cn |
commonName | cn=홍길동(韓国語の人名)、cn=dev-team |
uid |
userid | uid=hong |
sn / givenName |
姓 / 名 | sn=홍(韓国語の姓)、givenName=길동(韓国語の名) |
mail |
メール | hong@labhub.co.kr |
title |
役職 | title=과장(韓国語の役職名で、課長の意味) |
member |
グループのメンバー(DN) | member=uid=hong,ou=People,... |
検索フィルター: RFC 4515の前置記法
LDAPのフィルターは、演算子が先に来る括弧表記です。最初は見慣れませんが、ルールは単純です。
(uid=hong) 단순 일치
(cn=홍*) 앞부분 일치 (와일드카드)
(&(A)(B)) A 그리고 B
(|(A)(B)) A 또는 B
(!(A)) A 가 아님
(objectClass=*) 해당 속성이 존재
このコードブロックの韓国語は、各フィルターの意味を順に、単純一致、前方一致(ワイルドカード。韓国語の姓を使った例)、AかつB、AまたはB、Aではない、その属性が存在する、と説明しています。
組み合わせると、こうなります。
(&(objectClass=inetOrgPerson)(ou=개발팀)(title=과장))
(&(objectClass=inetOrgPerson)(|(title=부장)(title=차장))(!(ou=총무팀)))
これらのフィルターの韓国語の値は、順に、開発チーム(部署)、課長(役職)、部長と次長(役職)、総務チーム(部署)を意味します。
ldapsearchでは、こう書きます。
ldapsearch -x -H ldap://127.0.0.1:1389 \
-D "cn=admin,dc=labhub,dc=co,dc=kr" -w labhub123 \
-b "dc=labhub,dc=co,dc=kr" \
"(&(objectClass=inetOrgPerson)(title=과장))" uid cn ou
このコマンドのフィルターにある韓国語の値は、課長(役職)を意味します。
-xは単純認証(SASLの代わり)、-HはサーバーURI、-bは検索の開始点(Base)-D/-wはbind DNとパスワード。-wはプロセス一覧にパスワードが露出します。運用スクリプトでは-y 파일(プレースホルダーはファイルです)を使う必要があります。これはセキュリティ点検の常連の指摘です- 最後の引数たちは、取得する属性の一覧です。書かなければ全部が返ってきます。10万人のディレクトリで全属性を取得すると、サーバーとネットワークが一緒に苦しみます
検索範囲(-s)も知っておくと便利です: base(自分だけ)、one(直下の子だけ)、sub(配下すべて、デフォルト)。大きなディレクトリでsubを乱用すると遅くなります。
グループ: 2つの方向があります
メンバーシップを表現する方法は2つあります。そして、この違いが性能と同期を左右します。
- グループが人を指す(
groupOfNamesのmember属性)- グループのエントリに、メンバーのDN一覧が入っています
- 「このグループのメンバーは誰か」は速いです
- 「この人が所属するグループは何か」は、全グループを調べる必要があります
- 人がグループを指す(
memberOf属性)- ユーザーのエントリに、所属グループのDN一覧があります
- ADはこれを自動で管理します。OpenLDAPはオーバーレイを有効にする必要があります
- 「この人のグループは何か」が速いです。ログイン時の権限判定に有利です
実務の落とし穴: memberはあるのにmemberOfがないディレクトリで、ログインのたびに全グループをスキャンするコードが作られます。ユーザーが100人のときは気づかず、1万人になるとログインが3秒になります。
ADとOpenLDAPの実務上の違い
| 項目 | Active Directory | OpenLDAP |
|---|---|---|
| ログインID属性 | sAMAccountName(またはuserPrincipalName) |
通常はuid |
| 不変の識別子 | objectGUID |
entryUUID |
| グループの逆参照 | memberOfを標準で提供 |
memberofオーバーレイが必要 |
| パスワード属性 | unicodePwd(平文のLDAPでは変更不可) |
userPassword |
| アカウント状態 | userAccountControlのビット |
別の規約 |
不変の識別子がなぜ重要か。人は結婚して名前が変わり、部署を移ってDNが変わります。uidやDNをキーにして同期すると、同じ人が新規ユーザーとして2回できます。必ずobjectGUID / entryUUIDのような不変の値を連携キーに使います。この設計を誤ると、アカウントの重複作成事故が起き、それは静かに数か月間溜まります。
userAccountControlはビットフラグです。ADを扱うなら、この3つは覚えておくと便利です。
0x0002 ACCOUNTDISABLE 계정 비활성
0x0010 LOCKOUT 잠김
0x10000 DONT_EXPIRE_PASSWD 비밀번호 만료 없음
512 = 정상 계정
514 = 정상 + 비활성 (512 + 2)
66048 = 정상 + 비밀번호 만료 없음 (512 + 65536)
このコードブロックの韓国語は、各値の意味を順に、アカウント無効、ロックアウト、パスワード期限なし、正常なアカウント、正常+無効、正常+パスワード期限なし、と説明しています。
そのため、「有効なユーザーだけ」を取り出すADのフィルターは、こうなります。
(&(objectCategory=person)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))
真ん中のOIDは、「ビットAND」のマッチングルールです。初めて見ると暗号のようですが、「userAccountControlに2のビットが立っているものを除外」という意味です。このフィルターを使わないと、無効なアカウントやコンピューターアカウントまでユーザー一覧に入ってきます。
同期の2つの落とし穴
ディレクトリからこちらのシステム(またはIdP)へユーザーを定期的に同期するときに、必ず出会う問題です。
- 差分同期は削除を検出できません。 「最近変更されたエントリ」を取得する方式では、消えたエントリを見る方法がありません。そのため、退職者のアカウントがこちら側に残り続けます。これが幽霊アカウントの常連の原因です。 → 差分同期は頻繁に(1時間ごと)、全件同期は定期的に(週1回)あわせて実行します。
- 変更時刻はディレクトリサーバーの時計です。 NTPがずれたサーバーが1台でもあると、その区間の変更が静かに漏れます。症状は「たまに1人だけ反映されません」で、原因の特定が非常に難しいです。
平文の389は監査で指摘される対象です
ldap://の平文接続でbindすると、パスワードがネットワークを平文で通過します。本番環境では、ldaps://(636)またはStartTLSを使う必要があり、平文の構成はセキュリティ監査で即座に指摘されます。
(このラボ環境はcapabilityの制約で1024未満のポートを開けないため、1389の平文を使っています。学習用にそうしているだけで、本番の構成ではないことを覚えておきましょう。)