Настройка модуля для работы c Deckhouse Stronghold
Для автоматической настройки работы модуля secrets-store-integration в связке с модулем Deckhouse Stronghold потребуется ранее включенный и настроенный Stronghold.
Далее достаточно применить следующий ресурс:
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
name: secrets-store-integration
spec:
enabled: true
version: 1
Параметр connectionConfiguration можно опустить, поскольку он стоит в значении DiscoverLocalStronghold
по умолчанию.
Настройка модуля для работы с внешним хранилищем
Для работы модуля требуется предварительно настроенное хранилище секретов, совместимое с HashiCorp Vault. В хранилище предварительно должен быть настроен путь аутентификации. Пример настройки хранилища секретов ниже.
Чтобы убедиться, что каждый API запрос зашифрован, послан и отвечен правильным адресатом, потребуется валидный публичный сертификат Certificate Authority, который используется хранилищем секретов. Такой публичный сертификат CA в PEM-формате необходимо использовать в качестве переменной caCert
в конфигурации модуля.
Пример конфигурации модуля для использования Vault-совместимого хранилища секретов, запущенного по адресу «secretstoreexample.com» на TLS-порту по умолчанию - 443 TLS:
apiVersion: deckhouse.io/v1alpha1
kind: ModuleConfig
metadata:
name: secrets-store-integration
spec:
version: 1
enabled: true
settings:
connection:
url: "https://secretstoreexample.com"
authPath: "main-kube"
caCert: |
-----BEGIN CERTIFICATE-----
MIIFoTCCA4mgAwIBAgIUX9kFz7OxlBlALMEj8WsegZloXTowDQYJKoZIhvcNAQEL
................................................................
WoR9b11eYfyrnKCYoSqBoi2dwkCkV1a0GN9vStwiBnKnAmV3B8B5yMnSjmp+42gt
o2SYzqM=
-----END CERTIFICATE-----
Крайне рекомендуется задавать переменную caCert
. Если она не задана, будет использовано содержимое системного ca-certificates.
Подготовка тестового окружения
Для выполнения дальнейших команд необходим адрес и токен с правами root от Stronghold. Такой токен можно получить во время инициализации нового secrets store.
Далее в командах будет подразумеваться что данные настойки указаны в переменных окружения.
export VAULT_TOKEN=xxxxxxxxxxx
export VAULT_ADDR=https://secretstoreexample.com
В этом руководстве мы приводим два вида примерных команд:
- команда с использованием консольной версии Stronghold (Как получить бинарный файл stronghold);
- команда с использованием curl для выполнения прямых запросов в API secrets store.
Для использования инструкций по инжектированию секретов из примеров ниже вам понадобится:
- Создать в Stronghold секрет типа kv2 по пути
demo-kv/myapp-secret
и поместить туда значенияDB_USER
иDB_PASS
. - При необходимости добавляем путь аутентификации (authPath) для аутентификации и авторизации в Stronghold с помощью Kubernetes API удалённого кластера
- Создать в Stronghold политику
myapp-ro-policy
, разрешающую чтение секретов по путиdemo-kv/myapp-secret
. - Создать в Stronghold роль
myapp-role
для сервис-аккаунтаmyapp-sa
в неймспейсеmyapp-namespace
и привязать к ней созданную ранее политику. - Создать в кластере неймспейс
myapp-namespace
. - Создать в созданном неймспейсе сервис-аккаунт
myapp-sa
.
Пример команд, с помощью которых можно подготовить окружение
-
Включим и создадим Key-Value хранилище:
stronghold secrets enable -path=demo-kv -version=2 kv
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request POST \ --data '{"type":"kv","options":{"version":"2"}}' \ ${VAULT_ADDR}/v1/sys/mounts/demo-kv
-
Зададим имя пользователя и пароль базы данных в качестве значения секрета:
stronghold kv put demo-kv/myapp-secret DB_USER="username" DB_PASS="secret-password"
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request PUT \ --data '{"data":{"DB_USER":"username","DB_PASS":"secret-password"}}' \ ${VAULT_ADDR}/v1/demo-kv/data/myapp-secret
-
Проверим, правильно ли записались секреты:
stronghold kv get demo-kv/myapp-secret
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ ${VAULT_ADDR}/v1/demo-kv/data/myapp-secret
-
По умолчанию метод аутентификации в Stronghold через Kubernetes API кластера, на котором запущен сам Stronghold, – включён и настроен под именем
kubernetes_local
. Если требуется настроить доступ через удалённые кластера, задаём путь аутентификации (authPath
) и включаем аутентификацию и авторизацию в Stronghold с помощью Kubernetes API для каждого кластера:stronghold auth enable -path=remote-kube-1 kubernetes
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request POST \ --data '{"type":"kubernetes"}' \ ${VAULT_ADDR}/v1/sys/auth/remote-kube-1
-
Задаём адрес Kubernetes API для каждого кластера:
stronghold write auth/remote-kube-1/config \ kubernetes_host="https://api.kube.my-deckhouse.com"
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request PUT \ --data '{"kubernetes_host":"https://api.kube.my-deckhouse.com"}' \ ${VAULT_ADDR}/v1/auth/remote-kube-1/config
-
Создаём в Stronghold политику с названием
myapp-ro-policy
, разрешающую чтение секретаmyapp-secret
:stronghold policy write myapp-ro-policy - <<EOF path "demo-kv/data/myapp-secret" { capabilities = ["read"] } EOF
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request PUT \ --data '{"policy":"path \"demo-kv/data/myapp-secret\" {\n capabilities = [\"read\"]\n}\n"}' \ ${VAULT_ADDR}/v1/sys/policies/acl/myapp-ro-policy
-
Создаём роль, состоящую из названия пространства имён и политики. Связываем её с ServiceAccount
myapp-sa
из пространства имёнmyapp-namespace
и политикойmyapp-ro-policy
:Важно! Помимо настроек со стороны Stronghold, вы должны настроить разрешения авторизации используемых
serviceAccount
в кластере kubernetes. Подробности в пункте нижеstronghold write auth/kubernetes_local/role/myapp-role \ bound_service_account_names=myapp-sa \ bound_service_account_namespaces=myapp-namespace \ policies=myapp-ro-policy \ ttl=10m
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request PUT \ --data '{"bound_service_account_names":"myapp-sa","bound_service_account_namespaces":"myapp-namespace","policies":"myapp-ro-policy","ttl":"10m"}' \ ${VAULT_ADDR}/v1/auth/kubernetes_local/role/myapp-role
-
Повторяем то же самое для остальных кластеров, указав другой путь аутентификации:
stronghold write auth/remote-kube-1/role/myapp-role \ bound_service_account_names=myapp-sa \ bound_service_account_namespaces=myapp-namespace \ policies=myapp-ro-policy \ ttl=10m
Команда с использованием curl:
curl \ --header "X-Vault-Token: ${VAULT_TOKEN}" \ --request PUT \ --data '{"bound_service_account_names":"myapp-sa","bound_service_account_namespaces":"myapp-namespace","policies":"myapp-ro-policy","ttl":"10m"}' \ ${VAULT_ADDR}/v1/auth/remote-kube-1/role/myapp-role
Важно! Рекомендованное значение TTL для токена Kubernetes составляет 10m.
Эти настройки позволяют любому поду из пространства имён myapp-namespace
из обоих K8s-кластеров, который использует ServiceAccount myapp-sa
, аутентифицироваться и авторизоваться в Stronghold для чтения секретов согласно политике myapp-ro-policy
.
- Создадим namespace и ServiceAccount в указанном namespace:
kubectl create namespace myapp-namespace kubectl -n myapp-namespace create serviceaccount myapp-sa
Как разрешить ServiceAccount авторизоваться в Stronghold?
Для авторизации в Stronghold Pod использует токен, сгенерированный для своего ServiceAccount’а. Для того чтобы Stronghold мог проверить валидность предоставляемых данных ServiceAccount
, используемый сервисом Stronghold должен иметь разрешение на действия get
, list
и watch
для endpoints tokenreviews.authentication.k8s.io
и subjectaccessreviews.authorization.k8s.io
. Для этого также можно использовать clusterRole system:auth-delegator
.
Stronghold может использовать различные авторизационные данные для осуществления запросов в API Kubernetes:
- Использовать токен приложения, которое пытается авторизоваться в Stronghold. В этом случае для каждого сервиса, авторизующегося в Stronghold, требуется в используемом ServiceAccount’е иметь clusterRole
system:auth-delegator
(либо права на API представленные выше). - Использовать статичный токен отдельно созданного специально для Stronghold
ServiceAccount
у которого имеются необходимые права. Настройка Stronghold для такого случая подробно описана в документации Vault.
Инжектирование переменных окружения
Как работает
При включении модуля в кластере появляется mutating-webhook, который при наличии у пода аннотации secrets-store.deckhouse.io/role
изменяет манифест пода, добавляя туда инжектор. В измененном поде добавляется инит-контейнер, который помещает из служебного образа собранный статически бинарный файл-инжектор в общую для всех контейнеров пода временную директорию. В остальных контейнерах оригинальные команды запуска заменяются на запуск файла-инжектора, который получает из Vault-совместимого хранилища необходимые данные, используя для подключения сервисный аккаунт приложения, помещает эти переменные в ENV процесса, после чего выполняет системный вызов execve, запуская оригинальную команду.
Если в манифесте пода у контейнера отсутствует команда запуска, то выполняется извлечение манифеста образа из хранилица образов (реджистри), и команда извлекается из него.
Для получения манифеста из приватного хранилища образов используются заданные в манифесте пода учетные данные из imagePullSecrets
.
Доступные аннотации, позволяющие изменять поведение инжектора
Аннотация | Умолчание | Назначение |
---|---|---|
secrets-store.deckhouse.io/addr | из модуля | Адрес хранилища секретов в формате https://stronghold.mycompany.tld:8200 |
secrets-store.deckhouse.io/auth-path | из модуля | Путь, который следует использовать при аутентификации |
secrets-store.deckhouse.io/namespace | из модуля | Пространство имен, которое будет использоваться для подключения к хранилищу |
secrets-store.deckhouse.io/role | Роль, с которой будет выполнено подключение к хранилищу секретов | |
secrets-store.deckhouse.io/env-from-path | Строка, содержащя список путей к секретам в хранилище через запятую, из которых будут извлечены все ключи и помещены в environment. Приоритет имеют ключи, которые находятся в списке ближе к концу. | |
secrets-store.deckhouse.io/ignore-missing-secrets | false | Запускает оригинальное приложение в случае ошибки получения секрета из хранилища |
secrets-store.deckhouse.io/client-timeout | 10s | Таймаут операции получения секретов |
secrets-store.deckhouse.io/mutate-probes | false | Инжектирует переменные окружения в пробы |
secrets-store.deckhouse.io/log-level | info | Уровень логирования |
secrets-store.deckhouse.io/enable-json-log | false | Формат логов, строка или json |
secrets-store.deckhouse.io/skip-mutate-containers | Список имен контейнеров через пробел, к которым не будет применятся инжектирование |
Используя инжектор вы сможете задавать в манифестах пода вместо значений env-шаблоны, которые будут заменяться на этапе запуска контейнера на значения из хранилища.
Примечание
Подключение переменных из ветки хранилища имеет более высокий приоритет, чем подключение явно заданных переменных из хранилища. Это значит, что при использовании одновременно аннотации secrets-store.deckhouse.io/env-from-path
с путем до секрета, который содержит, к примеру, ключ MY_SECRET
, и переменную окружения в манифесте с тем же именем:
env:
- name: MY_SECRET
value: secrets-store:demo-kv/data/myapp-secret#password
в переменную окружения MY_SECRET
внутри контейнера запишется значение секрета из аннотации.
Пример: извлечь из Vault-совместимого хранилища ключ DB_PASS
из kv2-секрета по адресу demo-kv/myapp-secret
:
env:
- name: PASSWORD
value: secrets-store:demo-kv/data/myapp-secret#DB_PASS
Пример: извлечь из Vault-совместимого хранилища ключ DB_PASS
версии 4
из kv2-секрета по адресу demo-kv/myapp-secret
:
env:
- name: PASSWORD
value: secrets-store:demo-kv/data/myapp-secret#DB_PASS#4
Шаблон может также находиться в ConfigMap или в Secret и быть подключен с помощью envFrom
envFrom:
- secretRef:
name: app-secret-env
- configMapRef:
name: app-env
Инжектирование реальных секретов из Vault-совместимого хранилища выполнится только на этапе запуска приложения, в Secret и ConfigMap будут находиться шаблоны.
Подключение переменных из ветки хранилища (всех ключей одного секрета)
Создадим под с названием myapp1
, который подключит все переменные из хранилища по пути demo-kv/data/myapp-secret
:
kind: Pod
apiVersion: v1
metadata:
name: myapp1
namespace: myapp-namespace
annotations:
secrets-store.deckhouse.io/role: "myapp-role"
secrets-store.deckhouse.io/env-from-path: demo-kv/data/common-secret,demo-kv/data/myapp-secret
spec:
serviceAccountName: myapp-sa
containers:
- image: alpine:3.20
name: myapp
command:
- sh
- -c
- while printenv; do sleep 5; done
Применим его:
kubectl create --filename myapp1.yaml
Проверим логи пода после его запуска, мы должны увидеть все переменные из demo-kv/data/myapp-secret
:
kubectl -n myapp-namespace logs myapp1
Удалим под
kubectl -n myapp-namespace delete pod myapp1 --force
Подключение явно заданных переменных из хранилища
Создадим тестовый под с названием myapp2
, который подключит требуемые переменные из хранилища по шаблону:
kind: Pod
apiVersion: v1
metadata:
name: myapp2
namespace: myapp-namespace
annotations:
secrets-store.deckhouse.io/role: "myapp-role"
spec:
serviceAccountName: myapp-sa
containers:
- image: alpine:3.20
env:
- name: DB_USER
value: secrets-store:demo-kv/data/myapp-secret#DB_USER
- name: DB_PASS
value: secrets-store:demo-kv/data/myapp-secret#DB_PASS
name: myapp
command:
- sh
- -c
- while printenv; do sleep 5; done
Применим его:
kubectl create --filename myapp2.yaml
Проверим логи пода после его запуска, мы должны увидеть переменные из demo-kv/data/myapp-secret
:
kubectl -n myapp-namespace logs myapp2
Удалим под
kubectl -n myapp-namespace delete pod myapp2 --force
Монтирование секрета из хранилища в качестве файла в контейнер
Для доставки секретов в приложение нужно использовать CustomResource SecretStoreImport
.
В этом примере используем уже созданные ServiceAccount myapp-sa
и namespace myapp-namespace
из шага Подготовка тестового окружения
Создайте в кластере CustomResource SecretsStoreImport с названием myapp-ssi
:
apiVersion: deckhouse.io/v1alpha1
kind: SecretsStoreImport
metadata:
name: myapp-ssi
namespace: myapp-namespace
spec:
type: CSI
role: myapp-role
files:
- name: "db-password"
source:
path: "demo-kv/data/myapp-secret"
key: "DB_PASS"
Создайте в кластере тестовый под с названием myapp3
, который подключит требуемые переменные из хранилища в виде файла:
kind: Pod
apiVersion: v1
metadata:
name: myapp3
namespace: myapp-namespace
spec:
serviceAccountName: myapp-sa
containers:
- image: alpine:3.20
name: myapp
command:
- sh
- -c
- while cat /mnt/secrets/db-password; do echo; sleep 5; done
name: backend
volumeMounts:
- name: secrets
mountPath: "/mnt/secrets"
volumes:
- name: secrets
csi:
driver: secrets-store.csi.deckhouse.io
volumeAttributes:
secretsStoreImport: "myapp-ssi"
После применения этих ресурсов будет создан под, внутри которого запустится контейнер с названием backend
. В файловой системе этого контейнера будет каталог /mnt/secrets
с примонтированным к нему томом secrets
. Внутри этого каталога будет лежать файл db-password
с паролем от базы данных (DB_PASS
) из хранилища ключ-значение Stronghold.
Проверьте логи пода после его запуска (должно выводиться содержимое файла /mnt/secrets/db-password
):
kubectl -n myapp-namespace logs myapp3
Удалите под:
kubectl -n myapp-namespace delete pod myapp3 --force
Функция авторотации
Функция авторотации секретов в модуле secret-store-integration включена по умолчанию. Каждые две минуты модуль опрашивает Stronghold и синхронизирует секреты в примонтированном файле в случае его изменения.
Есть два варианта следить за изменениями файла с секретом в поде. Первый - следить за временем изменения примонтированного файла, реагируя на его изменение. Второй - использовать inotify API, который предоставляет механизм для подписки на события файловой системы. Inotify является частью ядра Linux. После обнаружения изменений есть большое количество вариантов реагирования на событие изменения в зависимости от используемой архитектуры приложения и используемого языка программирования. Самый простой — заставить K8s перезапустить под, перестав отвечать на liveness-пробу.
Пример использования inotify в приложении на Python с использованием пакета inotify:
#!/usr/bin/python3
import inotify.adapters
def _main():
i = inotify.adapters.Inotify()
i.add_watch('/mnt/secrets-store/db-password')
for event in i.event_gen(yield_nones=False):
(_, type_names, path, filename) = event
if 'IN_MODIFY' in type_names:
print("file modified")
if __name__ == '__main__':
_main()
Пример использования inotify в приложении на Go, используя пакет inotify:
watcher, err := inotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
err = watcher.Watch("/mnt/secrets-store/db-password")
if err != nil {
log.Fatal(err)
}
for {
select {
case ev := <-watcher.Event:
if ev == 'InModify' {
log.Println("file modified")}
case err := <-watcher.Error:
log.Println("error:", err)
}
}
Ограничения при обновлении секретов
Файлы с секретами не будут обновляться, если будет использован subPath
.
volumeMounts:
- mountPath: /app/settings.ini
name: app-config
subPath: settings.ini
...
volumes:
- name: app-config
csi:
driver: secrets-store.csi.deckhouse.io
volumeAttributes:
secretsStoreImport: "python-backend"
Скачать мультитул d8 для команд Stronghold
Официальный сайт Deckhouse Kubernetes Platform
Перейдите на официальный сайт и воспользуйтесь инструкцией
Субдомен вашей Deckhouse Kubernetes Platform
Для скачивания мультитула:
-
Перейдите на страницу
tools..<cluster_domain>
, где<cluster_domain>
— DNS-имя в соответствии с шаблоном из параметра modules.publicDomainTemplate глобальной конфигурации. -
Выберите Deckhouse CLI для вашей операционной системы.
-
Для Linux и MacOS:
- Добавьте права на выполнение
d8
черезchmod +x d8
. - Переместите исполняемый файл в каталог
/usr/local/bin/
.
Для Windows:
- Распакуйте архив, переместите файл
d8.exe
в выбранный вами каталог и добавьте этот каталог в переменную $PATH операционной системы. - Разблокируйте файл
d8.exe
, например, следующим способом:- Щелкните правой кнопкой мыши на файле и выберите Свойства в контекстном меню.
- В окне Свойства убедитесь, что находитесь на вкладке Общие.
- Внизу вкладки Общие вы можете увидеть раздел Безопасность с сообщением о блокировке файла.
- Установите флажок Разблокировать или нажмите кнопку Разблокировать, затем нажмите Применить и ОК, чтобы сохранить изменения.
- Добавьте права на выполнение
-
Проверьте, что утилита работает:
d8 help
Готово, вы установили d8 stronghold
.