Ещё

Взаимная аутентификация сервисов (mTLS)

mTLS между сервисами через SPIRE на базе Cilium.

Введение

Взаимная аутентификация позволяет Cilium проверить идентичность обеих сторон соединения до передачи трафика между подами. Для проверки используется SPIRE (SPIFFE Runtime Environment):

  • SPIRE Server управляет корнем доверия и выдаёт удостоверения рабочим нагрузкам;
  • SPIRE Agent работает на каждом узле и подтверждает сведения о запущенных на нём подах;
  • после аттестации рабочая нагрузка получает X.509-SVID — удостоверение SPIFFE, которое включает сертификат и закрытый ключ;
  • агенты Cilium используют SVID, чтобы взаимно подтвердить идентичность источника и получателя трафика.

В платформе «Штурвал» образы spiffe/spire-server и spiffe/spire-agent версии 1.15.3 входят в Модуль управления сетями кластера (shturval-networking, Cilium 1.20.4). Отдельный ShturvalServiceConfig (SSC) для SPIRE не создаётся. Чтобы включить SPIRE, примените ShturvalServicePatch (SSP) к SSC shturval-networking.

Чтобы потребовать взаимную аутентификацию для определённого трафика, задайте authentication.mode: required в правиле CiliumNetworkPolicy или CiliumClusterwideNetworkPolicy. Cilium разрешит соответствующий правилу трафик только после успешной проверки идентичности обеих сторон. Если поле не задано, политика разрешает или блокирует трафик по остальным условиям без взаимной аутентификации.

  • В Cilium 1.20 взаимная аутентификация имеет статус beta. Параметры и поведение функции могут измениться в следующих версиях.
  • Агенты Cilium выполняют взаимную аутентификацию отдельно от потока данных. Эта функция не шифрует трафик между подами. Для шифрования настройте WireGuard или IPsec.
  • Взаимная аутентификация не поддерживается для трафика между кластерами, объединёнными с помощью Cluster Mesh: SPIRE не выдаёт им удостоверения из общего корня доверия.
  • Используйте SPIRE, который устанавливает чарт shturval-networking. Подключить к этой функции собственную реализацию mTLS вместо платформенного SPIRE нельзя.

Взаимная аутентификация сервисов полезна в следующих случаях:

  • Проверка идентичности сервисов. Одного совпадения трафика с селекторами и портами сетевой политики недостаточно: перед передачей данных Cilium дополнительно проверяет удостоверения обеих сторон.
  • Защита от подключения неподтверждённых рабочих нагрузок. Удостоверение получает под, сведения о котором SPIRE Agent подтвердил по данным узла. Взаимная аутентификация не заменяет RBAC, политики допуска и контроль меток: легитимно созданные поды с одинаковыми метками могут иметь одну Cilium identity.
  • Выполнение требований к аутентификации межсервисных соединений. Проверку идентичности можно использовать вместе с шифрованием трафика через WireGuard или IPsec. Эти механизмы настраиваются независимо.

Включение SPIRE

Шаг 1. Применить SSP для включения SPIRE

Создайте SSP со следующими параметрами и загрузите его через импорт манифестов.

apiVersion: ops.shturval.tech/v1beta2
kind: ShturvalServicePatch
metadata:
  name: shturval-networking-mtls
spec:
  shturvalServiceConfigName: shturval-networking
  customvalues:
    authentication:
      enabled: true
      mutual:
        spire:
          enabled: true
          install:
            enabled: true
            server:
              podSecurityContext:
                fsGroup: 1000
                runAsGroup: 1000
                runAsUser: 1000
ПараметрОписание
authentication.enabledВключает подсистему аутентификации Cilium.
authentication.mutual.spire.enabledВключает использование SPIRE для взаимной аутентификации Cilium.
authentication.mutual.spire.install.enabledУстанавливает SPIRE Server и SPIRE Agent с помощью чарта модуля.
authentication.mutual.spire.install.server.podSecurityContext.fsGroupЗадаёт идентификатор группы, от имени которой под SPIRE Server получает доступ к подключённым томам.
authentication.mutual.spire.install.server.podSecurityContext.runAsGroupЗапускает процессы контейнеров SPIRE Server с идентификатором основной группы 1000.
authentication.mutual.spire.install.server.podSecurityContext.runAsUserЗапускает процессы контейнеров SPIRE Server с идентификатором пользователя 1000.

По умолчанию SPIRE Server хранит данные на постоянном диске, поэтому они сохраняются после перезапуска пода сервера. Если выделить постоянный диск невозможно, добавьте в этот же SSP параметр authentication.mutual.spire.install.server.dataStorage.enabled: false. В этом режиме данные хранятся только в памяти пода. После его перезапуска SPIRE Server потеряет сохранённое состояние и выдаст удостоверения заново.

