PythonでActive Directory管理:クリックの繰り返しを自動化で解決

Python tutorial - IT technology blog
Python tutorial - IT technology blog

実例:50回のクリックが苦痛に変わる時

管理者としての初仕事で、毎週50人の新入社員のオンボーディングを任されたことがありました。作業は退屈なルーチンの繰り返しでした。Active Directory ユーザーとコンピューター (ADUC)を開き、右クリックして「新規作成」、ユーザー名を入力し、パスワードを設定し、グループに追加する。1ユーザーあたり平均5分かかり、単純計算で機械的な作業のためだけに半日近くを費やしていました。

手作業は遅いだけでなく、ミスも非常に起こりやすいものです。たとえば、大量のテストデータを扱う場面で、displaynameの1文字を打ち間違えたり、「ユーザーは次回ログオン時にパスワードの変更が必要」のチェックボックスを忘れたりするだけで、翌日には問い合わせの電話が鳴り止みません。ユーザー数が1,000人を超えると、誰の期限が切れているか、誰が90日間パスワードを変更していないかを手動で確認するのは、もはや不可能なミッションとなります。

そこで、私はAD管理をチームのWindows自動化スタックに統合することに決めました。PowerShellを使う代わりに、既存のPythonスクリプトと完全に同期させるためにldap3を選択しました。

なぜldap3が最良の選択肢なのか?

多くのエンジニアがpython-ldapldap3のどちらを使うべきか迷います。しかし、python-ldapはWindowsへのインストールが非常に厄介で、CヘッダーやOpenLDAPライブラリの同梱を要求されます。もしMacでコードを書き、Windows上のADを管理するためにデプロイする場合、依存関係のエラーに悩まされ続けることになります。

ldap3は、ピュアPython(pure Python)設計であるため、この問題を根本から解決します。pip install一行だけで、あらゆる環境で動作します。このライブラリは同期・非同期の両方のメカニズムをサポートしており、Domain Controllerとのやり取りが非常にスムーズになります。

実装:接続と認証 (Bind)

まずは、ライブラリをインストールしましょう:

pip install ldap3

LDAPにおいて、ログイン操作は「Bind」と呼ばれます。以下は、Domain Controller (DC) への接続を設定する方法です:

from ldap3 import Server, Connection, ALL, SAFE_SYNC

LDAP_SERVER = '192.168.1.10'
USER_DN = 'CN=Admin,OU=IT,DC=itfromzero,DC=com'
PASSWORD = 'YourSecurePassword'

# サーバーの初期化と接続
server = Server(LDAP_SERVER, get_info=ALL)
conn = Connection(server, user=USER_DN, password=PASSWORD, client_strategy=SAFE_SYNC, auto_bind=True)

if conn.bound:
    print("接続に成功しました!")
    conn.unbind()
else:
    print(f"失敗: {conn.result}")

注意:ライブラリが初期化時に自動的に認証を行うよう、常にauto_bind=Trueを使用することをお勧めします。対話的なCLIツールやパスワードチェックツールを作成する場合は、そのユーザー自身の情報をUSER_DNPASSWORDに渡すだけです。

ユーザー情報の照会 (Search)

例えば、ユーザー名が「tung.nguyen」の従業員のメールアドレスと電話番号を検索する必要があるとする。ADの管理画面を開いて検索する代わりに、LDAPフィルタを使用します。

search_base = 'DC=itfromzero,DC=com'
search_filter = '(&(objectClass=user)(sAMAccountName=tung.nguyen))'

conn.search(search_base, search_filter, attributes=['mail', 'displayName', 'telephoneNumber'])

if conn.entries:
    user = conn.entries[0]
    print(f"名前: {user.displayName} - メール: {user.mail}")
else:
    print("データが見つかりませんでした。")

フィルタの構文は少し特殊に見えるかもしれません。&はAND演算を、|はOR演算を表します。上記のコマンドは、「userクラスに属し、かつsAMAccountNameが一致するオブジェクトを探す」という意味になります。

自動化:ユーザーの新規作成と有効化

これは、私が毎週4時間の作業時間を節約できるようになった部分です。LDAP経由で新しいユーザーを作成する場合、ADの必須属性を宣言する必要があります。

new_user_dn = 'CN=Nguyen Van A,OU=Users,DC=itfromzero,DC=com'
attributes = {
    'cn': 'Nguyen Van A',
    'sAMAccountName': 'a.nguyen',
    'userPrincipalName': '[email protected]',
    'userPassword': 'DefaultPassword123!',
}

if conn.add(new_user_dn, ['top', 'person', 'organizationalPerson', 'user'], attributes):
    # アカウントの有効化(デフォルトではADは新規作成されたユーザーを無効化します)
    conn.modify(new_user_dn, {'userAccountControl': [(('MODIFY_REPLACE'), [512])]})
    print("ユーザーの作成と有効化に成功しました!")

ヒント: userAccountControl属性の値512は、「通常のアカウント (Normal Account)」状態に対応します。このステップを忘れると、パスワードが正しくてもユーザーはログインできません。

システム拡張時の実践的な経験談

スクリプトが20行から、人事システムや勤怠管理システム全体をカバーする2,000行へと成長した過程で、4つの重要な教訓を得ました:

  • Connection Poolの使用: ADとのハンドシェイクはリソースを多く消費します。小さなリクエストごとに接続を開閉しないでください。ServerPoolを使用してパフォーマンスを最適化し、耐障害性を高めましょう。
  • 厳密なエラーハンドリング: Domain Controllerへのネットワークは不安定になることがあります。スクリプトが途中で停止しないよう、常にtry-exceptを使用してLDAPSocketOpenErrorなどの例外をキャッチしてください。
  • 抽象化レイヤーの構築: LDAPのコードをあちこちに散在させないでください。適切なデザインパターンを意識して、get_user()reset_password()などの関数を持つADManagerクラスにまとめましょう。
  • セキュリティを最優先に: 管理者パスワードをコード内に直接記述することは絶対に避けてください。環境変数や、設定管理ツール、HashiCorp Vaultなどのシークレット管理ツールを活用しましょう。

ldap3をマスターすれば、数千のアカウントをわずか数秒で処理できるようになります。クリック作業に追われる代わりに、その時間をシステムのより重要な部分の最適化に充てることができます。皆さんの業務への活用が成功することを願っています!

Share: