DNSGateway + Cloudflare to issue Let’s Encrypt certificates with DNS-01 challenges
До сегодня я переживал за свои Cloudflare токены разбросанные по кучке машин. Благо не так давно я начал писать DNSGateway, позволяющий [более]гранулярно управлять правами на DNS зону, а сегодня закончил пееезд на него :)
Intro
Как и многие в последние годы я стараюсь всегда настраивать https для сервисов - будь-то домашний или публичный вебчик, будь-то консоль роутера или какой-нить ESPHome. Во-первых, потому что к этому всячески принуждают браузеры (ограничение фичей, страшные/бесячие предупреждения и т.д.). Во-вторых, потому что это стоит примерно ничего и по деньгам, и по времени. Примерно ничего, кроме проблем с безопасностью, все как мы любим :)
Но, наверное, для начала нужно дать немного заурядных классических вводных:
- NSами я живу в Cloudflare. Мне нравится их относительная независимость + скорость + поддерживаемое API
- Сертификаты мне нужны на пачке роутеров, нескольких ingress виртуалках, cert-manager’е k8s и бог знает где еще
- В качестве Certificate Authority, конечно же, использую Let’s Encrypt. Была мысль развернуть собственный, но здравый смысл победил эту идею
А коль у меня в качестве CA используется Let’s Encrypt, то и ACME DNS-01 челленджи идеально вписываются в общую картинку:
- поддерживаются вайлкарды
- не нужно мутить туннели/DNS View/прочие костыли для HTTP доступа со стороны Let’s Encrypt
- на текущий момент поддержаны если не везде, то почти везде
Не стану перепечатывать тут RFC, стоит разок прочесть в оригинале (хотя б кусочек ^_^):
Но в общем случае DNS-01 challenge выглядит так:
- клиент хочет пруфнуть владеление доменом
lol.cheburek
для запроса сертификата - ACME сервер выдает список челленджей
- клиент добавляет TXT запись
_acme-challenge.lol.cheburek
и сообщает серверу “я сделяль” - ACME сервер убеждается в корректности TXT записи и позволяет выписать сертификат
И тут становится очевидной проблема, ради которой я DNSGateway и затеял - при запросе сертификата должен быть доступ в Cloudflare DNS для добавления/удаления TXT записи _acme-challenge.<domain>
, что и пруфнет владение доменом. К сожалению, в бесплатном тарифе (на самом деле я хз, читал на каком-то форуме, что в платных это не так) Cloudflare умеет ограничивать API токены только до зоны целиком. Т.е., например, вы не можете создать API токен, который может управлять только TXT записями или отдельными поддоменами. А это означает, что токен с полноценным доступом в DNS зону лежит на рандомных тачках, что не внушает оптимизма.
Варианты решения
Фуф, когда проблематика ясна, можно перейти к вариантам решения. Оговорюсь, что это навряд ли полный перечень, скорее те варианты, которые я рассматривал для себя.
Варик для крохотной инфры: заиспользовать Throwaway Domain (по сути CNAME в треш-домен) + ACME-DNS
В целом, идея acme-dns мне оч понравилась. И, наверное, будь у меня один wildcard, я бы на этом и остановился. Но попробовав перетащить несколько машинок на acme-dns, я осознал, что подход с DNS Alias/Throwaway Domain требует сильно больше сил, чем я рассчитывал :/
Варик для смекалистых: auth proxy
Я представлял себе его так:
- мимикрируем под Cloudflare API
- парсим запрос + проверяем аутентификацию/авторизацию
- если все норм, то докинули API Token и отправили запрос в апстрим
Из очевидных плюсов - не нужно ничего патчить на клиенте.
Из минусов, которые погубили для меня эту идею:
- слишком багоопасно
- не всякий клиент позволяет конфигурить альтернативный апстрим
Варик не для ленивых: все сам, все сам
Чтож, коль acme-dns мне не подошел, а auth proxy больно попахивает, остается только засучить рукава и:
Так-то оно так, но писать плагины под все возможные клиенты я очень не хотел. Поэтому было решено двигаться дальше и искать возможность реализации какого-нить wellknown сервера.
Мой выбор: DNS Update (RFC2136)
И варик с wellknown сервером нашелся, им стал DNS Update (RFC2136):
- у него хорошая поддержка со стороны клиентов, например:
- будучи не молодым RFC, у него есть реализации серверной стороны. Например, от miekg@
Таким образом, мы собираем одни плюсы:
- править acme клиенты не нужно
- имеет из коробки аутентификацию/авторизацию поверх TSIG
- не приходится отказываться от Cloudflare, плодить зоны или править оную руками
- токен от Cloudflare хранится в одном месте
О реализции ниже, не теряйтесь :)
DNSGateway: описание
Сказано - сделано! Так я и написал для себя DNSGateway. Он же, кстати, обеспечивает мне и синк локальной зоны в AdGuard Home, что тоже повлияло на принятие решения о необходимости заиметь его ;)
Технически DNSGateway представляет из себя DNS Update (RFC2136) сервер, который:
- поддерживает TSIG аутентификацию/авторизацию с по-клиентным:
- разграничением доступа к той или иной зоне. Например, разрешает управлять только
foo.buglloc.cc
иbar.buglloc.cc
, но неbaz.buglloc.cc
- ограничением по типам записей. Например, позволяет создавать только TXT записи
- опциональной поддержкой XFR (для ACME не нужно, но полезно)
- разграничением доступа к той или иной зоне. Например, разрешает управлять только
- умеет использовать в качестве бэкенда Cloudflare или AdGuard Home
Процесс получения сертификата с DNSGateway не стал слишком сложнее. Для наглядности:
Если точнее, то когда один из acme-клиентов (роутер, cert-manager, etc) хочет получить сертификат он:
- получает DNS-01 челлендж
- добаляет его в DNSGateway, который:
- проверит аутентификацию/авторизацию для пары TSIG key + zone
- проверит можно ли клиенту создавать TXT записи
- сходит в Cloudflare, дабы добавить запись
- получает сертификат и удаляет TXT запись с помощью DNSGateway, который в свою очередь также сходит в Cloudflare
Звучит не плохо, ммм?
DNSGateway: примерчики
В качестве минимального примера будет получение сертификата с помощью certbot на bookworm. Для начала сконфигурим DNSGateway:
listener:
kind: rfc2136
rfc2136:
addr: :5454
clients:
- name: tst.
secret: NzBjOTU4OTVlOTZlOTg5OGQwYTUxYTdjNWYzNTI3NzA5YjIyZTIxNWVjOTc3NWMxNzIxZjdjN2ExNjliNDc1ZCAgLQo=
xfr_allowed: false
zones:
- example.buglloc.cc.
types:
- txt
upstream:
kind: cloudflare
cloudflare:
zone_id: c74b3a3b1002eba34d6cb7f8a62b69da
token: 3f786850e387550fdab836ed7e6dc881de23001b
Тут мы видим, что:
- у нас один клиент
tst.
- ему разрешено править TXT записи в зоне
example.buglloc.cc.
- в качестве апстрима, конечно же, используется Cloudflare
Теперь ставим все нужные пакетики на виртуалочку:
$ sudo apt install -y certbot python3-certbot-nginx python3-certbot-dns-rfc2136
Создаем конфиг для dns_rfc2136 плагина:
$ sudo mkdir -p /etc/certbot
$ sudo touch /etc/certbot/rfc2136.ini && sudo chmod 600 /etc/certbot/rfc2136.ini
$ sudo tee -a /etc/certbot/rfc2136.ini <<EOT
# Target DNS server (IPv4 or IPv6 address, not a hostname)
dns_rfc2136_server = 10.10.10.3
# Target DNS port
dns_rfc2136_port = 5454
# TSIG key name
dns_rfc2136_name = tst.
# TSIG key secret
dns_rfc2136_secret = NzBjOTU4OTVlOTZlOTg5OGQwYTUxYTdjNWYzNTI3NzA5YjIyZTIxNWVjOTc3NWMxNzIxZjdjN2ExNjliNDc1ZCAgLQo=
# TSIG key algorithm
dns_rfc2136_algorithm = HMAC-SHA256
# TSIG sign SOA query (optional, default: false)
dns_rfc2136_sign_query = true
EOT
И заказываем сертификат:
$ sudo certbot certonly --dns-rfc2136 --dns-rfc2136-credentials /etc/certbot/rfc2136.ini --dns-rfc2136-propagation-seconds 30 -d example.buglloc.cc -d '*.example.buglloc.cc'
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for example.buglloc.cc and *.example.buglloc.cc
Waiting 30 seconds for DNS changes to propagate
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.buglloc.cc/fullchain.pem
Key is saved at: /etc/letsencrypt/live/example.buglloc.cc/privkey.pem
This certificate expires on 2023-12-16.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
We were unable to subscribe you the EFF mailing list because your e-mail address appears to be invalid. You can try again later by visiting https://act.eff.org.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
* Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate
* Donating to EFF: https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Для визуалов:
На этом как бы и все, всем TLS :)
Closure
На всякий случах, хотел бы обратить внимание, что я бы пока не советовал завязываться на DNSGateway, потому что он находится на слишком раней стадии разработки. А если и завязываться, то быть готовому к поломкам и валенкам :)
BTW, таким же образом можно и полноценно распилить управление зоной. Мне просто это не нужно, т.к. она у меня на 100% под управлением ExternalDNS и я не страдаю. Но потенциал имеется ;)