Заводим Caddy на DigitalOcean с Let's Encrypt

Dec 12, 2020 13:02 · 912 words · 5 minute read tutorial админство

За свои 15 лет в IT я перепробовал разные веб-серверы. Сначала это был Apache (куда же без него), потом перешёл на более лёгкий nginx (а как меня однажды OpenResty спас!). Поразвлекался с lighttpd когда держал дома FreeNAS, даже заводил pet-project под YAWS. И вот в одном из репозиториев увидел новый для себя Caddy. Поспрашивал в местных чатиках кто его использует, и меня убедили, что он уже вполне production-ready. Более того - настраивать ничего не надо, сертификаты сам обновляет, быстрый, всего один бинарник! Прям чудо, а не веб-сервер, так что решил взять его для очередного хобби проекта.

Настройка среды разработки для Go

Проект размещается на DO в одном контейнере. Приложение пока выглядит как docker-compose.yaml с двумя образами, так что Caddy нужен исключительно как reverse-proxy и управления сертификатами.

Нужен туториал. Первой же ссылкой в Google вышла статья на DigitalOcean, где вроде бы делалось всё, что мне надо. Изначально меня смутило, что надо ставить окружение для Go. Хотелось бы какого-нибудь apt install caddy2, но дальше я убедился, что настроить среду разарботки всё-таки необходимо. Проблема в том, что плагин для работы с DO можно подключить только через пересборку Caddy. Так что скачиваем, распаковываем, копируем сам Go:

$ curl -O https://dl.google.com/go/go1.15.6.linux-amd64.tar.gz
$ sudo tar -xvf go1.15.6.linux-amd64.tar.gz -C /usr/local
$ sudo chown -R root:root /usr/local/go

Создаём родительский каталог для всего, что связано с go:

$ mkdir -p $HOME/go/{bin,src}

Добавляем переменные окружения в .profile (если будете добавлять руками, не забудьте снять экранирование):

$ echo "export GOPATH=$HOME/go" >> ~/.profile
$ echo "export PATH=\"\$PATH:\$GOPATH/bin:/usr/local/go/bin\"" >> ~/.profile
$ . ~/.profile

Установка Caddy

Всё, Go поставили! Можно собирать Caddy. В статье было предложено подключать плагины через импорт, но! В этом случае сталкнётесь с ошибкой, что некоторые плагины не совместимы: panic: qtls.ClientSessionState not compatible with tls.ClientSessionState. Разработчики Caddy рекомендуют использовать xcaddy для пересборки. На деле вышло действительно удобнее. Всего одна команда ./xcaddy build master --with github.com/caddy-dns/digitalocean соберёт в текущем каталоге бинарник caddy, который надо лишь правильно положить в систему. Обратите внимание, что для плагина DigitalOcean я использовал не github.com/caddyserver/dnsproviders/digitalocean, который указан в статье. Всё потому, что его там уже нет. Итак, устанавливаем xcaddy:

$ curl -O https://github.com/caddyserver/xcaddy/releases/download/v0.1.6/xcaddy_0.1.6_linux_amd64.tar.gz
$ sudo tar -xvf xcaddy_0.1.6_linux_amd64.tar.gz -C /usr/local/bin

Копируем бинарник:

$ sudo mv $GOPATH/bin/caddy /usr/local/bin/
$ sudo chown root:root /usr/local/bin/caddy
$ sudo chmod 755 /usr/local/bin/caddy

Создаём каталог с настройками:

$ sudo mkdir /etc/caddy
$ sudo chown -R root:www-data /etc/caddy
$ sudo mkdir /etc/ssl/caddy
$ sudo chown -R root:www-data /etc/ssl/caddy
$ sudo chmod 0770 /etc/ssl/caddy

Подбрасываем конфиг в /etc/caddy/Caddyfile:

numi.site {
  reverse_proxy /* 127.0.0.1:8080
}

Добавляем сервис для systemd:

$ sudo sh -c 'curl https://raw.githubusercontent.com/caddyserver/dist/master/init/caddy.service > /etc/systemd/system/caddy.service'
$ sudo chmod 644 /etc/systemd/system/caddy.service
$ sudo systemctl daemon-reload
$ sudo systemctl status caddy

На этом шаге caddy должен успешно завестись, судя по статусу. Если что-то пошло не так - пишите, разберёмся. Логи можно посмотреть также через journalctl -u caddy.service.

Подключаем сертификаты Let’s Encrypt

Сначала немного необходимой теории. letsencrypt.org позволяет в автоматическом режиме обновлять сертификаты для вашего сайта. Для этого даже был разработан и утверждён протокол ACME, и существует много программ, которые его поддерживают (в том числе и Caddy). Самый известный, наверно, Certbot, который наиболее универсален и не привязан к языку программирования или веб-серверу. Для выпуска сертификата необходимо подтвердить владение доменом. Есть минимум 2 способа:

  • создать по специальному URL страницу с кодом, который скажет LE
  • создать DNS запись с определённым значением

Второй - наиболее универсален, т.к. позволяет получать сертификаты для поддоменов, но и более сложный, т.к. необходимо программно создавать DNS запись. К счастью, у всех крупных DNS-провайдеров есть API. К слову, для Caddy это единственный вариант, т.к. он не начнёт обрабатывать 80/443 порты, пока не будет настроен сертификат. И нет, свой подбросить вы не можете.

Далее начинается самое веселье. У Caddy есть плагин для работы с API DigitalOcean. Получить токен можно по короткой инструкции. Я предпочитаю его хранить не в конфигурационном файле, а в переменных окружения процесса. Для этого в /etc/systemd/system/caddy.service добавим строку в раздел Service

Environment=CADDYPATH=/etc/ssl/caddy DO_AUTH_TOKEN=<token>

Теперь в самом конфиге можно добавить раздел для установки tls сертификата `/etc/caddy/Caddyfile:

numi.site {
  reverse_proxy /* 127.0.0.1:8080

  tls {
    issuer acme {
      dir https://acme-v02.api.letsencrypt.org/directory
      resolvers 173.245.58.51:53
      dns digitalocean {env.DO_AUTH_TOKEN}
    }
  }
}

Внимание! Обязательно укажите resolvers, где хостятся ваши DNS-записи. В противном случае Let’s Encrypt будет опрашивать какой-нибудь корневой, а до него изменения не успеют дойти за таймаут запроса.

Запустите Caddy и посмотрите какую TXT запись он создаёт. Если она выглядит как _acme-challenge.example.org.example.org - всё в порядке, баг в плагине DigitalOcean ещё не исправлен. К счастью, добрый человек уже предложил рабочее решение, осталось им воспользоваться. Склонируем этот репозиторий и соберём Caddy с форком где есть исправление:

$ cd ~
$ git clone https://github.com/delthas/digitalocean.git
$ cd digitalocean
$ git checkout fix-record-name
$ xcaddy build --with github.com/caddy-dns/digitalocean --with github.com/libdns/digitalocean=~/digitalocean

После этого заменяем бинарник в /usr/local/bin/ и перезапускаем сервис (sudo systemctl daemon-reload && systemctl restart caddy). После этих действий должна создасться правильная TXT запись, сертификат должен подключиться, и сайт заработать на 80 и 443 портах. Но у меня оставались артефакты от прошлых проверок и экспериментов в каталоге /var/lib/caddy/.local/, пришлось их почистить. Вышел я на них через подробные debug логи. Кстати, чтобы включить в Caddy такой режим, надо в конфиге в глобальной секции дописать:

{
  debug
}

numi.site {
    ....
}

Субъективщина

Несмотря на то, что разработчиками была заявлена “лёгкость в установке и настройке”, я потратил на это полдня. Попутно изучив кучу ишью, натыкаясь на отсутствующие репозитории, разбираясь с go и т.п. Однако, инструмент мне понравился своей динамической настройкой (можно пушить конфиги как json файлы через POST запрос) и минимализмом конфигурационного файла. Мне кажется, он отлично зайдёт в микросервисном проекте, а также где везде, где есть service discovery.