fxTunnel

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

Пишу свой ngrok на Go: кастомные домены и автоматический TLS

У fxTunnel есть поддомены — myapp.tunnel.example.com. Работает. Но что если я хочу показать заказчику проект на demo.mycompany.com?

Контекст

30 января, вечер. У fxTunnel есть поддомены — myapp.tunnel.example.com. Работает. Но что если я хочу показать заказчику проект на demo.mycompany.com? Своём домене, а не чужом поддомене. Это совершенно другой уровень. Кастомные домены — фича, которая превращает self-hosted туннель в полноценную платформу.

Как это работает

Идея простая, реализация — не очень: 1. Пользователь добавляет свой домен в панели (например, app.mycompany.com) 2. Настраивает CNAME-запись, указывающую на сервер fxTunnel 3. fxTunnel проверяет DNS — убеждается, что домен действительно указывает на нас 4. Автоматически получает TLS-сертификат от Let's Encrypt 5. Трафик на app.mycompany.com маршрутизируется в нужный туннель Для пользователя: добавил домен, подождал минуту, работает. HTTPS из коробки, сертификат обновляется сам.

DNS-верификация

Прежде чем принять домен, нужно убедиться, что он действительно принадлежит пользователю. Проверяем DNS-записи: - Для поддоменов (3+ уровня, типа app.mycompany.com) — проверяем CNAME. Должен указывать на наш сервер. - Для apex-доменов (2 уровня, типа mycompany.com) — CNAME нельзя ставить на apex по стандарту DNS. Проверяем A/AAAA-записи — IP должен совпадать с нашим сервером. Это различие (apex vs subdomain) — частый источник путаницы. Многие пользователи не знают, что CNAME на корневом домене — это нарушение RFC. Поэтому в UI я добавил пошаговую инструкцию: для поддомена — CNAME, для apex — A-запись с IP сервера.

TLS: Let's Encrypt через ACME

Сертификаты получаем автоматически через ACME-протокол (тот же, что использует Let's Encrypt). Go-библиотека autocert делает почти всё сама: 1. Приходит первый запрос на домен 2. autocert видит, что сертификата нет 3. Запрашивает сертификат у Let's Encrypt (HTTP-01 или TLS-ALPN-01 challenge) 4. Let's Encrypt проверяет, что домен указывает на наш сервер 5. Выдаёт сертификат, autocert кэширует его Сертификаты хранятся в базе данных (не на диске) — это важно для будущего масштабирования. Отдельный HTTPS-listener на порту 443 обслуживает кастомные домены.

Что пришлось написать

Фича затронула 30 файлов, 2346 строк — одна из самых крупных в проекте: - Бэкенд: конфиг, модели БД, DNS-верификация, cert manager, API-хэндлеры (пользовательские + админские), интеграция с HTTP-роутером - Веб-панель: UI для добавления доменов, пошаговая инструкция DNS, статус верификации, админка - GUI-клиент: Wails-сервис, Pinia-стор, обновлённый экран доменов

Грабли

ACME fallback. Первая версия искала сертификат в БД — нет записи, значит ошибка. Но при первом запросе записи ещё нет — нужно дать autocert шанс получить сертификат. Добавил fallback: нет в кэше → пробуем ACME on-demand → сохраняем результат. TLS-ALPN-01 challenge. Для HTTP-01 challenge Let's Encrypt делает запрос на порт 80. Но у меня порт 80 занят HTTP-туннелями. TLS-ALPN-01 работает через порт 443 — как раз где висит HTTPS-listener. Добавил acme-tls/1 в NextProtos конфигурации TLS. Apex-домены. Первая версия поддерживала только CNAME (поддомены). Пользователи хотели привязать корневой домен — пришлось добавить проверку A/AAAA-записей и инструкцию с IP сервера.

Итог

30 января (вечер): - Кастомные домены с CNAME и A-записями - Автоматический TLS через Let's Encrypt - DNS-верификация с пошаговой инструкцией в UI - Apex-домены через A-записи - Админка для управления доменами всех пользователей

8288168 feat: add custom domains with CNAME binding and TLS certificates
cac4614 feat(custom-domains): support apex domains via A record
3747c90 fix(tls): fall back to ACME on-demand when cached cert is missing

Теперь пользователь может показать проект на своём домене. С HTTPS. Бесплатно.


В следующей части: крупный рефакторинг — как разбить монолитный server.go на части и не сломать всё.

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