Как-то я болтала с приятелем о том, как было бы здорово, если бы был такой сайт-песочница, где бы можно было просто скопировать-вставить конфиг Nginx-а и протестировать его. И я поняла, что это не так уж сложно сделать, поэтому погрузилась в тему и сделала: nginx-playground.wizardzines.com.


screenshot


Просьба от переводчика: пожалуйста, не насильничайте рабочий сервис хитрыми директивами вроде создания миллиона воркеров — автор не кичится супер-защищённой средой запуска Nginx. Даже наоборот — взывает к приличному поведению, потому что некоторые уязвимости явно открыты. Вы запросто сможете положить сервис, если хотя бы раз видели доку по настройке Nginx, поэтому тут нет повода для гордости. С другой стороны, мысли об уязвимых местах (а возможно сразу и методах избавления от них) приветствуются в комментариях. Спасибо!

Далее поговорим о том, как тут всё устроено, потому что в процессе я столкнулась с некоторыми любопытными нюансами и проблемами с неочевидными решениями.


Как пользоваться


Нужно задать конфигурацию Nginx, а также написать curl или http команду, которая отправит HTTP-запрос этому сконфигурированному вами веб-серверу.


Вы жмёте "Run" вверху-справа и получаете ответ:


  • результат выполненной команды (если Nginx запустился нормально), либо
  • лог ошибок Nginx-а (если что-то пошло не так).

Зачем нужна эта песочница?


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


А Nginx чрезвычайно привередлив в настройке, и поэтому, я думаю, что уж для него-то точно должен быть такой инструмент.


Вот три других похожих проекта, которые я сделала ранее:


  • SQL-песочница — позволяет выполнять произвольные SQL-запросы на небольших данных к SQLite (использует sql.js).
  • CSS примеры — используя CodePen демонстрируют некоторые поразительные примеры стилизации с помощью CSS, с которыми вы можете поиграться.
  • DNS lookup — позволяет выполнять любые DNS-запросы и видеть ответы.

И несколько других замечательных песочниц, разработанных другими людьми:


  • CodePen — для CSS/JS/HTML.
  • RegExr — для регулярных выражений.
  • DB Fiddle — для SQL (MySQL, PostgreSQL и SQLite).
  • Nginx location match tester — имитирует алгоритм перебора директив location конфига Nginx (написан на TypeScript).

Делаем быстро, без усложнений


Сайт состоит из:


  1. статичного фронтенда (и применением Vue.js и Tailwind, моего типичного фронтендового набора);
  2. написанного на Go бэкенда с единственным API-методом, который делает лишь одно — запускает Nginx с заданным конфигом.

План прост, потому красив: всего-то нужно написать один API-метод, ну и ещё фронт, который его вызывает. Так же просто выполнен инструмент DNS lookup. Уж очень нравится мне такой подход, думаю, я и остальные проекты буду делать так же.


Давайте расскажу о том, что делает код на бэкенде, когда к нему поступает запрос с фронта.


Что происходит при поступлении запроса


Вот что делает Go-бэкенд, когда вы жмёте "Run" (вот вам сразу заметка с кодом):


  1. записываем конфиг во временный файл;
  2. создаём новый сетевой неймспейс (ip netns add $RANDOM_NAMESPACE_NAME);
  3. запускаем go-httpbin на порту 7777, чтобы люди могли использовать его в качестве следующего узла в своих конфигурациях Nginx (для отладки обратного прокси);
  4. запускаем Nginx;
  5. ждём 100 миллисекунд — позволяем Nginx-у запуститься, а если он не заработал, то возвращаем клиенту лог с ошибками;
  6. выполняем заданную пользователем команду (предварительно убеждаемся, что команда начинается с curl или http);
  7. возвращаем результат выполнения команды;
  8. PROFIT!

Вопросы о безопасности


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


Подобные нюансы часто останавливали меня на пути к запуску похожих проектов, но конкретно с этим всё должно было получиться.


Подход к безопасности: немного изоляции и YOLO (!)


Вот, к чему я пришла по поводу безопасности после обсуждения с другом:


  1. Нужно разместить фронтенд отдельно от бэкенда — где-то в CDN (если бэкенд и будет взломан, то хотя бы на фронте никто не разместит какой-нибудь малварь).
  2. Не использовать базу данных, только localStorage браузера (невозможно взломать БД, если её нет).
  3. Изолировать Nginx в собственном сетевом неймспейсе, запретить выходить в глобальную сеть.
  4. Использовать бесплатный план на fly.io: слабые сервера, зато изолированные виртуальные машины, которые я при желании могу убивать и пересоздавать хоть каждый час.
  5. Попросить людей быть лапочками в разделе FAQ (это и есть "YOLO" :) )

