128 lines
4.0 KiB
Python
128 lines
4.0 KiB
Python
"""Probe remote host and register in SAC before agent deploy (WinRM / SSH)."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import re
|
||
from datetime import datetime, timezone
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.orm import Session
|
||
|
||
from app.models import Host
|
||
from app.services.agent_update_settings import PRODUCT_RDP, PRODUCT_SSH
|
||
from app.services.linux_admin_settings import get_effective_linux_admin_config
|
||
from app.services.ssh_connect import _is_ipv4, _ssh_output_hostname, test_ssh_connection
|
||
from app.services.win_admin_settings import get_effective_win_admin_config
|
||
from app.services.winrm_connect import test_winrm_connection
|
||
|
||
_IPV4_RE = re.compile(r"^\d{1,3}(?:\.\d{1,3}){3}$")
|
||
|
||
|
||
class ManualHostAddError(Exception):
|
||
pass
|
||
|
||
|
||
def validate_windows_target(target: str) -> str:
|
||
text = (target or "").strip()
|
||
if not text:
|
||
raise ManualHostAddError("Укажите имя Windows-ПК")
|
||
if _IPV4_RE.match(text):
|
||
raise ManualHostAddError(
|
||
"Для Windows укажите имя ПК (NetBIOS/DNS), не IP — WinRM с Kerberos по IP ненадёжен"
|
||
)
|
||
short = text.split(".")[0].split("\\")[-1].strip()
|
||
if not short or " " in short:
|
||
raise ManualHostAddError("Некорректное имя хоста")
|
||
return short
|
||
|
||
|
||
def validate_linux_target(target: str) -> str:
|
||
text = (target or "").strip()
|
||
if not text:
|
||
raise ManualHostAddError("Укажите IP или hostname Linux-хоста")
|
||
return text
|
||
|
||
|
||
def probe_windows_host(db: Session, hostname: str) -> str:
|
||
cfg = get_effective_win_admin_config(db)
|
||
if not cfg.configured:
|
||
raise ManualHostAddError("Windows domain admin не настроен в SAC")
|
||
probe = test_winrm_connection(
|
||
target=hostname,
|
||
user=cfg.user,
|
||
password=cfg.password,
|
||
)
|
||
if not probe.ok:
|
||
raise ManualHostAddError(probe.message)
|
||
return (probe.hostname or hostname).strip()
|
||
|
||
|
||
def probe_linux_host(db: Session, target: str) -> tuple[str, str | None]:
|
||
cfg = get_effective_linux_admin_config(db)
|
||
if not cfg.configured:
|
||
raise ManualHostAddError("Linux SSH admin не настроен в SAC")
|
||
probe = test_ssh_connection(
|
||
target=target,
|
||
user=cfg.user,
|
||
password=cfg.password,
|
||
)
|
||
if not probe.ok:
|
||
raise ManualHostAddError(probe.message)
|
||
hostname = _ssh_output_hostname(probe.stdout) or target
|
||
ipv4 = target if _is_ipv4(target) else None
|
||
return hostname, ipv4
|
||
|
||
|
||
def get_or_create_manual_host(
|
||
db: Session,
|
||
*,
|
||
hostname: str,
|
||
product: str,
|
||
os_family: str,
|
||
ipv4: str | None = None,
|
||
) -> Host:
|
||
host = db.scalar(
|
||
select(Host).where(Host.hostname == hostname, Host.product == product).limit(1)
|
||
)
|
||
now = datetime.now(timezone.utc)
|
||
if host is None:
|
||
host = Host(
|
||
hostname=hostname,
|
||
os_family=os_family,
|
||
product=product,
|
||
ipv4=ipv4,
|
||
last_seen_at=now,
|
||
)
|
||
db.add(host)
|
||
else:
|
||
host.os_family = os_family
|
||
if ipv4:
|
||
host.ipv4 = ipv4
|
||
host.last_seen_at = now
|
||
db.flush()
|
||
return host
|
||
|
||
|
||
def prepare_manual_host_add(db: Session, *, platform: str, target: str) -> Host:
|
||
platform_norm = (platform or "").strip().lower()
|
||
if platform_norm == "windows":
|
||
win_target = validate_windows_target(target)
|
||
hostname = probe_windows_host(db, win_target)
|
||
return get_or_create_manual_host(
|
||
db,
|
||
hostname=hostname,
|
||
product=PRODUCT_RDP,
|
||
os_family="windows",
|
||
)
|
||
if platform_norm == "linux":
|
||
linux_target = validate_linux_target(target)
|
||
hostname, ipv4 = probe_linux_host(db, linux_target)
|
||
return get_or_create_manual_host(
|
||
db,
|
||
hostname=hostname,
|
||
product=PRODUCT_SSH,
|
||
os_family="linux",
|
||
ipv4=ipv4,
|
||
)
|
||
raise ManualHostAddError("platform должен быть windows или linux")
|