Управление секретами Kubernetes с Sealed Secrets и Helm: GitOps way

от автора

В этой статье рассмотрим, как можно организовать простое управление секретами для приложений в Kubernetes при использовании GitOps-подхода. Храним секреты в git безопасно и управляем ими из Helm Chart приложения.

Kubernetes с секретами by Kandinsky 3.1

Kubernetes с секретами by Kandinsky 3.1

Рассмотрим приложение, которое развертывается в кластере Kubernetes с использованием Helm Chart и GitOps. Согласно принципам GitOps все данные, необходимые для развертывания приложения, должны храниться в git-репозитории. Артефакты: docker-образы, Helm-чарты и т.п., могут храниться в отдельных реестрах или репозиториях, но должны быть однозначно идентифицированы, например, с помощью версионирования. Таким образом, git-репозиторий является единым источником истины для развертывания приложения. Однако складывать секреты в git в открытом виде, или, как предлагают стандартные средства Kubernetes и Helm, просто в base64, совершенно не безопасно.

Конечно, для хранения секретов можно воспользоваться специальными инструментами, вроде HashiCorp Vault, когда это оправдано масштабом проекта. В этой статье я хочу остановиться на простом решении, почти не требующем дополнительных внешних зависимостей и дополнительных усилий при эксплуатации. Оно вполне применимо для небольших систем и простых политик безопасности.

Для решения задачи будем использовать следующие инструменты:

  • Универсальный Helm Chart от Nixys

  • Flux CD в качестве GitOps инструментария

  • Sealed Secrets для шифрования секретов

Аналогичную конструкцию можно реализовать для любого Helm Chart и другой GitOps-системы, например, ArgoCD.

Sealed Secrets представляет собой решение от Bitnami, специально предназначенное для организации хранения секретов в git-репозитории и работы в связке с GitOps-системами. Секрет предварительно зашифровывается и может быть сохранен в git в виде объекта типа SealedSecret. Контроллер Sealed Secrets расшифровывает секреты и предоставляет их приложениям обычным способом. Он довольно легковесный, не требует настройки и практически не потребляет ресурсы кластера. Шифрование секретов производится с помощью консольной команды kubeseal. При стандартной способе использования она создает готовый манифест для объекта SealedSecret.

Но тут кроется одно неудобство. Если размещать секрет в виде отдельного манифеста, он становится недоступен для управления из Helm Chart приложения. Например, затруднительно отслеживать его изменения для рестарта приложения, а также гарантировать наличие секрета до запуска пода. Одним из решений может быть включение зашифрованного секрета непосредственно в Helm Chart приложения.

Реализация

Для удобства использования нашего решения добавим в универсальный Helm Chart темплейт и хелпер для Sealed Secrets.

{{- range $sName, $val := .Values.sealedSecrets -}} --- apiVersion: bitnami.com/v1alpha1 kind: SealedSecret metadata:   name: {{ include "helpers.app.fullname" (dict "name" $sName "context" $) }}   namespace: {{ $.Release.Namespace | quote }}   labels:     {{- include "helpers.app.labels" $ | nindent 4 }}     {{- with $val.labels }}{{- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 4 }}{{ end }}   annotations:     {{- include "helpers.app.hooksAnnotations" $ | nindent 4 }}     {{- with $val.annotations }}{{- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 4 }}{{ end }} spec:   encryptedData:     {{- include "helpers.sealedSecrets.render" (dict "value" $val.encryptedData) | indent 4 }}   template:     metadata:       name: {{ include "helpers.app.fullname" (dict "name" $sName "context" $) }}       namespace: {{ $.Release.Namespace | quote }}       labels:         {{- include "helpers.app.labels" $ | nindent 8 }}         {{- with $val.labels }}{{- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8 }}{{ end }}       annotations:         {{- with $val.annotations }}{{- include "helpers.tplvalues.render" (dict "value" . "context" $) | nindent 8 }}{{ end }} {{- end }}
{{- define "helpers.sealedSecrets.render" -}} {{- $v := dict -}} {{- if kindIs "string" .value -}} {{- $v = fromYaml .value }} {{- else -}} {{- $v = .value }} {{- end -}} {{- range $key, $value := $v }} {{ printf "%s: %s" $key $value }} {{- end -}} {{- end -}}