Самое плохое, что может случиться при всех этих ограничениях:


  • кто-то сможет получить доступ к чужому конфигу Nginx, если два человека пользуются сайтом одновременно;
  • подменит бэкенд так, что последующие клиенты будут получать всякие гадости в качестве ответа сервера вместо ожидаемого лога с ошибками или результата выполнения curl;
  • какой-нибудь хитрец попробует майнить биткоин на этом крошечном виртуальном сервере (1 shared CPU, 256 MB RAM).

Не думаю, что перечисленное выглядит так уж плохо в свете потенциальной пользы от сайта. Впрочем, возможно я упускаю что-то ещё более важное. Например, кто-то уже показал, что можно украсть содержимое файла /etc/passwd (было занятно, но в нём всё равно ничего важного не было).


Можно было бы запускать Nginx в контейнере (вместо изоляции только с помощью сетевого неймспейса), но это сильно ударит по производительности. И без этого работает не то чтобы очень быстро.


К слову о производительности.


Кое-что о производительности


Как упомянуто ранее, бэкенд работает на довольно слабом железе (1 shared CPU, 256 MB RAM). По этому поводу имею сказать следующее:


  1. Фронтенд расположен в CDN, поэтому нагрузка бэкенда начинается только тогда, когда пользователь нажимает кнопку "Run". Уже только это значительно облегчает серверу жизнь.
  2. Если верить логам, запросы занимают порядка 400 миллисекунд. Неплохо!
  3. Сейчас проект живёт на сервере, расположенном в Торонто. Полагаю, что для удалённых от Торонто пользователей всё происходит ещё медленнее. Так что можно развернуть больше таких маленьких серверов и в других уголках планеты.
  4. Я применила написанный на Go клон httpbin вместо оригинального (написанного на Python), потому что эта версия показалась более лёгкой и быстрой.
  5. Производительность на стороне фронтенда слегка хромает: стили и скрипты разделены на несколько файлов. Я не хотела собирать это всё командой npm build, потому что я не очень сильна в JavaScript, и поэтому переживала, что в случае возникновения проблем в скриптах это будет дополнительным препятствием перед исправлением ошибок и обновлениями — просто будет лень.
  6. Добавила маленькую гифку с летящей ракетой — её видит пользователь, пока выполняется запрос.

Самая глупая ошибка, связанная с производительностью, была вызвана тем, что для остановки Nginx я отправляла SIGKILL. Это останавливало только главный процесс, а воркер-процессы продолжали работать, и в какой-то момент в системе заканчивалась свободная память. Когда я переделала всё так, чтобы отправлялся сигнал SIGTERM, ситуация улучшилась.


Дизайн


Внешне повторяет дизайн JSFiddle и CodePen.


В частности, у JSFiddle есть кое-что занятное: они вычисляют высоту рабочей области как calc(100vh - 60px), а для заголовка установлена высота 60px. Я бы не догадалась сама, а работает это хорошо.


Я использую CodeMirror в качестве редактора с подсветкой синтаксиса, потому что именно его применяют JSFiddle и CodePen. Настроить его было достаточно просто. К тому же, там есть подсветка синтаксиса для конфигов Nginx и shell. Всё, о чём только можно мечтать :)


Была головная боль с этим CodeMirror только из-за того, что я собиралась использовать готовый пакет vue-codemirror, но были проблемы совместимости с Vue 3. Я решила, что в этом нет необходимости, и просто написала свою крошечную интеграцию, которая обновляет Vue при обновлении текстового поля.


Вы можете взглянуть на код в файле script.js, его там не много.


Что бы сделать ещё: больше примеров конфигов


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


Это было легче, чем казалось