Скриншот: импорт SSP для включения SPIRE

Импорт SSP для включения SPIRE

Шаг 2. Проверить применение SSP и работу SPIRE

Откройте Сервисы и репозитории → Установленные сервисы, найдите Модуль управления сетями кластера (shturval-networking) и перейдите к управлению сервисом. Убедитесь, что на вкладке Примененные ShturvalServicePatch отображается shturval-networking-mtls, а на вкладке Сервис указан статус Patched.

Скриншоты: применённый SSP

SSP для включения SPIRE в списке применённых SSP

Параметры применённого SSP для включения SPIRE

Проверьте, что в неймспейсе cilium-spire запущены под StatefulSet spire-server и поды DaemonSet spire-agent. DaemonSet создаёт по одному агенту на каждом подходящем узле.

kubectl get pods -n cilium-spire
kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server healthcheck

Ответ Server is healthy. подтверждает, что SPIRE Server работает и готов принимать запросы агентов.

Скриншот: проверка подов и состояния SPIRE Server

Поды SPIRE и результат проверки состояния SPIRE Server

Проверьте список аттестованных SPIRE Agent:

kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server agent list

В результате должен отображаться отдельный агент для каждого узла, на котором работает DaemonSet spire-agent. Затем проверьте, что в SPIRE зарегистрированы делегированные удостоверения Cilium Agent и Cilium Operator:

kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -parentID spiffe://spiffe.cilium/ns/cilium-spire/sa/spire-agent

В выводе должны присутствовать SPIFFE ID spiffe://spiffe.cilium/cilium-agent и spiffe://spiffe.cilium/cilium-operator.

SPIFFE ID в сертификате пода имеет вид spiffe://spiffe.cilium/identity/<идентификатор-Cilium>. Число в конце соответствует имени объекта CiliumIdentity, назначенного поду.

Шаг 3. Потребовать взаимную аутентификацию в сетевой политике

Добавьте authentication.mode: required в правило ingress или egress, для которого требуется взаимная аутентификация.

Применяйте взаимную аутентификацию к фактическому межсервисному соединению.

В следующем примере сервис orders обращается к сервису payments в том же неймспейсе. Политика разрешает входящий трафик к подам с меткой app: payments только от подов с меткой app: orders, только на TCP-порт 8080 и только после взаимной аутентификации.

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: payments-mutual-auth
  namespace: app
spec:
  description: "Входящий трафик к payments только от orders и только после взаимной аутентификации"
  endpointSelector:
    matchLabels:
      app: payments
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: orders
            io.kubernetes.pod.namespace: app
      authentication:
        mode: required
      toPorts:
        - ports:
            - port: "8080"
              protocol: TCP

Наличие секции ingress включает для выбранных подов модель default deny для входящего трафика: Cilium отбрасывает любой поток, который не соответствует разрешающему правилу. Подробнее см. в разделе Поведение по умолчанию.

Пример проверки взаимной аутентификации

HTTP-код ответа не показывает, выполнялась ли взаимная аутентификация: разрешённый запрос успешно завершается как с authentication.mode: required, так и без этой настройки. Результат проверки можно дополнительно просмотреть в Hubble UI.

В примере далее создаются две рабочие нагрузки в неймспейсе mtls-test: под pod-worker обращается к сервису echo. Сначала политика разрешает только запрос GET /headers без взаимной аутентификации. Затем в то же правило добавляется authentication.mode: required.

Перед добавлением authentication.mode: required убедитесь, что SPIRE Server возвращает Server is healthy., как описано в разделе Проверка.

Образы echoserver и netshoot загружаются из публичных реестров. Для проверки в закрытом контуре замените их доступными образами: сервер должен принимать HTTP-запросы на порту 3000, а образ клиента — содержать команду curl.

Шаг 1. Создать тестовые рабочие нагрузки

Создайте неймспейс и поды:

apiVersion: v1
kind: Namespace
metadata:
  name: mtls-test
---
apiVersion: v1
kind: Service
metadata:
  name: echo
  namespace: mtls-test
  labels:
    app: echo
spec:
  selector:
    app: echo
  ports:
    - name: http
      port: 3000
      targetPort: 3000
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: echo
  namespace: mtls-test
  labels:
    app: echo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: echo
  template:
    metadata:
      labels:
        app: echo
    spec:
      containers:
        - name: echo
          image: gcr.io/k8s-staging-ingressconformance/echoserver:v20221109-7ee2f3e
          ports:
            - containerPort: 3000
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-worker
  namespace: mtls-test
  labels:
    app: pod-worker
spec:
  containers:
    - name: netshoot
      image: nicolaka/netshoot:latest
      command: ["sleep", "infinite"]

