Skip to content

📡 API демона: gRPC (ConnectRPC) + REST

📌 Описание

API-сервер демона: один и тот же axum-роутер — gRPC (tonic; совместим с ConnectRPC-клиентами платформы) и REST (JSON поверх HTTP) — обслуживается на двух листенерах: TCP-порт для платформы (bearer-токен) и локальный unix-сокет для CLI (аутентификация по peer credentials, DMN-042). Все транспорты вызывают один сервисный слой; каждый вызов несёт контекст пользователя, который ограничивает видимость приложений (пользователь видит свои, root — все). Контракты — protobuf-файлы в proto/, они же источник правды для клиентов платформы (buf-зависимость).

🎯 Сценарии использования

  • CLI asc управляет приложениями через unix-сокет без sudo и без группы docker: asc install, asc ls, asc app start работают у обычного пользователя и затрагивают только его приложения.
  • Платформа AdminService.Cloud управляет нодой через gRPC/ConnectRPC (через туннель nodeservice).
  • Скрипты и сторонние интеграции используют REST: curl -H "Authorization: Bearer <token>" http://127.0.0.1:8420/v1/apps, либо сокет: curl --unix-socket /run/asc/asc.sock http://localhost/v1/apps (без токена — личность определяет ядро).
  • Перед открытием WebSocket-консоли клиент запрашивает временный консольный токен (IssueConsoleToken) — платформа делает это автоматически после проверки прав.

🏗️ Техническое решение

Сервер

  • TCP-листенер на 127.0.0.1:8420 (настройка [api] listen в config.toml). Наружу порт не открывается — удалённый доступ идёт через туннель платформы (DMN-005 → tunnel).
  • Unix-сокет /run/asc/asc.sock (настройка [api] socket; каталог создаёт systemd-юнит через RuntimeDirectory=asc). Best-effort: если сокет не удаётся открыть, демон продолжает работать с одним TCP API и пишет предупреждение в лог.
  • Один роутер: axum REST + tonic-маршруты gRPC (h2c), диспетчеризация по пути/Content-Type.
  • Все блокирующие операции (docker/systemctl/git) уходят в spawn_blocking — event loop не блокируется.

🔐 Аутентификация и контекст пользователя

Каждый аутентифицированный запрос получает UserContext (uid, имя пользователя, признак root), который проставляет middleware транспорта; сервисный слой применяет из него правило владения приложениями — то же, что в in-process-режиме CLI (DMN-002).

TCP (платформа): bearer-токен.

  • Генерируется демоном при первом старте (32 байта из CSPRNG), хранится в api.token рядом с config.toml (root-only, 0600).
  • Обязателен для обоих транспортов: REST — заголовок Authorization: Bearer <token>, gRPC — metadata authorization. Сравнение — constant-time.
  • Без токена: REST → 401 {"error": ...}, gRPC → UNAUTHENTICATED.
  • Токен-вызовы действуют с полной видимостью (права пользователей платформа проверяет сама); пер-пользовательские API-токены — задача после MVP.

Unix-сокет (CLI): SO_PEERCRED. (DMN-042)

  • Без токена. Демон запрашивает у ядра uid подключившегося процесса (SO_PEERCRED) и строит контекст из него — ничто внутри запроса не может повысить привилегии.
  • Файл сокета нарочно доступен всем (0666): доступ к сокету сам по себе ничего не даёт, авторизация — peer uid, на каждый запрос. Обычный пользователь видит и управляет только своими приложениями; root-пир — всеми.
  • Атрибуция sudo asc ...: CLI передаёт SUDO_UID/SUDO_USER заголовками X-Asc-Sudo-Uid/X-Asc-Sudo-User; демон учитывает их только если сам пир — root, зеркаля in-process-поведение: новые приложения приписываются вызвавшему пользователю, sudo сохраняет полную видимость.
  • Маршрутизация CLI: команды жизненного цикла (ls/status/install/app start|stop|restart|logs|remove|info) идут через сокет, когда он существует. Нет файла сокета — CLI работает in-process, как раньше (DMN-041: root по системным путям, пользователь в своём дереве ~/.asc). Сокет есть, но не отвечает — ошибка для обычного пользователя и in-process-fallback с предупреждением для root (восстановление не должно зависеть от здоровья демона).
  • Известные ограничения (DMN-043): docker-контейнеры по-прежнему выполняются с привилегиями демона (root) — злонамеренный манифест может запросить опасные маунты, поэтому политика контейнеров для не-root владельцев — следующая задача; установка приватных репозиториев через демон использует git-креды демона, а не вызывающего; asc attach пока открывает Docker напрямую и требует root.

🎫 Временные консольные токены

  • AppService.IssueConsoleToken(app_id, session) / POST /v1/apps/{id}/console-token {"session": "logs"|"attach"}.
  • Токен одноразовый, TTL 30 секунд, привязан к приложению и типу сессии; хранится в памяти демона.
  • WebSocket-консоль (DMN-007) принимает подключение только по такому токену.

🗺️ Маршруты REST ↔ методы gRPC

RESTgRPCОписание
GET /v1/statusDaemonService.GetStatusВерсия, счётчики приложений
GET /v1/appsAppService.ListAppsСписок приложений (в объёме контекста вызывающего)
POST /v1/apps {"spec": ..., "source"?, "name"?, "branch"?, "tag"?, "license_ack"?}AppService.InstallAppУстановка из реестра или напрямую из git-URL (DMN-040); без license_ack репозиторий с LICENSE отвечает 409 + license_required, неоднозначный пакет — 409 + ambiguous (список кандидатов) — из них CLI строит диалог согласия / выбор источника
GET /v1/apps/{id}AppService.GetAppОдно приложение
GET /v1/apps/{id}/diskAppService.GetAppDiskДисковое пространство: образ, репозиторий, данные, кастомные тома
POST /v1/apps/{id}/start|stop|restartAppService.Start/Stop/RestartAppЖизненный цикл
GET /v1/apps/{id}/logs?tail=NAppService.GetAppLogsХвост логов
DELETE /v1/apps/{id}AppService.RemoveAppУдаление с данными
POST /v1/apps/{id}/console-tokenAppService.IssueConsoleTokenВременный токен консоли
GET /v1/metricsMonitorService.GetSystemMetricsТекущие системные метрики (503, пока нет первого сэмпла)
GET /v1/metrics/history?limit=NMonitorService.GetMetricsHistoryИстория метрик из кольцевого буфера, старые → новые

📜 Кодогенерация

  • Rust-код генерируется из proto/ в build.rs через protox (pure-Rust компилятор protobuf) + tonic-build — системный protoc не нужен, сборка герметична.
  • Изменения контрактов — только обратно совместимые (новые поля — optional, номера не переиспользовать).

🔗 Связанные задачи

DMN-005, DMN-007, DMN-042, DMN-043 в ROADMAP.md.

Распространяется по лицензии MIT.