Контекст
29 января, утро. fxTunnel работает, выглядит хорошо, пользователи подключаются. Но безопасность пока на уровне «авось никто не найдёт». Это не подход. Если сервис торчит в интернет — его найдут. Боты, сканеры, случайные люди. Вопрос не «если», а «когда». Сегодня закрываю все дыры. За один день. Чеклист из семи пунктов.
Чеклист безопасности
1. Обязательные секреты
Проблема: сервер мог запуститься с дефолтными JWT-секретами. Кто знает дефолт — может подделать токен авторизации.
Решение: сервер отказывается стартовать, если jwt_secret или totp_key не заданы или слишком короткие. Никаких дефолтов в продакшене.
Без этого: злоумышленник генерирует JWT с secret: "changeme" — и он валиден. Полный доступ к API.
2. Rate Limiting
Проблема: без ограничения скорости запросов можно подбирать пароли бесконечно или просто положить сервер потоком запросов. Решение: per-IP rate limiting на два уровня: - Глобальный — N запросов в минуту с одного IP на все эндпоинты - Auth — жёсткий лимит на логин и регистрацию
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 находит баги, которые руками не поймать.