domovoy/Проект - Домовой.md

25 KiB
Raw Permalink Blame History

Self-hosted система инвентаризации домашней/малой инфраструктуры с автосканированием сети, глубоким сканированием хостов и созданием карточек устройств/сервисов из найденного.

Ключевая цепочка:

скан сети→ найденные хосты→ подтверждение человеком→ карточки устройств→ добавление доступов→ глубокий скан хоста→ найденные сервисы/контейнеры/домены/бэкапы→ подтверждение человеком→ живая карта инфраструктуры

И важная поправка под Hermes/owl-alpha: не давать кодеру “построй весь Домовой” одним заходом. Лучше давать стадии по 12 экрана/модуля за раз, иначе он может нагенерировать архитектурной каши.


1. Базовый стек

Я бы оставил стек максимально простой и твой:

Backend:
	PHP 8.3+
	Framework: Slim Framework 4
	DB: MariaDB / MySQL
	Frontend: Bootstrap 5.3 + htmx + Alpine.js при необходимости
	Templates: PHP templates или Twig
	Auth: локальная авторизация
	Deploy: Docker Compose
	Scanning: PHP services + системные read-only команды
	SSH: phpseclib
	Encryption: defuse/php-encryption или libsodium

Slim 4 ставится через Composer как slim/slim:"4.*", а Bootstrap сейчас живёт в ветке 5.3, у которой официальная документация ведёт current major release v5.x и последнюю ветку 5.3.x. Для SSH из PHP нормально подходит phpseclib 3.x, он ставится через Composer как phpseclib/phpseclib:~3.0. Для шифрования секретов можно взять defuse/php-encryption, это PHP-библиотека для шифрования данных ключом/паролем.


2. Composer-зависимости

Минимальный набор:

composer require slim/slim:"4.*"
composer require slim/psr7
composer require php-di/php-di
composer require monolog/monolog
composer require vlucas/phpdotenv
composer require ramsey/uuid
composer require symfony/process
composer require phpseclib/phpseclib:"~3.0"
composer require defuse/php-encryption

Миграции и тесты:

composer require robmorgan/phinx
composer require --dev phpunit/phpunit

Опционально:

composer require twig/twig
composer require nesbot/carbon

Я бы не тащил ORM на первом этапе. Обычный PDO + Repository-классы. Так проще, прозрачнее и меньше магии.


3. Системные зависимости на хосте

Для контейнера/хоста, где крутится Домовой:

php-cli
php-fpm
php-mysql
php-curl
php-mbstring
php-xml
php-zip
mariadb-client
iproute2
iputils-ping
arp-sca
nnmap
net-tools
dnsutils
avahi-utils
smbclient / nbtscan опционально
snmp опционально
openssh-client
openssl
docker-cli опционально

Но важно: nmap и arp-scan — необязательные backends, а не обязательная основа. Первый MVP может жить на:

ping sweep
ARP table
TCP connect scan
reverse DNS

А потом уже добавить nmap/arp-scan как улучшение.


4. Границы безопасности

Это надо заложить сразу, иначе проект может превратиться в мутный сетевой сканер.

Жёсткие правила MVP

1. Сканируются только явно добавленные пользователем диапазоны.
2. По умолчанию разрешены только private/local ranges:   
    - 10.0.0.0/8   
	- 172.16.0.0/12
	- 192.168.0.0/16   
	- fd00::/8   
	- link-local
3. Никаких brute force.
4. Никаких exploit/probe.
5. Никаких попыток подобрать пароли.
6. Deep scan только после явного добавления доступа.
7. Все команды deep scan — read-only.
8. Секреты в UI никогда не показываются открытым текстом.
9. Любой найденный объект сначала попадает в "Найденное", а не сразу в основной инвентарь.
10. Всё, что делает сканер, логируется.

Идеология:

Домовой не чинит и не ломает.
Домовой смотрит, запоминает и предлагает.

Архитектура выполнения сканирования

HTTP-запрос НЕ должен выполнять сканирование напрямую. Сканирование сети и deep scan выполняются отдельным CLI worker-ом:

  1. Web UI создаёт scan_job со статусом "pending".
  2. CLI worker (bin/run-scan-worker.php) забирает pending-задачи из базы и выполняет их.
  3. Web UI показывает статус задачи через htmx polling.

Worker должен:

  • обрабатывать SIGTERM для graceful shutdown;
  • запускать только одну задачу за раз (для MVP);
  • обновлять статус scan_jobs в процессе;
  • логировать прогресс.

Аргументы запуска worker-а:

php bin/run-scan-worker.php          # забрать одну pending-задачу и выполнить
php bin/run-scan-worker.php --loop   # бесконечный цикл обработки

Стратегии сканирования сети (по приоритету):

Приоритет 1 — если доступны arp-scan или nmap:

  • arp-scan для быстрого обнаружения MAC + IP
  • nmap для диапазонного скана с портами

Приоритит 2 — системные команды, работающие без дополнительных PAK-тов:

  • ip neigh / arp table для ARP-записей
  • ping sweep с жёстким timeout (200ms)
  • TCP connect scan с timeout=200ms, concurrency=50

Приоритет 3 — fallback:

  • только ping sweep, если ничего другого не доступно

Порядок имеет значение — быстрые методы сначала, медленные как fallback. Для /24 сети с 18 портами TCP-скан может занять минуты. Поэтому CLI worker обязателен, а PHP-скан внутри HTTP-запроса запрещён.


5. Архитектура проекта

domovoy/  
	app/    
		Controllers/      
			AuthController.php      
			DashboardController.php      
			DiscoveryController.php      
			DeviceController.php      
			CredentialController.php      
			HostScanController.php      
			ServiceController.php      
			DocumentController.php    
		Services/      
			Discovery/        
				NetworkScanner.php        
				PingScanner.php        
				TcpPortScanner.php        
				ArpTableReader.php        
				MdnsScanner.php        
				HostFingerprintService.php      
			HostScan/        
				SshClientFactory.php        
				LinuxHostScanner.php        
				DockerScanner.php        
				NginxScanner.php        
				CronScanner.php        
				SystemdScanner.php        
				BackupHintScanner.php      
			Inventory/        
				DeviceService.php        
				ServiceInventoryService.php        
				MergeSuggestionService.php        
				RelationService.php      
			Security/        
				CredentialVault.php        
				CommandWhitelist.php        
				SecretMasker.php      
			Jobs/        
				ScanJobRunner.php        
				JobQueue.php      
			Analysis/        
				RiskAnalyzer.php        
				DiffAnalyzer.php    
		Repositories/      
			UserRepository.php      
			ScanJobRepository.php      
			DiscoveredHostRepository.php      
			DeviceRepository.php      
			CredentialRepository.php      
			HostScanRepository.php      
			ServiceRepository.php      
			RelationRepository.php     
			AuditLogRepository.php    
		Middleware/      
			AuthMiddleware.php      
			CsrfMiddleware.php  
	public/    
		index.php    
		assets/      
			css/      
			js/  
	templates/    
		layout.php    
		auth/    
		dashboard/    
		discovery/    
		devices/   
		credentials/    
		services/    
		documents/  
	migrations/  
	storage/    
		logs/    
		scans/    
		reports/  
	bin/    
		console    
		run-scan-worker.php  
	docker/  
	docker-compose.yml  
	composer.json  
	.env.example  
	README.md

6. Главные сущности БД

users

id
username
password_hash
created_at
updated_at

network_ranges

id
name
cidr
enabled
created_at
updated_at

Пример:

Home LAN — 192.168.1.0/24
WireGuard LAN — 10.12.1.0/24

scan_jobs

id
type
status
started_at
finished_at
error_message
created_by
created_at

Типы:

network_discovery
host_deep_scan
docker_scan
nginx_scan
cron_scan

Статусы:

pending
running
done
failed
cancelled

discovered_hosts

Это всё, что нашёл сканер, но пользователь ещё не подтвердил.

id
scan_job_id
ip_address
mac_address
hostname
vendor
detected_os
open_ports_json
protocols_json
fingerprint_json
confidence
status
matched_device_id
first_seen
last_seen
created_at
updated_at

