Доброго времени суток, читатели!
Хочу представить вам unissh – современный, простой опенсорсный SSH клиент с selfhosted zero-knowledge сервером для синхронизации данных.
Заранее предупреждаю, что проект – чистый вайбкод на расте + таури, но мы с друзьями старались его хорошо протестировать.
Далее я расскажу о причинах, которые побудили меня в начале июня заняться его разработкой.
Первой, и наверное основной причиной стало то, что я лично не смог найти нормальных кроссплатформенных ssh клиентов с синхронизацией данных между устройствами. Моя личная потребность на протяжении наверно уже десятка лет, заключается в том, что мне очень удобно иметь доступ к серверам и с телефона, и с ноутбука. Идея всегда носить с собой макбук, мне не слишком нравится 😉
Существующие решения с поддержкой синхронизации предлагают что-то из приведенного ниже:
-
синхронизация данных через iCloud (работает только для устройств из экосистемы Apple)
-
синхронизацию данных через внешние файлохранилища (например GitHub, гугл диск и прочие подобные решения)
-
сомнительный дизайн с моей точки зрения (я считаю это довольно важным нюансом)
-
отсутствие шифрования данных
-
отсутствие возможности экспортировать собственные данные без подписки
Я считаю, что нейронки должны нести в том числе и какую-то пользу для большого количества людей. Поэтому, решая собственную проблему – я выбрал навайбкодить свой собственный SSH клиент и сервер с веб-панелькой к нему, чисто для синхронизации волтов между устройствами.
И вот, спустя два месяца разработки рад представить вам результаты вайбкуколдинга:
Возможности клиента
-
Кроссплатформенность – Windows, macOS, Linux, iOS, Android
-
Терминалы – сплитование вкладок в разных вариациях, дублирование, переименовывание и прочие базовые фичи
-
SFTP – для работы с загрузкой / выгрузкой файлов с серверов; в настройках можно менять еще количество активных подключений для SFTP, если вы работаете с сотнями и тысячами файлов; также есть базовый текстовый редактор внутри
-
Два режима массового выполнения команд:
-
Broadcast – открытие множества ссш сессий к выбранным серверам и наблюдение за выводом команд
-
Fleet exec – от первого варианта отличается тем, что команды просто посылаются на сервер и показывается exit code с каждого сервера
-
-
Секреты – SSH-ключи, пароли для серверов, идентичности, заметки; также поддерживается импорт из ~/.ssh/config; имеются истории версий у секретов
-
SSH-туннели (Local, Remote, Dynamic)
-
Запись сессий в asciicast v2 формат
-
Сниппеты – вы всегда можете создать необходимые вам сниппеты для быстрого их выполнения на сервере
-
Гибкая кастомизация интерфейса и поддержка множества тем и их светлых / темных вариаций, и возможность создавать свои собственные темы. Сейчас из дефолтных тем приложений: Mono, Nebula, Barbie (да, барби тема 😉 и также несколько тем для терминала
-
Поддержка подключения к множеству серверов для синхронизации, когда у вас например есть личный сервер с вашими личными волтами, а есть сервер компании / команды и рабочие волты
Архитектура проекта
Общее кроссплатформенное ядро (rust-core) – это workspace из девяти крейтов, в котором живет вся крипта, волты на SQLCipher, SSH-стек (russh), встроенный in-memory ssh-агент и логика синхронизации. Клиенты подключаются к ядру через UniFFI, а веб-панелька для администраторов сервера – вообще через это же ядро, просто скомпилированное в wasm. Важное замечание: крипта и SSH не переписываются под каждую платформу. Клиент на iOS, десктопный клиент и админка в браузере дергают один и тот же код. Меньше возможностей для ошибок у слоп-машин 😉
Клиенты – Tauri v2 + React, один кодбейс на все пять платформ (macOS, Windows, Linux, iOS, Android). Клиент по сути тонкий: UI, xterm.js для терминала, а все чувствительное за FFI-границей в ядре.
Сервер – маленький бинарь на Rust (axum + sqlx, SQLite или PostgreSQL на выбор), который нужен только если вы хотите синхронизацию между устройствами или командную работу.
Главное про сервер: он ничего не знает
Сервер – это тупое хранилище шифроблобов с логикой членства и версий. Все данные шифруются на устройстве до отправки: ключи выводятся из Secret Key (это ваш Emergency Kit, как в 1Password) + пароля через Argon2id. Сервер хранит ciphertext, проверяет Ed25519-подписи записей и раздает дельты другим устройствам, но не выполняет никакой криптографии над содержимым. Он физически не может расшифровать волт, выписать себе доступ или подделать запись. Максимум, на что способен скомпрометированный сервер – не отдать или задержать данные.
И второй принципиальный момент: SSH-трафик никогда не ходит через сервер синхронизации. Соединение с вашими хостами всегда идет напрямую с устройства. Сервер синхронизации синкает только зашифрованные волты – если он упал, потерялся, взломали – к серверам вы все равно подключаетесь как ни в чем не бывало.
Отдельно порадовало, как получилось решить онбординг нового устройства: заходите с нового девайса через escrow sign-in (хэндл + пароль (при наличии) + Secret Key) – кейсет восстанавливается и расшифровывается прямо на устройстве, до сервера он не доезжает. Аккаунт один на все устройства, поэтому если коллеги выдали вам доступ к волту – он работает сразу везде. При этом у каждого девайса свой id, так что потерянный телефон можно отозвать отдельно, не убивая аккаунт.
Zero-knowledge не означает «сервер не видит ничего». Метаданные видны по дизайну: айди волтов и элементов, версии, tombstone’ы, публичные ключи участников, роли, sync-цели, размеры блобов и тайминги синхронизации. Не видит имен, содержимого, ключей волтов, элементов и приватных ключей. Тоесть скомпрометированный сервер узнает, что у вас 3 волта и 47 элементов, которые вы обновляли вчера в полночь, но не что в них.
И второе: не все гарантии криптографические, часть – server-trusted, то есть держится на корректном поведении сервера. Прежде всего это отзыв доступа: когда вы выкидываете участника, немедленный «отлуп» ему обеспечивает сервер. Криптографический отзыв тоже есть – через ротацию ключа волта и epoch floors на клиентах, но это следующая линия обороны, а не мгновенная.
Мы стараемся явно разделять эти два класса гарантий в документации, а полный разбор есть в репозитории в THREAT_MODEL.md и server/README.md.
Безопасность
Мы с слопусом не переизобретаем собственную криптографию – все построено на проверенной базе: RustCrypto, hpke, SQLCipher, Argon2id для вывода ключей, Ed25519 (verify_strict) для подписей.
Волты zero-knowledge: все содержимое шифруется на клиенте до отправки. На диске волт лежит в SQLCipher, а внутри per-item ключи, которые под per-vault ключом: компрометация одного элемента не раскрывает соседние, а сервер хранит только шифрованный текст, служебные метаданные и не выполняет никакой криптографии над содержимым.
Секреты не размазываются по памяти и диску: они зануляются после использования (zeroize), плейнтекст приватных ключей никогда не пишется на диск самим приложением, только через явный экспорт в менюшке секретов. Страницы памяти с ключами по возможности лочатся через mlock, чтобы не уехать в своп. Secret Key хранится только в системном кейчейне / Secure Enclave. Из приятных мелочей – настраиваемая авто-очистка буфера обмена после копирования пароля.
Секретная граница закрыта контрактом и тестом: секреты выходят из ядра только при вашем явном вызове: показать пароль или заметку, экспортировать ключ, сделать зашифрованный бэкап. Этот список зафиксирован в одном месте – тестом, который поименно перечисляет все методы, которые возвращают секреты и проверяет их type-gating вживую: попросить пароль у элемента-заметки или экспортировать ключ, который на самом деле пароль – ошибка. Новый метод, возвращающий секреты, обязан быть внесен в этот список явно – так поверхность утечки остается обозримой и не разрастается втихую. Туда честно внесен даже листинг сниппетов – с комментарием, что в сохраненных командах регулярно живут токены и хостнеймы.
Хост-ключи пинятся, классический TOFU: при первом подключении ключ хоста запоминается, и если он вдруг поменялся – соединение принудительно останавливается и вам показывается HostKeyMismatch с возможностью принять новый хост-ключ.
Целостность можно проверить локально: в ядре есть возможность проверки подписей по всем версиям записей, включая историю и tombstone’ы, — и check_consistency для структурной проверки базы.
Транспорт только TLS 1.3: либо in-process rustls, либо Caddy, либо ваш собственный reverse proxy.
Про неподписанные бинарники: я честно пока так и не придумал, что можно с этим сделать, поэтому релизы не подписаны сертификатами Apple/Microsoft и при первом запуске ОС на вас ругнется.
Вместо сертификата у каждого релиза три механизма проверки:
-
файл SHA256SUMS по всем артефактам
-
minisign-подпись поверх него – публичный ключ лежит в SECURITY.md, так что подмену чексумм прямо на странице релиза можно обнаружить
-
SLSA-провенанс через GitHub attestations: команда gh attestation verify <файл> —repo goduni/unissh доказывает, что артефакт собран публичным CI из публичного коммита, а не у меня на коленке
Автообновления десктопного клиента проверяются отдельным Ed25519-ключом, вшитым в приложение. Ключ, подписывающий «что вы скачали руками», намеренно не совпадает с ключом, авторизующим «что исполнится на вашей машине без спроса» – сливать эти два радиуса поражения в один было бы плохой идеей.
Ну а самый базовый путь никто не отменял – собрать все из исходников и доверять только своему компилятору.
Селфхостим сервер за пять минут
Напомню еще раз: сервер опционален и совершенно необязателен. Клиент из коробки работает в локальном режиме, и если синхронизация вам не нужна – можно вообще ничего не поднимать и дальше этот раздел не читать 😉
Но мы-то с вами помним, ради чего все затевалось – синхронизация для телефона и ноутбука, поэтому поднимаем.
Самый быстрый способ поднять сервер:
curl -O https://raw.githubusercontent.com/goduni/unissh/main/compose.prod.ymlcurl -o .env https://raw.githubusercontent.com/goduni/unissh/main/deploy/.env.example# в .env вписать UNISSH_DOMAIN=ssh.example.comdocker compose -f compose.prod.yml up -d
В компоузе в том числе есть Caddy – он сам сходит за сертификатом в Let’s Encrypt, сам терминирует TLS и сам же раздает веб-админку.
Если у вас нет домена, и поднимаете в локалке: UNISSH_TLS_DIRECTIVE="tls internal" и Caddy выпишет самоподписанный серт.
База по умолчанию – SQLite в докер-вольюме, и для личного пользования этого хватит с запасом. Захотелось посерьезнее – Postgres включается парой строк в .env. Наружу торчат только 80/443, сервер бежит non-root на read-only rootfs, метрики и внутренний порт остаются внутри compose-сети. Есть надежды на то, что за такой деплой перед безопасниками (при их наличии) не будет стыдно 😉
Знаете эти прекрасные квесты «создайте первого админа через переменную окружения, потом удалите ее, потом поменяйте пароль»? Так вот, тут этого нет, но есть немного другой 😉
Сервер печатает в логи одноразовый setup code:
docker compose logs server 2>&1 | grep -i "setup code"
Открываете клиент или админку, вводите адрес своего сервера и этот код – теперь вы владелец инстанса. Если вам требуется кого-то пригласить в пространство, то это уже через инвайт-ссылки (или прикручиваете корпоративный SSO через OIDC, если у вас все серьезно) – код им уже не нужен. Автоматизируете деплой ансиблами-терраформами? Код можно запинить детерминированно через UNISSH__SETUP__CODE.
Проверить, что все ожило: curl -k https://your-domain/healthz
Админ-панель сервера

Веб-панель – SPA, внутри которой крутится то же самое Rust-ядро, что и в клиентах, только скомпилированное в wasm. Тоесть вся крипта выполняется у вас в браузере: заходите по хэндлу + паролю (при его наличии) + Secret Key, кейсет восстанавливается и расшифровывается прямо на странице и до сервера не доезжает. Кнопка Lock – и он вычищается из памяти. Никаких «импортируйте файлик с ключом» (но справедливости ради, мне пришлось над этим поработать, так как такое реально было): новый браузер подтверждается QR-кодом с уже доверенного устройства, как в мессенджерах.
Внутри же пространства, участники, устройства и сессии, инвайты, волты с грантами, аудит. Есть опциональный ops-токен для break-glass сценариев, но он открывает только инфраструктурные ручки и не расшифровывает ровным счетом ничего.
По поводу бэкапов подсвечу: если вы восстановили сервер из старого снапшота, клиенты это заметят и откажутся синкаться с ошибкой TransportRollback – пока вы явно не поднимете версию через seq-bump. А еще старый restore может воскресить удаленный элемент. Это не баги, а anti-rollback защита делает свою работу. Но лучше узнать про нее из README, а не во время аварии 😉
Вместо заключения
Два месяца назад я просто хотел SSH-клиент, который не заставляет выбирать между удобно, безопасно и не платить подписку за доступ к собственным серверам. Теперь он у меня есть – и у вас тоже, если захотите.
Что я могу сказать: нейронки уже дозрели до того, чтобы за пару месяцев вечерами собрать кроссплатформенный клиент, сервер и админку, но все еще требуется человек, который хоть немного разбирается в делах, которое он решает нейронкой. Особенно необходимо уметь задавать неудобные вопросы, чтобы нейронка могла решать проблемы обнаруженные этими неудобными вопросами.
Проект полностью открыт под двойной лицензией MIT / Apache-2.0. Никакой платной версии, облачной подписки или энтерпрайз-фич за деньги нет и не планируется: это тулза, которую я делал для себя, а сервер вы и так сами поднимаете.
Буду рад, если вы потестируете клиент – все пять платформ лежит в релизах, сервер поднимается за пять минут из готовых образов или по инструкции выше.
По поводу найденных багов, пожеланий и всего такого – пишите в issues, либо в чатик проекта в телеграме. Уязвимости – не в ишьюсы, а в идеале на uni@goduni.me. Я серьезно: проект написан нейронкой, внешнего аудита криптографии не было, и каждый человек, который внимательно посмотрит в исходники, поможет сделать его лучше. Особенно приглашаю тех, кто в комментариях уже готовит тезис «вайбкоду нельзя доверять секреты» – вот вам открытый код, THREAT_MODEL.md и почта для репортов.
Ближайшие планы – собрать ваш фидбек, в надежде на то, что оно пригодится в том числе не мне одному, и допиливать согласно вашим пожеланиям.
Ссылки:
-
Репозиторий: https://github.com/goduni/unissh
-
Telegram: https://t.me/unissh
-
Сайт: https://unissh.dev
Спасибо, что дочитали! Буду рад вашим звездочкам на репозитории, комментариям и обратной связи!
ссылка на оригинал статьи https://habr.com/ru/articles/1068896/