
Всем привет! Меня зовут Сергей Смирнов, я DevOps экосистемы «Своё» в РСХБ-Интех (технологическая дочка Россельхозбанка).
Сегодня я расскажу вам об одном из способов доставки секретов в kubernetes из Hashicorp Vault.
Hashicorp Vault уже довольное время на слуху и зарекомендовал как надежная система хранения конфиденциальной информации(секретов): пароли, ssh ключи, api токены, сертификатов и пр.
Hashicorp Vault поможет решить сразу несколько задач:
• Создать единственное хранилище секретов;
• Защитить от утечки секретов;
• Сделать работу с секретами для пользователей удобной.
На текущий момент существует довольно много интергации Hashicorp Vault с популярными системами CI/CD, облачными провайдерами, системами оркестрации/контейнеризации
Мы в работе активно используем kubernetes. Основной задачей было найти решение, чтобы получить на выходе секрет kubernetes.
Перепробовав несколько способов доставить секреты в kubernetes используя Hashicorp Vault, мы остановились на External Secrets Operator.
Пробовали:
-
Средствами Hashicorp Vault. Vault Agent монтирует директорию с секретами через init или sidecar контейнеры. Не подходит, т.к. возможна лишняя нагрузка на кластер при создании лишних подов. Плюс не умеют преобразовать секреты Vault в секреты Kubernetes, а сохраняет секреты в файлы.
-
Bank Vault Не подходит, сохраняет секреты в переменные окружения.
-
ArgoCD Vault Plugin Умеет работать с секретами kubernetes, но показался сложным в настройке.
External Secrets Operator простыми словами — это оператор для kubernetes, который автоматически синхронизирует секреты из внешних API и создает их в kubernetes. Если секрет во внешнем API изменяется, контроллер обновляет секреты.
С точки зрения безопасности kubernetes есть пара моментов:
-
Необходимо настраивать RBAC в kubernetes, чтобы исключить доступ до ресурса secrets. Для того, чтобы пользователь не смог прочитать секрет.
-
Если есть необходимость, ограничить доступ к кластеру. Если у пользователя есть доступ к поду, он сможет посмотреть переменные/значение секрета через терминал или логи пода.
Со стороны Hashicorp Vault все зашифровано. Чтобы получить доступ к api, необходимо владеть соответствующими привилегиями для авторизации.
Плюс при рестарте кластер запечатывается, если конечно не настроено автораспечатывание кластера(autounseal) транзитными ключами. Для ручного распечатывания по умолчанию требуются 3 из 5 ключей(unseal keys). В идеале ключи должны храниться у разных людей.
Реализация по этапам
Что потребуется для реализации?
-
Кластер kubernetes версии минимум 1.22+ согласно документации
-
Helm версии 3.6+ согласно документации
Этап 1
1. Установка vault
Добавляем репозиторий hashicorp и устанавливаем vault. В качестве бекенда Integrated Storage (Raft).
Integrated Storage (Raft) работает таким образом, что все узлы в кластере Vault будут иметь реплицированную копию данных Vault. Integrated Storage Raft официально поддерживается Hashicorp.
Бекап можно запускать на любом из узлов.
Команды довольно простые:
-
vault operator raft snapshot save new.snapshot
-
vault operator raft snapshot restore new.snapshot
helm repo add hashicorp https://helm.releases.hashicorp.com helm install vault hashicorp/vault \ --set='server.ha.enabled=true' \ --set='server.ha.raft.enabled=true' \ -n vault \ --create-namespace
Проверяем, что все поды запустились:
kubectl get po -n vault NAME READY STATUS RESTARTS AGE vault-0 1/1 Running 0 1d vault-1 1/1 Running 0 1d vault-2 1/1 Running 0 1d
2. Инициализация и распечатывание кластера Hashicorp Vault
Инициализируем vault
kubectl exec -ti vault-0 -- vault operator init
После инициализации vault получим корневой ключ и 5 ключей для распечатки.
Unseal Key 1: wQRU1yjL88Plb3rPSQPUfLw1KOCsPMACpXLY0Ixbdhfg Unseal Key 2: ai3NajyVDFyqG5Lz6pDYNX118ti9Slqo2vueQhtg6Usq Unseal Key 3: x1a0VneA8cBkcXMDkxPwOOByPzlwuUw3dNaa7hfUqDAx Unseal Key 4: oztkcLGBAesVQwyO7Kc059xlqq9YSh1vkEkQFzKlnwae Unseal Key 5: 5cVmmDVZ7BIbQolCQdCoUXhdTRojPD2rgE1t83QgRKNn Initial Root Token: s.lTEYiTAv63CLsf0FqBcS672x
Распечатаем кластер. По умолчанию используются 3 unseal ключа.
kubectl exec -ti vault-0 -- vault operator unseal
После присоединим оставшиеся 2 пода к кластеру.
kubectl exec -ti vault-1 -- vault operator raft join http://vault-0.vault-internal:8200 kubectl exec -ti vault-1 -- vault operator unseal
kubectl exec -ti vault-2 -- vault operator raft join http://vault-0.vault-internal:8200 kubectl exec -ti vault-2 -- vault operator unseal
После распечатки кластера авторизируемся в vault используя корневой ключ(Initial Root Token).
kubectl exec -ti vault-0 -- vault login
Проверим состояние кластера
kubectl exec -ti vault-0 -- vault operator raft list-peers Node Address State Voter ---- ------- ----- ----- 4rzii8af-8847-7f28-23f0-p36vwkghqxng vault-0.vault-internal:8201 leader true aydfyon3-6b3x-1b7x-9b34-bdzpa1el2dzf vault-1.vault-internal:8201 follower true 2tjh4iqi-8bcv-8b2n-1b7s-wj6wtxgOk5cs vault-2.vault-internal:8201 follower true
3. Создание политики на чтение секретов из Hashicorp Vault
Мы используем вторую версию KV хранилища, поэтому путь до секрета будет /projects/data/ и /projects/metadata.
Одно из отличий между версиями, что вторая поддерживает версионирование.
Плюс выдадим права на обновление токена.
vault policy write read-secret - <<EOF path "/projects/data/dev/*" { capabilities = ["read", "list"] } path "/projects/metadata/dev/*" { capabilities = ["read", "list"] } path "auth/token/renew-self" { capabilities = ["update"] } EOF
4. Настройка авторизации kubernetes
vault auth enable kubernetes vault write auth/kubernetes/config \ kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \ issuer="https://kubernetes.default.svc.cluster.local"
создадим роль
vault write auth/kubernetes/role/read-secret \ bound_service_account_names=vault-auth \ bound_service_account_namespaces=vault \ policies=read-secret \ alias_name_source=serviceaccount_name \ ttl=1h
создадим service account
kubectl create serviceaccount vault-auth -n vault
6. Создадим секрет
vault kv put projects/dev/app user=admin password=123456 ==== Secret Path ==== projects/data/dev-app ======= Metadata ======= Key Value --- ----- created_time 2023-08-18T10:28:38.163062633Z custom_metadata <nil> deletion_time n/a destroyed false version 1
Этап 2
1. Установка External Secrets Operator
helm repo add external-secrets https://charts.external-secrets.io helm install external-secrets \ external-secrets/external-secrets \ -n vault \ --create-namespace
Проверяем, что все поды запустились:
kubectl get po -n vault NAME READY STATUS RESTARTS AGE external-secrets-h4ls02c0wd-1cxyr 1/1 Running 0 58d external-secrets-cert-controller-h4lfd8bfzg-km8ez 1/1 Running 0 58d external-secrets-webhook-su3x3fOjk-uxsy2 1/1 Running 0 58d
2. Настройка External Secrets Operator
External Secrets Operator — набор custom resources таких как: ExternalSecret, SecretStore и ClusterSecretStore.
Мы рассмотрим только ExternalSecret и SecretStore, т.к. для каждого пространства имен(namespace) свои секреты. ClusterSecretStore будет доступен из любого пространства имен(namespace).
SecretStore указывает как получить доступ.
ExternalSecret указывает какие данные нужно извлечь.
Создадим манифесты:
apiVersion: external-secrets.io/v1beta1 kind: SecretStore metadata: name: app namespace: dev-app spec: provider: vault: server: "http://vault.vault:8200" # адрес нашего vault. Складывается из имени сервиса и пространства имен. path: "projects" # имя kv version: "v2" # версия kv auth: kubernetes: # метод авторизации mountPath: "kubernetes" role: "read-secret" # роль для авторизации serviceAccountRef: name: "vault-auth" # имя нашего serviceAccount --- apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: app namespace: dev-app spec: refreshInterval: "15s" secretStoreRef: name: app # имя SecretStore kind: SecretStore target: name: vault-secrets # имя будущего секрета kubernetes data: - secretKey: user # ключ секрета remoteRef: key: dev/app # путь до секрета в vault property: user # ключ секрета в vault - secretKey: password remoteRef: key: dev/app property: password
Проверим, что все создалось
kubectl get externalsecrets -n vault NAME AGE STATUS CAPABILITIES READY secretstore.external-secrets.io/app 14m Valid ReadWrite True NAME STORE REFRESH INTERVAL STATUS READY externalsecret.external-secrets.io/app app 15s SecretSynced True
Проверим наш созданный секрет
kubectl describe secrets -n vault vault-secrets Name: vault-secrets Namespace: vault Labels: <none> Annotations: created-by: system:serviceaccount:vault:external-secrets reconcile.external-secrets.io/data-hash: b749f0dfb88266e3b81d63dbc2a4402b Type: Opaque Data ==== password: 6 bytes user: 5 bytes
kubectl get secrets/vault-secrets --template={{.data.user}} | base64 -D admin kubectl get secrets/vault-secrets --template={{.data.password}} | base64 -D 123456
Подведем небольшой итог. Этот инструмент хорошо решает задачу автоматической синхронизации секретов между Vault и kubernetes. Также ESO поддерживает другие системы управления секретами, такие как AWS Secrets Manager, GCP Secrets Manager, Yandex Lockbox, что упрощает работу. Предлагаю Вам его оценить и написать комментарий под этим постом.
Спасибо за уделенное время! Надеюсь, что эта информация будет полезной для вашей работы и общего развития. Кстати, у нас есть helm чарт для создания secret-store и external-secrets. Мы используем в связке с Argo CD, который очень нам пришёлся кстати. Может и вас он заинтересует, переходите по ссылке на github.
ссылка на оригинал статьи https://habr.com/ru/articles/759816/
Добавить комментарий