- PVSM.RU - https://www.pvsm.ru -
В этой статье я расскажу о том, как автоматизировать заказ и продление сертификатов от Let’s Encrypt (и не только) для Ingress’а в Kubernetes с помощью дополнения cert-manager. Но начну с краткого введения в суть проблемы.
Протокол HTTP, разработанный в начале 90-х годов прошлого века, вошёл в нашу повседневную жизнь настолько плотно, что тяжело представить хотя бы день без его использования. Однако сам по себе он не обеспечивает даже минимальный уровень безопасности при обмене информации между пользователем и веб-сервером. На помощь приходит HTTPS («S» — secure): используя упаковку передаваемых данных в SSL/TLS, этот протокол неплохо себя зарекомендовал в защите информации от перехвата и активно пропагандируется индустрией.
Например, Google с 2014 года придерживается [1] позиции «HTTPS везде» и даже понижает приоритет сайтам без него в поисковой выдаче. Не обходит стороной эта «пропаганда» и рядовых потребителей: современные браузеры предупреждают своих пользователей о наличии и корректности SSL-сертификатов у посещаемых сайтов.
Стоимость сертификата для личного сайта начинается с десятков долларов. Не всегда покупка его оправдана и целесообразна. Благо, с конца 2015 года доступна бесплатная альтернатива в виде сертификатов Let’s Encrypt [2] (LE). Этот некоммерческий проект был создан энтузиастами из Mozilla для того, чтобы покрыть большую часть интернет-сайтов шифрованием.
Центр сертификации выписывает сертификаты типа domain-validated [3] (самые простые среди имеющихся на рынке) сроком действия в 90 дней, и уже не первый год возможен выпуск так называемого wildcard-сертификата на несколько поддоменов.
Для получения сайтом сертификата используются алгоритмы, описанные в протоколе Automated Certificate Management Environment [4] (ACME), созданного специально для Let's Encrypt. При его использовании подтверждение владения доменом осуществляется запросами через размещение определенного HTTP-кода (называется HTTP-01) или установку DNS-записей (DNS-01) — подробнее о них будет ниже.
Cert-manager [5] — специальный проект для Kubernetes, представляющий собой набор CustomResourceDefinitions (отсюда ограничение [6] на минимально поддерживаемую версию K8s — v1.12) для конфигурации CA (удостоверяющих центров) и непосредственного заказа сертификатов. Установка CRD в кластер тривиальна и сводится [6] к применению одного YAML-файла:
kubectl create ns cert-manager
kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.13.0/cert-manager.yaml
(Существует [7] также и возможность установки с помощью Helm.)
Для инициирования процедуры заказа в кластере должны быть объявлены ресурсы центров сертификации (CA): Issuer
или ClusterIssuer
, — которые используются для подписи CSR (запросов на выпуск сертификата). Отличие первого ресурса от второго — в области видимости:
Issuer
может использоваться в рамках одного пространства имен,ClusterIssuer
является глобальным объектом кластера.Начнем с простейшего случая — заказа самоподписанного сертификата. Такой вариант довольно распространен, например, для динамических тестовых окружений для разработчиков или же в случае использования внешнего балансировщика, терминирующего 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 (см. примечание на сайте проекта [8]). Чтобы этого избежать, нужно указать в Certificate
путь до файла секрета, где содержится ca.crt
. Таковым может быть и корпоративный CA организации — чтобы подписать выпускаемые для Ingress сертификаты ключом, уже используемым для нужд других серверных служб/информационных систем.
Для выпуска сертификатов LE, как упоминалось ранее, доступны два типа подтверждения [9] владения доменом: HTTP-01 и DNS-01.
Первый подход (HTTP-01) заключается [10] в запуске небольшого веб-сервера отдельным deployment’ом, который будет отдавать в интернет по ссылке <YOUR_DOMAIN [11]>/.well-known/acme-challenge/<TOKEN> некие данные, запрашиваемые с сервера сертификации. Следовательно, этот метод подразумевает доступность Ingress’а извне по 80 порту и разрешение DNS-записи домена в публичные IP-адреса.
Второй вариант проверки выпускаемого сертификата (DNS-01) исходит [12] из наличия API к DNS-серверу, где размещены записи домена. Issuer с помощью указанных токенов создает TXT-записи на домене, которые потом получает в ходе подтверждения ACME-сервер. Среди официально поддерживаемых DNS-провайдеров — CloudFlare, AWS Route53, Google CloudDNS и другие, в том числе и собственная реализация (acme-dns [13]).
Примечание: у Let’s Encrypt существуют довольно строгие лимиты [14] на запросы к ACME-серверам. Чтобы не попасть в длительный бан, для отладки рекомендуется использовать тип сертификата letsencrypt-staging [15] (отличие только в 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
Усложним задачу, выписав сертификат сразу на все поддомены сайта и воспользовавшись на этот раз DNS-проверкой (через CloudFlare).
Для начала получим в панели управления CloudFlare токен для работы через API:
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: my-cloudflare-acc@example.com
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: my-cloudflare-acc@example.com
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"
Помимо прямого пути по созданию сертификатов в cert-manager есть возможность воспользоваться компонентом под названием ingress-shim [16] и явно не создавать ресурсы 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’а, то есть создавать на одну сущность меньше.
Кроме того, существует устаревшая [17] аннотация kube-lego [18] — kubernetes.io/tls-acme: "true"
, — которая требует задания Issuer
по умолчанию при установке cert-manager с помощью параметров Helm (или в параметрах запуска контейнера менеджера).
Мы в компании не пользуемся этими вариантами и не можем их посоветовать ввиду непрозрачности используемых подходов к заказу SSL-сертификатов (а заодно — и к возникающих разного рода проблем [19]), но все же решили упомянуть в статье для более полной картины.
Путём несложных манипуляций с CRD мы научились выписывать автопродляемые, самоподписанные и бесплатные SSL-сертификаты от проекта Let’s Encrypt для доменов сайтов, запущенных в рамках Ingress’ов в Kubernetes-кластерах.
В статье приведены примеры решения наиболее частых в нашей практике задач. Однако функции cert-manager не ограничиваются описанными выше возможностями. На сайте утилиты можно найти примеры работы с другими сервисами — например, связка с Vault [20] или же использование внешних выпускающих центров [21] (issuers).
Читайте также в нашем блоге:
Автор: Олег
Источник [25]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/https/352182
Ссылки в тексте:
[1] придерживается: https://security.googleblog.com/2014/08/https-as-ranking-signal_6.html
[2] Let’s Encrypt: https://letsencrypt.org/
[3] domain-validated: https://en.wikipedia.org/wiki/Domain-validated_certificate
[4] Automated Certificate Management Environment: https://en.wikipedia.org/wiki/Automated_Certificate_Management_Environment
[5] Cert-manager: https://cert-manager.io/
[6] ограничение: https://cert-manager.io/docs/installation/kubernetes/
[7] Существует: https://cert-manager.io/docs/installation/kubernetes/#installing-with-helm
[8] примечание на сайте проекта: https://cert-manager.io/docs/configuration/selfsigned/
[9] два типа подтверждения: https://letsencrypt.org/docs/challenge-types/
[10] заключается: https://cert-manager.io/docs/configuration/acme/http01/
[11] <YOUR_DOMAIN: http://<YOUR_DOMAIN
[12] исходит: https://cert-manager.io/docs/configuration/acme/dns01/
[13] acme-dns: https://cert-manager.io/docs/configuration/acme/dns01/acme-dns/
[14] строгие лимиты: https://letsencrypt.org/docs/rate-limits/
[15] letsencrypt-staging: https://letsencrypt.org/docs/staging-environment/
[16] ingress-shim: https://cert-manager.io/docs/usage/ingress/#supported-annotations
[17] устаревшая: https://cert-manager.io/docs/usage/ingress/#optional-configuration
[18] kube-lego: https://github.com/jetstack/kube-lego
[19] проблем: https://github.com/jetstack/cert-manager/issues/2314
[20] связка с Vault: https://cert-manager.io/docs/configuration/vault/
[21] использование внешних выпускающих центров: https://cert-manager.io/docs/configuration/external/
[22] Введение в сетевые политики Kubernetes для специалистов по безопасности: https://habr.com/ru/company/flant/blog/443190/
[23] Kubernetes tips & tricks: персонализированные страницы ошибок в NGINX Ingress: https://habr.com/ru/company/flant/blog/445596/
[24] Обзор и сравнение контроллеров Ingress для Kubernetes: https://habr.com/ru/company/flant/blog/447180/
[25] Источник: https://habr.com/ru/post/496936/?utm_source=habrahabr&utm_medium=rss&utm_campaign=496936
Нажмите здесь для печати.