Обычно внутри корпоративной сети нынче полно всяких приложений, и хотелось бы чтобы они работали по SSL. Можно, конечно, поднять свой УЦ, раздать сертификаты, прописать пользователям свой корневой сертификат - и это будет работать. А можно просто воспользоваться сервисом Let's Encrypt, раздав его сертификаты своим внутренним серверам, которые не видны из Интернета, причем сделать это просто и с минимумом трудозатрат на поддержку.
Необходимые условия
Наличие зарегистрированного домена, допустим, mycompany.ru и адресация внутренних серверов в нем.
Использование split-DNS, т.е. разрешение доменных имен mycompany.ru в разные IP адреса для внутренней сети и для всего Интернета. Делается либо через механизм View сервера BIND либо просто путем использования разных DNS серверов.
Внутренние сервера имеют доступ в Интернет по HTTPS - через маскирующий/NAT шлюз, файрвол, прокси, как угодно. Впрочем, это можно преодолеть, просто эту статью придется дочитать до конца, а поработать чуть больше.
Как это работает
Логика работы сервиса Let's Encrypt:
Сервер-претендент обращается к API Let's Encrypt по HTTPS, сообщает ему домены, для которых нужен сертификат. Например, server.mycompany.ru.
В ответ сервис формирует некий код, который должен быть размещен по запрошенному доменному адресу чтобы подтвердить принадлежность домена. Есть варианты: размещение файла с кодом на веб-сервере или добавление в DNS домена определенной записи. Мы будем использовать файл на веб-сервере.
"Проверялка" Let's Encrypt лезет простым GET запросом на адрес http://server.mycompany.ru/.well-known/acme-challenge/{а здесь сам код}, и если получает ожидаемый ответ - то считает, что домен проверен.
Let's Encrypt выпускает сертификат и предоставляет его серверу.
На самом деле все немного сложнее, но нам детали и не потребуются.
Что мы делаем и зачем
Нам понадобится один прокси-сервер (nginx) и правильная настройка DNS. Больше, удивительным образом, ничего. Прокси-сервер должен быть доступен из Интернет и иметь доступ во внутреннюю сеть (dual-homed).
Полагаем, что все наши сервера во внутреннем DNS прописаны по именам. Во внешнем DNS, в зоне mycompany.ru задаем имя для внешнего адреса прокси-сервера, пусть будет proxy. И там же для каждого сервера из внутренней сети делаем запись CNAME, указывающую на proxy.
Конфигурируем nginx, общая конфигурация самая обычная, нужные нам блоки server в контексте http выглядит так:
server {
listen 80;
server_name .mycompany.ru; # Принимаем любые имена в домене
location /.well-known {
if ($request_method != GET) { # Разрешаем только метод GET
return 444; # иначе - сбрасываем TCP соединение
}
resolver 10.0.0.2 10.0.1.2; # Обязательно ипользуем внутренние DNS
proxy_pass http://$host; # проксируется на сервер внутрь сети с тем же именем
}
location / { # Все остальные запросы -
return 444; # просто сбрасываем TCP соединение
log_not_found off; # и ничего не пишем в лог
access_log off;
}
}
server { # На все запросы по IP адресу без домена
listen 80 default_server; # отвечаем сбросом TCP соединения
server_name _;
return 444;
log_not_found off;
access_log off;
}
Кстати, если у вас уже есть dual-homed сервер с nginx для каких-то других задач, то эту конфигурацию можно просто добавить к нему, она не будет мешать обслуживанию других серверов даже с именами в том же домене.
Работает это очень просто:
Наш внутренний сервер посылает запрос, получает код авторизации и размещает его у себя как положено.
"Проверялка" Let's Encrypt разрешает запрошенное имя в адрес proxy и посылает туда запрос GET, естественно доменное имя указано в запросе.
Получив запрос, прокси еще раз разрешает доменное имя, но уже внутренним DNS и выполняет запрос на реальный сервер, получает ответ и отдает его "проверялке".
Проверка пройдена, наш внутренний сервер получает сертификат.
Вуаля! Все работает! Ставим на внутреннем сервере Certbot или активируем встроенную поддержку сертификатов Let's Encrypt у нужного нам софта - и погнали. Не забываем повесить задачу на автообновление сертификатов!
За, против и немного про безопасность
Преимущества:
Простота реализации. По плечу очень среднему админу.
Совместимость. Работают любые ACME-клиенты с проверкой по HTTP на всех платформах, в том числе родной Certbot, встроенные ACME клиенты (проверено на Proxmox), да и вообще нет ограничений по использованию ACME кроме верификации по HTTP. И да, работать должно не только с Let's Encrypt.
Минимальные усилия на изменения: чтобы Let's Encrypt стал доступен для любого сервера внутри - достаточно просто добавить CNAME для него во внешний DNS. Ну и сделать соответствующие ACME-настройки на самом сервере, конечно.
Недостатки:
Мы вроде как показываем имена внутренних серверов в Интернете. На самом деле - нет, если только внешний DNS настроен правильно и не позволяет кому попало забирать зону целиком, что является хорошей практикой независимо от целей.
UPDATE: Как правильно заметил в комментарии @HxShard , используя публичные сертификаты внутри сети мы неизбежно делаем доступными доменные имена узлов, для которых мы их получили, как минимум таким вот образом https://crt.sh/?q=habr.com. Тут уж придется каждому решить - насколько большой риск тем самым создается. Лично я оцениваю его как весьма небольшую цену за удобство, во всяком случае вряд ли именно это станет самым низким участком моего виртуального забора.Теоретически, прокси-сервер, как любой dual-homed, создает угрозу. Но и меры защиты - самые обычные. Если они непонятны и вы не можете правильно настроить защиту для Linux + Nginx - то вам вообще не стоит иметь дела с серверами, подключенными к Интернету. Позовите взрослых пожалуйста!
Опять же, теоретически, существует возможность DoS на внутренний сервер при условии что его имя получено - путем заваливания его запросами в каталог /.well-known. Крайне маловероятный сценарий, но его можно легко купировать, ограничив скорость запросов на прокси. Это же nginx!
Внутренние серверы должны иметь доступ в Интернет. Но если это для вас проблема - то ниже покажу как ее решить.
К сожалению, Let's Encrypt не поддерживает и не публикует список собственных IP-адресов, поэтому ограничить обращение внутренних серверов наружу и запросы извне по IP - не получится.
И все же, изолированные сервера, сделаем?
Ценою усложнения схемы - сделаем и это. Дело в том, что ACME-клиент посылает запрос по HTTPs. С одной стороны - его тоже можно проксировать, но при этом "ломается" сертификат, и скорее всего ACME-клиент выдаст ошибку. SSL соединение нормально проксируется только на уровне TCP, но к счастью nginx и это умеет.
Нам понадобится:
По два внутренних IP адреса на каждый ACME сервис (для основного и staging).
"Перехватить" домен сервиса - либо путем записей в hosts либо на своем внутреннем DNS.
Настроить nginx для проксирования на уровне TCP.
Кстати: адреса API разных ACME-сервисов можно взять из скрипта acme.sh
Ок, делаем, на примере Let's Encrypt. Будем считать, что для внутренних интерфейсов прокси выделены адреса 10.0.1.34 и 10.0.1.35.
Перехват DNS
Перехват через hosts проще, но его надо не забыть сделать на каждом внутреннем сервере, добавив в файл строки:
10.0.1.34 acme-v02.api.letsencrypt.org
10.0.1.35 acme-staging-v02.api.letsencrypt.org
Перехват внутренним DNS сервером сложнее, но зато сделать его надо один раз и работать он будет для всех серверов. Для этого на внутреннем DNS создаем зону api.letsencrypt.org, и заводим в ней нужные хосты, пример файла зоны для BIND:
;Перехват api.letsencrypt.org
; SOA
api.letsencrypt.org. 3600 IN SOA localhost. root.localhost. (
2022122900
28800
7200
604800
3600
)
;основной сервер
acme-v02 3600 IN A 10.0.1.34
;staging сервер
acme-staging-v02 3600 IN A 10.0.1.35
Вне зависимости от способа, проверяем результат пингуя с внутреннего сервера по именам acme-v02.api.letsencrypt.org и acme-staging-v02.api.letsencrypt.org, пинги должны проходить на .34 и .35 адреса соответственно. Значит, перехват DNS удался.
Настройка Nginx
Обратите внимание, что эти директивы должны находиться в контексте main, в то время как все привычные файлы конфигурации виртуальных хостов в каталоге "из коробки" уже находятся в контексте http. Поэтому надо либо добавлять в основной файл конфигурации /etc/nginx/nginx.conf, либо в каталог либо в отдельный файл и в правильном месте ставить include.
stream {
resolver 8.8.8.8; # А вот здесь нам нужен DNS, способный разрешать имена в Интернете
server { # Проксируем 443 порт на .34 адресе и отправляем на основной сервер
listen 10.0.1.34:443;
proxy_pass acme-v02.api.letsencrypt.org:443;
}
server { # Проксируем 443 порт на .35 адресе и отправляем на stageing сервер
listen 10.0.1.35:443;
proxy_pass acme-staging-v02.api.letsencrypt.org:443;
}
}
Ну вот и все! Теперь и серверы внутренние доступа наружу не имеют и Let's Encrypt на них работает.
P.S. Дед мой, добрая ему память, частенько говорил: "Кабы не клин да мох, да и плотник бы сдох!". Так и хочется перелицевать на "Кабы не nginx, да ???, так и сисадмин бы ???". Но вот что подставить? Предлагайте! :-)