Всем привет! Меня зовут Илья, я работаю DevOps-инженером в команде разработки. Мы активно используем микросервисный подход, и, из-за специфики нашей работы, для нас важна безопасность межсервисного взаимодействия. В этой статье я хочу описать принцип работы Istio и на примере показать, как использовать некоторые ее возможности по обеспечению безопасности. Надеюсь, это окажется полезным для решения ваших задач. Приятного чтения!



Для чего вообще нужна сервисная сеть


Service mesh, в данном случае Istio – это обвязка для всего того, что требуется для управления и конфигурирования межсервисного взаимодействия: маршрутизация, аутентификация, авторизация, трассировка, контроль доступа и многое другое. И хотя существует масса открытых библиотек, чтобы реализовать эти функции непосредственно в коде сервиса, с Istio можно получить все то же самое, ничего не добавляя в сам сервис.

Компоненты 


Статья написана для istio 1.6

Про изменения
Istio развивается семимильными шагами, и это очень чувствуется на практике. Нередки случаи, когда точно помнишь, что настраивал какую-то функциональность определенным образом, но на официальном сайте уже висит мануал с описанием конфигурации совсем других, новых объектов. Так произошло, например, в Istio 1.4 с внедрением  новой v1beta1 авторизационной политики, когда убрали множество объектов Istio RBAC. Или в версии 1.5 поменяли подход к компонентам, и вместо трех старых отдельных компонентов Pilot, Galley и Citadel появился один общий istiod. Множество пособий по настройке в сети может стать неактуальными из-за этих нововведений. Дальше по ходу статьи я буду специально выделять эти моменты.

Istio логически разбита на плоскость данных (data plane) и плоскость управления (control plane).
Плоскость данных это совокупность прокси-серверов (Envoy), добавленных к pod’у в виде сайдкаров. Эти прокси-серверы обеспечивают и контролируют всю сетевую связь между микросервисами и конфигурируются из плоскости управления.

Плоскость управления (istiod) обеспечивает service discovery, настройку и управление сертификатами. Она конвертирует Istio объекты в понятные для Envoy конфигурации и распространяет их в плоскости  данных.



Компоненты Istio service mesh

Добавить envoy в pod приложения можно как вручную, так и настроив автоматическое добавление с помощью Mutating Admission webhook, который Istio добавляет при своей установке. Для этого нужно проставить на необходимый неймспейс метку istio-injection=enabled.

Кроме прокси-сайдкара с envoy Istio добавит в pod специальный init контейнер, который будет перенаправлять боевой трафик в контейнер с envoy. Но каким же образом это достигается? В данном случае никакой магии нет, и реализуется это установкой дополнительных правил iptables в сетевой неймспейс pod’а.

Про потребление ресурсов
По нашему опыту, в небольшом кластере из примерно 100 сервисов добавление Istio увеличивает задержки ответа микросервисов на ~2-3 мс, каждый envoy занимает порядка 40 Мб памяти, и потребление CPU возрастает в среднем на 5%-7% на pod.

Давайте на практике посмотрим, как сайдкар захватывает входящий и исходящий трафик из контейнера. Для этого взглянем на сетевое пространство какого-нибудь pod’а с добавленным Istio сайдкаром подробнее. 

Демо стенд
Для практических примеров я буду использовать свеже установленный Kubernetes кластер с Istio. 
Локально развернуть Kubernetes несложно с помощью minikube:

Linux:
curl -Lo minikube https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 && chmod +x minikube
sudo mkdir -p /usr/local/bin/
sudo install minikube /usr/local/bin/

minikube start --driver=<driver_name> // --driver=none запустит все докер контейнеры прямо на локальной машине.


MacOS:
brew install minikube
minikube start --driver=<driver_name>



Istio с demo профилем можно установить по документу с официального сайта:

curl -L https://istio.io/downloadIstio | sh -
cd istio-1.6.3
export PATH=$PWD/bin:$PATH
istioctl install --set profile=demo

Для примеров межсервисного взаимодействия я буду использовать два микросервиса: productpage и details. Они оба идут в стандартной поставке Istio для демонстрации ее возможностей.

Установка демо микросервисов
kubectl label namespace default istio-injection=enabled
kubectl apply -f samples/bookinfo/platform/kube/bookinfo.yaml




Посмотрим список контейнеров приложения productpage:

kubectl -n default get pods productpage-v1-7df7cb7f86-ntwzz -o jsonpath="{.spec['containers','initContainers'][*].name}"
productpage 
istio-proxy 
istio-init

Кроме самого productpage, в pod'е работают sidecar istio-proxy (тот самый envoy) и init контейнер istio-init.

Посмотреть на настроенные в пространстве имена pod’а iptables-правила можно с помощью утилиты nsenter. Для этого нам надо узнать pid процесса контейнера:

docker inspect k8s_productpage --format '{{ .State.Pid }}'
16286

Теперь мы можем посмотреть правила iptables, установленные в этом контейнере.



Видно, что практически весь входящий и исходящий трафик теперь перехватывается и перенаправляется на порты, на которых его уже поджидает envoy. 

Включаем взаимное шифрование трафика


Объекты Policy и MeshPolicy были удалены из istio 1.6. Вместо них предлагается использовать объект PeerAuthentication

Istio позволяет зашифровать весь трафик между контейнерами, причем сами приложения даже не будут знать, что общаются через tls. Делается это самим Istio из коробки буквально одним манифестом, так как в сайдкары-прокси уже смонтированы клиентские сертификаты. 

Алгоритм такой: 

  1. Прокси-серверы envoy на стороне клиента и на стороне сервера проверяют подлинность друг друга перед отправкой запросов;
  2. Если проверка прошла успешно, клиентский прокси шифрует трафик и отправляет его на серверный прокси;
  3. Прокси-серверная сторона расшифровывает трафик и локально перенаправляет его фактической службе назначения.

Включить mTLS можно на разных уровнях:

  • На уровне всей сети;
  • На уровне неймспейса;
  • На уровне конкретного pod’а. 

Режимы работы:

  • PERMISSIVE: разрешен и зашифрованный, и plain text трафик;
  • STRICT: разрешен только TLS;
  • DISABLE: разрешен только plain text.

Обратимся к сервису details из pod’а productpage с помощью curl без включенного TLS и посмотрим, что придет к details с помощью tcpdump:

Запрос:



Дампим трафик:



Все тело и заголовки прекрасно читаемы в plain text.

Включим tls. Для этого создадим объект типа PeerAuthentication в неймспейсе с нашими pod’ами.

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: default
spec:
  mtls:
    mode: STRICT

Запустим запрос из product page к details опять и посмотрим, что удастся получить:



Трафик зашифрован

Авторизация


Объекты ClusterRbacConfig, ServiceRole, and ServiceRoleBinding были удалены вместе с внедрением новой авторизационной политики. Вместо них предлагается использовать объект AuthorizationPolicy.

С помощью политик авторизации Istio может настраивать доступ одного приложения к другому. Причем, в отличие от чистых Kubernetes network policies, это работает на L7 уровне. Например, для http-трафика можно тонко управлять методами и путями запроса.

Как мы уже видели в предыдущем примере, по умолчанию доступ открыт для всех pod’ов всего кластера.

Теперь запретим все активности в неймспейсе default с помощью такого yaml-файла:

apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: deny-all
  namespace: default
spec:
  {}

И попробуем достучаться до сервиса details:

curl details:9080
RBAC: access denied

Отлично, теперь наш запрос не проходит.

А теперь настроим доступ так, чтобы проходил только GET запрос и только по пути /details, а все остальные запросы отклонялись. Для этого есть несколько вариантов:

  • Можно настроить, чтобы проходили запросы с определенными хедерами;
  • По сервисному аккаунту приложения;
  • По исходящему ip-адресу;
  • По исходящему неймспейсу;
  • По claims в JWT токене.

Самое простое в обслуживании это настроить доступ по сервисному аккаунту приложения, благо предварительной настройки для этого не потребуется, так как демо-приложение bookinfo уже идет с созданными и подмонитрованными service account.

Для использования политик авторизации на основе сервис аккаунтов необходимо включить взаимную аутентификацию TLS.

Настраиваем новую политику доступа:

apiVersion: "security.istio.io/v1beta1"
kind: "AuthorizationPolicy"
metadata:
  name: "details-viewer"
  namespace: default
spec:
  selector:
    matchLabels:
      app: details
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/default/sa/bookinfo-productpage"]
    to:
    - operation:
        methods: ["GET"]
        paths: ["/details/*"]

И пробуем достучаться вновь:

root@productpage-v1-6b64c44d7c-2fpkc:/# curl details:9080/details/0
{"id":0,"author":"William Shakespeare","year":1595,"type":"paperback","pages":200,"publisher":"PublisherA","language":"English","ISBN-10":"1234567890","ISBN-13":"123-1234567890"}

Все работает. Попробуем другие методы и пути:

root@productpage-v1-6b64c44d7c-2fpkc:/# curl -XPOST details:9080/details/0
RBAC: access denied
root@productpage-v1-6b64c44d7c-2fpkc:/# 
root@productpage-v1-6b64c44d7c-2fpkc:/# curl -XGET details:9080/ping
RBAC: access denied

Заключение


В заключение отмечу, что рассмотренные возможности это лишь малая толика того, что умеет Istio. Из коробки мы получили и настроили межсервисное шифрование трафика и авторизацию, правда, ценой добавления дополнительных компонентов и, следовательно, дополнительным расходом ресурсов. 

Всем спасибо!