Ротация сертификатов в ingress-tls#
Описание#
IngressTLS — это пользовательский ресурс Kubernetes, который автоматизирует ротацию TLS-сертификатов для ingress-платформы DropApp и обновление пакета сертификатов CA для Kube-apiserver, используемого при OIDC-интеграции с Dex.
В текущей схеме в разных пространствах имен создаются TLS-секреты ingress-tls, в которых хранятся tls.crt и tls.key. Эти секреты используются ingress-ресурсами для HTTPS-терминации. При истечении срока действия сертификата или при смене цепочки доверия такие секреты обновляйте во всех пространствах имен, где они используются.
Дополнительно Kube-apiserver интегрирован с Dex по OIDC. При проверке Bearer-токен Kube-apiserver обращается к OIDC issuer/JWKS endpoint Dex по HTTPS и валидирует TLS-сертификат с использованием параметра:
--oidc-ca-file=/etc/kubernetes/pki/ingress-ca.crt
Файл /etc/kubernetes/pki/ingress-ca.crt расположен на control-plane узлах. После обновления этого файла перезапустите Kube-apiserver, так как --oidc-ca-file не перечитывается динамически.
Ресурс IngressTLS решает задачи:
Копирует TLS-материал из единого исходного секрета
dapp-system/ingress-tlsв целевые секретыingress-tlsв пространствах имен.По SSH обновляет
/etc/kubernetes/pki/ingress-ca.crtна всех control-plane узлах.Выполняет плавный перезапуск Kube-apiserver на control-plane узлах, по одному узлу за раз.
Отражает результат выполнения в
statusиconditions.
Источник TLS-данных - единый секрет в пространстве имен dapp-system:
apiVersion: v1
kind: Secret
metadata:
name: ingress-tls
namespace: dapp-ingress
type: kubernetes.io/tls
data:
tls.crt: <base64 fullchain>
tls.key: <base64 private key>
ca.crt: <base64 CA bundle>
Содержимое Secret:
Ключ |
Назначение |
|---|---|
|
Полная цепочка сертификатов для ingress: leaf-сертификат (конечный) и intermediate-сертификаты (промежуточные) |
|
Приватный ключ для ingress-сертификата |
|
Пакет сертификатов CA для параметра |
Важно
tls.crt и ca.crt - разные сущности:
tls.crtиспользуется ingress и должен содержать полную цепочку сертификатов.ca.crtиспользуется Kube-apiserver как доверенный пакет сертификатов для проверки TLS-сертификата Dex/OIDC issuer.
Приватный ключ CA (ingress-ca.key, ca.key) для работы Kube-apiserver не требуется. По умолчанию он не должен разворачиваться на control-plane узлы. Возможность записи ключа на узлы может быть оставлена как опциональная настройка только для совместимости с уже существующими инсталляциями.
Контекст использования#
Ручная ротация ingress TLS-сертификатов и OIDC-пакетов сертификатов CA создает несколько эксплуатационных рисков.
Неполное обновление TLS-секретов. Если сертификат обновлен только в части пространств имен, часть ingress продолжит отдавать старый сертификат. Это приведет к ошибкам TLS у пользователей и внешних клиентов.
Рассинхронизация между ingress TLS и Kube-apiserver. Dex может успешно подхватить новый сертификат, ingress начнет отдавать новую цепочку, но Kube-apiserver продолжит использовать старый пакет сертификатов CA из памяти. В результате OIDC-аутентификация начнет прерываться с ошибками вида:
oidc: verify token: failed to verify signature: fetching keys oidc: get keys failed tls: failed to verify certificate: x509: certificate signed by unknown authorityОбновление только части control-plane узлов. В HA-кластере запросы к API server могут попадать на разные control-plane узлы. Если пакет сертификатов CA обновлен и Kube-apiserver перезапущен только на одном узле, а на остальных осталась старая конфигурация, аутентификация будет работать нестабильно: часть запросов будет успешной, часть будет завершаться ошибкой
x509.Неправильное содержимое сертификатов. Для ingress-секретов в
tls.crtдолжен храниться fullchain, то есть leaf-сертификат вместе с intermediate-сертификатами. Если ingress отдает только leaf-сертификат, Go/TLS-клиенты, включая Kube-apiserver, могут не собрать цепочку доверия и получат ошибки:unable to get local issuer certificateилиcertificate signed by unknown authority.Хранение приватного ключа прямо в CRD. Если поместить
tls.crtиtls.keyнепосредственно вspecресурса, приватный ключ попадет в объект Kubernetes. Это ухудшает модель безопасности: любой субъект с правом чтенияIngressTLSсможет получить приватный ключ. ПоэтомуIngressTLSдолжен ссылаться на секрет Kubernetes, а не хранить TLS-материал в себе.
Введение IngressTLS делает процесс ротации декларативным и воспроизводимым:
источник данных хранится в одном секрете
dapp-system/ingress-tls;контроллер сам находит целевые пространства имен и секреты;
обновление выполняется идемпотентно;
Kube-apiserver перезапускается rolling-режимом на всех control-plane узлах;
состояние операции видно пользователю через
statusиconditions.
Создание IngressTLS#
Для запуска ротации необходимо создать ресурс IngressTLS.
Важно
IngressTLS - одноразовый ресурс. Предполагается только создание, изменение - нет. Если нужен другой сертификат, создайте новый CR IngressTLS.
Пример ресурса
apiVersion: tls.dropapp.ru/v1alpha1
kind: IngressTLS
metadata:
name: common-tls
spec:
sourceSecretRef:
namespace: dapp-ingress
name: ingress-tls
secretName: ingress-tls
namespaces:
- "*"
createIfMissing: false
Поля спецификации#
Поле |
Описание |
|---|---|
|
Пространство имен, в котором расположен исходный |
|
Имя исходного |
|
Имя целевого TLS-секрета в пространствах имен. Если не указано, используется |
|
Список пространств имен, в которых нужно обновить целевой секрет |
|
Определяет поведение, если целевой секрет отсутствует |
|
Включает обновление OIDC-пакета сертификатов CA на control-plane узлах |
|
Ключ в исходном |
|
Путь на control-plane узлах, куда записывается пакет сертификатов CA |
|
Включает опциональную запись |
|
Стратегия рестарта Kube-apiserver |
|
Пауза между удалением и возвратом static pod manifest |
|
Рестарт control-plane узлов по очереди |
Логика выбора пространств имен#
Если в
spec.namespacesуказано значение"*", контроллер обрабатывает все пространства имен кластера:spec: namespaces: - "*"Если вместе со
"*"указаны другие пространства имен, они игнорируются:spec: namespaces: - "*" - kube-systemТакой пример трактуется как «обработать все пространства имен».
Если
"*"не указан, контроллер обрабатывает только явно перечисленные пространства имен:spec: namespaces: - dapp-system - dapp-monitoring - dapp-security
Логика обновления ingress TLS-секретов#
В каждом целевом пространстве имен контроллер ищет секрет с именем из spec.secretName.
Если секрет найден, контроллер обновляет в нем:
data["tls.crt"] data["tls.key"]Значения берутся из
spec.sourceSecretRef.Если секрет не найден и
createIfMissing=false, контроллер не завершает reconcile-ошибкой. Отсутствующий секрет логируется на уровнеv=3и учитывается в статусе какmissing.Если секрет не найден и
createIfMissing=true, контроллер создает секрет типаkubernetes.io/tlsи записывает в негоtls.crtиtls.key.
Логика обновления OIDC CA на control-plane узлах#
Контроллер выполняет обновление пакета сертификатов CA на control-plane узлах, если включен apiServerOIDC:
spec:
apiServerOIDC:
enabled: true
Алгоритм:
Забрать пакет сертификатов CA из исходного
Secretпо ключуspec.apiServerOIDC.caKey.Для каждого control-plane узла выполнить SSH-подключение.
Записать пакет сертификатов CA во временный файл.
Атомарно переместить временный файл в
spec.apiServerOIDC.destCAPath.При необходимости записать
key-файл, еслиwriteKey.enabled=true.Выполнить
restart kube-apiserver.Перейти к следующему control-plane узлу только после завершения операции на текущем.
Рестарт Kube-apiserver выполняется через временное удаление static pod manifest:
sudo mv /etc/kubernetes/manifests/kube-apiserver.yaml /tmp/
sleep 20
sudo mv /tmp/kube-apiserver.yaml /etc/kubernetes/manifests/
Рестарт должен выполняться rolling-режимом, то есть по одному control-plane узлу за раз. Это необходимо, чтобы не потерять доступность Kubernetes API и не получить ситуацию, когда часть Kube-apiserver использует старый пакет сертификатов CA, а часть - новый.