Обычно внутри корпоративной сети нынче полно всяких приложений, и хотелось бы чтобы они работали по 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, да ???, так и сисадмин бы ???". Но вот что подставить? Предлагайте! :-)