Под кластером работает Talos, сетью управляет Cilium, а за хранилище отвечает Linstor. Поды уже могут общаться друг с другом и сохранять данные. Но большая часть этого взаимодействия по-прежнему идет через TLS с самоподписанными сертификатами, которым не доверяет ни одна из сторон. В этой статье мы это исправим.
TL;DR
В статье показано, как отказаться от InsecureSkipVerify и выстроить проверяемый TLS на нескольких уровнях Kubernetes-кластера.
Для внутренних сервисов создаётся отдельный самоподписанный CA через cert-manager. Сначала вспомогательный selfSigned-issuer выпускает сертификат самого центра сертификации, после чего основной ClusterIssuer начинает подписывать сертификаты рабочих нагрузок. Сертификаты и ключи сохраняются в Kubernetes Secret и автоматически обновляются до истечения срока действия.
Чтобы все пространства имён доверяли этому CA, используется trust-manager. Он забирает только открытую часть сертификата и распространяет её по кластеру в виде ConfigMap, не раскрывая закрытый ключ. Рабочим нагрузкам остаётся подключить этот набор сертификатов как том и указать его клиенту TLS.
Для внешних сервисов настраивается отдельный ACME-issuer Let’s Encrypt. В варианте с Gateway API cert-manager автоматически создаёт временный HTTPRoute для прохождения проверки HTTP-01, получает сертификат и удаляет служебный маршрут.
Отдельно разбирается серверный сертификат kubelet. По умолчанию kubelet использует самоподписанный сертификат, а API-сервер часто обращается к нему без полноценной проверки. В Talos это исправляется включением serverTLSBootstrap, после чего kubelet отправляет CSR на выпуск сертификата, подписанного CA кластера.
Такие запросы Kubernetes специально не одобряет автоматически: сначала нужно проверить, что SAN в сертификате действительно соответствуют имени и адресам узла. Для этого устанавливается контроллер kubelet-serving-cert-approver, который сверяет запрос с объектом Node и одобряет только корректные CSR для signer kubernetes.io/kubelet-serving.
В результате межсервисные соединения получают единый внутренний центр доверия, внешние сервисы — общедоверенные сертификаты, а API-сервер и системы мониторинга могут проверять сертификаты kubelet без insecure-skip-tls-verify.
Проблема может оставаться незамеченной очень долго, потому что Kubernetes не сигнализирует о ней явно. Если один под обращается к HTTPS-эндпоинту другого с InsecureSkipVerify, он получает успешный ответ. kubectl logs работает, потому что обращается к API-серверу, а не напрямую к kubelet, а API-сервер по умолчанию вообще не проверяет сертификат kubelet. Никакого сообщения о том, что половина трафика управляющей плоскости не проходит проверку, вы не увидите. Об этом нужно просто знать.
Решение состоит из трех частей:
cert-manager с собственным самоподписанным внутренним CA, чтобы любой компонент внутри кластера мог запросить TLS-сертификат и получить его.
trust-manager, который распространяет сертификат этого CA по всем пространствам имен в виде ConfigMap, подключаемого как том. Благодаря этому рабочие нагрузки смогут проверять сертификаты, выпущенные cert-manager.
Контроллер одобрения CSR, чтобы kubelet получал серверный сертификат, подписанный известным кластеру CA, а не самоподписанный сертификат, которому никто не доверяет.
Плюс issuer (издатель сертификатов) Let’s Encrypt для всего, что доступно из интернета. Ни один из этих компонентов не привязан к Talos: в любом дистрибутиве Kubernetes схема будет примерно такой же. Единственная специфичная для Talos деталь – одна строка в конфигурации узла, которая включает ротацию серверных сертификатов kubelet.
Перед тем как переходить к настройке, можно проверить, насколько уверенно вы ориентируетесь в устройстве Kubernetes-платформы. Вступительный тест поможет оценить текущий уровень и понять, какие темы стоит освежить.
Что означает «TLS» в Kubernetes-кластере по умолчанию
Прежде чем что-то добавлять, стоит точно разобраться, какой трафик уже шифруется, а какой нет.
В кластере уже есть инфраструктура PKI, построенная вокруг CA кластера kubeadm/Talos. Он подписывает серверный сертификат API-сервера, клиентский сертификат API-сервера для взаимодействия с другими компонентами, клиентские сертификаты controller-manager и scheduler, а также клиентский сертификат kubelet. Эта часть корректно настроена сразу после установки: компоненты управляющей плоскости проверяют сертификаты друг друга.
Но в этой схеме не хватает нескольких вещей:
Серверный эндпоинт kubelet. Каждый kubelet слушает порт 10250 с самоподписанным сертификатом, который генерирует при запуске. API-сервер обращается к нему для получения логов, выполнения команд, проброса портов и сбора метрик, но по умолчанию сертификат не проверяет. Семейство флагов
--kubelet-insecure-tlsфактически заложено в конфигурацию API-сервера большинства дистрибутивов. Соединение зашифровано, но подлинность другой стороны не проверяется, поэтому любой, кто контролирует участок сетевого маршрута, может перехватить трафик.Трафик между рабочими нагрузками. Service перед подом – это IP-адрес на уровне CNI. TLS там не появляется, пока его не реализует само приложение или сайдкар. Cilium может шифровать базовую транспортную сеть с помощью WireGuard или IPsec, но это шифрование трафика между узлами, а не сквозное TLS-шифрование между рабочими нагрузками.
Ingress. Под может обслуживать HTTPS, но для этого ему нужен сертификат. Без issuer такой сертификат будет либо самоподписанным, либо обновляемым вручную, либо переданным в кластер каким-то внешним способом.
CA кластера представляет собой закрытую систему. Его закрытый ключ доступен компонентам кластера, точнее controller-manager через флаги --cluster-signing-*, и используется только для подписи запросов, отправленных через Kubernetes CSR API. Этот CA намеренно не предназначен для выпуска произвольных TLS-сертификатов.
Поэтому уровню доверия нужен собственный CA: отдельный от CA кластера и доступный рабочим нагрузкам по запросу.
Именно это и дает cert-manager.
cert-manager: CA, у которого кластер может запрашивать сертификаты
cert-manager – это контроллер, который следит за пользовательскими ресурсами Certificate и на их основе создаёт Kubernetes Secret с TLS-сертификатами и закрытыми ключами. Самое интересное здесь – модель issuer: ресурс Certificate ссылается на Issuer или ClusterIssuer, а тот уже определяет, как именно будет выпущен сертификат. Разные issuer используют разные механизмы: ACME (Let’s Encrypt), Vault, внутренний CA, ключ и сертификат которого хранятся в Secret, или самоподписывающий issuer, который подписывает запрос ключом из самого запроса. При замене одного issuer на другой манифест рабочей нагрузки не меняется – достаточно изменить issuerRef.
Для установки нужен один официальный Helm-чарт:
helm repo add jetstack https://charts.jetstack.io helm repo update helm install cert-manager jetstack/cert-manager \ --namespace cert-manager --create-namespace \ --set crds.enabled=true
В результате вы получите контроллер, вебхуки и три CRD: Certificate, Issuer и ClusterIssuer. Но ни одного issuer установка не создаёт. Сразу после установки запрос Certificate завершится ошибкой, потому что выпускать сертификат пока некому.
Первоначальное создание внутреннего CA
Кластеру нужен один самоподписанный CA – назовём его internal-ca, – который будет подписывать все сертификаты для взаимодействия рабочих нагрузок между собой. Это CA, которому доверяет всё внутри кластера. Сертификаты для сервисов, доступных из интернета, выпускает Let’s Encrypt через отдельный issuer; до него мы ещё доберёмся.
В новом кластере CA приходится создавать с нуля, и здесь возникает классическая проблема курицы и яйца. cert-manager решает её с помощью специального bootstrap-приёма.
CA-issuer типа ClusterIssuer в cert-manager ссылается на Secret, где хранятся закрытый ключ и сертификат CA. Но в новом кластере такого Secret ещё нет. Значит, нельзя создать рабочий ClusterIssuer для CA. Без него нельзя выпустить сертификат самого CA, а без сертификата не появится и нужный Secret.
Чтобы разорвать этот цикл, в cert-manager есть issuer типа selfSigned. За ним не стоит никакого Secret: он подписывает переданный запрос ключом, который содержится в самом запросе. С его помощью выпускается ровно один Certificate – пара ключей самого CA. После этого настоящий ClusterIssuer типа ca можно направить на Secret, созданный этим ресурсом Certificate.
Для этого нужны три манифеста:
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: selfsigned-bootstrap spec: selfSigned: {} --- apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: internal-ca namespace: cert-manager spec: isCA: true commonName: internal-ca secretName: internal-ca privateKey: algorithm: Ed25519 issuerRef: name: selfsigned-bootstrap kind: ClusterIssuer --- apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: internal-ca spec: ca: secretName: internal-ca
ClusterIssuer с именем selfsigned-bootstrap – это вспомогательная конструкция для первоначальной настройки. В дальнейшем он также может пригодиться для разовых самоподписанных сертификатов. Ресурс Certificate с именем internal-ca cert-manager преобразует в Secret internal-ca в пространстве имён cert-manager. Второй ClusterIssuer, который тоже называется internal-ca, – это уже основной issuer, на который будут ссылаться ресурсы Certificate рабочих нагрузок.
Отдельно стоит сказать об алгоритме ключа. В большинстве примеров cert-manager используется ECDSA P-256, и он вполне подходит. Но там, где все потребители его поддерживают, Ed25519 – более удачный выбор. У него проще криптографическая конструкция: для каждой подписи не требуется отдельный nonce, а значит, нет характерного для ECDSA риска повторного использования nonce. Кроме того, у Ed25519 меньше ключи и быстрее подпись при сопоставимом уровне безопасности около 128 бит.
Проблемы начинаются только там, где приходится работать с устаревшими TLS-клиентами. Но внутренний CA по определению с ними не взаимодействует. Go-сервисы, современные бинарные файлы, связанные с актуальными версиями OpenSSL, и новые сайдкары нормально поддерживают Ed25519 – а именно они и составляют аудиторию внутреннего CA.
После применения этих манифестов команда kubectl get clusterissuers покажет для internal-ca состояние Ready: True, а Secret internal-ca в пространстве имён cert-manager будет содержать пару ключей CA. Теперь любое пространство имён может запросить сертификат для рабочей нагрузки:
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: my-app namespace: my-app spec: secretName: my-app-tls dnsNames: - my-app.my-app.svc.cluster.local - my-app.example.internal issuerRef: name: internal-ca kind: ClusterIssuer
cert-manager генерирует пару ключей, подписывает сертификат с помощью internal-ca, сохраняет результат в Secret my-app-tls под ключами tls.crt и tls.key, а затем обновляет сертификат до истечения срока действия. Рабочей нагрузке остаётся подключить этот Secret как том и больше не думать о жизненном цикле TLS-сертификата.
Так мы получаем одну половину доверия: любая рабочая нагрузка может запросить серверный сертификат. Теперь нужно решить вторую половину задачи – дать остальным участникам возможность этот сертификат проверить.
trust-manager: распространяем набор сертификатов CA по всему кластеру
Подписанный сертификат полезен только в том случае, если у другой стороны есть открытый сертификат CA, с которым можно его проверить. Когда CA один, достаточно подключить tls.crt из internal-ca ко всем рабочим нагрузкам, которым нужно проверять сертификаты других участников.
Наивный вариант – скопировать Secret в каждое пространство имён. Формально это работает, но плохо масштабируется: для каждого нового пространства имён нужна отдельная копия, а при ротации сертификата придётся обновлять их все. Хуже того, исходный Secret в пространстве имён cert-manager содержит не только сертификат CA, но и его закрытый ключ. Если подключать этот Secret для распространения доверия, закрытый ключ получит каждая рабочая нагрузка, которой на самом деле нужен только открытый сертификат.
Эту задачу решает trust-manager. Это небольшой контроллер от той же команды, который устанавливается отдельно от cert-manager. Он следит за пользовательским ресурсом Bundle, считывает запрошенные сертификаты – только открытую часть – из одного или нескольких Secret или ConfigMap, а затем распространяет их в виде ConfigMap по выбранным пространствам имён. В этом ConfigMap используется фиксированный ключ ca-bundle.crt, поэтому рабочим нагрузкам не нужно знать, какой именно CA находится внутри.
Устанавливается trust-manager из того же Helm-репозитория Jetstack:
helm install trust-manager jetstack/trust-manager \ --namespace cert-manager
После этого создаём один Bundle для внутреннего CA:
apiVersion: trust.cert-manager.io/v1alpha1 kind: Bundle metadata: name: internal-ca spec: sources: - secret: name: internal-ca key: tls.crt target: configMap: key: ca-bundle.crt namespaceSelector: matchLabels: {}
namespaceSelector.matchLabels: {} – это пустой селектор, поэтому набор сертификатов распространяется во все пространства имён. Начать с такого широкого охвата вполне нормально: открытый сертификат не является чувствительными данными. Защищать нужно закрытый ключ, а он никогда не покидает пространство имён cert-manager.
Чтобы использовать этот набор сертификатов в рабочей нагрузке, достаточно подключить один том:
volumes: - name: internal-ca configMap: name: internal-ca volumeMounts: - name: internal-ca mountPath: /etc/ssl/certs/internal-ca.crt subPath: ca-bundle.crt readOnly: true
Для Go-бинарников задайте SSL_CERT_FILE=/etc/ssl/certs/internal-ca.crt, а для других сред выполнения используйте соответствующий параметр. После этого рабочая нагрузка сможет проверять сертификаты любых участников, подписанные internal-ca, без InsecureSkipVerify. Когда CA ротируется, trust-manager обновляет ConfigMap, а kubelet обновляет подключённый том во время следующего цикла синхронизации.
Так мы закрыли TLS между рабочими нагрузками. Следующие две части относятся к границам кластера: входящему трафику из интернета и соединениям между API-сервером и kubelet.
Let’s Encrypt для внешнего периметра
Для сервисов, доступных из интернета, внутренний CA бесполезен. Браузеры ему не доверяют, а набор доверенных сертификатов пришлось бы устанавливать на каждое устройство, которое открывает сайт. Стандартное решение – Let’s Encrypt: общедоверенный центр сертификации, который бесплатно выпускает сертификаты сроком на 90 дней по протоколу ACME.
ACME-issuer в cert-manager берёт работу с протоколом на себя. Настроить нужно только способ прохождения проверки HTTP-01 со стороны сервера ACME. Если в кластере установлен Gateway API, cert-manager может использовать solver gatewayHTTPRoute: он создаёт временный HTTPRoute, направленный на сервер cert-manager, который отвечает на проверочный запрос, и привязывает его к указанному Gateway:
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt spec: acme: server: https://acme-v02.api.letsencrypt.org/directory email: you@example.com privateKeySecretRef: name: letsencrypt solvers: - http01: gatewayHTTPRoute: parentRefs: - group: gateway.networking.k8s.io kind: Gateway name: shared-gateway namespace: gateway-api-system
Когда ресурс Certificate ссылается на issuer letsencrypt, cert-manager создаёт на shared-gateway временный HTTPRoute, который отдаёт проверочный токен ACME по адресу /.well-known/acme-challenge/.... Let’s Encrypt запрашивает его по HTTP, проверяет, совпадает ли ответ с выданным токеном, подписывает сертификат, после чего cert-manager удаляет временный HTTPRoute. Обычно весь процесс занимает меньше тридцати секунд.
Solver для Gateway API работает только с одним ресурсом – родительским Gateway, к которому привязывается HTTPRoute. Поэтому RBAC-права можно строго ограничить, а остальным компонентам кластера вообще не нужно ничего знать о cert-manager.
На время тестирования лучше указать тестовый каталог ACME. Тестовые сертификаты не входят в общедоверенную цепочку, зато лимиты там гораздо мягче. Если неправильно настроенный Certificate начнёт бесконечно повторять запросы, он не исчерпает вашу квоту. В production действуют ограничения: 50 сертификатов на один зарегистрированный домен в неделю и пять повторных сертификатов в неделю. Зациклившийся неисправный Certificate способен упереться в оба лимита меньше чем за час.
Проверка DNS-01 подходит для wildcard-сертификатов и доменов, недоступных по HTTP. Но для неё нужны API-учётные данные DNS-провайдера, а для одного кластера это заметно больше настроек, чем требует HTTP-01. Пока wildcard-сертификаты не понадобились, DNS-01 можно не трогать.
Let’s Encrypt закрывает внешний периметр. Другая граница – соединения между API-сервером и kubelet – устроена совсем иначе и требует отдельного решения.
Проблема с серверным сертификатом kubelet
Каждый kubelet открывает на порту 10250 TLS-эндпоинт. API-сервер обращается к нему при выполнении kubectl logs, kubectl exec, kubectl port-forward, а также при запросах к метрикам, которые проксируются через kubelet/metrics. По умолчанию этот эндпоинт использует сертификат, который kubelet самостоятельно генерирует при запуске, без участия какого-либо signer.
Этого сертификата нет ни в одном хранилище доверенных сертификатов. В большинстве дистрибутивов API-сервер всё равно настроен так, чтобы его принимать: либо параметр --kubelet-certificate-authority не задан и проверка сертификата пропускается, либо он задан, но самоподписанный сертификат всё равно не проходит проверку по CA кластера, поэтому соединение фактически принимается без проверки подлинности kubelet. Результат в обоих случаях один: внутренний трафик управляющей плоскости между API-сервером и каждым kubelet зашифрован, но подлинность другой стороны не проверяется. Злоумышленник, способный вклиниться в канал связи между ними, сможет перехватывать сеансы kubectl exec.
Чтобы исправить это, kubelet должен запрашивать серверный сертификат у настоящего signer, а не подписывать его самостоятельно. В Kubernetes для этого есть встроенный signer kubernetes.io/kubelet-serving, который использует CA кластера. Если включить первоначальный автоматический выпуск серверного сертификата, kubelet при запуске отправляет в API-сервер ресурс CertificateSigningRequest, запрашивая сертификат с именем узла и его IP-адресом в SAN. После одобрения запроса CA кластера подписывает сертификат, kubelet забирает его, а API-сервер с параметром --kubelet-certificate-authority, указывающим на CA кластера, наконец получает возможность проверить сертификат.
Для работы этой схемы нужны два условия:
kubelet должен использовать начальную выдачу сертификата через CSR вместо самоподписанного сертификата;
кто-то должен одобрять поступающие CSR.
Включаем bootstrap – единственный шаг, специфичный для Talos
В Talos конфигурация kubelet находится внутри конфигурации узла. Нужный параметр задаётся в machine.kubelet.extraConfig, откуда он без изменений передаётся в конфигурационный файл самого kubelet:
machine: kubelet: extraConfig: serverTLSBootstrap: true
Эта единственная строка заставляет kubelet при запуске отправлять CSR для серверного сертификата, а перед истечением срока действия – отправлять новый запрос для ротации. После следующей перезагрузки или выполнения talosctl service kubelet restart у каждого узла в API-сервере появится CSR в состоянии Pending, ожидающий одобрения.
Такая же настройка есть в любом дистрибутиве, различается только способ её задания. В кластерах kubeadm она указывается через KubeletConfiguration, передаваемую в kubeadm init или kubeadm join; в k3s – через флаг --kubelet-arg=server-tls-bootstrap=true; в обычной установке kubeadm или аналогичной схеме – напрямую в конфигурационном файле kubelet. Сам механизм везде одинаков.
Почему Kubernetes не одобряет эти запросы автоматически
Следующий закономерный вопрос: почему Kubernetes просто не одобряет такие CSR автоматически? На самом деле он автоматически одобряет запросы для другого signer – kubernetes.io/kube-apiserver-client-kubelet, который выпускает клиентский сертификат kubelet для подключения к API-серверу. Такой сертификат подписывается автоматически, потому что bootstrap-токен kubelet однозначно подтверждает подлинность запроса.
С signer серверных сертификатов всё иначе. В CSR указывается набор SAN – имена узла и его IP-адреса, – но Kubernetes не может проверить, соответствуют ли они действительности. Скомпрометированный kubelet может отправить CSR с SAN другого узла и после автоматического одобрения получить серверный сертификат, действительный для этого узла. Затем он сможет выдать себя за чужой kubelet перед API-сервером.
Поэтому разработчики Kubernetes выбрали безопасное поведение по умолчанию: оставлять такие CSR в состоянии Pending, пока администратор кластера или явно настроенный контроллер одобрения не примет решение. Так они и остаются там. Бесконечно. Пока кто-нибудь их не одобрит.
Автоматическое одобрение с проверкой реального узла
Рабочая схема – небольшой контроллер, который следит за CSR для signer kubernetes.io/kubelet-serving и одобряет их только после того, как сверит указанные SAN с данными реального узла. Стандартный вариант – alex1989hu/kubelet-serving-cert-approver.
Это один Deployment с минимальным набором RBAC-прав, который решает ровно одну задачу: когда приходит CSR для signer kubelet-serving, контроллер находит запрашивающий Node, сравнивает SAN из запроса со значениями status.addresses узла и его DNS-именем и одобряет CSR только при полном совпадении.
Проект не публикует Helm-чарт, только обычные манифесты в каталоге deploy/. Установка в одном экземпляре выполняется одной командой:
kubectl apply -f https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
Если нужны две реплики с выбором лидера, в deploy/ha-install.yaml есть HA-вариант.
Самая интересная часть этих манифестов – RBAC. Контроллеру одобрения нужны три группы полномочий:
- apiGroups: - certificates.k8s.io resources: - certificatesigningrequests verbs: - get - list - watch - apiGroups: - certificates.k8s.io resources: - certificatesigningrequests/approval verbs: - update - apiGroups: - authorization.k8s.io resources: - subjectaccessreviews verbs: - create - apiGroups: - certificates.k8s.io resourceNames: - kubernetes.io/kubelet-serving resources: - signers verbs: - approve
Первый блок позволяет контроллеру видеть новые CSR. Второй даёт право обновлять подресурс одобрения CSR. Третий, subjectaccessreviews, нужен контроллеру, чтобы спросить у API-сервера: «Пользователь, отправивший запрос, действительно является kubelet того узла, за который себя выдаёт?» Это и есть основная проверка безопасности. Последний блок с resourceNames: ["kubernetes.io/kubelet-serving"] задаёт критически важное узкое ограничение: контроллер может одобрять только CSR для signer kubernetes.io/kubelet-serving. Например, он не сможет одобрить клиентский сертификат, с помощью которого кто-то мог бы выдать себя за API-сервер, даже если увидит такой запрос. RBAC ограничивает ущерб, который способен нанести скомпрометированный контроллер одобрения.
Сам Deployment работает в одной реплике с минимальными запросами ресурсов, отключает все Linux capabilities, запускается без root прав, использует корневую файловую систему только для чтения и задаёт профиль seccomp RuntimeDefault. Обычное усиление защиты, о котором стоило бы и не упоминать, если бы это не был минимально допустимый уровень для компонента с полномочиями одобрять сертификаты на уровне всего кластера для signer, которому доверяет API-сервер.
Проверяем, что всё действительно работает
После распространения конфигурации kubelet и запуска контроллера одобрения CSR обрабатываются почти мгновенно. Команда kubectl get csr показывает их в состоянии Approved,Issued:
$ kubectl get csr NAME AGE SIGNERNAME REQUESTOR CONDITION csr-7gpld 12s kubernetes.io/kubelet-serving system:node:talos-node-0 Approved,Issued csr-ll3rp 12s kubernetes.io/kubelet-serving system:node:talos-node-1 Approved,Issued csr-wqx4n 12s kubernetes.io/kubelet-serving system:node:talos-node-2 Approved,Issued
Если CSR остаётся в состоянии Pending дольше нескольких секунд, контроллер не справляется со своей задачей – проверьте его логи. Обычно причин две: ошибка в RBAC, когда сервисный аккаунт не привязан к кластерной роли, чаще всего из-за опечатки в пространстве имён, или несовпадение SAN. Второе означает, что фактические адреса узла не совпадают с указанными в CSR. Такое бывает, если узел переименовали, но не перезагрузили, либо kubelet подхватил устаревшее имя хоста.
После выпуска сертификатов можно проверить всю цепочку напрямую: подключиться к порту kubelet и с помощью openssl посмотреть, кто подписал сертификат:
echo | openssl s_client -connect <node-ip>:10250 -showcerts 2>/dev/null \ | openssl x509 -noout -issuer -subject
Теперь в поле issuer должен быть указан CA кластера, а не самоподписанный CN самого kubelet. В CN субъекта будет system:node:<node-name>, а в SAN – имя и адреса узла. API-сервер с параметром --kubelet-certificate-authority, указывающим на CA кластера, наконец сможет проверить этот сертификат. Теперь kubectl logs и kubectl exec работают через сквозное TLS-соединение с аутентификацией.
Это также устраняет проблему для сборщиков метрик, которые обращаются к kubelet напрямую. Задания VictoriaMetrics и Prometheus для сбора метрик kubelet, а также любые другие компоненты, работающие с :10250/metrics/cadvisor, могут отказаться от --insecure-skip-tls-verify и проверять сертификат по набору сертификатов CA кластера. Для каждой отдельной рабочей нагрузки это мелочь, но именно она отличает стек наблюдаемости, который действительно проверяет источник метрик, от стека, доверяющего любому ответившему серверу.
Что это даёт
Проверяемый TLS на каждом уровне – основа, на которой держится остальная часть стека. Межсервисный mTLS теперь сводится к одному манифесту Certificate, а не превращается в отдельный проект. Сервисы на внешнем периметре получают общедоверенные сертификаты с помощью одной аннотации issuer. Инструменты наблюдаемости избавляются от длинного хвоста флагов insecure-skip-tls-verify. А в следующей статье – о секретах, идентификации, ingress и остальных компонентах кластерного уровня – всё это уже можно будет считать решённой задачей.

