# Vault CSI Driver

<https://habr.com/ru/companies/ru_mts/articles/716624/>

Добрый день, Хабр! Мы — Михаил Панов и [Евгений Прудченко](https://habr.com/ru/users/eapdark/), [DevOps‑инженеры](https://career.habr.com/companies/mts/vacancies) из команды МТС Digital, работаем на проекте External WebSSO. Мы занимаемся внедрением DevOps практик и инструментов в рамках нашего проекта. В этой статье расскажем о интеграции и доставке секретов из Vault в Kubernetes с помощью Vault CSI Provider.

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-8c65e593267b525eedd44ffd50e45dbc3e212675%2Feaaf2393b84146f2b4ff39ba9d0b27e0.jpg?alt=media)

Изучив вопрос доставки секретов, мы выяснили, что мало кто использует **Vault CSI Provider**. Нам это показалось несправедливым, ведь, на наш взгляд, это отличный инструмент. Поэтому мы и решили поделиться нашим опытом.

Основная проблема которую хотелось решить — как получить секреты из Vault, меняя всего лишь несколько строк в файле values.yaml нашего helm‑chart. Задача грандиозная, поэтому нам пришлось пройти длинный путь к ее решению.

## Часть 1: Выбор и сравнение функционала (CSI Provider vs Sidecar Agent Injector)

Так как мы определились с выбором хранилища секретов, нам осталось определиться со способом доставки секретов в pods. Выбирали между **CSI Provider** и **Sidecar Agent Injector**. Оба официально поддерживаются Hashicorp и интегрируются с **Kubernetes**.

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

### 1. Sidecar Agent Injector

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-08543ee5d94f7953f62f4ac82027243b54f43576%2F60060257abc313841e12c64a251e5696.png?alt=media)

Мы сразу определили для себя некоторые минусы этого подхода, а именно:

* дополнительный sidecar для каждого pod — он будет дополнительно «нагружать наш кластер»;
* Sidecar Agent Injector не поддерживает преобразование секретов Vault в секреты Kubernetes, что требовалось в нашем случае.

### 2. CSI Provider

