Vault от HashiСorp — довольно известное open-source-решение для хранения секретов и неплохая альтернатива реализации секретов в Kubernetes. Vault использует свой сайдкар-контейнер на каждом поде, который получает секреты из хранилища и доставляет их в под или же реализует доступ к секретам через csi-драйвер.

Но как быть, если необходимо положить секреты из Vault в секреты Kubernetes? Например, мы хотим хранить и обновлять свой tls-сертификат для ингресса из Vault. Или мы решили использовать gitops и хотим, чтобы в репозитории безопасно хранилось все описание инфраструктуры, в том числе и секретов Kubernetes?

Разберем этот сценарий на практике и реализуем его с помощью vault-secrets-operator.

Схема работы vault-secrets-operator

При использовании оператора у нас появляется возможность создать CRD-объект с описанием kubernetes-секрета. Cамо это описание мы можем безопасно хранить в репозитории, а его данные будут находиться в Vault:

Установка и настройка Vault. На эту тему написано уже много мануалов, так что  подробно останавливаться не буду. Например, можно положиться на официальную документацию установки через helm-чарт в кластер Kubernetes

С его помощью несложно сделать HA-установку c базой PostgreSQL и настроенным ингрессом. Главное в этом случае — не забыть создать в базе таблицы для HA-режима. После необходимо проинициализировать под командой vault operator init, если хотите вручную распечатывать Vault при перезапусках подов.

Итак, у нас на руках токен доступа, наш Vault настроен и доступен извне. Ставим себе консольную утилиту Vault на десктоп и создаем переменные окружения (у вас, конечно, будут свои значения).

export VAULT_ADDR=https://vault.example.cloud 
VAULT_TOKEN=s.cyVpg9kDV10CQt9fEaIhdo

Проверяем статус нашего vault-сервера.

vault status
Key         	Value
---         	-----
Seal Type   	shamir
Initialized 	true
Sealed      	false
Total Shares	5
Threshold   	3
Version     	1.7.3
Storage Type	postgresql
Cluster Name	vault-cluster-d487efe3
Cluster ID      11aa752a-d492-8784-6c26-e9a3e05952f9
HA Enabled  	true
HA Cluster      https://vault-0.vault-internal:8201
HA Mode     	active
Active Since    2021-07-08T09:30:53.565132822Z

Здесь мы также можем обратить внимание на: 

  • Sealed — запечатан ли наш Vault;

  • HA Enabled — включен ли HA-режим работы;

  • Total shares — общее количество ключей;

  • Threshold — количество ключей, необходимое для распечатывания.

Отправка сертификата в Vault. В нашем примере для простоты воспроизведения будем использовать самоподписанный tls-сертификат.

Генерируем его для домена site.example.cloud:

openssl req -x509 -newkey rsa:4096 -keyout tls.key -out tls.crt -nodes -days 365 -subj '/CN=site.example.cloud'

Создаем key-value хранилище в Vault и закидываем в него сгенерированный сертификат:

vault secrets enable -path=ingress-tls kv
vault write ingress-tls/site.example.cloud tls.crt=@tls.crt tls.key=@tls.key

На всякий случай проверим наш сертификат в Vault:

vault read ingress-tls/site.example.cloud

Cоздание политики на чтение секретов и генерация токена. Сертификат на месте. Теперь создадим политику и сгенерируем на ее основе токен доступа к хранилищу, где лежит сертификат.

cat <<EOF | vault policy write vault-secrets-operator -
path "ingress-tls/site.example.cloud" {
  capabilities = ["read"]
}
EOF
vault token create -period=720h -policy=vault-secrets-operator

Настройка vault-secrets-operator

Сначала устанавливаем репозиторий с чартом:

helm repo add ricoberger https://ricoberger.github.io/helm-charts
helm repo update

Предполагается, что наш токен доступа к Vault в процессе работы будет продлеваться.  Создаем kubernetes-секрет для его хранения.

Переводим токен в формат base64 и описываем секрет.

cat <<EOF >> operator-secrets.yaml
apiVersion: v1
kind: Secret
metadata:
  name: vault-secrets-operator
  namespace: vault-operator
type: Opaque
data:
  VAULT_TOKEN: $(echo -n s.W4ndAJbuoDMsoLDZyVBG18F2 | base64)
