Управление секретами при деплое в k8s

от автора

Привет, Хабр!

В определенный момент мы приходим к понимаю, что процесс, который работает «хорошо», должен начать работать «правильно», особенно, если речь зашла про секреты приложений.

Дано: gitlab(onprem), облако (в моем случае Yandex Cloud), 10+ сервисов, которые нужно активно и часто деплоить (с возможностью быстрого наращивания кол-ва сервисов)

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

Решение: Оговорюсь сразу — мое решение построено на Yandex Cloud, но справедливо для любого облачного или onprem решения.

Пропускаю путь от деплоя файлом локально через kubectl до Helm (можно в отдельную тему)

Итак есть репозитории с кодом, в них размещаем файлы .{environment}.values.yaml
При деплое указываем —values=/path/to/file

Возникает главный вопрос — если configmap хранить в репозитории (через helm переменные — это правильно), то как быть с секретами?

Вариант 1: использовать helm-secrets, sops и другие решения

Получаем в репозитории зашифрованные файлы с секретами. При деплое указываем путь к файлу, helm дефишрует и деплоит вместе с секретами приложение. Однако тут же прилетают накладные расходы в виде системы для передачи ключа с которым шифровали, обязательно не забыть в gitignore добавить *.dec, иначе в какой то момент секреты утекают в репозиторий и какой был смысл тогда? Ну и велик риск потерять ключ и секреты заодно. Главный минус подхода — при изменении секрета нужно заново пройти весь pipeline, а значит и проверки, и валидацию кода, и тесты — долго.

Вариант 2: использование управление секретами через внешние системы.

Дополнительно нам понадобится:

  • Место для хранения секретов. Я использую Yandex Cloud Lockbox, можно использовать например Vault или похожие системы
  • External secret operator, как инструмент для синхронизации секретов
  • Kyverno, как инструмент для выполнения политик к кластере k8s

Выбор хранилища секретов ложиться на плечи системного инженера или архитектора. По опыту скажу — я использовал vault и lockbox. Первый, конечно более мощный, но для небольшого энтерпрайза хватит второго решения.

Далее разворачиваем в кластере External Secret Operator.

Создаем сервисный аккаунт с ролью lockbox.editor

export HELM_EXPERIMENTAL_OCI=1 && \ helm pull oci://cr.yandex/yc-marketplace/yandex-cloud/external-secrets/chart/external-secrets \   --version 0.5.5 \   --untar && \ helm install \   --namespace external-secret-operator \   --create-namespace \   --set-file auth.json=sa-key.json \   external-secrets ./external-secrets/ 

Для работы с секретами так же развернем secretstore. Стоит обратить внимание, что secretstore работает в режиме namespace и изолирует секреты в рамках одного ns, ClusterSecretStore позволяет делить секреты между разными ns. Это следует помнить и не шарить лишний раз секреты, которые не будут использоваться в двух и более ns.

Предварительно создайте секрет ( в примере yc-auth) c ключами от сервисного аккаунта созданного ранее. Затем установите secretstore в нужный namespace

apiVersion: external-secrets.io/v1alpha1 kind: SecretStore metadata:   name: secret-store spec:   provider:     yandexlockbox:       auth:         authorizedKeySecretRef:           name: yc-auth           key: authorized-key'

На данном этапе подготовительные работы выполнены.

В чарт приложения можно добавить шаблон для external-secret (или измените пример для использования, как обычный манифест)

 apiVersion: external-secrets.io/v1alpha1 kind: ExternalSecret metadata:   namespace: {{.Values.namespace }}   name: {{ include "helm-chart.name" . }} spec:   refreshInterval: {{ .Values.secretRefreshInterval }}   secretStoreRef:     name: secret-store     kind: SecretStore   target:     name: {{ include "helm-chart.name" . }}   data:   - secretKey: DATABASE_URL     remoteRef:       key: {{ .Values.secretRemote }}       property: DATABASE_URL

В данном примере создается external-secret ресурс, который называется по названию чарта, вызывает создание одноименного k8s секрета из внешнего секрета.

Обратим внимание на два параметра:

  • secretRefreshInterval — интевал с которым secret-operator будет опрашивать lockbox на предмет изменения секрета (по умолчанию 1h0m0s)
  • secretRemote — идентификатор секрета в lockbox (будет захвачена последняя актуальная версия секрета)

После применения манифеста убедитесь, что ресурс перешел в состояние synced

На этом моменте мы получили управляемые секреты в к8s, меняем в lockbox — через какое то время — меняется в кубернетес. Но этого мало, деплоймент не узнает о том, что вы поменяли секрет или добавили новую переменную в него.

Тут на сцену выходит инструмент Kyverno (если будет необходимость — о настройке я напишу отдельный материал). В данный момент предположим, что он у вас уже стоит.

 apiVersion: kyverno.io/v1 kind: ClusterPolicy metadata:   name: deployment-restart-policy   namespace: test-ns   annotations:     policies.kyverno.io/title: Restart Deployment On Secret Change     policies.kyverno.io/category: Other     policies.kyverno.io/severity: medium     policies.kyverno.io/subject: Deployment     kyverno.io/kyverno-version: 1.9.0     policies.kyverno.io/minversion: 1.7.0     kyverno.io/kubernetes-version: "1.27" spec:   schemaValidation: false   rules:   - name: update-secret     match:       any:       - resources:           kinds:           - Secret           names:           - test-database-adapter           namespaces:           - sup     preconditions:       all:       - key: "{{request.operation || 'BACKGROUND'}}"         operator: Equals         value: UPDATE     mutate:       targets:         - apiVersion: apps/v1           kind: Deployment           name: test-database-adapter           namespace: test-ns       patchStrategicMerge:         spec:           template:             metadata:               annotations:                 ops.corp.com/triggerrestart: "{{request.object.metadata.resourceVersion}}" 

Данная политика не претендует на эталон, но она выполняет свою задачу — а именно выполнить restart деплоймента, если изменится контрольная сумма указанного секрета.

В итоге, какое решение мы получили.

При деплое приложения создается external secret, который занимается обновлением секрета k8s. Изменение последнего служит триггером для перезапуска приложения.


ссылка на оригинал статьи https://habr.com/ru/articles/804479/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *