На сервере висит куча HTTP-сервисов. Одни внешние, другие внутренние — доступные только через VPN. Как настроить для них TLS, чтобы не мучиться с самоподписанными сертификатами?
Вариант первый — взять домен .internal. Создаём A-запись grafana.tuxnet.internal на IP 10.0.1.10. TLS? Нужен самоподписанный сертификат. И каждый клиент вручную настраивать под него. Либо учить пользователей игнорировать ошибки TLS. И то и другое — боль.
Вариант второй — «правильный»: split-horizon DNS. Публичный домен (tuxnet.dev) для внешних DNS резолвится в публичный IP. А для клиентов внутри VPN — в 10.0.1.10. Раз адрес публичный, можно спокойно использовать Let’s Encrypt или ZeroSSL. Правда, нужен WAF, который режет трафик, идущий не из VPN. Но настроить WAF в одном месте (на сервере) проще, чем пихать сертификат на каждую машину в сети.
Автор использует NetBird (с фичей Custom Zones) — он сам разруливает split-horizon. ACME-клиент — acme.sh. В качестве WAF — nginx.
Сертификат получаем через acme.sh в standalone-режиме. Nginx не обязан даже слушать 80-й порт — acme.sh сам поднимает socat на время получения.
Главный трюк в конфиге nginx — listen our-server.netbird.cloud:443 ssl;. Вместо домена можно указать VPN-IP. Так nginx привязывается только к VPN-интерфейсу. Весь трафик к grafana.tuxnet.dev из публичного интернета просто отвалится — это и есть WAF. Первый слой защиты — split-horizon DNS. Второй — сам WAF.
Для автообновления сертификатов — cron с acme.sh --cron. В скрипте проверяются контрольные суммы сертификатов, если не совпали — копируются в папку nginx, и сервис перезагружается. acme.sh запускается не от root — для привязки к 80-му порту используется setcap CAP_NET_BIND_SERVICE=+ep /usr/bin/socat1.
Бонус — если сервисов много, не нужно плодить сертификаты. Достаточно одного с SAN (Subject Alternative Name). Создаём A-запись для internal.tuxnet.dev, CNAME для grafana.tuxnet.dev и analytics.tuxnet.dev на неё. Один сертификат сразу на три домена — и готово.