diff --git a/playbooks/scan_inventory.yml b/playbooks/scan_inventory.yml new file mode 100644 index 0000000..e60c503 --- /dev/null +++ b/playbooks/scan_inventory.yml @@ -0,0 +1,171 @@ +--- +- name: Сбор инвентаря Windows (Nmap + Smart DNS) + hosts: localhost + connection: local + gather_facts: yes + vars: + domain: "zag.lan" + # Ваши DNS (важен порядок) + dns_servers: + - "192.168.1.250" + - "192.168.1.254" + inventory_file: "/tmp/windows_inventory.ini" + + # Список подсетей + subnets: + - "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 HTTP, WinRM HTTPS, SSH Std, SSH Custom + scan_ports: + - 5985 + - 5986 + - 22 + - 22233 + + tasks: + - name: Проверка nmap + command: which nmap + register: nmap_check + failed_when: nmap_check.rc != 0 + changed_when: false + + # 1. СКАНИРОВАНИЕ + # -Pn : Не пинговать (считать хост живым). РЕШАЕТ ПРОБЛЕМУ "НЕ ВИДИТ ХОСТЫ" + # -n : Не делать Reverse DNS самим nmap-ом (сделаем сами точнее) + # --min-rate : Ускоряет процесс + # --open : Показать только открытые + - name: Сканирование сети (Nmap) + command: > + nmap -p {{ scan_ports | join(',') }} + -Pn -n --open --min-rate 1000 -T4 -oG - + {{ subnets | join(' ') }} + register: nmap_result + changed_when: false + + # 2. ПАРСИНГ РЕЗУЛЬТАТОВ + - name: Парсинг активных IP + set_fact: + # Регулярка ищет строки, где открыт ХОТЯ БЫ ОДИН из нужных портов + active_ips: "{{ nmap_result.stdout | regex_findall('Host: ([0-9.]+).*Ports:.*(?:' + scan_ports | join('|') + ')/open') | unique | list }}" + + - name: Статистика сканирования + debug: + msg: "Найдено активных хостов: {{ active_ips | length }}" + + # 3. DNS РЕЗОЛВ (Оптимизированный Shell) + - name: Определение Hostname через корпоративные DNS + shell: | + IP="{{ item }}" + # Функция для запроса к конкретному DNS серверу + ask_dns() { + nslookup -timeout=1 $1 $2 2>/dev/null | grep 'name =' | awk '{print $NF}' | sed 's/\.$//' | head -n 1 + } + + # Пробуем первый DNS + NAME=$(ask_dns $IP {{ dns_servers[0] }}) + + # Если пусто, пробуем второй + if [ -z "$NAME" ]; then + NAME=$(ask_dns $IP {{ dns_servers[1] }}) + fi + + # Вывод результата + if [ -z "$NAME" ]; then + echo "NO_DNS" + else + # Переводим в нижний регистр для удобства + echo "$NAME" | tr '[:upper:]' '[:lower:]' + fi + loop: "{{ active_ips }}" + register: dns_results + changed_when: false + # Не спамим в лог, если хостов много + no_log: true + + # 4. ГРУППИРОВКА РЕЗУЛЬТАТОВ + - name: Обработка результатов DNS + set_fact: + # Хосты с правильным именем pc...zag.lan + pc_hosts: >- + {{ pc_hosts | default([]) + + [{'hostname': item.stdout, 'ip': item.item}] + }} + # Хосты без имени или с левым именем + other_hosts: >- + {{ other_hosts | default([]) + + [{'ip': item.item, 'resolved_name': item.stdout}] + }} + loop: "{{ dns_results.results }}" + when: + - item.stdout is defined + - item.stdout != "NO_DNS" + - item.stdout | trim is match('^pc\\d+.*' + domain + '$') + + # Собираем тех, кто не попал в pc_hosts (включая NO_DNS и странные имена) + - name: Сбор остальных (Unknown) + set_fact: + unknown_list: >- + {{ dns_results.results | rejectattr('stdout', 'match', '^pc\\d+.*' + domain + '$') | map(attribute='item') | list }} + + # 5. ГЕНЕРАЦИЯ ФАЙЛА + - name: Запись инвентаря + copy: + content: | + # Автоматический инвентарь от {{ ansible_date_time.iso8601 }} + + # === Корпоративные PC (pcXX.zag.lan) === + [windows_pcs] + {% for host in pc_hosts | default([]) | sort(attribute='hostname') %} + {{ host.hostname | regex_replace('\\.' + domain + '$', '') }} ansible_host={{ host.hostname }} ansible_host_ip={{ host.ip }} + {% endfor %} + + # === Остальные найденные (Unknown / IP only) === + [windows_unknown] + {% for res in dns_results.results if res.item in unknown_list %} + {% if res.stdout != "NO_DNS" %} + # Найден по DNS как: {{ res.stdout }} + unknown_{{ res.item | replace('.', '_') }} ansible_host={{ res.item }} + {% else %} + # DNS имя не найдено + unknown_{{ res.item | replace('.', '_') }} ansible_host={{ res.item }} + {% endif %} + {% endfor %} + + # === Группы и переменные === + [windows:children] + windows_pcs + windows_unknown + + [windows:vars] + # Настройки подключения + ansible_connection=winrm + ansible_winrm_transport=ntlm + ansible_winrm_server_cert_validation=ignore + ansible_port=5985 + + # Если вы хотите использовать SSH для unknown хостов, можно переопределить ниже: + # [windows_unknown:vars] + # ansible_port=22233 + # ansible_connection=ssh + # ansible_shell_type=powershell + dest: "{{ inventory_file }}" + + - name: Финальный отчет + debug: + msg: + - "Найдено всего IP: {{ active_ips | length }}" + - "Распознано как PC: {{ pc_hosts | default([]) | length }}" + - "Не распознано: {{ unknown_list | default([]) | length }}" + - "Файл сохранен: {{ inventory_file }}" \ No newline at end of file