fxTunnel

· Часть 6 серии «Пишу свой ngrok на Go: дневник разработки»

Пишу свой ngrok на Go: безопасность за один день

Но безопасность пока на уровне «авось никто не найдёт». Это не подход. Если сервис торчит в интернет — его найдут. Боты, сканеры, случайные люди.

Контекст

29 января, утро. fxTunnel работает, выглядит хорошо, пользователи подключаются. Но безопасность пока на уровне «авось никто не найдёт». Это не подход. Если сервис торчит в интернет — его найдут. Боты, сканеры, случайные люди. Вопрос не «если», а «когда». Сегодня закрываю все дыры. За один день. Чеклист из семи пунктов.

Чеклист безопасности

1. Обязательные секреты

Проблема: сервер мог запуститься с дефолтными JWT-секретами. Кто знает дефолт — может подделать токен авторизации. Решение: сервер отказывается стартовать, если jwt_secret или totp_key не заданы или слишком короткие. Никаких дефолтов в продакшене. Без этого: злоумышленник генерирует JWT с secret: "changeme" — и он валиден. Полный доступ к API.

2. Rate Limiting

Проблема: без ограничения скорости запросов можно подбирать пароли бесконечно или просто положить сервер потоком запросов. Решение: per-IP rate limiting на два уровня: - Глобальный — N запросов в минуту с одного IP на все эндпоинты - Auth — жёсткий лимит на логин и регистрацию

go
globalRL := newIPRateLimiter(cfg.Web.RateLimit.GlobalPerMin)
r.Use(rateLimitMiddleware(globalRL))

authRL := newIPRateLimiter(cfg.Web.RateLimit.AuthPerMin)
authRoutes.Use(rateLimitMiddleware(authRL))

Без этого: брутфорс паролей за минуты.

3. CORS

Проблема: CORS с wildcard * означает, что любой сайт может делать запросы к вашему API. Злоумышленник создаёт фишинговый сайт — и он работает с вашим бэкендом. Решение: заменил AllowedOrigins: ["*"] на конфигурируемый список доменов. Только ваш фронтенд может общаться с API.

4. Security-заголовки

Middleware, который добавляет заголовки на каждый ответ: - X-Content-Type-Options: nosniff — не угадывай MIME-тип - X-Frame-Options: DENY — не открывайся в iframe (защита от clickjacking) - Strict-Transport-Security — только HTTPS - Content-Security-Policy — что можно загружать, а что нет

5. IP Whitelist на токенах

Проблема: API-токен утёк (лог, скриншот, чужой компьютер). Кто угодно может создавать туннели. Решение: при создании токена можно указать список разрешённых IP. Токен работает только с этих адресов. Утёк — и бесполезен за пределами вашей сети.

6. Interstitial-страница

Проблема: туннели — мощный инструмент, в том числе для фишинга. Злоумышленник поднимает туннель, получает URL типа paypal-login.example.com и отправляет ссылку жертве. Решение: предупреждающая страница перед открытием туннеля: «Вы переходите на туннель, созданный пользователем X. Продолжить?». ngrok делает так же. Защищает не от хакера, а от обычного человека, который мог перейти по подозрительной ссылке.

7. Docker: не root

В Dockerfile создаём непривилегированного пользователя и запускаем от него. Если злоумышленник найдёт уязвимость — он НЕ root.

Грабли

Rate limiter и утечка памяти. Каждый IP создаёт запись. Без очистки за неделю сервер съест всю RAM (боты сканируют с тысяч IP). Фоновая горутина чистит записи каждые 5 минут. Interstitial и API. Предупреждение не должно показываться для API-запросов — только для браузеров. Определяю по Accept: text/html. Нет заголовка — пропускаю. Админы тоже bypass'ят — нет смысла показывать предупреждение владельцу. CORS и preflight. После замены wildcard сломались preflight-запросы (OPTIONS). Забыл добавить OPTIONS в allowed methods — весь фронтенд перестал работать. Классика.

Итог

29 января (утро) — семь коммитов, сервис защищён:

a7ca5e2 feat(security): require jwt_secret and totp_key
a0a5876 feat(security): add per-IP rate limiting
490d77f feat(security): replace wildcard CORS
bc6c3ca feat(security): add security response headers
a5bb9fc feat: add IP whitelist on API tokens
25e06c5 feat(server): add interstitial warning page
a2cbcf5 fix(docker): add healthcheck and run as non-root user

Всё это — базовый набор для любого сервиса в интернете. Не «потом добавим». Я опоздал на месяц — но лучше поздно.


В следующей части: тесты, гонки данных и паники в горутинах. Как go test -race находит баги, которые руками не поймать.

Все части дневника разработки