Блог Amazon Web Services

Новые возможности: представляем группы безопасности (security groups) для подов

Оригинал статьи: ссылка (Mike Stefaniak, Sr. Product Manager и Sri Saran Balaji, Sr. Software Development Engineer)

Группы безопасности (security groups), представляющие собой сетевой экран на уровне инстансов (instances), являются одним из наиболее важных и часто используемых сервисов в любой архитектуре на AWS. Нас не удивило, что интеграция групп безопасности с подами Kubernetes стала одной из самых запрашиваемых функций в Amazon Elastic Kubernetes Service (Amazon EKS), как можно увидеть в нашей общедоступной дорожной карте. Мы рады объявить о возможности назначения групп безопасности EC2 непосредственно на поды, запущенные в кластерах Amazon EKS.

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

Защита приложений на уровне сети

Контейнерные приложения часто требуют доступа к другим сервисам, запущенным в кластере, а также к внешним сервисам AWS, таким как Amazon Relational Database Service (Amazon RDS) или Amazon ElastiCache. В AWS контроль сетевого доступа между сервисами часто осуществляется с помощью групп безопасности EC2. До сегодняшнего дня назначать группы безопасности можно было только на уровне узлов кластера, и все поды, расположенные на одном узле, использовали одинаковые группы. Чтобы обойти это ограничение, приходилось разворачивать отдельные группы узлов (node groups) для каждого приложения и настраивать сложные правила taint и affinity, чтобы поды были запущены на необходимых узлах. Таким неэффективным процессом трудно управлять в больших масштабах, и он может привести к недоиспользованию узлов, как показано ниже.

Архитектура без использования групп безопасности для подов

Роли IAM для сервисных учётных записей решают проблему безопасности подов на уровне аутентификации, но требования к безопасности во многих компаниях также предписывают сегментацию на уровне сети в качестве дополнительного шага по усилению защиты. Сетевые политики Kubernetes предоставляют возможность контроля сетевого трафика внутри кластера, но не поддерживают контроль доступа к ресурсам AWS за его пределами. Кроме того, компаниям, которые проводят модернизацию приложений путём миграции сервисов из виртуальных машин в контейнеры Kubernetes, может оказаться проще использовать уже накопленные в процессе эксплуатации приложений знания, инструменты и опыт по работе с политиками групп безопасности, вместо того чтобы переделывать правила с использованием сетевых политик Kubernetes. Это особенно важно в случае, если команда, отвечающая за безопасность, уже создала политики соответствия правилам безопасности (compliance programs), основанные на использовании групп безопасности.

Группы безопасности для подов позволяют запускать приложения с разными требованиями к сетевому доступу на общих вычислительных ресурсах, что упрощает выполнение требований сетевой безопасности. С помощью групп безопасности EC2 правила сетевого доступа между подами или от подов к внешним сервисам AWS могут быть заданы в одном месте и применяться к приложениям с помощью стандартных API Kubernetes. Используя группы безопасности на уровне подов, можно упростить архитектуру приложения и групп узлов, как показано ниже.

Архитектура с использованием групп безопасности для подов

Как это работает

В кластеры Amazon EKS были добавлены два новых компонента: мутирующий webhook и контроллер ресурсов Amazon Virtual Private Cloud (VPC), в котором запущен ваш кластер. Webhook отвечает за добавление значений limits и requests к подам, которым требуются группы безопасности. Контроллер отвечает за управление сетевыми интерфейсами, которые назначены этим подам. Для реализации функции управления группами безопасности на каждый узел будет выделен один сетевой интерфейс trunk и несколько branch-интерфейсов. Trunk-интерфейс работает как обычный сетевой интерфейс, назначенный на инстанс. Контроллер ресурсов VPC затем привязывает branch-интерфейсы к trunk-интерфейсу. Благодаря этому увеличивается количество сетевых интерфейсов, которые могут быть созданы на каждом инстансе. Так как группы безопасности назначаются на сетевые интерфейсы, теперь мы можем создавать поды с необходимыми группами безопасности, используя дополнительные branch-интерфейсы на рабочих узлах кластера. Давайте рассмотрим подробнее, как работает эта функция.

Этап 1: Инициализация узла и объявление о доступных branch-интерфейсах

Этап 1: Инициализация узла и объявление о доступных branch-интерфейсах

