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

1221 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

**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:
```bash
#!/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 — нет завершения.
---