![](https://296194292-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FLoAqAoOfr7XVUQw7Gff8%2Fuploads%2Fgit-blob-da070edc0221a745d361fee37d1a0bf141e111ef%2Fdc1d1cb80058c19821dd936b25bede61.png?alt=media)

*Плюсы:*

* разворачивается как Daemonset;
* позволяет монтировать секреты в файловую систему pod;
* драйвер Vault CSI Provider поддерживает преобразование секретов Vault в секреты и переменные среды Kubernetes.

*Минусы:*

* Vault CSI Provider не поддерживает ротацию и извлечение секретов при их изменении в Vault. Но об этом расскажем в следующем пункте.

## Часть 2: Проблемы, с которыми мы столкнулись, и их решения

**Проблема 1:** мы не добавляли ротацию секретов, поэтому получить секреты можно только при деплое.

**Решение** — чтобы секреты обновлялись по мере их добавления/обновления можно использовать:

* [Reloader](https://gitlab.com/johnmkane/tech-recipe-book/-/blob/main/Book/Architect/Kubernetes/Secrets/Vault%20CSI%20Driver/\[https:/github.com/stakater/Reloader]\(https:/github.com/stakater/Reloader\)/README.md);
* Hash для Helm, но тут есть трудности с тем, что секрет создается после деплоя и нужно добавлять аннотацию уже на задеплоенное приложение.

**Проблема 2:** переполнение кластера Vault незавершенными арендами и дальнейшая его недоступность.

Это связано с тем, что на каждый запрос секрета CSI Driver создает новую аренду в методе аутентификации kubernetes/, а так как по умолчанию ее ttl составляет 60 минут, кластер переполняется очень быстро — за 1–3 дня.

**Решение** — установка default‑lease‑ttl для всего метода аутентификации kubernetes/ в 30 секунд. Этого достаточно, чтобы получить секрет и не захламлять кластер.

## Часть 3: Реализация

Этот этап — самый простой.

*Помните — пример ниже категорически не рекомендован к установке в «продуктовом» контуре.*

**1. Прежде всего необходимо развернуть в кластере Kubernetes Vault и Vault CSI Provider:**

```
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
    --set "server.dev.enabled=true" \
    --set "injector.enabled=false" \
    --set "csi.enabled=true"
```

**2. Переходим к установке secret store csi driver:**

При установке secret store csi driver необходимо добавить пару «ключей», а именно:

* включить ротацию секретов enableSecretRotation для того, чтобы получать секреты из Vault при их изменении;
* добавить интервал ротации этих секретов rotationPollInterval — как часто secret store csi driver будет проверять изменения секрета.

```
helm repo add secrets-store-csi-driver https://kubernetes-sigs.github.io/secrets-store-csi-driver/charts
helm install csi secrets-store-csi-driver/secrets-store-csi-driver \
    --set syncSecret.enabled=true \
    --set enableSecretRotation=true \
    --set "rotationPollInterval=30s"
```

**3. Настраиваем авторизацию Kubernetes и Default lease TTL:**

```
vault auth enable kubernetes
vault write auth/kubernetes/config \
    kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
```

Командой ниже выставляем Default-lease-ttl, для того чтобы наш Vault не заполнялся ненужными «арендами».

```
vault auth tune -default-lease-ttl=30s kubernetes/
```

**4. Добавляем Роль, Политику и Секрет.**

Теперь добавим тестовые данные, с которыми будем работать.

Роль:

```
vault write auth/kubernetes/role/database \
    bound_service_account_names=webapp-sa \
    bound_service_account_namespaces=default \
    policies=internal-app
```

Политика:

```
vault policy write internal-app - <<EOF
path "secret/data/test-pass" {
  capabilities = ["read"]
}
```

Секрет:

```
vault kv put secret/test-pass test-password="db-secret-password"
```

**5. Тестирование.**

Проверим что все pod «стартанули»:

```
kubectl get pods
```

Создадим Custom Resource:

```
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-database
spec:
  provider: vault
  parameters:
    vaultAddress: "http://vault.default:8200"
    roleName: "database"
    objects: |
      - objectName: "test-secret"
        secretPath: "secret/data/test-pass"
        secretKey: "test-password"
```

Создадим Serviceaccount:

```
kubectl create serviceaccount webapp-sa
```

Создадим pod который будет запрашивать секрет из Vault:

```
kind: Pod
apiVersion: v1
metadata:
  name: webapp
spec:
  serviceAccountName: webapp-sa
  containers:
  - image: jweissig/app:0.0.1
    name: webapp
    volumeMounts:
    - name: secrets-store-inline
      mountPath: "/mnt/secrets-store"
      readOnly: true
  volumes:
    - name: secrets-store-inline
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: "vault-database"
```

Проверим секрет внутри нашего pod:

```
kubectl exec webapp -- cat /mnt/secrets-store/test-secret
```

Тут мы должны получить вывод: db‑secret‑password.

**Теперь смонтируем секрет как переменную среды.**

Добавим еще один CustomResource:

```
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: vault-database
spec:
  provider: vault
  secretObjects:
  - data:
    - key: test-password
      objectName: test-password
    secretName: dbpass
    type: Opaque
  parameters:
    vaultAddress: "http://vault.default:8200"
    roleName: "database"
    objects: |
      - objectName: "test-password"
        secretPath: "secret/data/test-pass"
        secretKey: "test-password"
```

Создадим pod, который будет запрашивать секрет из Vault и монтировать как переменную среды.

```
kind: Pod
apiVersion: v1
metadata:
  name: webapp-env
spec:
  serviceAccountName: webapp-sa
  containers:
  - image: jweissig/app:0.0.1
    name: webapp
    env:
    - name: DB_PASSWORD
      valueFrom:
        secretKeyRef:
          name: dbpass
          key: test-password
    volumeMounts:
    - name: secrets-store-inline
      mountPath: "/mnt/secrets-store"
      readOnly: true
  volumes:
    - name: secrets-store-inline
      csi:
        driver: secrets-store.csi.k8s.io
        readOnly: true
        volumeAttributes:
          secretProviderClass: "vault-database"
```

На этом установка, настройка и тестирование закончены.

Вуаля, теперь мы можем полноценно добавлять секреты в Vault и использовать их в Kubernetes как:

* Secret;
* монтировать как volume в pod и использовать как конфиг‑файл.

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