Если коротко: После дефейса сайта нашего знакомого из-за утекшего пароля от админки мы поняли, что управлять доступами нетехнической команды (редакторы, SEO, подрядчики) через VPN или статические IP - это боль. Существующие proxy требовали рестартов и рулились конфигами. В итоге мы написали свой forward-proxy на Go, где доступ выдается токеном через расширение браузера, а правила (TTL, лимиты трафика, доступные домены) применяются на лету без разрыва соединений.


Так видит наше решение Google Gemini
Так видит наше решение Google Gemini

Почему мы отказались от классических решений

Задача казалась тривиальной: дать безопасный доступ в админки ограниченному кругу лиц. Но на практике стандарты ломаются о реальные процессы:

  • VPN: Избыточен. Заворачивает весь трафик (привет, торренты и абузы на сервер), сложен в настройке для гуманитариев, а сегментация доступа (ACL) быстро превращается в хаос.

  • Статические IP: В эпоху 4G/5G, коворкингов и удаленки - почти не работают. Whitelist на сервере не дает сегментации по юзерам и гибкости отзыва.

  • Готовые forward-proxy (например, 3X-UI): Конфигурация через файлы. Выдать доступ подрядчику "на час" - это правка конфига, деплой и рестарт. UI поверх таких решений - просто обертка, каждое изменение требует reload, сбрасывая текущие сессии. Плюс зачастую настроить HTTPS прокси с сертификатом нормального CA - тот ещё квест.

  • Pomerium и другие Reverse Proxy / SSO-шлюзы: Отличные инструменты, но со своими фатальными для нашей задачи недостатками.

    • Во-первых, reverse-proxy меняет домен сайта (или требует сложных настроек) и терминирует TLS, устраивая MITM (Man-in-the-Middle).

    • Во-вторых, тяжелый онбординг. Чтобы пустить внешних аудиторов или временных подрядчиков, нужно сначала выпросить их email, завести учетки в IdP, настроить права, дать доступ к нужным ресурсам, а после завершения работ - не забыть удалить.

Разрыв между "инструмент работает" и "им удобно пользоваться" оказался большим. Нам нужен был инструмент управления доступами, а не инфраструктурой, без сложного онбординга.

Ключевая идея: доступ к конфигурации, а не к пользователю

Мы изменили подход: админ создает конфигурацию (разрешенные домены, TTL, лимиты трафика). Для неё генерируется короткий base58-токен.

Этот токен передается пользователю. Никаких паролей, VPN-клиентов, сбора email-ов или IP-адресов. Скинул токен аудитору в Telegram - через заданный TTL доступ сам "превратился в тыкву".

Архитектура состоит из трех частей:

  1. Forward-proxy на Go.

  2. API управления (Control Plane).

  3. Расширение для браузера.

Чуть ниже я покажу 3 неочевидных юзкейса (например, как мы навсегда убили необходимость править /etc/hosts при переездах сайтов), но чтобы магия была понятна, давайте быстро заглянем под капот движка.

Миграция сайта теперь без правки /etc/hosts
Миграция сайта теперь без правки /etc/hosts

Как это работает под капотом