Статусы:

new
accepted
merged
ignored
ignored_always

devices

Это уже подтверждённые железки/виртуалки.

id
name
type
description
primary_ip
mac_address
hostname
vendor
os_name
os_version
location
importance
status
created_at
updated_at

Типы:

server
router
nas
desktop
laptop
phone
printer
iot
vm
container_host
unknown

credentials

Доступы к устройствам.

id
device_id
type
name
username
port
auth_method
encrypted_secret
encrypted_private_key
public_key_fingerprint
last_test_status
last_test_at
created_at
updated_at

Типы:

ssh
snmp
http_api
routeros
manual

Для MVP реально нужен только ssh.

host_scans

Результаты глубокого скана устройства.

id
device_id
credential_id
scan_job_id
status
raw_json_path
summary_json
started_at
finished_at
error_message
created_at

detected_services

То, что найдено на хосте, но ещё не подтверждено как сервис.

id
host_scan_id
device_id
kind
name
source
port
protocol
raw_json
suggested_service_id
status
created_at
updated_at

kind:

open_port
systemd_unit
docker_container
nginx_vhost
cron_job
backup_hint
database_hint

services

Подтверждённые сервисы.

id
name
type
description
device_id
status
importance
url
main_port
created_at
updated_at

Типы:

web_app
database
reverse_proxy
backup
monitoring
storage
git
media
system
unknown

service_endpoints

id
service_id
protocol
host
port
url
is_public
created_at
updated_at

domains

id
domain
target_service_id
target_device_id
source
notes
created_at
updated_at

relations

Универсальный граф связей.

id
from_type
from_id
to_type
to_id
relation_type
created_at

Типы связей:

runs_on
depends_on
proxied_by
uses_database
uses_volume
backed_up_by
exposes
resolves_to

documents

id
entity_type
entity_id
title
body_markdown
created_at
updated_at

Для заметок/runbook-ов.

audit_log

id
user_id
action
entity_type
entity_id
details_json
created_at

7. Логика сканирования сети

Что делает network discovery

На входе:

CIDR: 192.168.1.0/24

На выходе:

список DiscoveredHost

Методы первого MVP:

1. Ping sweep
2. TCP connect scan по базовым портам
3. чтение локальной ARP-таблицы
4. reverse DNS lookup
5. определение vendor по MAC OUI, если MAC известен

Базовые порты:

22    SSH
23    Telnet
53    DNS
80    HTTP
443   HTTPS
445   SMB
548   AFP
631   CUPS
3306  MySQL
5432  PostgreSQL
6379  Redis
8000  HTTP-alt
8080  HTTP-alt
8443  HTTPS-alt
9000  Portainer/various
9090  Prometheus/various
9100  Printer/Node exporter

Позже добавить:

mDNS
NetBIOS
SNMP
nmap backend
router DHCP leases importer

8. Логика deep scan по SSH

После того как пользователь создал устройство и добавил SSH-доступ, можно нажать:

[Тест подключения]
[Глубокий скан]

Команды Linux scan

Только read-only:

hostname
hostnamectl
uname -a
cat /etc/os-release
ip -j addr
ip route
ss -tulpen
df -h
lsblk -J
mount
systemctl list-units --type=service --all --no-pager
systemctl list-timers --all --no-pager
crontab -l
cat /etc/crontab
ls -la /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.weekly /etc/cron.monthly
docker ps --format '{{json .}}'
docker network ls --format '{{json .}}'
docker volume ls --format '{{json .}}'
docker compose ls --format json

Команды должны идти через whitelist, а не через произвольный shell.

То есть не так:

$ssh->exec($userCommand);

А так:

$scanner->runAllowedCommand('linux.hostnamectl');

И уже внутри:

'linux.hostnamectl' => ['hostnamectl']

Формат команд в CommandWhitelist — массивы аргументов, НЕ строки. Это важно: docker-команды содержат {{json .}}, что ломает и Symfony Process, и Twig-шаблонизатор. Массив аргументов не требует экранирования.

ЗАПРЕЩЕНО:

