domovoy/Техническое задание - Домов...

1006 lines
26 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 web-приложение "Домовой" для инвентаризации домашней и малой серверной инфраструктуры.
Главная идея: приложение не требует вручную заносить всю инфраструктуру с нуля. Оно умеет сканировать заданные локальные сетевые диапазоны, находить устройства, предлагать создать карточки устройств, затем после добавления доступа к устройству запускать read-only deep scan по SSH и предлагать создать карточки сервисов, контейнеров, портов, доменов, cron-задач и backup-подсказок.
Приложение должно быть безопасным, локальным и read-only в части сканирования. Оно не должно ничего менять на удалённых хостах.
## Основной стек
Backend:
- PHP 8.3+
- Slim Framework 4
- PDO
- MariaDB/MySQL
Frontend:
- Bootstrap 5.3
- Vanilla JavaScript (только для тривиальных действий)
- htmx для частичных обновлений без перезагрузки страницы
- server-side rendered templates
htmx использовать для:
- обновления таблицы scan jobs;
- принятия/игнорирования найденного host;
- запуска сканирования;
- подгрузки деталей без полной перезагрузки;
- создания устройства из discovered_host.
Alpine.js — только если htmx не покрывает кейс.
Никакого "чистый JS потом допишем" — весь JS должен быть
заявлен заранее и обоснован.
Deploy:
- Docker Compose
Composer-зависимости:
- slim/slim:"4.*"
- slim/psr7
- php-di/php-di
- monolog/monolog
- vlucas/phpdotenv
- ramsey/uuid
- symfony/process
- phpseclib/phpseclib:"~3.0"
- defuse/php-encryption
composer require robmorgan/phinx
Dev-зависимости:
composer require --dev phpunit/phpunit
Не использовать тяжёлый frontend framework.
Не использовать ORM в первой версии.
Не использовать произвольное выполнение shell-команд от пользователя.
Миграции выполняются через Phinx:
php vendor/bin/phinx migrate
php vendor/bin/phinx rollback
php vendor/bin/phinx create MigrationName
Каждая миграция — отдельный класс с методами up() и down().
Файл phinx.php — точка входа для конфигурации Phinx.
## Архитектура выполнения задач
HTTP-запросы НЕ должны выполнять сканирование напрямую.
Правильная архитектура:
1. Web UI создаёт запись в scan_jobs (status=pending).
2. Отдельный CLI worker (bin/run-scan-worker.php) забирает
pending-задачи и выполняет их.
3. Web UI показывает статус задачи через polling (htmx).
Это означает:
- PHP не висит внутри HTTP-запроса во время сканирования.
- Время выполнения HTTP-запроса — миллисекунды.
- Worker можно запускать вручную или через cron/supervisor.
- Worker должен обрабатывать сигналы (SIGTERM) для graceful
shutdown.
## Важные ограничения безопасности
1. Сканировать только явно добавленные пользователем диапазоны.
2. По умолчанию считать допустимыми только private/local сети:
- 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. Секреты хранить только в зашифрованном виде.
9. Секреты не выводить в UI и логи.
10. Любой найденный объект сначала попадает в "Найденное", а не автоматически в основной инвентарь.
11. Все действия сканера логировать.
12. Запрещено делать универсальный shell executor, принимающий произвольную команду из UI.
## Главный пользовательский сценарий
1. Пользователь устанавливает приложение через Docker Compose.
2. Заходит в web UI.
3. Создаёт первый локальный аккаунт.
4. Добавляет сетевой диапазон, например 192.168.1.0/24.
5. Запускает скан сети.
6. Видит список найденных хостов.
7. Для нужных хостов нажимает "Создать устройство".
8. Открывает карточку устройства.
9. Добавляет SSH-доступ.
10. Нажимает "Тест подключения".
11. Если тест успешен, нажимает "Глубокий скан".
12. Приложение собирает read-only информацию с хоста.
13. Приложение показывает найденные порты, Docker-контейнеры, systemd-сервисы, cron-задачи, nginx-vhost-ы.
14. Пользователь подтверждает найденные сервисы.
15. Приложение создаёт карточки сервисов и связи между устройством, сервисами, портами, доменами и backup-подсказками.
## Архитектура каталогов
Создать структуру:
domovoy/
app/
Controllers/
Services/
Discovery/
HostScan/
Inventory/
Security/
Jobs/
Analysis/
Repositories/
Middleware/
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
## База данных
Нужны миграции для таблиц:
### users
- id
- username
- password_hash
- created_at
- updated_at
### network_ranges
- id
- name
- cidr
- enabled
- created_at
- updated_at
### scan_jobs
- id
- type
- status
- started_at
- finished_at
- error_message
- created_by
- created_at
type:
- network_discovery
- host_deep_scan
- docker_scan
- nginx_scan
- cron_scan
status:
- 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
status:
- 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
type:
- 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
type:
- 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
status:
- new
- accepted
- merged
- ignored
### services
- id
- name
- type
- description
- device_id
- status
- importance
- url
- main_port
- created_at
- updated_at
type:
- 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
relation_type:
- 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
### audit_log
- id
- user_id
- action
- entity_type
- entity_id
- details_json
- created_at
## Web UI
### Общий layout
Сделать Bootstrap layout:
Левое меню:
- Dashboard
- Сканирование сети
- Устройства
- Сервисы
- Документы
- Настройки
Верхняя панель:
- название "Домовой"
- текущий пользователь
- logout
### Dashboard
Показывать:
- количество устройств
- количество сервисов
- количество новых находок
- количество предупреждений
- последний сетевой скан
- последние найденные хосты
- последние deep scan результаты
### Сканирование сети
Страница должна позволять:
1. Добавить network range.
2. Включить/выключить range.
3. Запустить scan.
4. Посмотреть список scan jobs.
5. Посмотреть результаты discovered_hosts.
Таблица discovered_hosts:
- IP
- Hostname
- MAC
- Vendor
- Open ports
- Confidence
- Status
- Actions
Actions:
- Создать устройство
- Объединить с устройством
- Игнорировать
- Детали
### Устройства
Страницы:
- список устройств
- создание устройства вручную
- редактирование устройства
- карточка устройства
Карточка устройства должна показывать:
- основные поля
- IP/MAC/hostname/vendor
- тип
- важность
- заметки
- доступы
- найденные сервисы
- подтверждённые сервисы
- история сканов
- документы
### Доступы
На карточке устройства добавить блок SSH-доступов.
Для SSH-доступа:
- name
- username
- port
- auth_method password/private_key
- password/private_key
Кнопки:
- Тест
- Сохранить
Тест должен проверять подключение через phpseclib.
Секреты хранить в зашифрованном виде.
### Deep scan
На карточке устройства добавить кнопку:
"Глубокий скан"
После запуска:
- создать scan_job
- выполнить read-only SSH команды
- сохранить raw JSON в storage/scans/
- summary сохранить в host_scans.summary_json
- создать detected_services
## Network discovery implementation
Сделать сервисы:
- NetworkScanner
- PingScanner
- TcpPortScanner
- ArpTableReader
- HostFingerprintService
Первый MVP network scan:
1. Получить список IP из CIDR.
2. Для каждого IP:
- ping check
- TCP connect scan по базовым портам
- reverse DNS lookup
3. После sweep прочитать ARP table:
- ip neigh
- arp -a как fallback
4. Обогатить найденные хосты MAC-адресами.
5. Если MAC известен — попытаться определить vendor через локальную OUI-базу или оставить пустым.
6. Сохранить discovered_hosts.
Базовые TCP-порты:
- 22
- 23
- 53
- 80
- 443
- 445
- 548
- 631
- 3306
- 5432
- 6379
- 8000
- 8080
- 8443
- 9000
- 9090
- 9100
Не делать агрессивное сканирование.
Добавить timeout.
Добавить limit параллельности.
## SSH deep scan implementation
Сделать сервисы:
- SshClientFactory
- LinuxHostScanner
- DockerScanner
- NginxScanner
- CronScanner
- SystemdScanner
- BackupHintScanner
- CommandWhitelist
- SecretMasker
Все команды должны быть в CommandWhitelist.
Запрещено принимать произвольную команду из UI.
Формат команд в CommandWhitelist — массивы аргументов, НЕ строки:
'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}']
'linux.hostnamectl' => ['hostnamectl']
'linux.ip_addr' => ['ip', '-j', 'addr']
ЗАПРЕЩЕНО использовать строковые шаблоны:
// НЕПРАВИЛЬНО:
'linux.docker_ps' => "docker ps --format '{{json .}}'"
Массивы аргументов безопаснее — не ломаются при передаче
через Symfony Process, не конфликтуют с Twig-шаблонами,
не требуют экранирования кавычек и фигурных скобок.
Разрешённые read-only команды (формат: ключ => массив аргументов):
'linux.hostname' => ['hostname']
'linux.hostnamectl' => ['hostnamectl']
'linux.uname' => ['uname', '-a']
'linux.os_release' => ['cat', '/etc/os-release']
'linux.ip_addr' => ['ip', '-j', 'addr']
'linux.ip_route' => ['ip', 'route']
'linux.ss_tulpen' => ['ss', '-tulpen']
'linux.df' => ['df', '-h']
'linux.lsblk' => ['lsblk', '-J']
'linux.mount' => ['mount']
'linux.systemctl_units' => ['systemctl', 'list-units', '--type=service', '--all', '--no-pager']
'linux.systemctl_timers' => ['systemctl', 'list-timers', '--all', '--no-pager']
'linux.crontab_user' => ['crontab', '-l']
'linux.crontab_system' => ['cat', '/etc/crontab']
'linux.crontab_d' => ['ls', '-la', '/etc/cron.d', '/etc/cron.daily', '/etc/cron.hourly', '/etc/cron.weekly', '/etc/cron.monthly']
'linux.docker_ps' => ['docker', 'ps', '--format', '{{json .}}']
'linux.docker_networks' => ['docker', 'network', 'ls', '--format', '{{json .}}']
'linux.docker_volumes' => ['docker', 'volume', 'ls', '--format', '{{json .}}']
'linux.docker_compose_ls' => ['docker', 'compose', 'ls', '--format', 'json']
Каждая команда выполняется отдельно. Если отдельная команда
возвращает ошибку или timeout — записать ошибку в raw JSON
этого collector-а, продолжить остальные.
## 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.
Если команда недоступна или возвращает ошибку, не падать всем
сканом. Записать ошибку конкретного collector-а в raw JSON.
## DockerScanner
Если docker доступен:
1. Получить список контейнеров.
2. Для каждого контейнера получить:
- name
- image
- status
- ports
- mounts
- networks
- labels
- restart policy
- health status
3. Не показывать значения секретных env-переменных.
4. По контейнерам создать detected_services kind=docker_container.
Секретные имена:
- PASSWORD
- PASS
- SECRET
- TOKEN
- KEY
- PRIVATE
- CREDENTIAL
## NginxScanner
Если nginx найден:
1. Проверить наличие:
- /etc/nginx/nginx.conf
- /etc/nginx/sites-enabled/
- /etc/nginx/conf.d/
2. Прочитать только конфиги.
3. Не читать private key files.
4. Вытащить:
- server_name
- listen
- proxy_pass
- root
- ssl_certificate
5. Создать detected_services kind=nginx_vhost.
6. Создать domain suggestions.
## CronScanner и BackupHintScanner
CronScanner должен собрать:
- crontab -l
- /etc/crontab
- файлы /etc/cron.d
BackupHintScanner должен искать признаки:
- rsync
- borg
- restic
- rclone
- tar
- zip
- mysqldump
- mariadb-dump
- pg_dump
- sqlite
- docker exec
- scp
- sftp
Если найдено, создать detected_services kind=backup_hint.
Формулировка в UI:
"Похоже на backup job", а не "Это точно backup".
## Создание сервиса из detected_service
На странице найденных сервисов добавить действие:
"Создать сервис"
При создании:
1. Создать services.
2. Создать service_endpoints, если есть порт/url.
3. Создать relation:
- Device runs_on Service
или
- Service exposes Endpoint
4. detected_service.status = accepted
## Merge / deduplication
Для discovered_hosts сделать простую логику:
Если MAC совпадает с device.mac_address:
- предложить merge с высокой уверенностью.
Если hostname совпадает:
- предложить merge со средней уверенностью.
Если IP совпадает:
- предложить merge с низкой уверенностью, потому что IP может меняться.
В MVP достаточно показывать suggestions в UI.
Автоматически не объединять.
## Audit log
Логировать:
- login
- logout
- network scan started
- host scan started
- device created from discovered host
- credential created
- credential tested
- service created from detected service
- object ignored
- object merged
## Docker Compose
Сделать docker-compose.yml:
services:
- app
- db
app:
- PHP 8.3
- composer install
- volume для проекта
- volume storage
- depends_on db
db:
- mariadb
- volume db_data
- env из .env
Добавить .env.example:
- APP_ENV
- APP_SECRET
- DB_HOST
- DB_PORT
- DB_DATABASE
- DB_USERNAME
- DB_PASSWORD
- ENCRYPTION_KEY
## README
README должен содержать:
1. Что такое Домовой.
2. Предупреждение: сканировать только свои сети.
3. Установка.
4. Запуск docker-compose.
5. Создание первого пользователя.
6. Добавление network range.
7. Первый scan.
8. Создание устройства из найденного хоста.
9. Добавление SSH-доступа.
10. Запуск deep scan.
11. Где смотреть логи.
12. Как запустить миграции.
## CI/CD и smoke tests
Создать файл scripts/check.sh — локальный чек-скрипт,
который запускается после каждой итерации.
Минимальный набор проверок:
#!/usr/bin/env bash
set -euo pipefail
echo "=== composer validate ==="
composer validate --no-check-publish
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 ==="
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 ==="
Для следующих итераций добавлять свои проверки:
- network scan: "docker compose exec app php bin/console scan:test"
- device CRUD: curl к страницам devices
- credentials: тест SSH mock-connection
Головное правило: итерация не считается завершённой,
пока check.sh не прошёл успешно без ошибок.
## Технические требования
1. Код должен быть простым и читаемым.
2. Не делать преждевременно сложную архитектуру.
3. Контроллеры не должны содержать бизнес-логику.
4. SQL изолировать в Repository-классах.
5. Сканеры изолировать в Services.
6. Все shell-команды запускать только через CommandWhitelist.
7. Все ошибки scanner collector-ов сохранять, но не ронять весь scan.
8. UI должен быть рабочим, пусть и простым.
9. Bootstrap использовать без сборки frontend.
10. Vanilla JS только для небольших интерактивных действий.
11. Никаких React/Vue.
12. Никаких внешних SaaS-зависимостей.
## Порядок реализации
### Итерация 1
Сделать каркас:
- Slim app
- Docker Compose
- DB connection
- migrations runner
- auth
- dashboard
- layout
Результат должен запускаться.
### Итерация 2
Сделать:
- network_ranges CRUD
- scan_jobs table
- discovered_hosts table
- NetworkScanner с ping/tcp scan
- страницу результатов
### Итерация 3
Сделать:
- devices CRUD
- создание device из discovered_host
- ignore discovered_host
- карточку устройства
### Итерация 4
Сделать:
- credentials CRUD для SSH
- шифрование секрета
- тест SSH-подключения через phpseclib
### Итерация 5
Сделать:
- HostScan по SSH
- LinuxHostScanner
- raw JSON сохранение
- summary на карточке устройства
### Итерация 6
Сделать:
- DockerScanner
- detected_services из контейнеров
- страницу найденных сервисов
### Итерация 7
Сделать:
- создание service из detected_service
- service_endpoints
- relations
### Итерация 8
Сделать:
- NginxScanner
- CronScanner
- BackupHintScanner
### Итерация 9
Сделать:
- RiskAnalyzer
- dashboard warnings
- scan history
- basic diff
## Формат ответа после каждой итерации
После работы нужно выдать:
1. Что сделано.
2. Какие файлы созданы/изменены.
3. Как запустить.
4. Какие команды выполнить.
5. Что проверить вручную.
6. Известные ограничения.
7. Что делать следующей итерацией.
Не переходить к следующей итерации без успешного запуска текущей.
Обязательно запустить scripts/check.sh и показать вывод.
Без успешного smoke test итерация не считается завершённой.
## Правила для owl-alpha / LLM-кодера
Запрещено:
- переписывать уже готовое без причины;
- делать большие коммиты — только маленькие атомарные;
- выдумывать несуществующие зависимости;
- делать произвольный shell executor;
- использовать строковые шаблоны для docker-команд;
- писать "чистый JS" без обоснования — весь JS заранее заявлен.
Требуется:
- маленькие коммиты;
- список изменённых файлов после каждой итерации;
- инструкции запуска;
- self-check через scripts/check.sh;
- показать реальный вывод smoke test, а не утверждать "готово".
## Итерация 1
Сделай только Итерацию 1 проекта "Домовой".
Нужно:
- создать каркас Slim 4 приложения;
- настроить Docker Compose с app + MariaDB;
- сделать .env.example;
- сделать простой migrations runner;
- сделать таблицу users;
- сделать регистрацию первого пользователя через CLI или временную страницу setup;
- сделать login/logout;
- сделать Dashboard;
- сделать Bootstrap layout с меню;
- написать README с запуском.
Не реализовывать network scan, devices, credentials и deep scan в этой итерации.
После выполнения выдать список файлов, команды запуска и что проверить вручную.