EOF

Описываем конфигурацию нашего helm-чарта:

cat <<EOF >> values.yaml
environmentVars:
  - name: VAULT_TOKEN
    valueFrom:
      secretKeyRef:
        name: vault-secrets-operator   # указываем наш секрет
        key: VAULT_TOKEN
  - name: VAULT_TOKEN_LEASE_DURATION
value: "720"
vault:
  address: "https://vault.example.cloud"
  authMethod: token
  reconciliationTime: 15  #Время в секундах, через которое оператор будет обновлять секрет
EOF

Создаем неймспейс и применяем созданную конфигурацию:

kubectl create ns vault-operator
kubectl create -f operator-secrets.yaml

Устанавливаем helm-chart:

helm install vault-secrets-operator ricoberger/vault-secrets-operator -n vault-operator -f values.yaml

Установка закончена.

Теперь с помощью оператора посмотрим, как будет выглядеть наш секрет. 

cat <<EOF | kubectl apply -f -
apiVersion: ricoberger.de/v1alpha1
kind: VaultSecret
metadata:
  name: ingress-tls
spec:
  path: ingress-tls/site.example.cloud
  type: kubernetes.io/tls 
EOF

В дальнейшем при работе проверить взаимодействие секрет-оператора с vault-сервером можно в логах пода.

Там мы увидим, как по тайм-ауту синхронизируются наши секреты, а также происходит процесс продления токена (renew token):

{"level":"info","ts":1627985973.7251098,"logger":"vault","msg":"Renew Vault token"}
{"level":"info","ts":1627985978.8396158,"logger":"controllers.VaultSecret","msg":"Use shared client to get secret from Vault","vaultsecret":"default/ingress-tls"}
{"level":"info","ts":1627985978.839656,"logger":"vault","msg":"Read secret ingress-tls/site.example.cloud"}
{"level":"info","ts":1627985978.90391,"logger":"controllers.VaultSecret","msg":"Updating a Secret","vaultsecret":"default/ingress-tls","Secret.Namespace":"default","Secret.Name":"ingress-tls"}
{"level":"info","ts":1627985993.925628,"logger":"controllers.VaultSecret","msg":"Use shared client to get secret from Vault","vaultsecret":"default/ingress-tls"}
{"level":"info","ts":1627985993.9256816,"logger":"vault","msg":"Read secret ingress-tls/site.example.cloud"}
{"level":"info","ts":1627985993.9865305,"logger":"controllers.VaultSecret","msg":"Updating a Secret","vaultsecret":"default/ingress-tls","Secret.Namespace":"default","Secret.Name":"ingress-tls"}

Проверяем kubernetes-секрет в кластере:

kubectl get secrets ingress-tls -o yaml

Секрет на месте. Все готово!

В дальнейшем мы можем применить tls-сертификат, к примеру, на нашем тестовом ингрессе. Не забудьте перед запуском отредактировать под себя.

 kubectl create -f https://raw.githubusercontent.com/xor222xor/habr-ingress/main/manifest.yml

Смотрим наш сертификат снаружи:

openssl s_client -showcerts -connect site.example.cloud:443 </dev/null 2>/dev/null | openssl x509 -noout -text | grep Issuer

Что в результате

Я разобрал простой способ использования HashiCorp Vault для хранения секретов Kubernetes. В дальнейшем манифест c описанием секрета можно безопасно хранить в git-репозитории и использовать в своих интеграциях, а данные централизованно держать в Vault. 

Конечно, не стоит забывать, что при получении полного доступа к кластеру даже Vault не решит проблем с безопасностью.

Комментарии (2)


  1. epicfilemcnulty
    05.08.2021 12:38
    +1

    А смысл использовать сторонний vault-operator, когда официальный vault-agent, который ставится официальным же чартом, позволяет в аннотациях указать, какие секреты из волта нужны deployment'у?

         annotations:
            vault.hashicorp.com/agent-inject: "true"
            vault.hashicorp.com/role: "consul-agent"
            vault.hashicorp.com/secret-volume-path: "/consul/config"
            vault.hashicorp.com/agent-inject-secret-ca.crt: "consul-pki/issue/agent"
            ...


    1. kubedtln Автор
      05.08.2021 12:48

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