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

26 KiB
Raw Permalink Blame History

Краткое описание

Нужно разработать 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 в этой итерации. После выполнения выдать список файлов, команды запуска и что проверить вручную.