Наш инструмент - это не универсальный прокси, а узкоспециализированный интернет-шлюз.

  • Деплой и автономность (Zero Config): Сам шлюз - это один бинарник на 8 МБ с нулем зависимостей, который ставится на "голую" Debian 13 (x86/ARM). У него вообще нет конфигурационного файла. Все настройки он получает с Control Plane. При этом шлюз умеет работать автономно: если после перезагрузки сервера связи с CP не будет, прокси поднимется из локального стейта и продолжит пускать пользователей по правилам (вплоть до протухания TLS-сертификата).

  • Безопасность транспорта (TLS): Соединение с прокси работает поверх TLS. Мы автоматизировали этот процесс: даже для self-hosted нод система сама выписывает и продлевает сертификаты (Google Trust Services, Let's Encrypt, ZeroSSL).

  • Скорость и TTFB: почему прокси работает быстрее прямого подключения: Казалось бы, любой шлюз - это лишний хоп, который должен замедлять загрузку. На практике мы получили обратный эффект. За счет того, что в шлюз встроен собственный кэширующий DNS-резолвер (работающий с 1.1.1.1/8.8.8.8), а сами ноды стоят на широких магистральных каналах дата-центров, Time To First Byte (TTFB) через наш прокси часто оказывается ниже, чем при прямом подключении через обычного домашнего провайдера с его медленными DNS.

  • Защита от сканеров и контроль шеринга (TLS SNI): Для маршрутизации к прокси используется wildcard DNS (например, *.de.l7ag.run), а каждый пользователь через расширение получает уникальный адрес вида DevID-user-v4.de.l7ag.run. Это дает две суперсилы. Во-первых, жесткая пред-авторизация на уровне TLS SNI: если бот или сканер стучится по IP или без корректного SNI - коннект мгновенно обрывается, а IP улетает в бан. Во-вторых, так мы идентифицируем конкретные устройства на одном токене (можно задать лимит: например, не больше 3 человек на токен).

  • Модель соединений: HTTP CONNECT. Шлюз работает как туннель: TLS целевого сайта не терминируется, payload не анализируется (zero-copy). Результат - нулевой MITM, низкая нагрузка на CPU и минимальная задержка.

  • Авторизация: Basic Auth, где логин/пароль привязаны к конфигурации, а не к конкретному человеку. Это позволяет одной ссылкой пустить команду подрядчиков, а затем в один клик убить токен для всех.

  • Хранилище: Ушли от Redis к embedded-базе BuntDB. При старте всё кэшируется в in-memory map. Никаких походов в БД на каждый CONNECT - задержка минимальна.

Продление доступа в реальном времени. В пару кликов и без разрыва соединений.
Продление доступа в реальном времени. В пару кликов и без разрыва соединений.

Киллер-фича: Динамическое обновление без разрыва соединений

Главная проблема классических proxy: изменил конфиг -> сделал reload -> активные соединения упали. Если в этот момент редактор грузил большое видео в админку, он вас возненавидит.

Мы реализовали применение правил в рантайме. Шлюз отслеживает активные туннели:

  • Доступ выдан на час. Человек грузит дамп БД.

  • Время истекает - админ в UI жмет "Продлить на 30 минут".

  • Соединение не рвется. Движок шлюза обновляет параметры активного туннеля на лету.

  • И наоборот: если превышен лимит трафика или админ отозвал токен, шлюз мгновенно убивает активный туннель, не дожидаясь закрытия сессии клиентом.

Клиентская часть: Расширение и PAC-файлы

Вместо системного proxy мы написали браузерное расширение, которое принимает токен и генерирует PAC-файл (Proxy Auto-Configuration) на лету.

Никаких "черных ящиков": вес 38 КБ и апрув в сторах

Наше расширение весит всего 38 КБ (вместе с иконками) - меньше, чем превью-картинка к этой статье. Мы принципиально не используем минификацию, обфускацию или аналитические трекеры. В коде даже оставлены оригинальные комментарии.

Именно благодаря такой прозрачности расширение уже успешно прошло модерацию и доступно в официальных сторах Chrome и Firefox. Нам нечего скрывать: любой разработчик может распаковать исходники и лично убедиться в безопасности плагина буквально за пару минут.

Почему это удобно:

  1. Легкость настройки: Нужно просто вставить 5-символьный токен, и маршрутизация работает. Никаких настроек с кучей опций или пересылок json-файлов с конфигами.

  2. "Настроил и забыл" + быстрый тест: Благодаря селективной маршрутизации вам не нужно постоянно включать или выключать расширение (как это бывает с VPN) - браузер сам знает, когда идти через шлюз, а когда напрямую. А тумблер вкл/выкл в расширении позволяет в один клик проверить, как сайт ведет себя через шлюз и как он выглядит "снаружи" для обычных посетителей.

  3. Изоляция трафика: Админка магазина идет через прокси, а YouTube в соседней вкладке - напрямую.

  4. Мультирегиональность: Расширение умеет раскидывать запросы по разным proxy-инстансам (админка для EU - через один шлюз, для US - через другой).

Пример селективной маршрутизации. Один сайт через Австрию, другой через Германию, админка доступна через шлюз (без него показывает 404), а YouTube идёт напрямую.   Демо сделано в Vivaldi
Пример селективной маршрутизации. Один сайт через Австрию, другой через Германию, админка доступна через шлюз (без него показывает 404), а YouTube идёт напрямую. Демо сделано в Vivaldi

3 неочевидных юзкейса, которые закрыл наш подход

Помимо банальной защиты админок, такая маршрутизация на уровне браузера решила нам еще несколько головных болей:

  1. Тестирование при переезде на новый сервер (Убийца /etc/hosts)

    Когда вы переносите сайт на новый сервер, нужно, чтобы QA, SEOшники и заказчик всё проверили до обновления публичных DNS. Раньше приходилось писать инструкции для нетехнических людей: "откройте блокнот от имени администратора, найдите файл hosts...".

    Теперь мы просто создаем токен "Новый сервер", где прописываем кастомный резолв домена на новый IP. Человек включает расширение - видит сайт на новом сервере. Выключает - видит старый (продакшен).

  2. Безопасный доступ к localhost и private IP

    Можно легко выдавать доступ к внутренним сервисам, которые физически не торчат наружу. Например, нужно дать девопсу доступ к Caddy API, который слушает только localhost:2019 на конкретном сервере, или пустить разработчика в админку базы данных во внутренней серой подсети. Используя домены, типа caddy.internal.

  3. Внутренние DNS-имена без реальных DNS-записей

    Доступ к инфраструктурным инструментам, для которых вы принципиально не хотите светить DNS-записи. Вы можете замапить в конфиге условный grafana.site.com на localhost:3000 (на машине, где крутится шлюз). Пользователь с токеном просто вводит grafana.site.com в адресную строку браузера (или нажимает на ссылку в расширении) и попадает в дашборды. Снаружи этого домена и порта не существует.

Интерфейс

Отдельная боль многих энтерпрайз-решений (того же Pomerium) - это интерфейс. Настраивать каждого пользователя заново, прокликивать десятки чекбоксов, чтобы изменить права для группы доменов - это долго.

Поэтому UI личного кабинета мы спроектировали с оглядкой на десктопный UX. Нужно выделить десяток объектов? Не надо кликать десяток чекбоксов - просто выделяете их рамкой, как в Проводнике Windows, или через Shift/Ctrl. Нужные действия вызываются привычным контекстным меню по правой кнопке мыши. Любой объект можно продублировать в один клик, чтобы не повторять рутину. Никакого перегруженного веб-интерфейса - только быстрый SPA (Single-Page Application) на ванильном JS, где данные летают по WebSocket.

Выделение рамкой, контекстные меню - десктопный UX
Выделение рамкой, контекстные меню - десктопный UX

Модели изоляции: от Shared до Self-hosted

Поскольку мы делали SaaS, нам пришлось сразу закладывать архитектуру под разные уровни паранойи и требований к инфраструктуре:

  1. Shared-шлюзы (Managed)

    Полностью наша инфраструктура. Идеально для быстрого старта и небольших команд. Создал токен - и сразу работаешь. Единственный нюанс - мы применяем "ленивую" модерацию доменов, чтобы наши IP не улетели в блэклисты из-за любителей торрентов и спама.

  2. Dedicated-шлюзы (Managed)

    Тоже наша инфраструктура, но выделенный инстанс под одного клиента. Это решает проблему "соседей" и дает чистый статический IP-адрес шлюза, который можно жестко прописать в whitelist на целевом сервере.

  3. Self-hosted (Своя инфраструктура)

    Высший уровень контроля. В этом случае весь трафик идет через ваш сервер, у вас нет лимитов на трафик и домены без модерации, а оплата за сервер идет напрямую вашему хостинг-провайдеру

Делаем self-hosted доступнее

Мы максимально упростили деплой self-hosted шлюзов. Наша цель была в том, чтобы поднять свою ноду мог менеджер проекта или руководитель агентства, который в глаза не видел консоль Linux и SSH.

Все делается из интерфейса: вводите API-ключ вашего облачного провайдера (Hetzner, Vultr, DigitalOcean, Upcloud, Akamai (Linode)), выбираете локацию и нажимаете кнопку. Наш бэкенд сам создаст сервер, скачает бинарник шлюза и параллельно выпустит сертификат Google Trust Services (Let's Encrypt или ZeroSSL). Меньше чем через 25 секунд вы получаете готовый к работе и безопасный прокси без единой строчки кода в терминале.

Либо можете поставить его руками на абсолютно любой Linux-сервер (хоть на Raspberry Pi в офисной кладовке).

Установка сводится к классическому one-liner:
curl -fsS https://l7.run | sh -s -- -t <token>

Для тех, кто обоснованно считает curl | sh плохой практикой

Вы можете просто скачать этот скрипт и изучить его. Там нет никакой магии или обфускации. Он определяет архитектуру, скачивает бинарник, прописывает systemd-сервис и закидывает конфиг в /etc/sysctl.d/ для тюнинга сетевого стека под высокие нагрузки: включаем алгоритм BBR, переиспользование TIME_WAIT сокетов, отключаем медленный старт после простоя и тюнит буферы.

Мы заморочились с концепцией Zero Dependency. Чтобы при установке не зависеть от наличия unzip или zstd на сервере, мы используем HTTP-сжатие zstd при скачивании через curl. Экономим трафик, распаковываем поток на лету, создаём юзера с минимальными правами и прописываем бинарник в автозапуск.

Дальше шлюз берет рутину на себя: сам запрашивает сертификаты и актуальные конфиги из Control Plane. А следуя лучшим современным практикам, бинарник умеет безопасно и "мягко" обновлять сам себя без разрыва активных соединений (graceful update). Никаких прерванных сессий или отвалившихся загрузок у пользователей! По умолчанию шлюз вешается на 443 порт, но изменить его можно в пару кликов прямо в веб-интерфейсе. Опять же, никаких текстовых конфигов и возни в терминале.

Эволюция костыля, или как мы докатились до SaaS

Забавно, но этот проект вообще не планировался как SaaS. Всё началось с крошечного расширения для Chrome, которое мы написали для себя.

С чего всё начиналось
С чего всё начиналось

Изначально бэкенда не было в принципе. Под капотом крутился обычный 3X-UI, а в расширение нужно было просто скопировать JSON-конфиг. Но быстро выяснилось, что пересылать JSON-файлы нетехническим юзерам - это боль. Человеку нужно было куда-то его сохранить, потом зайти в настройки расширения, найти куда он сохранил файл... Мы решили упростить: пусть юзер вводит просто короткий токен, а расширение само скачивает конфиг по API. Идея зашла отлично.

Но тут начал напрягать сам 3X-UI. Процесс добавления доступа выглядел так: зайди в админку, вручную заведи юзера, потом отдельно пропиши правила (на какие сайты ему можно ходить). Потом не забудь сохранить конфиг и самое бесячее - сделать рестарт сервера. Во время одного из таких ручных ковыряний я случайно заблочил служебный канал самого 3X-UI. У меня и в мыслях не было, что инструмент может так легко заблокировать сам себя прямо из своего же UI, а потом просто крашиться при каждом рестарте :)

Чтобы не плодить ошибки, я набросал простенькую веб-страничку: вводишь домены и креды, а она генерирует готовый JSON. Но данные всё равно приходилось вводить в двух местах. Это жутко раздражало.

Генератор JSON-конфига
Генератор JSON-конфига

Тогда пришла крамольная мысль:

А что если выкинуть 3X-UI и написать свой максимально тупой и простой прокси, который будет рулиться по API и применять правила без рестартов?

Так началась череда экспериментов:

  1. Первая итерация: Взяли библиотеку elazarl/goproxy и прикрутили Redis для хранения стейта. Позже поняли, что это дикий оверинжиниринг: нам не нужен был MITM и глубокий анализ трафика, требовалось просто максимально быстро перекидывать байтики из сокета в сокет. В итоге выпилили оба инструмента, перейдя на самописный zero-copy движок и in-memory/BuntDB.

  2. Битва протоколов и публичный Wi-Fi: Попробовали SOCKS5, но уткнулись в ограничения браузеров - они просто не умеют передавать SOCKS5-авторизацию, плюс отваливался TLS. Пришлось возвращаться к HTTP CONNECT. При этом у нас было принципиальное требование безопасности: мы не хотели, чтобы человек, сидя в общественном Wi-Fi, светил кредами шлюза в открытом виде. Поэтому всё нужно было обязательно заворачивать в TLS.

  3. Браузерные грабли с кэшем: Вылезла максимально неочевидная проблема. Если человек менял токен в расширении (заходил под другим юзером), браузер продолжал упрямо слать старые заголовки авторизации (Proxy-Authorization). Браузеры жестко кэшируют креды прокси до полного перезапуска! Решение нашлось изящное: мы начали генерировать динамические SNI под каждого юзера, чтобы для браузера это выглядело как совершенно новый прокси-сервер. Кэш сбрасывался, магия работала.

Решая одну маленькую проблему за другой, мы шаг за шагом избавились от чужих костылей, пока в какой-то момент не посмотрели на результат и не поняли:

"Упс, кажется, мы написали полноценный SaaS"

P.S. В процессе разработки мы собрали приличное количество граблей и нашли много неочевидных решений по их обходу (особенно в части работы с браузерами и расширениями). Если вам интересна техническая изнанка проекта - дайте знать в комментариях, и я напишу вторую статью с подробным разбором "подводных камней"!

P.P.S. Сервис пока на стадии закрытого бета тестирования. Желающие пощупать, могут зайти на временный лэндинг L7 Admin Guard и попробовать, как работают живые демки, расширение уже в сторах.

Комментарии (17)


  1. HeOS
    23.04.2026 11:59

    Почему не использовали mutual TLS? Вроде проще и дешевле, чем вот такая развес стая система.


    1. zapimir Автор
      23.04.2026 11:59

      В основном из-за более сложного онбординга. Там творческие люди зачастую не могли найти куда они сохранили json-файл с конфигом, который прислали по телеге.
      Да и mTLS решает вопрос с авторизацией, но не решает вопрос с селективной маршрутизацией. Просто когда-то использовать VPN, пока пару раз не прилетел абуз, из-за того, что кто-то торрент качал при включенном VPN. Потому очень хотелось разделить трафик.


  1. Shaman_RSHU
    23.04.2026 11:59

    Странно, что в опросе отсутствует PAM (Privileged Access Managers)


    1. zapimir Автор
      23.04.2026 11:59

      Ну, PAM это уже можно сказать тяжелый энтерпрайз. И больше заточен на доступ к инфраструктуре (SSH, RDP, базы данных). Опять же, как и в случае mTLS более сложный онбоардинг, нужно ставить клиенты. А у нас всё же фокус был на простоте использования, как не техническим юзерам (редакторы, дизайнеры, аудиторы, фрилансеры и т.п.), так и чтобы для выдачи доступа не нужен был специально обученный человек.


  1. Granulex
    23.04.2026 11:59

    VPN для выдачи доступа редакторам – это как выдать ключ от всего офиса человеку, которому нужно только полить цветы. Удивительно, что стандартная связка nginx + openresty с JWT-авторизацией закрывает ту же задачу без написания своего прокси на Go – но зато своя реализация даёт нужную гибкость.


    1. zapimir Автор
      23.04.2026 11:59

      это как выдать ключ от всего офиса человеку

      Ну не всё так страшно, это просто была нода на Vultr с OpenVPN.
      Что касается nginx + openresty с JWT. Nginx как reverse прокси работает, для доступа к ресурсам на том же сервере нормально, но для доступа к условно рандомным доменам на других серверах не очень удобно. C JWT весьма проблемно сделать отзыв доступа.


      1. Granulex
        23.04.2026 11:59

        Согласен – пока один сервер. Но редакторский «доступ к ресурсам» обычно расширяется быстрее, чем ожидаешь :)))


  1. aytugan
    23.04.2026 11:59

    Огонь, молодцы что делаете нестандартные решения, но в тоже время без костылестроений.


  1. Dagor
    23.04.2026 11:59

    ну а маршруты в openvpn прописать конечно религия не позволяет, и acl на пользователей его тоже…


    1. zapimir Автор
      23.04.2026 11:59

      А как прописать маршруты на OpenVPN, если на одном сервере (т.е. один IP) десяток клиентов, как их разделись по SNI? Тут же дело не только в технической возможности, что-то сделать, а и в удобстве. Вы будете настраивать новый конфиг OpenVPN (и объяснять, что с ним делать) для фрилансера, которому нужно дать доступ на пару часов?


      1. Dagor
        23.04.2026 11:59

        Сервер просто пришлет пуш клиенту, с условными только 192.168.100.0/24 ко мне, остальное сам, клиенту присваивать статический ип внутри впн, на впн сервере ограничивать этой статике доступ необходимым сервером фаирволом. И никаких тебе абузов от прова и шляющихся по локалке удаленщиков.

        Все это настраивпется на сервере.


        1. zapimir Автор
          23.04.2026 11:59

          Не совсем понял, как это поможет с разными доменами на одном IP?


    1. Xelld
      23.04.2026 11:59

      Утверждение про весь трафик в VPN из статьи конечно такое себе, да.

      Тем не менее, проблема ZTNA для веб ресурсов за всякими балансировщиками или ingress есть: за одним IP могут находиться множество ресурсов. Рано или поздно сегментировать их станет больно и бессмысленно.


  1. Xelld
    23.04.2026 11:59

    Интересный способ, выглядит заманчиво.

    А что с аутентификацией?

    У вас есть roadmap? Планы на on-prem? Open source? :)


    1. zapimir Автор
      23.04.2026 11:59

      С аутентификацией, типа SSO пока не прикручивали (в личном кабинете уже есть Google, Github, Microsoft, Telegram). Токен можно сделать персональным (вплоть до того, что ограничить количество регистраций, даже если токен кому-то отдадут - то он не будет работать), также есть защищенные токены, где дополнительно мастер-пароль можно задать (конфиг зашифрован AES-GCM, расшифровка на клиенте).

      Что касается Roadmap, то как раз добавить SSO (поскольку мы при регистрации генерим dev_id, то SSO будет привязывать аутентификацию к dev_id). Более глобально в планах сделать ещё такую штуку, как TCP-over-HTTPS (MySQL, Redis, ClickHouse, SSH,...).
      Как это будет работать: Легкий Go-клиент на Wails с WebUI скачивает конфиги по токену и создает локальные алиасы (например, mysql.grafana). Вы подключаетесь к ним через любой софт (IDE, HeidiSQL, DBeaver) как к локалхосту, а клиент сам строит туннель через наш шлюз. Профит: Проксируем даже те программы, которые не умеют в прокси. Бонус - работа через 443 порт, плюс для текстовых протоколов (MySQL, Redis и т.п.) можно использовать Zstd сжатие. Мы даже реализовали Encrypted Client Hello (ECH), но браузеры пока его не поддерживают для прокси подключения.

      Про On-prem (Self-hosted)

      На самом деле, базовый On-prem у нас уже есть (в статье есть блок "Модели изоляции"). Мы используем популярную сейчас гибридную архитектуру (как у Tailscale или Cloudflare Tunnels):

      • Data Plane (Сам шлюз): Вы можете развернуть полностью на своих серверах (через тот самый curl | sh или автодеплой в личном кабинете). Весь ваш трафик пойдет только через ваши серверы, мы его не видим и не пропускаем через себя.

      • Control Plane (Управление): Админка, генерация токенов, API для расширения, оркестрация сертификатов - остаются на нашей SaaS-стороне.

      Делать 100% On-prem (когда клиент хостит у себя еще и наш бэкенд, UI, базу данных) мы пока не планируем.

      Про OpenSource

      Сейчас таких планов нет. Мы находимся на стадии валидации бизнес-модели и строим коммерческий SaaS. Сделать код открытым - это же не просто нажать кнопку "Make Public" на GitHub. Это огромная работа по написанию документации, разбору issue, ревью чужих пулл-реквестов и поддержке комьюнити. Сейчас мы физически направляем все ресурсы команды в разработку фич и стабильность.


      1. Xelld
        23.04.2026 11:59

        Спасибо за развернутый ответ!

        Делать 100% On-prem (когда клиент хостит у себя еще и наш бэкенд, UI, базу данных) мы пока не планируем.

        Ну... Я бы все же подумал об этом, тем более с вашими планами на TCP tunneling. Это уже ближе к Teleport или Boundary, такой софт чаще (по моему опыту) хотят контролировать и не зависеть от паблика.


        1. zapimir Автор
          23.04.2026 11:59

          Подумаем конечно, но пока главное запуститься :) А то интересные идеи приходят каждый день, а запуск всё откладывается.