В Helm используется библиотека Go шаблонов Sprig. Sprig сильно расширяет возможности динамической шаблонизации Helm, добавляя более 70 полезных функций. Основными функциями в реализации нативного шифрования являются encryptAES/decryptAES. Данные функции написаны на Go с помощью пакета crypto/aes и используют алгоритм шифрования AES-256 CBC. Функции AES добавлены в релизе Sprig v2.21.10. В Helm функции стали доступны впервые в релизе 2.16.0. В те же даты произошел первый релиз Helm 3 мажорной версии. Начиная с helm версий ^3.0.0 используется библиотека Sprig v3.0.0 уже с поддержкой функций AES. У алгоритма AES есть 5 методов: ECB (Electronic Code Book) CBC (Cipher Block Chaining) CFB (Cipher FeedBack) OFB (Output FeedBack) CTR (Counter) Ознакомиться с ними можно тут. В решении используется метод CBC: В этом режиме, если мы будем шифровать один и тот же блок текста N раз, мы получим N разных блоков зашифрованного текста. Добавлю также, что Sprig AES-256 CBC не совместим с OpenSSL AES-256 CBC. Связано это с разными способами вычисления ключа K из пароля/мастер ключа. Функция encryptAES возвращает base64 зашифрованную строку, что позволяет хранить чувствительные данные в git для комфортной работы с чартами. Работа с зашифрованными строками в git'е (коммиты с добавлением новых строк, пулл реквесты с изменением строк) не будут влиять на весь остальной файл. Изменения будут читабельными и не перегруженными. На вход функция принимает мастер ключ, который используется для шифрования алгоритмом AES-256 CBC, и строку. Функция так же умеет работать со строкой, передаваемой с помощью пайплайна (|). Функция decryptAES на вход принимает мастер ключ и зашифрованную с помощью алогоритма AES-256 CBC строку. Данная функция тоже умеет работать в режиме пайплайна с зашифрованной строкой. На примерах выше мы шифровали одну и ту же строку, а получили разные значения на выходе. Это одна из особенностей алгоритма AES-256 CBC. Для шифрования значений и комфортной работы с зашифрованными значениями предлагаю написать чарт-утилиту, который будет использоваться для локального шифрования/дешифрования данных. Входные переменные будем отражать в локальном values.yaml, а полученный результат после шифрования забирать после локального рендера чарта. Файловая структура чарта содержит обязательный Chart.yaml, локальный values.yaml c манифестом func.yaml, который мы будем рендерить, и файл-хелпер _helpers.tpl, содержащий наши кастомные функции. Локальный values.yaml содержит мастер ключ в переменной AESKey и два словаря encrypt и decrypt. Словарь encrypt используется для шифрования значений, в него мы помещаем значения в формате ключ: значение. Словарь decrypt, куда мы помещаем значения в формате ключ: зашифрованное значение, используется для дешифрования значений. ./practice/encrypter-decrypter-v1/values.yaml: Файл-хелпер _helpers.tpl определяет внутри себя две функции encrypter и decrypter. Обе функции работают по одному принципу: На вход первым параметром принимают мастер ключ в переменную $aesKey На вход вторым параметром принимают словарь значений формата key: value в переменную $vallist После чего по словарю проходит цикл и значения value попадают в переменную $val и шифруются в функции encrypter/дешифруются в функции decrypter Значения выводятся в формате key: зашифрованное/дешифрованное value ./practice/encrypter-decrypter-v1/templates/_helpers.tpl: Манифест func.yaml используется для рендера зашифрованных и дешифрованных значений с помощью функций определенных в файле-хелпере _helpers.tpl. В начале манифеста определяется переменная $AESKey, забирающая значение мастер ключа из локального values.yaml. В функцию encrypter передается словарь encrypt, а в функцию decrypter словарь decrypt из локального values.yaml. ./practice/encrypter-decrypter-v1/templates/func.yaml: После определения значений для шифрования/дешифрования в локальном values.yaml в словарях encrypt и decrypt, необходимо зарендерить манифест func.yaml для получения зашифрованных/дешифрованных значений. Получаем следующий результат: Предлагаю добавить префикс для идентификации значений, зашифрованных с помощью функции encryptAES. Для этого изменим функции encrypter и decrypter в файле-хелпере _helpers.tpl. ./practice/encrypter-decrypter-v2/templates/_helpers.tpl: Теперь к зашифрованному значению на выходе будет добавляться $CommonID, состоящий из строки AES256:. А при дешифровании функция decrypter будет проверять наличие префикса $CommonID и расшифровывать значение при успешной проверке. Рассмотрим на примере нового локального values.yaml. ./practice/encrypter-decrypter-v2/values.yaml: Заренденрим чарт: Получаем следующий результат: Создадим чарт с акцентом на сущность типа Secret, который предназначен для хранения чувстительных данных в Kubernetes. В файл-хелпер _helpers.tpl перенесем только функцию decrypter, так как для чартов сервисов не требуется функционал шифрования данных. Для примера, в локальный values.yaml я добавил зашифрованный пароль от суперпользователя БД вместе с не зашифрованными значениями хоста и суперпользователя. ./practice/pseudo-service-v1/values.yaml: Сущность типа Secret вызывает уже известную нам функцию decrypter. В функцию передается мастер ключ AESKey и словарь decrypt из локального values.yaml. ./practice/pseudo-service-v1/templates/secret.yaml: Стоит обратить внимание на подход с передачей мастер ключа AESKey. Поскольку мы не храним мастер ключ в чарте сервиса, при попытке локального рендера чарта хелм будет ругаться на пустую переменную `.Values.AESKey: Получаем следующий результат: Поэтому для корректного локального рендера и последующего развертывания чарта в Kubernetes, будем передавать мастер ключ в рантайме: Получаем следующий результат: Постоянно передавать мастер ключ в рантайме при локальной разработке и обновления чарта не удобно, поэтому предлагаю генерировать префикс зашифрованных значений по другому правилу. ./practice/encrypter-decrypter-v3/templates/_helpers.tpl: Теперь функции имеют новый входной параметр $contour. Данный параметр отвечает за контур для которого шифруется значения (например, dev/test/prod). Параметр используется в $CommonID для генерации префикса формата $contour:AES256:. При дешифровании функция decrypter также будет проверять наличие префикса $CommonID и расшифровывать значение при успешной проверке. Обновленный values.yaml выглядит следующим образом. ./practice/encrypter-decrypter-v3/values.yaml: Заренденрим чарт: Получаем следующий результат: Вернемся к нашему примеру чарта сервиса, в котором обновленный локальный values.yaml уже содержит переменную contour. ./practice/pseudo-service-v2/values.yaml: В такой конфигурации мы можем не передавать мастер ключ в рантайме, а динамически переопределять контур для которого рендерится чарт. Получаем следующий результат: После переопределения контура чарт ренедрится с зашифрованными значениями, что позволяет комфортно заниматься разработкой чарта и не отвлекаться на подстановку мастер ключа на каждый локальный рендер чарта. Данное решение задачи шифрования чувствительных данных позволяет избежать хранения чувствительных данных в репозитории с чартами. С помощью встроенных функций Helm и файлов-хелперов это решение легко встраивается в Cd процессы. DevOoops