Возможность управления группами безопасности на уровне подов включается с помощью переменной конфигурации плагина Amazon VPC CNI. После этого IP address management daemon (ipamd) добавит метку (label) в Kubernetes для всех поддерживаемых типов инстансов. Контроллер ресурсов VPC затем объявит branch-интерфейсы как дополнительные ресурсы (extended resources) на соответствующих узлах кластера. Максимальное количество branch-интерфейсов дополняет существующие ограничения количества вторичных IP-адресов в инстансах. Например, инстанс c5.4xlarge может, как и раньше, иметь до 234 вторичных IP-адресов, назначенных на стандартные сетевые интерфейсы, и до 54 branch-интерфейсов. Trunk и branch интерфейсы доступны на большинстве инстансов, основанных на AWS Nitro, включая семейства m5, m6g, c5, c6g, r5, r6g, g4 и p3. Если для ваших рабочих нагрузок не требуется изоляции с использованием групп безопасности, вам не нужно производить никаких изменений, чтобы они продолжали использовать вторичные IP-адреса на общих сетевых интерфейсах.

Этап 2: Создание подов на узлах

Этап 2: Создание подов на узлах

Для рабочих нагрузок, которым требуется назначение различных групп безопасности, мы выбрали стандартный подход в Kubernetes и добавили новый ресурс с помощью Custom Resource Definition (CRD). Администраторы кластера могут указать, какие группы безопасности назначить на поды, с помощью CRD SecurityGroupPolicy. В рамках одного пространства имён (namespace), поды можно выбрать с помощью их собственных меток или меток, назначенных на их сервисную учётную запись. Также необходимо указать идентификаторы групп безопасности, которые будут применяться ко всем выбранным подам.

Webhook следит за любыми изменениями в ресурсах SecurityGroupPolicy и автоматически назначает подходящим подам request, чтобы они создавались на узлах с необходимым количеством доступных branch-интерфейсов. После того, как под был создан, контроллер ресурсов создаст branch-интерфейс и подключит его к trunk-интерфейсу. После его успешного создания контроллер добавит аннотацию к объекту пода с информацией о branch-интерфейсе.

Контроллеру ресурсов VPC необходимы права доступа EC2 для изменения ресурсов VPC в соответствии с требованиями подов в кластере. Чтобы облегчить задачу настройки доступа, мы создали IAM-политику AmazonEKSVPCResourceController, управляемую AWS. Так как контроллер запускается в Kubernetes control plane, для использования групп безопасности с подами необходимо назначить эту политику на IAM роль, используемую кластером.

Этап 3: Настройка сети подов

Настройка сети подов

На этом этапе расширение VPC CNI настраивает сеть пода. Расширение запрашивает у ipamd информацию о branch-интерфейсе, а затем получает аннотацию пода от сервера API Kubernetes. После получения информации CNI создаёт устройство Virtual LAN (vlan) на trunk-интерфейсе. Это устройство предназначено только для указанного branch-интерфейса пода и не используется совместно с другими подами на узле. CNI затем создаёт таблицу маршрутизации со стандартными маршрутами, используя устройство vlan и ассоциирует virtual ethernet device (veth) пода с этим интерфейсом. Наконец, расширение CNI добавляет правила iptables, чтобы весь трафик, идущий в указанные veth и vlan, использовал созданную таблицу маршрутизации.

Начало работы

В следующем руководстве мы рассмотрим типичный сценарий, в котором полезно использовать прямое назначение групп безопасности на поды для разрешения доступа к базе данных Amazon RDS. В примере ниже мы используем комбинацию ролей IAM для сервисных учётных записей, а также групп безопасности на уровне подов для реализации стратегии глубокой защиты.

Создание кластера EKS

Используйте утилиту eksctl для создания кластера. Убедитесь, что вы используете как минимум версию 0.27.0. Скопируйте конфигурацию ниже и сохраните её в файле cluster.yaml:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: sgp-cluster
  region: us-west-2
 
iam:
  withOIDC: true
 
managedNodeGroups:
  - name: sample-ng
    instanceType: m5.xlarge
    desiredCapacity: 1
    privateNetworking: true
eksctl create cluster -f cluster.yml

Получите идентификатор VPC, который был создан eksctl вместе с кластером.

