- Регистрация
- 23.09.18
- Сообщения
- 12.347
- Реакции
- 176
- Репутация
- 0

В этой статье я расскажу о том, как автоматизировать заказ и продление сертификатов от Let’s Encrypt (и не только) для Ingress’а в Kubernetes с помощью дополнения cert-manager. Но начну с краткого введения в суть проблемы.
Немного ликбеза
Протокол HTTP, разработанный в начале 90-х годов прошлого века, вошёл в нашу повседневную жизнь настолько плотно, что тяжело представить хотя бы день без его использования. Однако сам по себе он не обеспечивает даже минимальный уровень безопасности при обмене информации между пользователем и веб-сервером. На помощь приходит HTTPS («S» — secure): используя упаковку передаваемых данных в SSL/TLS, этот протокол неплохо себя зарекомендовал в защите информации от перехвата и активно пропагандируется индустрией.
Например, Google с 2014 года
You must be registered for see links
позиции «HTTPS везде» и даже понижает приоритет сайтам без него в поисковой выдаче. Не обходит стороной эта «пропаганда» и рядовых потребителей: современные браузеры предупреждают своих пользователей о наличии и корректности SSL-сертификатов у посещаемых сайтов.


Стоимость сертификата для личного сайта начинается с десятков долларов. Не всегда покупка его оправдана и целесообразна. Благо, с конца 2015 года доступна бесплатная альтернатива в виде сертификатов
You must be registered for see links
(LE). Этот некоммерческий проект был создан энтузиастами из Mozilla для того, чтобы покрыть большую часть интернет-сайтов шифрованием.Центр сертификации выписывает сертификаты типа
You must be registered for see links
(самые простые среди имеющихся на рынке) сроком действия в 90 дней, и уже не первый год возможен выпуск так называемого wildcard-сертификата на несколько поддоменов.Для получения сайтом сертификата используются алгоритмы, описанные в протоколе
You must be registered for see links
(ACME), созданного специально для Let's Encrypt. При его использовании подтверждение владения доменом осуществляется запросами через размещение определенного HTTP-кода (называется HTTP-01) или установку DNS-записей (DNS-01) — подробнее о них будет ниже.Cert-manager
You must be registered for see links
— специальный проект для Kubernetes, представляющий собой набор CustomResourceDefinitions (отсюда
You must be registered for see links
на минимально поддерживаемую версию K8s — v1.12) для конфигурации CA (удостоверяющих центров) и непосредственного заказа сертификатов. Установка CRD в кластер тривиальна и
You must be registered for see links
к применению одного YAML-файла:kubectl create ns cert-manager
kubectl apply -f
You must be registered for see links
(
You must be registered for see links
также и возможность установки с помощью Helm.)Для инициирования процедуры заказа в кластере должны быть объявлены ресурсы центров сертификации (CA): Issuer или ClusterIssuer, — которые используются для подписи CSR (запросов на выпуск сертификата). Отличие первого ресурса от второго — в области видимости:
- Issuer может использоваться в рамках одного пространства имен,
- ClusterIssuer является глобальным объектом кластера.
Практика с cert-manager
№1. Самоподписанный сертификат
Начнем с простейшего случая — заказа самоподписанного сертификата. Такой вариант довольно распространен, например, для динамических тестовых окружений для разработчиков или же в случае использования внешнего балансировщика, терминирующего SSL-трафик.
Ресурс Issuer будет выглядеть так:
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: selfsigned
spec:
selfSigned: {}
А чтобы выпустить сертификат, необходимо описать ресурс Certificate, где указывается, как произвести выпуск (см. раздел issuerRef ниже) и где размещен приватный ключ (поле secretName). После этого в Ingress потребуется сослаться на этот ключ (см. раздел tls в spec):
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: selfsigned-crt
spec:
secretName: tls-secret
issuerRef:
kind: Issuer
name: selfsigned
commonName: "yet-another.website"
dnsNames:
- "yet-another.website"
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: app
spec:
tls:
- hosts:
- "yet-another.website"
secretName: tls-secret
rules:
- host: "yet-another.website"
http:
paths:
- path: /
backend:
serviceName: app
servicePort: 8080
Через несколько секунд после добавления этих ресурсов в кластер сертификат будет выписан. Увидеть соответствующий отчёт можно в выводе команды:
kubectl -n app describe certificate selfsigned-crt
...
Normal GeneratedKey 5s cert-manager Generated a new private key
Normal Requested 5s cert-manager Created new CertificateRequest resource "selfsigned-crt-4198958557"
Normal Issued 5s cert-manager Certificate issued successfully
Если посмотреть на сам ресурс секрета, то в нём лежат:
- приватный ключ tls.key,
- корневой сертификат ca.crt,
- наш самоподписанный сертификат tls.crt.
Содержимое этих файлов можно увидеть с помощью утилиты openssl, например, так:
kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -issuer
notBefore=Feb 10 21:01:59 2020 GMT
notAfter=May 10 21:01:59 2020 GMT
issuer=O = cert-manager, CN = yet-another.website
Стоит отметить, что в общем случае сертификату, выписанному с помощью такого Issuer, подключаемые клиенты доверять не будут. Причина проста: он не имеет CA (см.
You must be registered for see links
). Чтобы этого избежать, нужно указать в Certificate путь до файла секрета, где содержится ca.crt. Таковым может быть и корпоративный CA организации — чтобы подписать выпускаемые для Ingress сертификаты ключом, уже используемым для нужд других серверных служб/информационных систем.№2. Сертификат Let’s Encrypt с HTTP-валидацией
Для выпуска сертификатов LE, как упоминалось ранее, доступны
You must be registered for see links
владения доменом: HTTP-01 и DNS-01.Первый подход (HTTP-01)
You must be registered for see links
в запуске небольшого веб-сервера отдельным deployment’ом, который будет отдавать в интернет по ссылке
You must be registered for see links
из наличия API к DNS-серверу, где размещены записи домена. Issuer с помощью указанных токенов создает TXT-записи на домене, которые потом получает в ходе подтверждения ACME-сервер. Среди официально поддерживаемых DNS-провайдеров — CloudFlare, AWS Route53, Google CloudDNS и другие, в том числе и собственная реализация (
You must be registered for see links
).Примечание: у Let’s Encrypt существуют довольно
You must be registered for see links
на запросы к ACME-серверам. Чтобы не попасть в длительный бан, для отладки рекомендуется использовать тип сертификата
You must be registered for see links
(отличие только в ACME-сервере).Итак, опишем ресурсы:
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: nginx
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: le-crt
spec:
secretName: tls-secret
issuerRef:
kind: Issuer
name: letsencrypt
commonName: yet-another.website
dnsNames:
- yet-another.website
Обратите внимание, что в качестве server у acme (в Issuer) указан адрес staging-сервера. Заменить его на боевой можно будет позже.
Применив эту конфигурацию, проследим весь путь заказа:
- Создание Certificate породило новый ресурс CertificateRequest:
kubectl -n app describe certificate le-crt
...
Created new CertificateRequest resource "le-crt-1127528680" - В его описании — отметка о создании Order:
kubectl -n app describe certificaterequests le-crt-1127528680
…
Created Order resource app/le-crt-1127528680-1805948596 - В Order описано, с какими параметрами проходит проверка и какой у неё текущий статус. Такая проверка осуществляется ресурсом Challenge:
kubectl -n app describe order le-crt-1127528680-1805948596
…
Created Challenge resource "le-crt-1127528680-1805948596-1231544594" for domain "yet-another.website" - Наконец, в подробностях этого ресурса содержится информация о статусе самой проверки:
kubectl -n app describe challenges le-crt-1127528680-1805948596-1231544594
...
Reason: Successfully authorized domain
...
Normal Started 2m45s cert-manager Challenge scheduled for processing
Normal Presented 2m45s cert-manager Presented challenge using http-01 challenge mechanism
Normal DomainVerified 2m22s cert-manager Domain "yet-another.website" verified with "http-01" validation
Если все условия были соблюдены (т.е. домен доступен снаружи, нет бана со стороны LE…) — меньше, чем через минуту, сертификат будет выпущен. В случае успеха в выводе describe certificate le-tls появится запись Certificate issued successfully.
Теперь можно смело менять адрес сервера на боевой (https://acme-v02.api.letsencrypt.org/directory) и перезаказывать уже настоящие сертификаты, подписанные не Fake LE Intermediate X1, а Let's Encrypt Authority X3.
Для этого сначала потребуется удалить ресурс Certificate: иначе никакие процедуры заказа не активируются, потому что сертификат уже есть и он актуален. Удаление секрета приведет к его немедленному возврату с сообщением в describe certificate:
Normal PrivateKeyLost 44s cert-manager Lost private key for CertificateRequest "le-crt-613810377", deleting old resource
Остается применить «боевой» манифест для Issuer с уже описанным выше Certificate (он не изменился):
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt
solvers:
- http01:
ingress:
class: nginx
После получения сообщения Certificate issued successfully в describe проверим его:
kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -issuer
notBefore=Feb 10 21:11:48 2020 GMT
notAfter=May 10 21:11:48 2020 GMT
issuer=C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
№3. Wildcard LE с валидацией через DNS
Усложним задачу, выписав сертификат сразу на все поддомены сайта и воспользовавшись на этот раз DNS-проверкой (через CloudFlare).
Для начала получим в панели управления CloudFlare токен для работы через API:
- Profile → API Tokens → Create Token.
- Выставляем права доступа следующим образом:
- Permissions:
- Zone — DNS — Edit
- Zone — Zone — Read
- Zone Resources:
- Include — All Zones
- Permissions:
- Копируем полученный после сохранения токен (например: y_JNkgQwkroIsflbbYqYmBooyspN6BskXZpsiH4M).
Создадим Secret, в котором будет храниться этот токен, и сошлемся на него в Issuer:
apiVersion: v1
kind: Secret
metadata:
name: cloudflare-api-token
type: Opaque
stringData:
api-token: y_JNkgQwkroIsflbbYqYmBooyspN6BskXZpsiH4M
---
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt
solvers:
- dns01:
cloudflare:
email: [email protected]
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
---
apiVersion: cert-manager.io/v1alpha2
kind: Certificate
metadata:
name: le-crt
spec:
secretName: tls-secret
issuerRef:
kind: Issuer
name: letsencrypt
commonName: yet-another.website
dnsNames:
- "yet-another.website"
- "*.yet-another.website"
(Не забудьте про использование staging, если экспериментируете!)
Пройдем процедуру подтверждения владения доменом:
kubectl -n app describe challenges.acme.cert-manager.io le-crt-613810377-1285319347-3806582233
...
Status:
Presented: true
Processing: true
Reason: Waiting for dns-01 challenge propagation: DNS record for "yet-another.website" not yet propagated
State: pending
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Started 54s cert-manager Challenge scheduled for processing
Normal Presented 53s cert-manager Presented challenge using dns-01 challenge mechanism
В панели появится TXT-запись:

… а через некоторое время статус сменится на:
Domain "yet-another.website" verified with "dns-01" validation
Убедимся в том, что сертификат валиден для любого поддомена:
kubectl -n app get secret tls-secret -ojson | jq -r '.data."tls.crt"' | base64 -d | openssl x509 -dates -noout -text |grep DNS:
DNS:*.yet-another.website, DNS:yet-another.website
Валидация по DNS, как правило, происходит не быстро, так как для большинства DNS-провайдеров характерен период обновления данных, показывающий, сколько времени пройдёт с момента изменения DNS-записи до фактического обновления всех DNS-серверов провайдера. Однако стандарт ACME предусматривает и комбинацию двух вариантов проверок, что можно использовать для ускорения получения сертификата на основной домен. В этом случае описание Issuer будет следующим:
apiVersion: cert-manager.io/v1alpha2
kind: Issuer
metadata:
name: letsencrypt
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt
solvers:
- selector:
dnsNames:
- "*.yet-another.website"
dns01:
cloudflare:
email: [email protected]
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
- selector:
dnsNames:
- "yet-another.website"
http01:
ingress:
class: nginx
Если применить эту конфигурацию, будут созданы два ресурса Challenge:
kubectl -n app describe orders le-crt-613810377-1285319347
…
Normal Created 3m29s cert-manager Created Challenge resource "le-crt-613810377-1285319347-3996324737" for domain "yet-another.website"
Normal Created 3m29s cert-manager Created Challenge resource "le-crt-613810377-1285319347-1443470517" for domain "yet-another.website"
№4. Использование специальных аннотаций Ingress
Помимо прямого пути по созданию сертификатов в cert-manager есть возможность воспользоваться компонентом под названием
You must be registered for see links
и явно не создавать ресурсы Certificate. Идея заключается в том, что с помощью специальных аннотаций Ingress’а сертификат будет автоматически заказан с помощью указанного в них Issuer. В результате получается примерно следующий ресурс Ingress’а:apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
annotations:
cert-manager.io/cluster-issuer: letsencrypt
spec:
tls:
- hosts:
- "yet-another.website"
secretName: tls-secret
rules:
- host: "yet-another.website"
http:
paths:
- path: /
backend:
serviceName: app
servicePort: 8080
Для корректной работы тут достаточно только наличия Issuer’а, то есть создавать на одну сущность меньше.
Кроме того, существует
You must be registered for see links
аннотация
You must be registered for see links
— kubernetes.io/tls-acme: "true", — которая требует задания Issuer по умолчанию при установке cert-manager с помощью параметров Helm (или в параметрах запуска контейнера менеджера).Мы в компании не пользуемся этими вариантами и не можем их посоветовать ввиду непрозрачности используемых подходов к заказу SSL-сертификатов (а заодно — и к возникающих разного рода
You must be registered for see links
), но все же решили упомянуть в статье для более полной картины.Вместо заключения
Путём несложных манипуляций с CRD мы научились выписывать автопродляемые, самоподписанные и бесплатные SSL-сертификаты от проекта Let’s Encrypt для доменов сайтов, запущенных в рамках Ingress’ов в Kubernetes-кластерах.
В статье приведены примеры решения наиболее частых в нашей практике задач. Однако функции cert-manager не ограничиваются описанными выше возможностями. На сайте утилиты можно найти примеры работы с другими сервисами — например,
You must be registered for see links
или же
You must be registered for see links
(issuers).P.S.
Читайте также в нашем блоге:
- «
You must be registered for see links»;
- «
You must be registered for see links»;
- «
You must be registered for see links».