Как-то я болтала с приятелем о том, как было бы здорово, если бы был такой сайт-песочница, где бы можно было просто скопировать-вставить конфиг Nginx-а и протестировать его. И я поняла, что это не так уж сложно сделать, поэтому погрузилась в тему и сделала: nginx-playground.wizardzines.com.
Просьба от переводчика: пожалуйста, не насильничайте рабочий сервис хитрыми директивами вроде создания миллиона воркеров — автор не кичится супер-защищённой средой запуска 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).
Делаем быстро, без усложнений
Сайт состоит из:
- статичного фронтенда (и применением Vue.js и Tailwind, моего типичного фронтендового набора);
- написанного на Go бэкенда с единственным API-методом, который делает лишь одно — запускает Nginx с заданным конфигом.
План прост, потому красив: всего-то нужно написать один API-метод, ну и ещё фронт, который его вызывает. Так же просто выполнен инструмент DNS lookup. Уж очень нравится мне такой подход, думаю, я и остальные проекты буду делать так же.
Давайте расскажу о том, что делает код на бэкенде, когда к нему поступает запрос с фронта.
Что происходит при поступлении запроса
Вот что делает Go-бэкенд, когда вы жмёте "Run" (вот вам сразу заметка с кодом):
- записываем конфиг во временный файл;
- создаём новый сетевой неймспейс (
ip netns add $RANDOM_NAMESPACE_NAME
); - запускаем go-httpbin на порту 7777, чтобы люди могли использовать его в качестве следующего узла в своих конфигурациях Nginx (для отладки обратного прокси);
- запускаем Nginx;
- ждём 100 миллисекунд — позволяем Nginx-у запуститься, а если он не заработал, то возвращаем клиенту лог с ошибками;
- выполняем заданную пользователем команду (предварительно убеждаемся, что команда начинается с
curl
илиhttp
); - возвращаем результат выполнения команды;
- PROFIT!
Вопросы о безопасности
Основная идея инструмента состоит в том, чтобы дать пользователю возможность пробовать совершенно любые конфигурации Nginx. На фоне этого легко представить, сколько всего нехорошего можно натворить на сервере. Я хотела сделать инструмент бесплатным для пользователей, при этом не слишком тратиться на хостинг. В идеале — обойтись одним сервером для обслуживания всех запросов.
Подобные нюансы часто останавливали меня на пути к запуску похожих проектов, но конкретно с этим всё должно было получиться.
Подход к безопасности: немного изоляции и YOLO (!)
Вот, к чему я пришла по поводу безопасности после обсуждения с другом:
- Нужно разместить фронтенд отдельно от бэкенда — где-то в CDN (если бэкенд и будет взломан, то хотя бы на фронте никто не разместит какой-нибудь малварь).
- Не использовать базу данных, только localStorage браузера (невозможно взломать БД, если её нет).
- Изолировать Nginx в собственном сетевом неймспейсе, запретить выходить в глобальную сеть.
- Использовать бесплатный план на fly.io: слабые сервера, зато изолированные виртуальные машины, которые я при желании могу убивать и пересоздавать хоть каждый час.
- Попросить людей быть лапочками в разделе FAQ (это и есть "YOLO" :) )
Самое плохое, что может случиться при всех этих ограничениях:
- кто-то сможет получить доступ к чужому конфигу Nginx, если два человека пользуются сайтом одновременно;
- подменит бэкенд так, что последующие клиенты будут получать всякие гадости в качестве ответа сервера вместо ожидаемого лога с ошибками или результата выполнения curl;
- какой-нибудь хитрец попробует майнить биткоин на этом крошечном виртуальном сервере (1 shared CPU, 256 MB RAM).
Не думаю, что перечисленное выглядит так уж плохо в свете потенциальной пользы от сайта. Впрочем, возможно я упускаю что-то ещё более важное. Например, кто-то уже показал, что можно украсть содержимое файла /etc/passwd
(было занятно, но в нём всё равно ничего важного не было).
Можно было бы запускать Nginx в контейнере (вместо изоляции только с помощью сетевого неймспейса), но это сильно ударит по производительности. И без этого работает не то чтобы очень быстро.
К слову о производительности.
Кое-что о производительности
Как упомянуто ранее, бэкенд работает на довольно слабом железе (1 shared CPU, 256 MB RAM). По этому поводу имею сказать следующее:
- Фронтенд расположен в CDN, поэтому нагрузка бэкенда начинается только тогда, когда пользователь нажимает кнопку "Run". Уже только это значительно облегчает серверу жизнь.
- Если верить логам, запросы занимают порядка 400 миллисекунд. Неплохо!
- Сейчас проект живёт на сервере, расположенном в Торонто. Полагаю, что для удалённых от Торонто пользователей всё происходит ещё медленнее. Так что можно развернуть больше таких маленьких серверов и в других уголках планеты.
- Я применила написанный на Go клон httpbin вместо оригинального (написанного на Python), потому что эта версия показалась более лёгкой и быстрой.
- Производительность на стороне фронтенда слегка хромает: стили и скрипты разделены на несколько файлов. Я не хотела собирать это всё командой
npm build
, потому что я не очень сильна в JavaScript, и поэтому переживала, что в случае возникновения проблем в скриптах это будет дополнительным препятствием перед исправлением ошибок и обновлениями — просто будет лень. - Добавила маленькую гифку с летящей ракетой — её видит пользователь, пока выполняется запрос.
Самая глупая ошибка, связанная с производительностью, была вызвана тем, что для остановки Nginx я отправляла SIGKILL
. Это останавливало только главный процесс, а воркер-процессы продолжали работать, и в какой-то момент в системе заканчивалась свободная память. Когда я переделала всё так, чтобы отправлялся сигнал SIGTERM
, ситуация улучшилась.
Дизайн
Внешне повторяет дизайн JSFiddle и CodePen.
В частности, у JSFiddle есть кое-что занятное: они вычисляют высоту рабочей области как calc(100vh - 60px)
, а для заголовка установлена высота 60px
. Я бы не догадалась сама, а работает это хорошо.
Я использую CodeMirror в качестве редактора с подсветкой синтаксиса, потому что именно его применяют JSFiddle и CodePen. Настроить его было достаточно просто. К тому же, там есть подсветка синтаксиса для конфигов Nginx и shell. Всё, о чём только можно мечтать :)
Была головная боль с этим CodeMirror только из-за того, что я собиралась использовать готовый пакет vue-codemirror
, но были проблемы совместимости с Vue 3. Я решила, что в этом нет необходимости, и просто написала свою крошечную интеграцию, которая обновляет Vue при обновлении текстового поля.
Вы можете взглянуть на код в файле script.js, его там не много.
Что бы сделать ещё: больше примеров конфигов
Я до сих пор не понимаю, какими именно должны быть эти примеры конфигурации, но не лишним было бы добавить парочку разных типичных конфигов в качестве стартовых.
Это было легче, чем казалось
Сделать этот инструмент было гораздо проще, чем я думала. И этот успех побуждает делать ещё больше подобных песочниц для других программ, хотя я и не выбрала следующую. HAProxy кажется хорошим кандидатом.