Блог Amazon Web Services

AWS Secrets Controller: прототип интеграции AWS Secrets Manager с Kubernetes

Введение

Kubernetes позволяет хранить и управлять настройками безопасности за пределами PodSpec, используя объект типа «секрет», т.е. API ключ или сертификат безопасности.  Концептуально, это позволяет вам взаимодействовать с «секретами» и их конфигурацией способом, отличным от других механизмов конфигурации объектов в Kubernetes. Тем не менее, большинство клиентов избегали использования секретов Kubernetes для хранения секретных данных, когда этот функционал только появился, по причине отсутствия возможности сильного шифрования с ключом, управляемым клиентом. Именно по этой причине мы решили сделать прототип, описанный в данной статье. Он демонстрирует способ получения секретных данных из внешнего сервиса (AWS Secrets Manager), используя Kubernetes dynamic admission controller.

Последние доработки в EKS

Недавно, EKS добавил поддержку KMS envelope encryption для секретов Kubernetes. С помощью envelope encryption, вы можете использовать ключ, управляемый клиентом AWS KMS, для шифрования ключа данных, который Kubernetes использует для шифрования секретов. Это позволяет улучшить ваш контур безопасности, т.к. появляется дополнительный ключ, хранящийся за пределами Kubernetes. Это идет в дополнение к полному шифрованию диска, которое AWS уже использует для защиты данных, сохраняемых в etcd. Для дополнительной информации о механизмах шифрования данных, применяемых в секретах Kubernetes, смотрите в документации шифрование данных в Kubernetes.

Хотя envelope encryption делает секреты Kubernetes подходящей опцией для хранения секретных данных, есть несколько недостатков в этом подходе. Во-первых, поды (pods) и секреты ограничены namespace. Если поды и секреты находятся в одном namespace, то поды могут прочитать все секреты в этом namespace. Во-вторых, секреты Kubernetes не обновляются автоматически. Если вам нужно обновлять (ротировать) секреты на периодической основе, вы вынуждены это делать вручную.

Альтернатива секретам Kubernetes

Исторически, клиенты обходили описанные выше ограничения секретов Kubernetes с помощью использования внешних провайдеров хранения секретов, таких как Hashicorp Vault, который подддерживает и автоматическую ротацию секретов, и гранулярные пермиссии. Он также интегрируется с Kubernetes с помощью Kubernetes Service Accounts и мутирующих webhooks. Service Account назначает на под удостоверение, которое используется для предоставления доступа к секретам в Vault, в то же время webhook используется для встраивания котейнера инициализации пода, который подключает Секрет из Vault как временный диск. Всё вместе это упрощает использование секретов из Vault изнутри Kubernetes.

Разработанный прототип использует похожий подход, только вместо Vault для хранения секретов используется AWS Secrets Manager. По сравнению с нативным механизмом секретов Kubernetes, использование AWS Secrets Manager имеет несколько преимуществ. Во-первых, вы можете легко ротировать, управлять и запрашивать секреты для баз данных, API ключей, и других. Во-вторых, сервис предоставляет встроенную поддержку авто-ротации секретов с несколькими AWS сервисами, такими как Amazon RDS, Amazon Redshift, и Amazon DocumentDB. Также есть возможность настроить авто-ротацию других типов секретов. И последнее, сервис даёт возможность управлять доступом к секретам, используя детальные пермиссии контроля доступа и централизованный аудит ротации секретов для ресурсов в AWS Cloud, а также со сторонними сервисами и ресурсами, работающими в локальных дата центрах.

Обзор решения прототипа

