資格情報は通り、トンネルが上がり始め、そのあとセッションはパスワードではなくグループに関するメッセージで失敗する。この組み合わせは、障害を LDAP プラグインの仕事の後半に置く。バインドとパスワード確認は成功し、続くメンバー判定が失敗している。両方の半分は同じ設定ブロックを読むので、片方を直しても他方が手つかずのまま残ることがある。まず、どの段階かを示すログ行から見る。たまたま試しているアカウントから始めない。

拒否を認可の答えとして扱う

プラグインはサービスバインドを行い、アカウントを検索し、渡されたパスワードを検証し、そのアカウントが許可されたグループに属するかを問い合わせる。これは四つの別々の操作であり、グループに関わるのは最後の一つだけだ。したがってバインド成功後に届く拒否は、クライアントが一般的な認証エラーを表示していても、パスワードについては何も語らない。設定を変える前にプラグインのログ水準を上げることが、出力の中で四つを切り分ける。

グループ評価そのものを探す。グループ問い合わせがまったく現れないときは、認可ブロックに到達していない。これは通常、ディレクティブがそれを所有するブロックの外にあるか、必須のオプションが欠けていることを意味する。問い合わせが現れて何も返さないときは、エンドポイントかフィルタが意図とは別の問いに答えている。

メンバーシップがフィルタにあるのかグループブロックにあるのかを決める

サポートされる形は二つあり、それを混ぜることが、動いている設定が動かなくなる道だ。一つ目はユーザーオブジェクトの属性を問い合わせることでメンバーシップをユーザー検索に押し込む。二つ目はユーザー検索をそのままにして、あとからプラグインにグループオブジェクトを検索させる。デプロイごとにどちらかを選び、選んだ方を書き留める。

<Authorization>
    BaseDN       "OU=People,DC=example,DC=org"
    SearchFilter "(&(sAMAccountName=%u)(memberOf=CN=VPN Access,OU=Groups,DC=example,DC=org))"
    RequireGroup false
</Authorization>

その形ではフィルタがすべてを決めるので、RequireGroup は false でなければならず、そうでなければプラグインはフィルタが一度も触れていないグループオブジェクトを探しに行く。フィルタ内の属性はユーザーオブジェクトに存在し、値が入っている必要があるが、グループが入れ子になるとそれは保証されない。

もう一つの形は検索を単純に保ち、そのあとにグループ検索を追加する。グループオブジェクトは明示的に名指しされ、プラグインはユーザーをディレクトリが返すメンバーと比較する。ユーザーフィルタは何も変わらないので、グループを別のチームが管理しているときにこの形は監査しやすい。

<Authorization>
    BaseDN       "OU=People,DC=example,DC=org"
    SearchFilter "(sAMAccountName=%u)"
    RequireGroup true
    Group        "CN=VPN Access,OU=Groups,DC=example,DC=org"
</Authorization>

盲点は共通だ。単純なメンバー比較は入れ子のグループを展開しないため、親グループ経由でアクセスが来るアカウントは失敗し、コンソールにはメンバーシップが表示されたままになる。Active Directory はグループに member を公開し、他のスキーマは uniqueMember を使い、間違った属性を指すとエラーではなく空の結果が返る。

グループオブジェクトを保持するエンドポイントを選ぶ

URL はプラグインがどのディレクトリに問い合わせるかを決め、ポート 389 のドメインコントローラは自分の名前付けコンテキストにしか答えない。ポート 3268 のグローバルカタログはフォレスト全体に答え、読み取り専用で部分的な属性セットを持つ。アカウントが一方のドメインにあり VPN グループが別のドメインにあると、単一ドメインの問い合わせはユーザーを見つけてもメンバーシップを取りこぼす。URL をカタログのポートへ移すと拒否が解消するのはこの場合であり、人が過度に一般化するのもこの場合だ。

部分的な属性セットが限界だ。カタログが保持しない属性ではフィルタできず、同じ変更が一方のデプロイを動かし、他方では何も返さないことがある。ブロックを書き直す前に、実際のユーザーとグループに対してエンドポイントが本当に何を返すかを確認する。参照と TLS もまた別の問題で、参照は追従する必要があり、TLS を要求するエンドポイントには対応する信頼ファイルが要る。

アカウント状態と検索範囲をそれぞれのフィールドに置く

無効化フラグはフィルタに属し、グループ確認には属さない。Active Directory では userAccountControl の二番目のビットであり、この条件がないと有効なパスワードを持つ無効なアカウントが認証を通り、そのあとグループの段階で失敗し、認可の不具合のように見える。検索範囲はベース DN に属し、ドメインのルートから始めると遅く、対象にしたかった組織単位の外のオブジェクトに一致しうる。

バインドアカウントの権限も狭く保つ。広いディレクトリ権限は、設定を誤ったフィルタを成功する問い合わせに変え、誰かがアカウントを監査するまで不具合を隠す。メンバーシップはディレクトリ自体で変更し、決してローカルの例外リストでは変えず、アカウントの無効化だけでアクセスを外せるようにする。グループを別のチームが管理しているなら、フィルタを書く前に彼らが埋める属性について合意する。

三つのアカウントで結果を証明する

三つの ID に対して設定を実行する。許可されたメンバー、グループ外の有効なアカウント、無効化されたアカウントだ。三つとも拒否されればバインドかグループ問い合わせが壊れており、三つとも受け入れられればグループ条件がまったく適用されていない。混在した結果だけが、設定がこちらにとって重要な場合を区別している証拠になる。

プラグインを責める前に、同じ三つの答えをディレクトリから直接読む。最初の問い合わせはフィルタが依存するグループ属性を示し、二つ目はアカウント制御の値を示す。自分のベース DN、アカウント、エンドポイントに置き換えてほしい:

ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=alice)" memberOf
ldapsearch -H ldap://dc01.example.org:3268 -x -D "[email protected]" -W -b "DC=example,DC=org" "(sAMAccountName=bob)" userAccountControl

memberOf が空で返るユーザーはディレクトリの事実であってプラグインの不具合ではなく、無効化されたあとも認証が通るアカウントはフィルタにアカウント制御の条件が欠けていることを意味する。まずその証拠を直し、それから三アカウントのテストを再実行する。失敗した段階を示すログ行を設定変更のそばに残しておけば、次の人が検索を繰り返さずに認可と認証を切り分けられる。

関連記事