Тема доверия внутри Kubernetes не заканчивается на TLS: рядом всегда остаются управление секретами и безопасная доставка изменений. На бесплатных занятиях можно разобрать эти части инфраструктуры с экспертами и заодно оценить формат обучения:
27 июля в 20:00. «K8S + Vault — как получать секреты?». Записаться
10 августа в 20:00. «Kubernetes + CI/CD + GitOps — делаем стабильный деплой без выхода из кластера». Записаться
Больше бесплатных уроков и других полезных материалов по инфраструктуре смотрите в дайджесте.
m_sinelnikov
Отличная статья, но можно проще, как мне кажется:
Включить ротацию сертификатов на узлах: В конфигурацию каждого узла (в Talos это
machine.kubelet.extraConfig, в других дистрибутивах — аргументы kubelet) добавьте параметрserverTLSBootstrap: trueилиrotate-server-certificates: true. Это заставит kubelet запрашивать у Kubernetes новый сертификат, подписанный доверенным CA кластера.Установите автоматический аппрувер: Примените в кластере манифест контроллера
kubelet-serving-cert-approverодной командойkubectl apply -f https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
Этот контроллер автоматически проверяет и одобряет запросы (CSR) от kubelet, сверяя их с информацией о нодах . Он настроен так, чтобы одобрять только запросы, подписанные
kubernetes.io/kubelet-serving