refactor: use Passwork for AD credentials (like prod config)

This commit is contained in:
ohmo
2026-07-04 13:33:57 +07:00
parent ecc9443400
commit 7bd7700314
+122 -97
View File
@@ -1,59 +1,88 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Тестовый скрипт для проверки обращения к Active Directory. Тестовый скрипт — поиск пользователя в Active Directory.
Аналог find_user_in_ad() из sd_dispatcher.py — standalone. Аналог find_user_in_ad() из sd_dispatcher.py.
Установка зависимостей (на сервере 192.168.1.106): Использует Passwork для загрузки учётных данных AD (как в основном коде).
python3 -m pip install ldap3 Если Passwork недоступен — просит ввести учётные данные вручную.
Использование: Единственная зависимость: ldap3
# 1) Через аргументы: pip install ldap3
python3 test_ad_lookup.py --user "sibcem\\svc_dispatcher" --pass "password" --login ivanov
# 2) Через env-переменные: Запуск:
export AD_USER="sibcem\\svc_dispatcher"
export AD_PASS="password"
python3 test_ad_lookup.py --login ivanov python3 test_ad_lookup.py --login ivanov
# 3) По email:
python3 test_ad_lookup.py --email ivanov@sibcem.ru python3 test_ad_lookup.py --email ivanov@sibcem.ru
# 4) По телефону:
python3 test_ad_lookup.py --phone 79131234567 python3 test_ad_lookup.py --phone 79131234567
# 5) Тест: найти первого попавшегося пользователя и проверить все методы:
python3 test_ad_lookup.py --test-all python3 test_ad_lookup.py --test-all
""" """
import argparse import argparse
import base64
import json
import os import os
import re import re
import sys import sys
# ---------------------------------------------------------------------------
# 1. Загрузка учётных данных
# ---------------------------------------------------------------------------
def get_credentials(): # =========================================================================
"""Возвращает (user, password, base_dn).""" # 1. Загрузка учётных данных AD
user = os.getenv("AD_USER", "").strip() # =========================================================================
password = os.getenv("AD_PASS", "").strip()
base = os.getenv("AD_BASE", "").strip() or "OU=-Пользователи,DC=sibcem,DC=ru"
return user, password, base def load_ad_credentials():
"""
Загружает AD_USER и AD_PASSWORD из Passwork (как в config.py).
fallback: ручной ввод.
"""
# --- Путь 1: через модуль Passwork ---
passwork_dir = "/opt/passwork"
card_name = os.getenv("PW_ID_AD", "AD").strip()
if passwork_dir not in sys.path:
sys.path.insert(0, passwork_dir)
try:
from passwork import get_passwork_secrets
print(f"[Passwork] Загрузка карточки '{card_name}'...")
pool = get_passwork_secrets(required_cards=[card_name])
card = pool.get(card_name)
if card:
ad_user = card.get("login", "").strip()
ad_pass = card.get("password", "").strip()
if ad_user and ad_pass:
print(f"[Passwork] ✅ AD_USER: {ad_user}")
return ad_user, ad_pass
except Exception:
pass
# --- Путь 2: env-переменные ---
ad_user = os.getenv("AD_USER", "").strip()
ad_pass = os.getenv("AD_PASS", "").strip()
if ad_user and ad_pass:
print("[Env] ✅ Учётные данные загружены из окружения")
return ad_user, ad_pass
# --- Путь 3: ручной ввод ---
print("\n❌ Passwork недоступен.")
print("Введите учётные данные AD вручную:\n")
ad_user = input(" AD user (DOMAIN\\account): ").strip()
ad_pass = input(" AD password: ").strip()
if not ad_user or not ad_pass:
print("❌ Отмена.")
sys.exit(1)
print(f"[Manual] ✅ AD_USER: {ad_user}")
return ad_user, ad_pass
# --------------------------------------------------------------------------- # =========================================================================
# 2. Подключение к LDAP # 2. Подключение к LDAP
# --------------------------------------------------------------------------- # =========================================================================
def connect_to_ad(ad_user, ad_pass, ad_base): def connect_to_ad(ad_user, ad_pass):
"""Возвращает подключённый ldap3.Connection."""
from ldap3 import Server, Connection from ldap3 import Server, Connection
server = Server( server = Server(
"ldap://172.16.20.20", "ldap://172.16.20.20",
get_info=None, # не тянем schema — экономим время get_info=None,
connect_timeout=5, connect_timeout=5,
) )
conn = Connection( conn = Connection(
@@ -63,17 +92,17 @@ def connect_to_ad(ad_user, ad_pass, ad_base):
auto_bind=True, auto_bind=True,
receive_timeout=5, receive_timeout=5,
) )
return conn, ad_base return conn
# --------------------------------------------------------------------------- # =========================================================================
# 3. Поиск пользователя # 3. Поиск пользователя
# --------------------------------------------------------------------------- # =========================================================================
def find_user(conn, ad_base, login=None, email=None, phone=None): def find_user(conn, login=None, email=None, phone=None):
""" """
Ищет пользователя в AD. Приоритет: телефон > логин > email. Приоритет: телефон > логин > email.
Возвращает dict с атрибутами или None. Возвращает dict атрибутов или None.
""" """
if phone: if phone:
filt = ( filt = (
@@ -93,20 +122,21 @@ def find_user(conn, ad_base, login=None, email=None, phone=None):
print("❌ Укажите --login, --email или --phone") print("❌ Укажите --login, --email или --phone")
sys.exit(1) sys.exit(1)
print(f"\n[LDAP] Поиск: {filt}") print(f"\n[LDAP] Фильтр: {filt}")
print(f"[LDAP] Base DN: {ad_base}") print(f"[LDAP] Base: OU=-Пользователи,DC=sibcem,DC=ru")
# Запрос 1 — основной
conn.search( conn.search(
search_base=ad_base, search_base="OU=-Пользователи,DC=sibcem,DC=ru",
search_filter=filt, search_filter=filt,
attributes=["l", "company", "manager", "department", "ou", attributes=[
"displayName", "mail", "sAMAccountName", "title", "sAMAccountName", "displayName", "givenName", "sn",
"telephoneNumber", "mobile", "givenName", "sn"], "mail", "title", "department", "l", "company",
"manager", "telephoneNumber", "mobile",
],
) )
if not conn.entries: if not conn.entries:
print(f"\nПользователь не найден по {by}") print(f"\nНе найден по {by}")
return None return None
user = conn.entries[0] user = conn.entries[0]
@@ -127,16 +157,16 @@ def find_user(conn, ad_base, login=None, email=None, phone=None):
"mobile": user.mobile.value if "mobile" in user else None, "mobile": user.mobile.value if "mobile" in user else None,
} }
# Город из OU-пути если атрибут l пустой # Город из OU если атрибут l пустой
if not result["city"]: if not result["city"]:
match = re.search(r"OU=([^,]+),OU=-Пользователи", result["dn"], re.IGNORECASE) match = re.search(r"OU=([^,]+),OU=-Пользователи", result["dn"], re.IGNORECASE)
if match: if match:
result["city"] = match.group(1).strip() result["city"] = match.group(1).strip()
if result["city"].lower() == "красноярск" and "OU=ООО Комбинат Волна" in result["dn"]: if result["city"].lower() == "красноярск" and "OU=ООО Комбинат Волна" in result["dn"]:
result["company"] = "ООО Комбинат Волна" result["company"] = "ООО Комбинат Волна"
print(f" Город извлечён из OU: {result['city']}") print(f" Город из OU: {result['city']}")
# Запрос 2 — email руководителя # Руководитель — второй запрос
manager_dn = user.manager.value if "manager" in user else None manager_dn = user.manager.value if "manager" in user else None
if manager_dn: if manager_dn:
conn.search( conn.search(
@@ -147,7 +177,9 @@ def find_user(conn, ad_base, login=None, email=None, phone=None):
) )
if conn.entries and "mail" in conn.entries[0]: if conn.entries and "mail" in conn.entries[0]:
result["manager_email"] = conn.entries[0].mail.value result["manager_email"] = conn.entries[0].mail.value
result["manager_name"] = conn.entries[0].displayName.value if "displayName" in conn.entries[0] else None result["manager_name"] = (
conn.entries[0].displayName.value if "displayName" in conn.entries[0] else None
)
else: else:
result["manager_email"] = None result["manager_email"] = None
else: else:
@@ -156,12 +188,11 @@ def find_user(conn, ad_base, login=None, email=None, phone=None):
return result return result
# --------------------------------------------------------------------------- # =========================================================================
# 4. Форматирование вывода # 4. Вывод
# --------------------------------------------------------------------------- # =========================================================================
def print_report(data): def print_report(data):
"""Красивый вывод результатов."""
print("\n" + "=" * 60) print("\n" + "=" * 60)
print(" РЕЗУЛЬТАТ ПОИСКА В ACTIVE DIRECTORY") print(" РЕЗУЛЬТАТ ПОИСКА В ACTIVE DIRECTORY")
print("=" * 60) print("=" * 60)
@@ -183,38 +214,33 @@ def print_report(data):
print(f" DN: {data['dn']}") print(f" DN: {data['dn']}")
# OU-дерево # OU-дерево
dn = data["dn"] ou_parts = [p.strip() for p in data["dn"].split(",") if p.strip().startswith("OU=")]
parts = [p.strip() for p in dn.split(",")]
ou_parts = [p for p in parts if p.startswith("OU=")]
if ou_parts: if ou_parts:
print(f"\n OU-структура:") print(f"\n OU-структура:")
for i, ou in enumerate(reversed(ou_parts)): for i, ou in enumerate(reversed(ou_parts)):
indent = " " * i print(f" {' ' * i}└── {ou}")
print(f" {indent}└── {ou}")
print("=" * 60) print("=" * 60)
# --------------------------------------------------------------------------- # =========================================================================
# 5. Тестирование всех методов поиска # 5. Тест всех методов
# --------------------------------------------------------------------------- # =========================================================================
def test_all_methods(conn, ad_base): def test_all_methods(conn):
"""Находит первого пользователя и проверяет все методы поиска."""
print("\n" + "" * 60) print("\n" + "" * 60)
print(" РЕЖИМ ТЕСТИРОВАНИЯ: поиск по всем методам") print(" ТЕСТИРОВАНИЕ: все методы поиска")
print("" * 60) print("" * 60)
conn.search( conn.search(
search_base=ad_base, search_base="OU=-Пользователи,DC=sibcem,DC=ru",
search_filter="(objectClass=user)", search_filter="(objectClass=user)",
attributes=["sAMAccountName", "mail", "telephoneNumber", "displayName", attributes=["sAMAccountName", "mail", "telephoneNumber"],
"l", "company", "department"],
size_limit=1, size_limit=1,
) )
if not conn.entries: if not conn.entries:
print("В AD нет пользователей.") print("Нет пользователей в AD.")
return return
sample = conn.entries[0] sample = conn.entries[0]
@@ -222,61 +248,60 @@ def test_all_methods(conn, ad_base):
mail = sample.mail.value if "mail" in sample else None mail = sample.mail.value if "mail" in sample else None
phone = sample.telephoneNumber.value if "telephoneNumber" in sample else None phone = sample.telephoneNumber.value if "telephoneNumber" in sample else None
print(f"\n📌 Тестовый пользователь: {sam} ({mail})") print(f"\n📌 Тестовый: {sam} ({mail})")
for label, kwargs in [("логину", {"login": sam}), for label, kwargs in [("логину", {"login": sam}),
("email", {"email": mail}) if mail else None, ("email", {"email": mail}) if mail else None,
("телефону", {"phone": phone}) if phone else None]: ("телефону", {"phone": phone}) if phone else None]:
if kwargs is None: if kwargs is None:
continue continue
print(f"\n[Тест] Поиск по {label}...") print(f"\n[Тест] По {label}...")
r = find_user(conn, ad_base, **kwargs) r = find_user(conn, **kwargs)
if r: if r:
print_report(r) print_report(r)
print("\n" + "" * 60) print("\n" + "" * 60)
print("Тестирование завершено") print("Готово")
print("" * 60) print("" * 60)
# --------------------------------------------------------------------------- # =========================================================================
# MAIN # MAIN
# --------------------------------------------------------------------------- # =========================================================================
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Тестовый поиск пользователя в AD") parser = argparse.ArgumentParser(
parser.add_argument("--user", help="LDAP user (sAMAccountName или DN)") description="Поиск пользователя в Active Directory",
parser.add_argument("--pass", dest="password", help="LDAP password") formatter_class=argparse.RawDescriptionHelpFormatter,
parser.add_argument("--base", help="Base DN (по умолчанию OU=-Пользователи,DC=sibcem,DC=ru)") epilog="""
Примеры:
python3 test_ad_lookup.py --login ivanov
python3 test_ad_lookup.py --email ivanov@sibcem.ru
python3 test_ad_lookup.py --phone 79131234567
python3 test_ad_lookup.py --test-all
Учётные данные AD загружаются из Passwork (как в основном коде).
Если Passwork недоступен — запрашиваются вручную.
""",
)
group = parser.add_mutually_exclusive_group(required=True) group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--login", help="sAMAccountName пользователя для поиска") group.add_argument("--login", help="sAMAccountName пользователя")
group.add_argument("--email", help="Email пользователя для поиска") group.add_argument("--email", help="Email пользователя")
group.add_argument("--phone", help="Номер телефона для поиска") group.add_argument("--phone", help="Номер телефона")
parser.add_argument("--test-all", action="store_true", parser.add_argument("--test-all", action="store_true",
help="Найти первого пользователя и протестировать все методы поиска") help="Найти первого пользователя и проверить все методы")
args = parser.parse_args() args = parser.parse_args()
# Учётные данные: аргументы > env > prompt # Загрузка учётных данных (Passwork → env → ручной ввод)
ad_user = args.user or os.getenv("AD_USER", "") ad_user, ad_pass = load_ad_credentials()
ad_pass = args.password or os.getenv("AD_PASS", "")
ad_base = args.base or os.getenv("AD_BASE", "OU=-Пользователи,DC=sibcem,DC=ru")
if not ad_user or not ad_pass: conn = connect_to_ad(ad_user, ad_pass)
# Пробуем получить из .env файла проекта
env_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "sd_dispatcher", "config", ".env")
if os.path.exists(env_path):
print(f"[.env] Загружено: {env_path}")
else:
print("❌ Укажите --user и --pass, или задайте AD_USER / AD_PASS")
sys.exit(1)
conn, ad_base = connect_to_ad(ad_user, ad_pass, ad_base)
print(f"[LDAP] Подключено к ldap://172.16.20.20 как {ad_user}") print(f"[LDAP] Подключено к ldap://172.16.20.20 как {ad_user}")
if args.test_all: if args.test_all:
test_all_methods(conn, ad_base) test_all_methods(conn)
else: else:
data = find_user(conn, ad_base, login=args.login, email=args.email, phone=args.phone) data = find_user(conn, login=args.login, email=args.email, phone=args.phone)
if data: if data:
print_report(data) print_report(data)