Данный прототип использует следующие концепции Kubernetes:

  • Аннотации являются массивом пар ключ-значение. Мы используем аннотации, чтобы управлять включением/выключением контейнера инициализации и указывать AWS ARN секрета.
  • Downward API – механизм получения метаданных о поде. В нашем решении мы используем его для получения пар ключ-значение из полей аннотаций пода.
  • Мутирующий webhook вызывается, когда создаётся под. Он реализован как под, который запущен внутри кластера. Если secret.k8s.aws/sidecarInjectorWebhook: enabled появляется в поле аннотаций для пода, webhook будет встраивать контейнер инициализации в под.
  • IAM Roles for Service Accounts (IRSA) – это способ назначения IAM роли на под Kubernetes. Данное решение использует IRSA для предоставления поду доступа к секрету из AWS Secrets Manager и расшифровки секретов с использованием KMS ключа. Именно посредством ServiceAccount предоставляется доступ к секретам в AWS Secrets Manager.
  • Контейнер инициализации – это контейнер, который выполняется и завершает работу перед непосредственным запуском контейнера вашего приложения. В прототипе контейнер инициализации используется для запроса секрета из Secrets Manager и записи его в emptyDir (RAM хранилище) диск, который подключён к контейнеру приложения.

Когда под с необходимыми аннотациями запускается в кластере, webhook обновит этот под, чтобы тот запустил сначала контейнер инициализации. Указанный в podSpec ServiceAccount имеет доступ к секрету, указанному в secrets.k8s.aws/secret-arn: <secret arn>, контейнер инициализации получит секрет из Secrets Manager и запишет его на RAM диск. Это реализуется путём указания носителя типа Memory для emptyDir диска. Это позволяет избежать хранения секрета на физическом носителе после окончания работы пода. После завершения работы контейнера инициализации, контейнер приложения запускается и подключает RAM диск как свое временное хранилище. Когда приложению нужно обратиться к секрету, он запрашивает его с этого временного подключенного диска.  Схема ниже показывает процесс работы webhook для данного прототипа:

K8S secrets flow

Развёртывание AWS Secrets Admission Controller Webhook

AWS Secrets Admission Controller может быть развёрнут с помощью чарта (chart) Helm, который создаёт следующие объекты в Kubernetes:

  • Развёртывание (deployment) Kubernetes, запускающее admission controller.
  • Сервис Kubernetes для этого развёртывания.
  • Секрет Kubernetes который содержит TLS сертификаты для admission controller.
  • Объект MutatingWebhookConfiguration.

Если вам нужны инструкции по установке Helm, обратитесь к официальной документации Helm здесь.

  1. Добавьте Helm репозиторий, который содержит чарты Helm для secret-inject admission controller webhook.
$ helm repo add secret-inject https://aws-samples.github.io/aws-secret-sidecar-injector/
  1. Helm репозитории часто обновляются. Чтобы синхронизировать локальный репозиторий Helm, нужно периодически запускать команду обновления репозитория.
$ helm repo update
  1. Установите AWS Secret Controller с помощью установки чарта Helm.
$ helm install secret-inject secret-inject/secret-inject
  1. Проверьте, что необходимые объекты Kubernetes были созданы.
$ kubectl get mutatingwebhookconfiguration
NAME CREATED AT
aws-secret-inject 2020-05-10T04:29:20Z

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

Вы можете создавать и управлять секретами в Secrets Manager, используя нативный AWS APIs, но, скорее всего, вы захотите управлять секретами напрямую из Kubernetes. Проект Native Secrets (NASE) – это бессерверный мутирующий webhook. Он реализован как Lambda функция с точкой доступа HTTP API, зарегестрированной в Kubernetes как часть мутирующего webhook объекта. Запросы на создание и обновление нативных секретов Kubernetes “перенаправляются” в webhook, который записывает секреты в Secrets Manager и возвращает ARN секрета обратно в Kubernetes, который сохраняет их как объект типа секрет.

Пример сценария: запрос учётной записи базы данных из AWS Secrets Manager

Для данного прототипа мы развернём тестовый веб-сервер, который использует пароль для доступа к базе данных. Пароль сохранён как секрет в AWS Secrets Manager. Фактический секрет запрашивается контейнером инициализации, который внедряется в под нашим мутирующим webhook.