VPCID=$(aws eks describe-cluster --name sgp-cluster \
--query "cluster.resourcesVpcConfig.vpcId" \
--output text)
echo $VPCID

Создание базы данных PostgreSQL с помощью Amazon RDS

Перед созданием базы данных давайте создадим группу безопасности, которая будет использоваться приложениями, которым требуется доступ к базе.

RDSSG=$(aws ec2 create-security-group --group-name RDSDbAccessSG \
   --description "Security group to apply to apps that need access to RDS" --vpc-id $VPCID \
   --query "GroupId" \
   --output text)

# By default, security groups have unrestricted outbound traffic. If you are modifying a custom SG, uncomment the below section to allow it
#aws ec2 authorize-security-group-egress --group-id $RDSSG \
#   --protocol all \
#   --cidr 0.0.0.0/0

echo $RDSSG

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

Теперь следуйте инструкциям по созданию базы данных PostgreSQL (убедитесь, что вы используете тот же VPC, в котором был создан кластер Amazon EKS). Используйте только что созданную группу безопасности при создании экземпляра вашей базы данных. Включите аутентификацию с помощью IAM и создайте в базе данных учётную запись, использующую аутентификацию IAM.

Разрешение подам использовать свои собственные сетевые интерфейсы

Для включения групп безопасности на уровне подов необходимо использовать плагин Amazon VPC CNI как минимум версии 1.7. Документация Amazon EKS содержит инструкции, как узнать текущую версию и обновить её, если требуется. После того, как вы убедились, что в кластере используется необходимая версия VPC CNI, запустите следующую команду, чтобы включить сетевые интерфейсы на подах:

kubectl set env daemonset -n kube-system aws-node ENABLE_POD_ENI=true

Создание сервисной учётной записи для подов, которым требуется доступ к RDS

Скопируйте следующую конфигурацию и замените ARN политики на тот, который был создан при настройке базы данных RDS. Сохраните конфигурацию в файл serviceaccount.yaml:

apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
 
metadata:
  name: sgp-cluster
  region: us-west-2
 
iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: rds-db-access
      namespace: default
      labels: {role: "backend"}
    attachPolicyARNs:
    - "arn:aws:iam::123456789012:policy/my-policy"
eksctl create iamserviceaccount --config-file=serviceaccount.yaml

Применение SecurityGroupPolicy к кластеру

Давайте выведем на экран идентификаторы групп безопасности, которые мы добавим к SecurityGroupPolicy. Первая группа, которую необходимо использовать – это группа безопасности кластера. Она позволяет подам взаимодействовать с другими подами в кластере, например, с CoreDNS. Вторая группа безопасности была создана выше и используется для приложений, которым нужен доступ к базе данных RDS.

CLUSTERSG=$(aws eks describe-cluster --name sgp-cluster \
   --query "cluster.resourcesVpcConfig.clusterSecurityGroupId" \
   --output text)
# print security group IDs
echo $CLUSTERSG $RDSSG

Скопируйте конфигурацию ниже и замените идентификаторы групп безопасности в секции groupIds на значения, полученные на предыдущем шаге, и сохраните результат в файл sgp-policy.yaml:

apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: my-sg-policy
spec:
  serviceAccountSelector: 
    matchLabels: 
      role: backend
  securityGroups:
    groupIds: 
      - sg-yyyyyy
      - sg-zzzzzz
kubectl apply -f sgp-policy.yaml

SecurityGroupPolicy является ресурсом CustomResourceDefinition в рамках одного пространства имён. В указанной конфигурации мы назначаем группы безопасности любым подам в пространстве имён по умолчанию и с сервисной учётной записью, содержащей метку с ключом role и значением backend. Обратите внимание, что политики SecurityGroupPolicy применяются только к новым созданным подам и не влияют на уже запущенные поды.

Создайте приложение-пример для подключения к RDS

На следующем шаге мы создаём контейнер с простым приложением на Python, которое подключается к нашей базе данных PostgreSQL и выводит её версию, если подключение прошло успешно, либо ошибку в противном случае. Этот пример создан на основе документации RDS. Сохраните его в файл postgres_test_iam.py.

import os

import boto3
import psycopg2

HOST = os.getenv('HOST')
PORT = "5432"
USER = os.getenv('USER')
REGION = "us-west-2"
DBNAME = os.getenv('DATABASE')