'linux.docker_ps' => "docker ps --format '{{json .}}'"

ПРАВИЛЬНО:

'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}']

SSH таймауты

Все SSH-операции должны иметь жёсткие таймауты. Конфигурируются через .env:

SSH_CONNECT_TIMEOUT_SECONDS=5
SSH_AUTH_TIMEOUT_SECONDS=10
SSH_COMMAND_TIMEOUT_SECONDS=8
SSH_TOTAL_SCAN_TIMEOUT_SECONDS=60
SSH_RETRY_COUNT=0

Для MVP retry отключён — лучше быстро и честно упасть, чем зависнуть. Каждый collector внутри deep scan обрабатывает timeout отдельно — ошибка одного collector-а не роняет весь scan.


9. Сервисные сканеры

DockerScanner

Собирает:

containers
images
ports
mounts
volumes
networks
labels
restart policy
health status
compose project

Из контейнеров создаёт detected_services.

Пример:

container: nextcloud-app
image: nextcloud:apache
ports: 8080:80
mounts: /srv/nextcloud/data:/var/www/html/data
suggested service type: web_app

NginxScanner

Читает:

/etc/nginx/nginx.conf
/etc/nginx/sites-enabled/*
/etc/nginx/conf.d/*

Вынимает:

server_name
listen
proxy_pass
root
ssl_certificate
ssl_certificate_key path, но ключ не читать
access_log
error_log

Важно: приватные ключи сертификатов не читать.

CronScanner

Читает:

user crontab
/etc/crontab
/etc/cron.d/*

И создаёт кандидатов:

cron_job
backup_hint
maintenance_task
unknown_task

BackupHintScanner

Ищет в командах/скриптах признаки:

rsync
borg
restic
rclone
tar
zip
mysqldump
mariadb-dump
pg_dump
sqlite dump
docker exec ... dump
scp
sftp

Но вывод должен быть осторожным:

Похоже на backup job

а не:

Это точно полноценный бэкап

10. Экранная структура

Dashboard

Домовой

Устройства: 12
Сервисы: 18
Новые находки: 7
Требуют внимания: 4
Последний скан сети: сегодня 14:22

Блоки:

- Новые найденные устройства
- Сервисы без подтверждённого бэкапа
- Хосты без свежего скана
- Истекающие сертификаты
- Последние изменения

Discovery

Сканирование сети

[Добавить диапазон]
[Запустить скан]

Таблица:
IP | Hostname | MAC | Vendor | Ports | Статус | Действия

Действия:

[Создать устройство]
[Объединить]
[Игнорировать]
[Детали]

Devices

Карточка устройства:

Название
Тип
IP
MAC
Hostname
Vendor
OS
Роль
Важность
Заметки
Доступы
Найденные сервисы
Подтверждённые сервисы
Документы
История

Credentials

Для SSH:

Название доступа
Host
Port
Username
Auth method:
  - password
  - private key
Password/private key
[Тест]
[Сохранить]

Host Scan

На карточке устройства:

[Глубокий скан]

Результаты:
- Система
- Открытые порты
- Docker
- Systemd
- Cron
- Nginx
- Возможные сервисы
- Возможные бэкапы

Найденные сервисы

Название | Тип | Источник | Устройство | Порт | Уверенность | Действия

Действия:

[Создать сервис]
[Объединить]
[Игнорировать]
[Детали]

11. CI/CD и smoke tests

Создать файл scripts/check.sh:

#!/usr/bin/env bash
set -euo pipefail

echo "=== composer validate ==="
composer validate --no-check-publish

echo "=== composer install ==="
composer install --no-interaction --prefer-dist

echo "=== PHP syntax check ==="
find app public bin -name "*.php" -print0 | xargs -0 -n1 php -l

echo "=== docker compose config ==="
docker compose config > /dev/null

echo "=== docker compose up ==="
docker compose up -d --build

echo "=== phinx migrate (test DB) ==="
docker compose exec app php vendor/bin/phinx migrate

echo "=== HTTP health check ==="
curl -sI http://localhost:8080/login | head -1

echo "=== ALL CHECKS PASSED ==="

Головное правило: итерация не считается завершённой, пока check.sh не прошёл успешно без ошибок.

Для следующих итераций добавлять свои проверки:

  • network scan: "docker compose exec app php bin/console scan:test"
  • device CRUD: curl к /devices, /devices/create
  • credentials: тест SSH mock-connection

Не переходить к следующей итерации без успешного запуска check.sh. Обязательно показывать реальный вывод, а не утверждать "готово".


12. План реализации

Этап 0. Каркас проекта

Цель: приложение открывается, есть логин, база, миграции, layout.

Результат:

- docker-compose поднимает app + db
- есть /login
- есть /dashboard
- есть миграции
- есть базовый UI

Этап 1. Инвентарь устройств вручную

Цель: можно создавать карточки устройств руками.

Результат:

- CRUD устройств
- типы устройств
- заметки
- список устройств
- карточка устройства

Этап 2. Сканирование сети

Цель: можно добавить диапазон и найти хосты.

Результат:

- CRUD network_ranges
- запуск network scan
- таблица discovered_hosts
- ping/tcp scan
- ARP table enrichment
- reverse DNS

Этап 3. Превращение найденного в устройство

Цель: найденный хост можно принять в инвентарь.

Результат:

- кнопка "Создать устройство"
- кнопка "Игнорировать"
- простое объединение с существующим устройством
- discovered_host получает статус

Этап 4. SSH-доступы

Цель: к устройству можно добавить SSH-доступ и проверить его.

Результат:

- CRUD credentials
- шифрование секрета
- тест подключения
- сохранение результата теста

Этап 5. Deep scan Linux-хоста

Цель: по SSH собрать базовую информацию.

Результат:

- hostname/os/ip/ports/disk/systemd/timers/cron
- сохранение raw scan JSON
- отображение summary

Этап 6. Docker scan

Цель: найти контейнеры и предложить сервисы.

Результат:

- docker ps
- docker inspect для контейнеров
- volumes/networks/ports
- detected_services kind=docker_container

Этап 7. Nginx scan

Цель: найти домены и proxy_pass.

Результат:

- чтение nginx-конфигов
- парсинг server_name/listen/proxy_pass
- detected_services kind=nginx_vhost
- domains/proxy suggestions

Этап 8. Создание сервисов из найденного

Цель: пользователь подтверждает найденный сервис.

Результат:

- service proposals
- create service from detected_service
- relation Device → Service
- endpoint creation

Этап 9. Cron/backup hints

Цель: находить задачи, похожие на бэкапы.

Результат:

- cron parser
- systemd timer parser
- backup keyword detection
- detected_services kind=backup_hint

Этап 10. Риски

Цель: простые предупреждения.

Результат:

- сервис без backup_hint
- публичный web endpoint без документации
- SSH открыт
- нет свежего deep scan
- TLS скоро истекает, если cert найден

Этап 11. История и diff

Цель: видеть, что изменилось между сканами.

Результат:

- история scan jobs
- сравнение open ports
- новые/пропавшие хосты
- новые/пропавшие контейнеры

13. Как давать задание Hermes/owl-alpha

Я бы не давал ему “весь проект” целиком. Лучше так:

1. Сначала дать общее ТЗ и попросить создать каркас.
2. Потом отдельной задачей миграции.
3. Потом отдельной задачей CRUD устройств.
4. Потом отдельной задачей network scan.
5. Потом отдельной задачей SSH credentials.

Для owl-alpha особенно важно:

- запрещать переписывать уже готовое без причины;
- требовать маленькие коммиты;
- требовать список изменённых файлов;
- требовать инструкции запуска;
- требовать self-check;
- не разрешать выдумывать несуществующие зависимости;
- не разрешать делать произвольный shell executor;
- не разрешать писать "чистый JS" без обоснования;
- запрещать строковые шаблоны для docker-команд;
- требовать запуск scripts/check.sh после каждой итерации;
- требовать показать реальный вывод check.sh, а не утверждать "готово".

Головное правило: итерация не считается завершённой, пока check.sh не прошёл успешно. Без smoke test — нет завершения.