Сделать этот инструмент было гораздо проще, чем я думала. И этот успех побуждает делать ещё больше подобных песочниц для других программ, хотя я и не выбрала следующую. HAProxy кажется хорошим кандидатом.

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


  1. alexxxst
    14.10.2021 23:22
    -1

    Ок.
    worker_processes 10000000;


    1. bBars Автор
      14.10.2021 23:35
      +2

      Да, это проблема. Думаю, я бы не успокоился, пока не сделал свой простенький парсер для обрезания таких директив перед стартом. Попробовал - сервис лёг на какое-то время, но спустя минуты 3 опять встал.

      Видимо, на этот счёт автор ограничился просьбой жить дружно в FAQ :)


      1. nochkin
        15.10.2021 04:00

        Или лучше сразу урезать ресурсы конкретного процесса или пользователя на уровне ОС. Иначе для "простенького парсера" всё предугадать достаточно сложно.


      1. Oxyd
        15.10.2021 04:45
        +1

        Или плодить процессы в отдельных cgroups с выставлением ограничений по количеству процессов, потребляемой памяти и этому вот всему.


  1. bBars Автор
    14.10.2021 23:27
    +1

    Проба пера в переводах. Понравилась тема, привлекла простота изложения и перевода - чем не повод опубликовать на Хабре? Суть вроде бы передал. Где-то есть дискуссионные моменты, а кое-чего вообще не понял: например, про "vue-codemirror ... интеграцию, которая обновляет Vue при обновлении текстового поля" и про "стили и скрипты разделены на несколько файлов".- тут не моя область, пожалуйста, поправьте, если знаете, что имелось в виду.

    Сейчас лично мне такая песочница без надобности, но ещё каких-то лет 5 назад я и мечтать о такой штуке не мог (даже в голову не приходило, если честно). Вдруг кому-то пригодится, а может кого-то подстегнёт запилить что-то своё.


    1. Loggus66
      14.10.2021 23:46

      У Yandex когда-то был проект gixy, который, честно говоря, распространения не увидел, но это интересный проект, который позволил поправить пару ошибок, со сбросом X-Frame-Options, ЕМНИП.

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


      1. bBars Автор
        14.10.2021 23:57
        +1

        Да, было бы здорово, если бы эта штука выдавала отладочные логи, а не только ошибки. Типа такого: error_log filename.log debug


        1. Stesh
          15.10.2021 12:56

          и nginx -t при перед стартом процесса.


      1. Smashrock
        15.10.2021 14:59

        Да я бы не сказал, что не увидел распространения. На многих зарубежных сайтах на него ссылаются, но да, сам проект не развивается с 2019, если не ошибаюсь. А штука реально классная.


  1. SilverFire
    15.10.2021 00:04

    Идея очень классная и реализация простая.

    За счёт netns есть изоляция по сети, но файловая система открыта и можно делать что-то типа

    curl file:///etc/passwd

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


    1. SilverFire
      15.10.2021 00:07
      +1

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


      1. bBars Автор
        15.10.2021 00:16

        Да, главная стратегия защиты от злоумышленников в том, что защиты почти нет, но и ценного ничего нет тоже.

        А про контейнеры автор писала, что мысли были, но производительность бы слишком пострадала. Хотя и мне кажется, что это нормальный способ. Сейчас вообще многое на каждый чих запускается в контейнерах или даже виртуалках. Но это ресурсоёмкая затея, а сервис этот попроще, обычный инди-проект


      1. myz0ne
        15.10.2021 00:39

        Оно и так в контейнере судя по содержимому на файловой системе (autoindex on). Просто каждый запуск не изолирован от других. Сходу как минимум пара идей на RCE (и потенциально с выход из докера, т.к. скорее всего есть нужные capabilities), но проверять лень. Тот же докерхаб вообще дает из коробке RCE (сборка докер образов) и никто в этом трагедии не видит.


      1. Smashrock
        15.10.2021 14:59

        Я просто использую systemd sandboxing - дешево, сердито и эффективно. Ну и AppArmor для надёжности.


  1. easyman
    15.10.2021 01:43

    Открываешь консоль gcloud и запускаешь прямо там что угодно. Прямо в этой сессии. Докер не тормозит


    1. bBars Автор
      15.10.2021 15:11

      Не соглашусь, что простота сравнима, вы пропустили несколько муторных телодвижений


  1. ertaquo
    15.10.2021 10:12
    +1

    Как вариант, можно парсить конфиг и проверять, нет ли чересчур больших значений для каких-нибудь параметров, которые могут уронить сервер. Есть пара библиотек для Go: [1], [2].
    Сам когда-то давно писал парсер конфигов, только на сях. Написано криво-косо, но работает до сих пор :)


  1. hzone
    15.10.2021 10:38
    +1

    нормальный конфиг nginx состоит из множества файлов.

    посему рекомендую допилить песочницу до использования инклюдов.

    в качестве инклюдов инклюдить не файлы, а табы, парся их в файлы, уже на стороне апи.


    1. bBars Автор
      15.10.2021 14:40

      А что вы выносите в отдельные файлы? Типа, отдельно ssl, отдельно gzip, отдельно каждый локейшн?


      1. Smashrock
        15.10.2021 15:02
        +1

        А вот как в этом генераторе - мне нравится, как он раскидывает конфиги. NGINXConfig | DigitalOcean


  1. dmitriym09
    15.10.2021 16:54
    -1

    А если собрать nginx webassembly и запускать его прямо у клиента в браузере в песочнице?