session = boto3.Session()
client = boto3.client('rds', region_name=REGION)

token = client.generate_db_auth_token(DBHostname=HOST, Port=PORT, DBUsername=USER, Region=REGION)

conn = None
try:
    conn = psycopg2.connect(host=HOST, port=PORT, database=DBNAME, user=USER, password=token, connect_timeout=3)
    cur = conn.cursor()
    cur.execute("""SELECT version()""")
    query_results = cur.fetchone()
    print(query_results)
    cur.close()
except Exception as e:
    print("Database connection failed due to {}".format(e))
finally:
    if conn is not None:
        conn.close()

Сохраните следующие инструкции в файл Dockerfile.

FROM python:3.8.5-slim-buster
ADD postgres_test_iam.py /
RUN pip install psycopg2-binary boto3
CMD [ "python", "-u", "./postgres_test_iam.py" ]

Теперь давайте создадим образ нашего контейнера и сохраним его в Amazon ECR. Убедитесь, что вы используете свой идентификатор аккаунта AWS в командах ниже.

docker build -t postgres-test .
aws ecr create-repository --repository-name postgres-test-demo
aws ecr get-login-password | docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-west-2.amazonaws.com
docker tag postgres-test 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
docker push 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest

Развёртывание приложения-примера

Теперь давайте развернём это приложение и проверим, что только нужные поды могут получить доступ к базе данных RDS. Сохраните пример ниже в файл postgres-test.yaml. Замените переменные окружения HOST, DATABASE и USER на значения из шага выше, где вы создавали базу данных RDS.

apiVersion: v1
kind: Pod
metadata:
  name: postgres-test
spec:
  serviceAccountName: rds-db-access
  containers:
  - name: postgres-test
    image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: "REPLACE_DATABASE_HOSTNAME"
    - name: DATABASE
      value: "REPLACE_DATABASE_NAME"
    - name: USER
      value: "REPLACE_USER"
kubectl apply -f postgres-test.yaml

Давайте проверим в логах, что под действительно смог подключиться к базе данных RDS.

kubectl logs postgres-test
('PostgreSQL 12.3 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 4.8.5 20150623 (Red Hat 4.8.5-11), 64-bit',)

Успех! Теперь, чтобы провести более тщательное тестирование, давайте немного поменяем конфигурацию пода и уберём сервисную учётную запись. В этом случае под больше не будет подходить под метки, заданные в SecurityGroupPolicy, и не должен иметь доступа к базе данных. Имейте в виду, что мы создали наш кластер с одним рабочим узлом, поэтому под будет создан на том же узле, что и раньше. Сохраните конфигурацию ниже в файл postgres-test-no-sa.yaml.

apiVersion: v1
kind: Pod
metadata:
  name: postgres-test-no-sa
spec:
  containers:
  - name: postgres-test
    image: 123456789012.dkr.ecr.us-west-2.amazonaws.com/postgres-test-demo:latest
    env:
    - name: HOST
      value: "REPLACE_DATABASE_HOSTNAME"
    - name: DATABASE
      value: "REPLACE_DATABASE_NAME"
    - name: USER
      value: "REPLACE_USER"
kubectl apply -f postgres-test-no-sa.yaml 
kubectl logs postgres-test-no-sa 
Database connection failed due to timeout expired

И всё! Два пода на одном и том же узле, но только у одного из них есть доступ к нашей базе данных.

Заключение

Есть много вещей, которые стоит учитывать при создании защищённого кластера Kubernetes. У каждой компании есть свои политики безопасности и соответствия, некоторые из которых тесно связаны с группами безопасности. Если это ваш случай, то возможность назначения групп безопасности напрямую к подам может упростить существующие паттерны развёртывания приложений и облегчить миграцию рабочих нагрузок из Amazon EC2 в Amazon EKS.

В этой статье мы показали, как группы безопасности подов могут использоваться вместе с ролями IAM для сервисных учётных записей, при имплементации стратегии глубокой безопасности для подов на сетевом уровне и уровне аутентификации. Группы безопасности для подов доступны уже сегодня на всех новых кластерах Amazon EKS, использующих Kubernetes версии 1.17. Поддержка этого функционала для существующих кластеров будет развёрнута в ближайшие недели. Узнать больше можно в документации Amazon EKS.