Доменные имена с валидным SSL для локальных Docker-контейнеров
Запускать приложение со всеми его зависимостями, используя всего одну команду — становится всё более и более привычным действием. Использование Docker в процессе разработки стало уже стандартом де-факто. Если приложение предоставляет доступ используя web-интерфейс или какое-либо HTTP API — скорее всего "фронтовой" контейнер пробрасывает свой, уникальный (среди остальных приложений, разработкой которых вы занимаетесь параллельно) порт в хост, постучавшись на который мы можем взаимодействовать с приложением в контейнере.
И это отлично работает, пока у вас не появляется целый зоопарк приложений, переключение между которыми начинает вызывать некоторые неудобства, так как надо помнить и схему, и порт, и где-то фиксировать какие порты для какого приложения вы когда-то выделили, дабы не возникло коллизии со временем.
А потом ещё тебе хочется проверить работу по https — и приходится либо использовать свой корневой сертификат, либо всегда использовать curl --insecure ...
, а когда над приложениями работают различные команды — количество запар начинает возрастать в геометрической прогрессии.
Для нетерпеливых, традиционно — ссылочка. Столкнувшись с такой проблемой в очередной раз — в голове промелькнула мысль "Хватит это терпеть!", и результатом работы на паре выходных стал сервис, который решает эту проблему на корню, о чем будет рассказано ниже.
Мир Нас спасёт реверс-прокси
0. По-хорошему, нам нужна какая-то доменная зона, все под-домены из которой всегда будут резольвить локалхост (127. 1). 0. Повозившись со своим корневым сертификатом удалось завести curl
без ошибок, но не все браузеры корректно принимали его, и продолжали вываливать ошибку. Беглые поиски навели на домены вида *.localho.st
, *.lvh.me
, *.vcap.me
и другие, но как к ним прикрутить валидный SSL сертификат? Кроме того — крайне не хотелось "возни" с SSL.
0. "Чтож, зайдём с другой стороны!" — и тут же был приобретен домен с именем localhost.tools
, делегирован на CloudFlare, настроен требуемый резольвинг (все под-домены резольвят 127. 1
): 0.
$ dig foo.localhost.tools | grep -v '^;\|^$' foo.localhost.tools. 190 IN A 127.0.0.1
После этого был запущен certbot в контейнере, который на входе получая API KEY от CF при помощи DNS записи подтверждает право владения доменом, и выдаёт валидный SSL сертификат на выходе:
$ docker run \ --entrypoint="" \ -v "$(pwd)/cf-config.conf:/cf-credentials:ro" \ -v "$(pwd)/cert:/out:rw" \ -v "/etc/passwd:/etc/passwd:ro" \ -v "/etc/group:/etc/group:ro" \ certbot/dns-cloudflare:latest sh -c \ "certbot certonly \ --dns-cloudflare \ --dns-cloudflare-credentials '/cf-credentials' \ -d '*.localhost.tools' \ --non-interactive \ --agree-tos \ --email '$CF_EMAIL' \ --server 'https://acme-v02.api.letsencrypt.org/directory' \ && cp -f /etc/letsencrypt/live/localhost.tools/* /out \ && chown '$(id -u):$(id -g)' /out/*"
Файл
./cf-config.conf
содержит данные авторизации на CF, подробнее можно прочитать в документации по certbot,$CF_EMAIL
— переменная окружения с твоим email
Остается как-то научиться проксировать все запросы, что приходят на локалхост в нужный контейнер. Ок, теперь у нас есть и валидный SSL сертификат (пускай и на 3 месяца, и только для под-доменов одного уровня).
Запустив его локально, пробросив в его контейнер через volume докер-сокет — он умеет проксировать запросы в тот контейнер, у которого есть необходимый docker label
. И тут нам на помощь приходит Traefik (спойлер — он прекрасен). Таким образом, нам нет необходимости в какой-либо дополнительной конфигурации, кроме как при запуске указать нужный label у контейнера (и docker сеть, но при запуске без docker-compose даже это не обязательно, хотя и очень желательно), к которому мы хотим получить доступ по доменному имени и с валидным SSL!
Проделав весь этот путь свет увидел докер-контейнер с этим самым пред-настроенным Traefik-ом и wildcard SSL сертификатом (да, он публичный).
Приватный ключ от SSL в публичном контейнере?
MitM в этом случае не имеет особого смысла в принципе. Да, но я считаю что это не страшно, так как он на доменную зону, которая всегда резольвит локалхост.
Что делать когда сертификат протухнет?
У проекта настроен CI, который в автоматическом режиме, раз в месяц (на данный момент) обновляет сертификат и публикует свежий образ. Просто стянуть свежий образ, перезапустив контейнер.
Хочу попробовать!
Первым делом, убедись что локальные порты 80
и 443
у тебя свободны, и выполни: Нет ничего проще.
# Создаём docker-сеть для нашего реверс-прокси
$ docker network create localhost-tools-network # Запускаем сам реверс-прокси
$ docker run -d --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ --network localhost-tools-network \ --name localhost.tools \ -p 80:80 -p 443:443 \ tarampampam/localhost # Запускаем nginx, говоря ему откликаться на "my-nginx.localhost.tools"
$ docker run -d --rm \ --network localhost-tools-network \ --label "traefik.frontend.rule=Host:my-nginx.localhost.tools" \ --label "traefik.port=80" \ nginx:latest
И теперь можем потестировать:
$ curl -sS http://my-nginx.localhost.tools | grep Welcome
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1> $ curl -sS https://my-nginx.localhost.tools | grep Welcome
<title>Welcome to nginx!</title>
<h1>Welcome to nginx!</h1>
Как видим — работает 🙂
Где живёт документация, описание?
Более того, морда — отзывчивая, и умеет смотреть запущен ли у тебя локально демон реверс-прокси, и выводить список запущенных и доступных для взаимодействия (если таковые имеются) контейнеров. Всё, как не сложно догадаться, живёт по адресу https://localhost.tools.
Сколько стоит?
Совсем. Нисколько. Более того — денег стоит только доменное имя, всё остальное — юзается без необходимости в оплате. Сделав данную штуку для себя и своей команды пришло понимание того, что она может пригодиться и другим разработчикам/ops-ам.
S. P. — просто черканите в личку. Сервис ещё в beta, посему — если будут найдены какие-либо недочёты, опечатки и т.д. Хабы "Программирование" и "Разработка веб-сайтов" указаны по причине того, что данный подход может быть полезен в первую очередь в этих отраслях.