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

This commit is contained in:
2025-12-10 11:03:01 +00:00
parent 5081e710e2
commit f3a9275b29

View File

@@ -1,188 +1,222 @@
--- ---
- name: Умное сканирование сети и классификация устройств - name: Сканирование сети через SNMP (Community: public)
hosts: localhost hosts: localhost
connection: local connection: local
gather_facts: no gather_facts: no
become: yes # !!! ВАЖНО: Для определения OS (nmap -O) нужны права root become: yes # Нужно для сканирования UDP (SNMP)
vars: vars:
# --- НАСТРОЙКИ SEMAPHORE --- # --- SEMAPHORE API ---
semaphore_url: "http://192.168.0.198:9999" semaphore_url: "http://192.168.0.198:9999"
semaphore_project_id: 1 semaphore_project_id: 1
semaphore_key_id: 7 #semaphore_key_id: 7
semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ=" semaphore_api_token: "9ojexqiwt1xkemig7j1bd1pe-frh7hkre4reryk2occ="
inventory_name: "Smart Network Scan" inventory_name: "Smart Network Scan"
# --- ID КЛЮЧЕЙ ДЛЯ БУДУЩИХ ПОДКЛЮЧЕНИЙ ---
# Даже если мы нашли устройство по SNMP, для управления им позже нужен ключ.
# Если ключей нет, можно указать ID "пустого" ключа или любого существующего.
key_windows: 7
key_linux: 8
key_mikrotik: 9
key_printers: 7 # Можно тот же, принтерами обычно ansible не управляет
# --- СЕТИ --- # --- СЕТИ ---
subnets: subnets: [
- "192.168.0.0/23" "192.168.0.0/24"
- "192.168.2.0/24" "192.168.0.0/23"
- "192.168.3.0/24" "192.168.2.0/24"
- "172.19.8.0/24" "192.168.3.0/24"
- "172.19.9.0/24" "172.19.8.0/24"
- "172.19.10.0/24" "172.19.9.0/24"
- "172.19.24.0/24" "172.19.10.0/24"
- "172.19.26.0/24" "172.19.24.0/24"
- "172.19.40.0/24" "172.19.26.0/24"
- "172.19.42.0/24" "172.19.40.0/24"
- "172.19.56.0/24" "172.19.42.0/24"
- "172.19.58.0/24" "172.19.56.0/24"
- "172.19.90.0/24" "172.19.58.0/24"
# добавьте другие подсети "172.19.90.0/24"
]
# Порты для проверки (добавил 8291 для MikroTik, 80/443 для веб-морд, 631 для принтеров) # SNMP Community (пароль для чтения)
scan_ports: [22, 445, 5985, 80, 443, 8291, 631] snmp_community: "public"
tasks: tasks:
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# ШАГ 1: Быстрый поиск живых хостов (Ping Sweep) # ШАГ 1: Поиск живых хостов (Ping)
# Это нужно, чтобы не сканировать пустоту тяжелым сканом
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Быстрый поиск активных IP - name: Ping Sweep
command: > command: "nmap -sn -n --min-rate 1000 -T4 -oG - {{ subnets | join(' ') }}"
nmap -sn -n --min-rate 1000 -T4 -oG -
{{ subnets | join(' ') }}
register: ping_scan register: ping_scan
changed_when: false changed_when: false
- name: Формирование списка живых IP - name: Список IP
set_fact: set_fact:
active_ips: "{{ ping_scan.stdout | regex_findall('Host: ([0-9.]+)') | unique | list }}" active_ips: "{{ ping_scan.stdout | regex_findall('Host: ([0-9.]+)') | unique | list }}"
- name: Проверка - fail: msg="Сеть пуста" when: active_ips|length == 0
fail:
msg: "Живых хостов не найдено."
when: active_ips | length == 0
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# ШАГ 2: Глубокий анализ каждого IP (ОС, Версии, Имена) # ШАГ 2: Опрос через SNMP + Проверка портов
# Используем -O (OS), -sV (Versions) и скрипты # -sU -p 161: Сканируем UDP порт SNMP
# --script snmp-sysdescr: Скрипт, который тянет описание ОС
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Глубокое сканирование найденных хостов - name: SNMP Discovery
shell: | shell: |
# Запускаем Nmap с определением ОС и скриптами SMB/Banner nmap -sU -sS -p U:161,T:22,T:445,T:8291,T:9100 \
# -O: Определение ОС (требует sudo) --script snmp-sysdescr \
# --osscan-guess: Пытаться угадать, если не уверен --script-args snmpcommunity={{ snmp_community }} \
# --script: Скрипты для точного имени Windows и SSH баннеров -Pn -n -T4 {{ item }}
nmap -O -sV -p {{ scan_ports | join(',') }} --script=smb-os-discovery,banner -Pn -n -T4 {{ item }}
loop: "{{ active_ips }}" loop: "{{ active_ips }}"
register: deep_scan_results register: scan_results
changed_when: false changed_when: false
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# ШАГ 3: Анализ и классификация (Магия Ansible + Jinja2) # ШАГ 3: Классификация на основе ответа SNMP
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Классификация хостов - name: Анализ устройств
set_fact: set_fact:
classified_hosts: [] classified_hosts: "{{ classified_hosts | default([]) + [ host_data ] }}"
- name: Разбор результатов сканирования
set_fact:
classified_hosts: "{{ classified_hosts + [ host_data ] }}"
vars: vars:
output: "{{ item.stdout }}" out: "{{ item.stdout }}"
ip: "{{ item.item }}" ip: "{{ item.item }}"
# --- ЛОГИКА ОПРЕДЕЛЕНИЯ ИМЕНИ --- # Пытаемся вытащить строку sysDescr из вывода Nmap
# Пытаемся найти имя через SMB, если нет - DNS, если нет - IP # Пример: "SNMPv2-MIB::sysDescr.0: RouterOS rb750..."
smb_name: "{{ output | regex_search('Computer name: ([\\w-]+)', '\\1') | first | default('') }}" snmp_desc: "{{ out | regex_search('sysDescr\\.0: ([^\\n]+)', '\\1') | first | default('') }}"
dns_name_cmd: "nslookup {{ ip }} 192.168.1.250 | grep 'name =' | awk '{print $NF}' | sed 's/\\.$//'"
# (В реальном плейбуке nslookup внутри loop медленно, тут упрощено) # Определяем тип на основе SNMP ответа (или открытых портов, если SNMP молчит)
final_name: >- detected_type: >-
{% if smb_name != '' %}{{ smb_name | lower }} {% if 'Windows' in snmp_desc or '445/tcp open' in out %}windows
{% else %}host_{{ ip | replace('.', '_') }}{% endif %} {% elif 'RouterOS' in snmp_desc or 'MikroTik' in snmp_desc or '8291/tcp open' in out %}mikrotik
{% elif 'Linux' in snmp_desc or 'Ubuntu' in snmp_desc or '22/tcp open' in out %}linux
# --- ЛОГИКА ОПРЕДЕЛЕНИЯ ТИПА --- {% elif 'JetDirect' in snmp_desc or 'LaserJet' in snmp_desc or 'Samsung' in snmp_desc or 'Kyocera' in snmp_desc or '9100/tcp open' in out %}printer
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 %} {% else %}other{% endif %}
# --- СОБИРАЕМ ОБЪЕКТ --- # Формируем имя: Либо берем из SNMP, либо генерируем host_IP
# Часто в SNMP имя есть в sysName, но для упрощения оставим генерацию или возьмем первое слово из описания
host_data: host_data:
ip: "{{ ip }}" ip: "{{ ip }}"
name: "{{ final_name }}" type: "{{ detected_type }}"
type: "{{ host_type }}" desc: "{{ snmp_desc | replace('\"', '') | default('No SNMP info') }}"
raw_os: "{{ output | regex_search('OS details: ([^\\n]+)', '\\1') | first | default('Unknown') }}" name: "{{ detected_type }}_{{ ip | replace('.', '_') }}"
loop: "{{ deep_scan_results.results }}" loop: "{{ scan_results.results }}"
no_log: true no_log: true
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# ШАГ 4: Генерация инвентаря по группам # ШАГ 4: Разбиение на группы
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Генерация текста инвентаря - set_fact:
set_fact: list_win: "{{ classified_hosts | selectattr('type', 'equalto', 'windows') | list }}"
inventory_content: | list_lin: "{{ classified_hosts | selectattr('type', 'equalto', 'linux') | list }}"
# === Windows Systems === list_tik: "{{ classified_hosts | selectattr('type', 'equalto', 'mikrotik') | list }}"
[windows] list_prn: "{{ classified_hosts | selectattr('type', 'equalto', 'printer') | list }}"
{% for host in classified_hosts if host.type == 'windows' %}
{{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }}
{% endfor %}
[windows:vars]
ansible_connection=ssh
ansible_shell_type=powershell
ansible_user=o.grechko
ansible_port=22
# === Linux Systems ===
[linux]
{% for host in classified_hosts if host.type == 'linux' %}
{{ host.name }} ansible_host={{ host.ip }} # Detected: {{ host.raw_os }}
{% endfor %}
[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 (Ваш рабочий метод через файл) # ШАГ 5: Отправка в Semaphore (Windows)
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: Сохранение JSON - block:
copy: - set_fact:
content: | content_win: |
{ [windows]
"name": "{{ inventory_name }} {{ 1000 | random }}", {% for h in list_win %}
"project_id": {{ semaphore_project_id | int }}, {{ h.name }} ansible_host={{ h.ip }} # SNMP: {{ h.desc }}
"type": "static", {% endfor %}
"ssh_key_id": {{ semaphore_key_id | int }}, [windows:vars]
"become_key_id": null, ansible_connection=ssh
"repository_id": null, ansible_shell_type=powershell
"inventory": {{ inventory_content | to_json }} ansible_port=22
}
dest: /tmp/semaphore_smart_payload.json
- name: Отправка через CURL - copy:
command: > content: |
curl -v -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory" {
-H "Authorization: Bearer {{ semaphore_api_token }}" "name": "SNMP Windows {{ 1000 | random }}",
-H "Content-Type: application/json" "project_id": {{ semaphore_project_id }},
-H "Accept: application/json" "type": "static",
-d @/tmp/semaphore_smart_payload.json "ssh_key_id": {{ key_windows }},
register: curl_result "become_key_id": null,
ignore_errors: yes "repository_id": null,
"inventory": {{ content_win | to_json }}
}
dest: /tmp/p_win.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_win.json
ignore_errors: yes
when: list_win | length > 0
# ----------------------------------------------------------------
# ШАГ 6: Отправка в Semaphore (MikroTik)
# ----------------------------------------------------------------
- block:
- set_fact:
content_tik: |
[routers]
{% for h in list_tik %}
{{ h.name }} ansible_host={{ h.ip }} # {{ h.desc }}
{% endfor %}
[routers:vars]
ansible_connection=network_cli
ansible_network_os=routeros
- copy:
content: |
{
"name": "SNMP MikroTik {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_mikrotik }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_tik | to_json }}
}
dest: /tmp/p_tik.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_tik.json
ignore_errors: yes
when: list_tik | length > 0
# ----------------------------------------------------------------
# ШАГ 7: Отправка в Semaphore (Принтеры)
# Принтеры полезно видеть, но ansible ими обычно не управляет
# ----------------------------------------------------------------
- block:
- set_fact:
content_prn: |
[printers]
{% for h in list_prn %}
{{ h.name }} ansible_host={{ h.ip }} # {{ h.desc }}
{% endfor %}
[printers:vars]
ansible_connection=local
- copy:
content: |
{
"name": "SNMP Printers {{ 1000 | random }}",
"project_id": {{ semaphore_project_id }},
"type": "static",
"ssh_key_id": {{ key_printers }},
"become_key_id": null,
"repository_id": null,
"inventory": {{ content_prn | to_json }}
}
dest: /tmp/p_prn.json
- command: >
curl -X POST "{{ semaphore_url }}/api/project/{{ semaphore_project_id }}/inventory"
-H "Authorization: Bearer {{ semaphore_api_token }}"
-H "Content-Type: application/json"
-d @/tmp/p_prn.json
ignore_errors: yes
when: list_prn | length > 0
- name: Очистка - name: Очистка
file: path=/tmp/semaphore_smart_payload.json state=absent shell: rm -f /tmp/p_*.json
- name: Результат
debug:
msg: "Проверьте инвентарь! Создан ID, если статус 200/201"