# 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.

![](/files/4ttBC50Dn3kg79ez5iU5)

Изучив вопрос доставки секретов, мы выяснили, что мало кто использует **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

![](/files/kGWJaHRHfUJDSs02yUzj)

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

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

### 2. CSI Provider

![](/files/MVBfvqLG9gyUOFvDXhys)

*Плюсы:*

* разворачивается как 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://book.konstantinsecurity.com/readme/architect/kubernetes/secrets/vault-csi-driver.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
