Комментарий пользователя
Поставил скрипт сканирования в cron, запускается раз в час. Через сутки обнаружил, что в интерфейсе висит несколько задач на одну и ту же подсеть, а часть из них в непонятном состоянии. Похоже, новый запуск стартует, не дожидаясь, пока закончится предыдущий, и лепит задачи поверх. Увеличивать интервал наугад не хочется, так как сканирование сети и поиск уязвимостей каждый раз занимают разное время. Как сделать правильно?
Ответ специалиста
Здравствуйте, Кирилл!
Вы наткнулись на ограничение cron: он умеет запускать скрипт в заданное время, но не знает, закончилось ли предыдущее сканирование. Увеличение интервала тут не лечит, а маскирует — вы правы, что время выполнения плавает.
Решение — конвейер, в котором состояние каждой задачи хранится между запусками. Планировщик запускает скрипт, тот через API создает задачи, проверяет их состояние, последовательно запускает сетевое сканирование и поиск уязвимостей, генерирует отчет и уже после завершения всей цепочки отправляет уведомление.
В начале каждого запуска выполняется авторизация и обновление баз уязвимостей:
auth_cookies = authorization()
if not auth_cookies:
print("Авторизация не удалась. Программа завершает работу.")
return
base_update(auth_cookies)
Затем мы загружаем список проверяемых адресов, который хранится отдельно от скрипта. Это позволяет изменять состав проверяемой инфраструктуры, не меняя код. Для каждого адреса создается ресурс, а его идентификатор сохраняется в локальном файле. Если ресурс уже создан, он не перезаписывается, а пропускается. Файл становится хранилищем состояния для конвейера.
clients = load_client_list(client_list_file)
for client in clients:
client_ip = client.get("ip")
id_asset = client.get("id_asset")
Затем создается задача сетевого сканирования, и отдельным запросом она запускается:
task_id = add_netscan_task(auth_cookies, client_name, client_ips)
if task_id:
client["id_netscan"] = task_id
save_client_list(client_list_file, clients)
run_task(auth_cookies, task_id)
А вот ключевой для вашей ситуации фрагмент. После запуска задачи скрипт записывает состояние задачи как «запущена» и периодически запрашивает ее состояние, проверяя, не завершилась ли она. Если сканирование выполняется, далее по скрипту мы не продвинемся:
task_state = get_task_status(auth_cookies, existing_netscan_task_id)
if task_state == "completed":
print(f"Задача Netscan для клиента {client_name} уже завершена. Переходим к созданию Vulnscan.")
elif task_state in ["running", "progress"]:
print(f"Задача Netscan для клиента {client_name} еще выполняется. Пропускаем.")
continue
Это защищает сразу от двух сценариев: скрипт не создаст второе сканирование поверх первого и не побежит дальше, создавая задачи на поиск уязвимостей по хостам, которые еще не досканированы. Тот же принцип применяется и к задаче поиска уязвимостей, и к формированию отчета — каждый этап сначала смотрит, завершен ли предыдущий. После завершения сетевого сканирования создается следующая задача, и все идентификаторы передаются из уже созданных ресурсов:
vulnscan_task_id = add_vulnscan_task(auth_cookies, client_name, asset_ids)
if vulnscan_task_id:
for client in clients:
if client.get("name") == client_name:
client["id_vulnscan"] = vulnscan_task_id
save_client_list(client_list_file, clients)
run_task(auth_cookies, vulnscan_task_id)
После завершения поиска уязвимостей генерируется отчет. Готовность также фиксируется в списке клиентов:
if client_status == "done":
print(f" Отчет для клиента {client_name} уже создан. Пропускаем.")
continue
После этого завершенные задачи удаляются, список клиентов очищается, и мы получаем оповещение в Telegram:
all_reports_done = all(client.get("status") == "done" for client in clients)
send_telegram_message("Все отчеты успешно созданы и ресурсы удалены.")
Практический вывод для вашего расписания: оставляйте ежечасный запуск. При такой схеме лишние запуски безвредны — если предыдущий этап не завершен, скрипт просто выйдет, ничего не создав. Параллельных сканирований, которые нагрузят машину, тоже не будет.