Мы уже создали секрет в AWS Secrets Manager. Если вам нужна подробная инструкция по созданию секретов в AWS Secrets Manager, обратитесь к документации AWS.

$ aws secretsmanager list-secrets 
SecretList:- ARN: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF
  Description: Password for the MySQL database
  LastChangedDate: '2020-05-18T00:49:46.912000+00:00'
  Name: database-password
  SecretVersionsToStages:
    bc50ebbf-2811-4561-8b6b-7bc1c564267a:
    - AWSCURRENT
  Tags: []

Запишите ARN секрета – он будет использоваться в следующих шагах.

Создание AWS роли для доступа к секретам в AWS Secrets Manager

Далее, мы создаём IAM роль, которая используется нашим веб-сервером для доступа к секрету в AWS Secrets Manager. Мы начнём с создания IAM политики для чтения секретов из AWS Secrets Manager:

aws iam create-policy --policy-name webserver-secrets-policy --policy-document file://policy.json

Ниже вы найдёте пример policy.json файла, который предоставляет пермиссии для чтения секрета базы данных из AWS Secrets Manager:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "webserversecret",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetResourcePolicy",
                "secretsmanager:GetSecretValue",
                "secretsmanager:DescribeSecret",
                "secretsmanager:ListSecretVersionIds"
            ],
            "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF"
        },
        {
            "Sid": "secretslists",
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetRandomPassword",
                "secretsmanager:ListSecrets"
            ],
            "Resource": "*"
        }
    ]
}

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

Примечание: Используйте ARN секрета, созданного на предыдущем шаге.

Теперь, после создания политики, нам нужно создать IAM роль, которую будет использовать наш под. Для данного прототипа, мы используем IAM Roles for Service Accounts (IRSA) для предоставления гранулярного доступа для запроса секретов из AWS Secrets Manager. IRSA требует создания поставщика удостоверений (identity provider) OIDC для IAM. Инструкции по созданию и настройке поставщика удостоверений OIDC вы можете найти здесь.

Чтобы создать IAM роль для использования с IRSA, нам необходимо выполнить следующие шаги:

  1. Установите переменную окружения AWS_Account_ID, выполнив команду:
AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
  1. Установите поставщика удостоверений OIDC в переменную окружения:
OIDC_PROVIDER=$(aws eks describe-cluster --name <cluster-name> --query "cluster.identity.oidc.issuer" --output text | sed -e "s/^https:\/\///")
  1. Мы создаем политику доверенности для роли, чтобы федеративный пользователь OIDC мог использовать роль. Выполните следующий кусок кода:
read -r -d '' TRUST_RELATIONSHIP <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "${OIDC_PROVIDER}:sub": "system:serviceaccount:default:webserver-service-account",
          "${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}
EOF
echo "${TRUST_RELATIONSHIP}" > trust.json

Эта команда создаст файл с названием trust.json

  1. Создайте IAM роль:
$ aws iam create-role --role-name webserver-secrets-role --assume-role-policy-document file://trust.json --description "IAM Role to access webserver secret"
  1. Назначьте политику webserver-secrets-policy, созданную в самом начале раздела, на эту роль:
$ aws iam attach-role-policy --role-name webserver-secrets-role --policy-arn=arn:aws:iam::123456789012:policy/webserver-secret-policy

Создание Kubernetes Service Account

Теперь, когда мы создали IAM роль для использования с IRSA, нам нужно создать service account и связать его с нашей ролью. Для этого нужно выполнить следующие действия:

  1. Создать Kubernetes Service Account.
$ kubectl create sa webserver-service-account
  1. Добавить аннотацию для service account, которая ссылается на IAM роль, созданную ранее. Мы используем ARN роли с именем webserver-secrets-role:
$ kubectl edit sa webserver-service-account
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/webserver-secrets-role
  creationTimestamp: "2020-05-08T00:17:04Z"
  name: webserver-service-account
  namespace: default
  resourceVersion: "13330471"
  selfLink: /api/v1/namespaces/default/serviceaccounts/webserver-service-account
  uid: eef8b19d-7bd0-4390-94ab-186a5d677fd0