Дождитесь Running у обоих подов:

kubectl -n mtls-test get pods

Шаг 2. Проверить удостоверение SPIFFE

Проверьте, что для рабочей нагрузки echo создано удостоверение SPIFFE. Следующие команды предназначены для Bash:

IDENTITY_ID=$(kubectl -n mtls-test get cep -l app=echo -o=jsonpath='{.items[0].status.identity.id}')
echo $IDENTITY_ID
kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -spiffeID spiffe://spiffe.cilium/identity/$IDENTITY_ID

Команда echo должна вывести числовой идентификатор. В результате entry show должен отображаться SPIFFE ID с тем же числом и селектор cilium:mutual-auth.

Чтобы просмотреть все удостоверения рабочих нагрузок, зарегистрированные Cilium Operator в SPIRE, и сопоставить их с объектами CiliumIdentity, выполните:

kubectl exec -n cilium-spire spire-server-0 -c spire-server -- /opt/spire/bin/spire-server entry show -selector cilium:mutual-auth
kubectl get ciliumidentities

Числа в конце SPIFFE ID должны соответствовать значениям в столбце NAME объектов CiliumIdentity.

Шаг 3. Применить политику без взаимной аутентификации

Примените политику без взаимной аутентификации. Она разрешает поду pod-worker отправлять только запрос GET /headers на порт 3000 сервиса echo:

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: mutual-auth-echo
  namespace: mtls-test
spec:
  endpointSelector:
    matchLabels:
      app: echo
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: pod-worker
      toPorts:
        - ports:
            - port: "3000"
              protocol: TCP
          rules:
            http:
              - method: GET
                path: /headers
Скриншот: импорт политики без взаимной аутентификации

Импорт политики без взаимной аутентификации

kubectl -n mtls-test exec pod-worker -- curl -s -o /dev/null -w "%{http_code}\n" --max-time 10 http://echo:3000/headers
kubectl -n mtls-test exec pod-worker -- curl -s --max-time 10 http://echo:3000/headers-1
Скриншот: проверка L7-правила политики

Результат проверки разрешённого и запрещённого HTTP-пути

Первая команда должна вывести код 200. Второй запрос не соответствует L7-правилу, поэтому Cilium блокирует обращение к пути /headers-1.

Шаг 4. Применить политику с обязательной взаимной аутентификацией

Замените политику вариантом с обязательной взаимной аутентификацией:

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: mutual-auth-echo
  namespace: mtls-test
spec:
  endpointSelector:
    matchLabels:
      app: echo
  ingress:
    - fromEndpoints:
        - matchLabels:
            app: pod-worker
      authentication:
        mode: required
      toPorts:
        - ports:
            - port: "3000"
              protocol: TCP
          rules:
            http:
              - method: GET
                path: /headers
Скриншот: состояние политики с обязательной взаимной аутентификацией

Состояние политики с обязательной взаимной аутентификацией

Шаг 5. Проверить взаимную аутентификацию

Повторите две команды проверки. Ожидаемый результат не изменится: запрос к /headers вернёт 200, а запрос к /headers-1 будет заблокирован L7-правилом. При первом обращении Cilium может временно отбрасывать пакеты, пока агенты выполняют взаимную аутентификацию. Если первый запрос завершился по тайм-ауту, выполните его повторно.

Откройте Hubble UI и отфильтруйте потоки по неймспейсу mtls-test. Сначала для соединения отобразится событие отбрасывания пакета с причиной Authentication required, затем — событие Authentication со значением Spire. Код ответа 200 вместе с событием Spire подтверждает, что взаимная аутентификация прошла успешно.

Если для Cilium включён уровень журналирования debug, успешную взаимную аутентификацию можно дополнительно проверить по журналам агентов:

kubectl -n kube-system -c cilium-agent logs -l k8s-app=cilium --timestamps=true | grep "Policy is requiring authentication\|Validating Server SNI\|Validated certificate\|Successfully authenticated"
Скриншот: успешная взаимная аутентификация в журналах Cilium

Сообщения об успешной взаимной аутентификации в журналах Cilium

Сообщение Policy is requiring authentication означает, что политика потребовала аутентификацию. Сообщения Validated certificate и Successfully authenticated подтверждают успешную проверку сертификата и завершение взаимной аутентификации. Если журналирование debug выключено, эти сообщения могут отсутствовать; в этом случае проверяйте результат в Hubble UI.

Шаг 6. Удалить тестовые ресурсы

Удалите тестовый неймспейс вместе с созданными в нём подами, сервисом и политикой. SSP shturval-networking-mtls останется в кластере:

kubectl delete namespace mtls-test

Поле authentication поддерживается только в политиках Cilium. В стандартном Kubernetes NetworkPolicy это поле недоступно.