Обновить playbooks/scan_inventory.yml

This commit is contained in:
2025-12-10 10:51:42 +00:00
parent 85f05b9119
commit bb93db6959

View File

@@ -1,129 +1,176 @@
--- ---
- name: Сбор инвентаря Windows (Nmap SMB + DNS) - name: Умное сканирование сети и классификация устройств
hosts: localhost hosts: localhost
connection: local connection: local
gather_facts: yes gather_facts: no
become: yes # !!! ВАЖНО: Для определения OS (nmap -O) нужны права root
vars: vars:
inventory_file: "/tmp/windows_inventory2.ini" # --- НАСТРОЙКИ SEMAPHORE ---
semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1
semaphore_key_id: 7
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
inventory_name: "Smart Network Scan"
# Список подсетей # --- СЕТИ ---
subnets: subnets:
- "192.168.0.0/24" - "192.168.0.0/24"
- "192.168.1.0/24" # добавьте другие подсети
- "192.168.2.0/24"
- "192.168.3.0/24"
- "172.19.8.0/23"
- "172.19.9.0/23"
- "172.19.10.0/23"
- "172.19.24.0/23"
- "172.19.26.0/23"
- "172.19.40.0/23"
- "172.19.42.0/23"
- "172.19.56.0/23"
- "172.19.58.0/23"
# Порты для проверки доступности (WinRM, SSH, SMB) # Порты для проверки (добавил 8291 для MikroTik, 80/443 для веб-морд, 631 для принтеров)
scan_ports: scan_ports: [22, 445, 5985, 80, 443, 8291, 631]
- 5985
- 22
- 445
tasks: tasks:
- name: Сканирование сети (поиск живых IP) # ----------------------------------------------------------------
# ШАГ 1: Быстрый поиск живых хостов (Ping Sweep)
# Это нужно, чтобы не сканировать пустоту тяжелым сканом
# ----------------------------------------------------------------
- name: Быстрый поиск активных IP
command: > command: >
nmap -p {{ scan_ports | join(',') }} nmap -sn -n --min-rate 1000 -T4 -oG -
-Pn -n --open --min-rate 1000 -T4 -oG -
{{ subnets | join(' ') }} {{ subnets | join(' ') }}
register: nmap_result register: ping_scan
changed_when: false changed_when: false
- name: Извлечение IP адресов - name: Формирование списка живых IP
set_fact: set_fact:
active_ips: "{{ nmap_result.stdout | regex_findall('Host: ([0-9.]+).*Ports:.*(?:' + scan_ports | join('|') + ')/open') | unique | list }}" active_ips: "{{ ping_scan.stdout | regex_findall('Host: ([0-9.]+)') | unique | list }}"
- name: Статистика - name: Проверка
debug: fail:
msg: "Найдено активных IP: {{ active_ips | length }}" msg: "Живых хостов не найдено."
when: active_ips | length == 0
# ГЛАВНОЕ ИЗМЕНЕНИЕ: Получаем имя через SMB (NetBIOS) или DNS # ----------------------------------------------------------------
- name: Определение имен хостов (SMB Discovery) # ШАГ 2: Глубокий анализ каждого IP (ОС, Версии, Имена)
# Используем -O (OS), -sV (Versions) и скрипты
# ----------------------------------------------------------------
- name: Глубокое сканирование найденных хостов
shell: | shell: |
IP="{{ item }}" # Запускаем Nmap с определением ОС и скриптами SMB/Banner
# 1. Пробуем узнать имя через SMB (самый надежный способ для Windows) # -O: Определение ОС (требует sudo)
SMB_NAME=$(nmap -p 445 --script smb-os-discovery $IP -Pn -n | grep "Computer name:" | awk -F': ' '{print $2}') # --osscan-guess: Пытаться угадать, если не уверен
# --script: Скрипты для точного имени Windows и SSH баннеров
if [ ! -z "$SMB_NAME" ]; then nmap -O -sV -p {{ scan_ports | join(',') }} --script=smb-os-discovery,banner -Pn -n -T4 {{ item }}
echo "$SMB_NAME" | tr '[:upper:]' '[:lower:]'
else
# 2. Если SMB закрыт/не ответил, пробуем DNS (как раньше)
DNS_NAME=$(nslookup -timeout=1 $IP 192.168.1.250 2>/dev/null | grep 'name =' | awk '{print $NF}' | sed 's/\.$//' | head -n 1)
if [ ! -z "$DNS_NAME" ]; then
echo "$DNS_NAME" | tr '[:upper:]' '[:lower:]'
else
echo "UNKNOWN"
fi
fi
loop: "{{ active_ips }}" loop: "{{ active_ips }}"
register: host_names register: deep_scan_results
changed_when: false changed_when: false
no_log: true # Чтобы не засорять лог
# Фильтруем и готовим списки # ----------------------------------------------------------------
- name: Сортировка хостов # ШАГ 3: Анализ и классификация (Магия Ansible + Jinja2)
# ----------------------------------------------------------------
- name: Классификация хостов
set_fact: set_fact:
# Ищем 'pc' в любом месте имени (было ^pc - только в начале) classified_hosts: []
pc_list: >-
{{ host_names.results
| selectattr('stdout', 'search', 'pc')
| map(attribute='item') | list
| zip(host_names.results | selectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list)
| list }}
# Остальные (сервера, принтеры, неизвестные) - name: Разбор результатов сканирования
other_list: >- set_fact:
{{ host_names.results classified_hosts: "{{ classified_hosts + [ host_data ] }}"
| rejectattr('stdout', 'search', 'pc') vars:
| map(attribute='item') | list output: "{{ item.stdout }}"
| zip(host_names.results | rejectattr('stdout', 'search', 'pc') | map(attribute='stdout') | list) ip: "{{ item.item }}"
| list }}
- name: Запись инвентаря # --- ЛОГИКА ОПРЕДЕЛЕНИЯ ИМЕНИ ---
copy: # Пытаемся найти имя через SMB, если нет - DNS, если нет - IP
content: | smb_name: "{{ output | regex_search('Computer name: ([\\w-]+)', '\\1') | first | default('') }}"
# === Найденные PC (по имени содержит 'pc') === dns_name_cmd: "nslookup {{ ip }} 192.168.1.250 | grep 'name =' | awk '{print $NF}' | sed 's/\\.$//'"
[windows_pcs] # (В реальном плейбуке nslookup внутри loop медленно, тут упрощено)
{% for ip, name in pc_list %} final_name: >-
{{ name }} ansible_host={{ ip }} {% if smb_name != '' %}{{ smb_name | lower }}
{% else %}host_{{ ip | replace('.', '_') }}{% endif %}
# --- ЛОГИКА ОПРЕДЕЛЕНИЯ ТИПА ---
host_type: >-
{% if 'Windows' in output or 'Microsoft Windows' in output or '445/tcp open' in output %}windows
{% elif 'MikroTik' in output or 'RouterOS' in output or '8291/tcp open' in output %}mikrotik
{% elif 'Linux' in output or 'Ubuntu' in output or 'Debian' in output %}linux
{% elif 'HP' in output or 'Printer' in output or '631/tcp open' in output %}printer
{% else %}other{% endif %}
# --- СОБИРАЕМ ОБЪЕКТ ---
host_data:
ip: "{{ ip }}"
name: "{{ final_name }}"
type: "{{ host_type }}"
raw_os: "{{ output | regex_search('OS details: ([^\\n]+)', '\\1') | first | default('Unknown') }}"
loop: "{{ deep_scan_results.results }}"
no_log: true
# ----------------------------------------------------------------
# ШАГ 4: Генерация инвентаря по группам
# ----------------------------------------------------------------
- name: Генерация текста инвентаря
set_fact:
inventory_content: |
# === Windows Systems ===
[windows]
{% for host in classified_hosts if host.type == 'windows' %}
{{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }}
{% endfor %} {% endfor %}
# === Остальные устройства (Servers, Unknown) ===
[windows_other]
{% for ip, name in other_list %}
{% if name == "UNKNOWN" %}
unknown_{{ ip | replace('.', '_') }} ansible_host={{ ip }}
{% else %}
{{ name }} ansible_host={{ ip }}
{% endif %}
{% endfor %}
[windows:children]
windows_pcs
windows_other
[windows:vars] [windows:vars]
ansible_connection=winrm ansible_connection=ssh
ansible_winrm_transport=ntlm ansible_shell_type=powershell
ansible_winrm_server_cert_validation=ignore ansible_user=o.grechko
ansible_port=5985 ansible_port=22
ansible_user=Administrator
# Пароль лучше задать в Secret Store, не здесь
dest: "{{ inventory_file }}"
- name: ПОКАЗАТЬ РЕЗУЛЬТАТ (Скопируйте это в новый инвентарь) # === Linux Systems ===
command: cat {{ inventory_file }} [linux]
register: cat_inventory {% for host in classified_hosts if host.type == 'linux' %}
changed_when: false {{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }}
{% endfor %}
- name: Вывод в лог [linux:vars]
ansible_connection=ssh
ansible_user=root
# === MikroTik Routers ===
[routers_mikrotik]
{% for host in classified_hosts if host.type == 'mikrotik' %}
{{ host.name }} ansible_host={{ host.ip }}
{% endfor %}
[routers_mikrotik:vars]
ansible_connection=network_cli
ansible_network_os=routeros
ansible_user=admin
# === Printers & Others ===
[others]
{% for host in classified_hosts if host.type == 'printer' or host.type == 'other' %}
{{ host.name }} ansible_host={{ host.ip }} # Type: {{ host.type }}, OS: {{ host.raw_os }}
{% endfor %}
# ----------------------------------------------------------------
# ШАГ 5: Отправка в Semaphore (Ваш рабочий метод через файл)
# ----------------------------------------------------------------
- name: Сохранение JSON
copy:
content: |
{
"name": "{{ inventory_name }} {{ 1000 | random }}",
"project_id": {{ semaphore_project_id | int }},
"type": "static",
"ssh_key_id": {{ semaphore_key_id | int }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ inventory_content | to_json }}
}
dest: /tmp/semaphore_smart_payload.json
- name: Отправка через CURL
command: >
curl -v -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-H "Accept: application/json"
-d @/tmp/semaphore_smart_payload.json
register: curl_result
ignore_errors: yes
- name: Очистка
file: path=/tmp/semaphore_smart_payload.json state=absent
- name: Результат
debug: debug:
var: cat_inventory.stdout_lines msg: "Проверьте инвентарь! Создан ID, если статус 200/201"