secrets:
- name: webserver-service-account-token-x5t4q

Давайте всё проверим в действии

Сначала, развернём под веб-сервера, который подключается к базе данных. Учётная запись доступа к базе данных сохранена как секрет в AWS Secrets Manager.

Создаём Kubernetes объект развёртывания для веб-сервера:

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: webserver
  name: webserver
spec:
  replicas: 1
  selector:
    matchLabels:
      run: webserver
  template:
    metadata:
      annotations:
        secrets.k8s.aws/sidecarInjectorWebhook: enabled
        secrets.k8s.aws/secret-arn: arn:aws:secretsmanager:us-east-1:123456789012:secret:database-password-hlRvvF
      labels:
        run: webserver
    spec:
      serviceAccountName: webserver-service-account
      containers:
      - image: busybox:1.28
        name: webserver
        command: ['sh', '-c', 'echo $(cat /tmp/secret) && sleep 3600']
EOF

Обратите внимание, что аннотация и service account, созданный ранее, указаны в спецификации пода.

Мутирущий webhook создаёт emptyDir диск secret-vol и подключает его в директории /tmp/ для всех контейнеров в поде. Расшифрованное значение секрета записано в /tmp/secret.

В целях демонстрации, под также выводит значение секрета в STDOUT. Это НЕ рекомендуется для производственного окружения.

 $  kubectl logs webserver-66f6bb988f-x774k
{"username":"admin","password":"P@$$word1024","engine":"mysql","host":"database-1.cluster.us-east-1.rds.amazonaws.com","port":3306,"dbClusterIdentifier":"database-1"}

Предостережения

Хотя данный прототип даёт вам нативный для Kubernetes способ запрашивать секреты из AWS Secrets Manager, у него есть несколько нюансов, о которых вам следует знать. Во-первых, это стоимость хранения и извлечения секретов. Во-вторых, Secrets Manager имеет ограничения на размер (64 КБ) секретов и скорость их извлечения, например ограничение для GetSecretValue – 2000 в секунду. Перед внедрением данного решения убедитесь, что вы изучили стоимость решения и указанные ограничения.

Прототип также более сложен по сравнению с нативным способом хранения секретов в Kubernetes. Например, вам нужно установить и зарегистрировать мутирующий webhook. Также нужно правильно аннотировать поды, которые будут использовать секреты из Secrets Manager, и они должны ссылаться на Service Account, который имеет необходимые пермиссии для получения секрета, указанного в аннотации. Тем не менее, если вам нужен механизм для предоставления гранулярного доступа к секретам или вам необходимо иметь возможность ротации секретов, дополнительные накладные расходы могут того стоить.

Наконец, цель данного решения – продемонстрировать способ интеграции, который может быть достигнут между AWS Secrets Manager и Kubernetes. Данное решение не предназначено для использования в производственном окружении.

Направления доработок и улучшений

Прототип AWS Secret Controller позволяет получить доступ к секрету из AWS Secrets Manager, запустив его в качестве контейнера инициализации во время запуска пода. Будущие усовершенствования для этого решения включают запуск sidecar контейнера в поде, чтобы держать секрет в актуальном состоянии всякий раз, когда он меняется в AWS Secrets Manager.

Container Storage Interface (CSI) драйвер, используемый для хранилища секретов, позволяет подключать секреты, пароли и сертификаты из внешних хранилищ секретов корпоративного уровня в виде временных дисков или томов. Данный проект предоставляет подключаемый интерфейс, который можно использовать для интеграции AWS Secrets Manager в качестве внешнего хранилища секретов для Kubernetes.

Выводы

Мы надеемся, что вам было интересно узнать об улучшении сохранности ваших секретов для приложений Kubernetes. Исходный код этого решения можно найти здесь. Не стесняйтесь обращаться через GitHub с любыми вопросами, комментариями и отзывами.