Наш темплейт будет создавать секреты из раздела .Values.sealedSecrets, добавлять к ним лейблы и аннотации, определенные, как для приложения, так и для самого ресурса. Зашифрованные данные помещаются в encryptedData в виде стандартного словаря.

Стоит обратить внимание на то, что здесь используются хуки, с помощью которых Helm создает объект SealedSecret до создания и запуска подов приложения. Этот подход используется в Helm Chart от Nixys для объектов типа ConfigMap и Secret. Он гарантирует, что приложение при запуске получит правильную версию конфигурации, однако при этом ресурс не будет автоматически удален, когда перестанет использоваться. Аналогичным образом можно определить темплейт и без хуков, если такое поведение неудобно.

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

checksum/secrets: '{{ include "helpers.workload.checksum" (printf "%s" $.Values.sealedScrets) }}'

Теперь мы можем зашифровать секрет, например так:

kubeseal --raw --scope=namespace-wide --namespace=yournamespace --from-file=yoursecret.txt

Таким образом мы получаем строку содержащую контент файла yoursecret.txt в зашифрованном виде. Мы указали тут скоуп namespace-wide для того, чтобы не привязываться к имени ресурса, которое может генерировать Helm при рендеринге чарта.

Полученную строку мы добавим в Values следующим образом:

sealedSecrets:   yoursecretname:     annotations:       sealedsecrets.bitnami.com/namespace-wide: "true"     encryptedData:       FOO: "encrypted-secret-string"

Стоит обратить внимание, что здесь мы дополнительно добавляем аннотацию sealedsecrets.bitnami.com/namespace-wide: "true", чтобы скоуп ресурса соответствовал нашим зашифрованным данным.

Проверка

Опишем наше приложение через values универсального чарта. Для примера возьмем тестовый микросервис podinfo, который не требует какой-либо конфигурации, но позволит нам протестировать правильную передачу секрета.

Для начала зашифруем наш секрет. Для удобства передадим его прямо из командной строки:

echo -n 'very-secret-string' | \ kubeseal --raw --scope=namespace-wide --namespace=podinfo --from-file=/dev/stdin

Для деплоя приложения через Flux CD создадим описание объекта HelmRelease, содержащее минимально необходимые параметры values для деплоя podinfo с помощью универсального чарта. Мы определим deployment, service, ingress и SealedSecret. Полученную ранее зашифрованную строку вставим в sealedSecrets.app-secret.encryptedData.

apiVersion: helm.toolkit.fluxcd.io/v2beta1 kind: HelmRelease metadata:   name: podinfo   namespace: podinfo spec:   interval: 10m   chart:     spec:       chart: universal-chart       version: '>=2.8.0'       sourceRef:         kind: HelmRepository         name: your-helm-repository         namespace: your-repository-namespace       interval: 10m   values:     deployments:       app:         containers:         - name: podinfo           image: stefanprodan/podinfo           imagePullPolicy: IfNotPresent           ports:           - name: http             containerPort: 9898           envSecrets:           - app-secret     services:       app:         type: ClusterIP         ports:         - name: http           protocol: TCP           port: 9898     ingresses:       app:         hosts:         - hostname: podinfo.example.com           paths:           - serviceName: app             servicePort: 9898             path: "/"     sealedSecrets:       app-secret:         annotations:           sealedsecrets.bitnami.com/namespace-wide: "true"         encryptedData:           SECRET_VARIABLE: <your-encrypted-string>

После деплоя проверим, правильно ли передался секрет в приложение. Для podinfo достаточно выполнить команду:

curl -X 'GET' 'https://podinfo.example.com/env'

В ответ мы должны получить массив переменных, содержащий и наш секрет:

[   ...   "SECRET_VARIABLE=very-secret-string",   ... ]

Аналогично можно добавить темплейт для SealedSecret в любой другой «библиотечный» Chart, например генерируемый helm create. При этом отличаться будут только используемые внутри хелперы и структура values-файла.


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


Комментарии

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

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