Привет, Хабр! Меня зовут Евгений Симигин, я занимаюсь внедрением DevOps-практик в Центре компетенций по разработке облачных и интернет-решений в МТС Digital. А еще я куратор практикумов docker и kubernetes на платформе rebrainme.com.

Практика показывает, что далеко не все инженеры знают о том, как шифровать секреты в своих репозиториях. Поэтому расскажу об инструментах helm-secrets, sops и vals, которые помогают быстро и просто решить эту задачу. Надеюсь, что после выхода моей статьи закоммиченных паролей в репах станет меньше :).

О каких инструментах я расскажу:

  1. Mozilla sops для шифрования yaml/json (без helm)

  2. Helm-secrets и sops

  3. Helm-secrets и философский камень envsubst

  4. Один vals, чтоб править всеми

Mozilla sops

Позволяет шифровать yaml/json на gpg-ключах. Репозиторий проекта тут.

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

Для того, чтобы воспользоваться инструментом, необходимо сгенерировать ключи:

gpg --gen-key

Отвечаем на вопросы о пользователе и почте и получаем сообщение, что все прошло успешно:

…
gpg: key 45369266A697BDC7 marked as ultimately trusted
gpg: revocation certificate stored as '/home/dadmin/.gnupg/openpgp-revocs.d/0D955807E53158CB209E3A5645369266A697BDC7.rev'
public and secret key created and signed.

pub   rsa3072 2022-03-18 [SC] [expires: 2024-03-17]
      0D955807E53158CB209E3A5645369266A697BDC7
uid                      prod-habrauser <prod-habramail@prod-habramail.ru>
sub   rsa3072 2022-03-18 [E] [expires: 2024-03-17]

Список всех ключей можно просмотреть командой gpg --list-keys

 /home/dadmin/.gnupg/pubring.kbx
-------------------------------
pub   rsa3072 2022-03-18 [SC] [expires: 2024-03-17]
      48EF05B385B1DA8713ADCA7694D3BD5F3B78E0F7
uid           [ultimate] habrauser <habramail@habramail.ru>
sub   rsa3072 2022-03-18 [E] [expires: 2024-03-17]

pub   rsa3072 2022-03-18 [SC] [expires: 2024-03-17]
      0D955807E53158CB209E3A5645369266A697BDC7
uid           [ultimate] prod-habrauser <prod-habramail@prod-habramail.ru>
sub   rsa3072 2022-03-18 [E] [expires: 2024-03-17]

У меня в системе 2 ключа так как мы будем имитировать dev- и prod-окружение, шифруемое на разных ключах. Создадим два файла с секретами, которые будем шифровать.

secrets.dev.yaml:

secrets-dev:
    secret1: value1
    secret2: value2

secrets.prod.yaml:

secrets-prod:
    secret1: value1
    secret2: value2

Создадим файл .sops.yaml , в котором пропишем маску имени файла и отпечаток ключа, который будет к нему применяться.

creation_rules:
  - path_regex: \.*prod*\.yaml$
    pgp: 0D955807E53158CB209E3A5645369266A697BDC7
  - path_regex: \.*dev*\.yaml$
    pgp: 48EF05B385B1DA8713ADCA7694D3BD5F3B78E0F7

Зашифруем секрет: sops -e secrets.dev.yaml > enc.secrets.dev.yaml

cat enc.secrets.dev.yaml

secrets-dev:
    secret1: ENC[AES256_GCM,data:sM0ZcEj3,iv:wJsvNcOGTNB7KETqUUYeS5HT5YcAI3mvomrSFKDjuWg=,tag:6qZ7RJaDulpk59ll2rzqLw==,type:str]
    secret2: ENC[AES256_GCM,data:H6jpTf+X,iv:MOOmbabkzJmyKWmFngNUDm2PFYaHmKGh3KCbofilqKQ=,tag:zqzmpaA877K058VhAeqFmg==,type:str]
sops:
    kms: []
    gcp_kms: []
    azure_kv: []
    hc_vault: []
    age: []
    lastmodified: "2022-03-18T04:00:30Z"
    mac: ENC[AES256_GCM,data:KkHyTaHMljUS86GQJCMFFjKPi77mFbwF48dF4WK7F5UrSmGUkqJnfzvjFDb3bAoGU8d+SIdEHg+XQDAzF5LJi2itqppRI+NQA9AKIiEj1t8ebiGkOcPfz2NFD4XltofhbBzr5FjcLwTz+FZ74V//5RKxCmEBHfXDiZo2BOo5qeE=,iv:1FGM3gBkk40DkL/Cxmg08CpG4HqfxnjX+XhwYAVvlSY=,tag:iYpDEuPUAF2m/be3nBM+6g==,type:str]
    pgp:
        - created_at: "2022-03-18T04:00:30Z"
          enc: |
            -----BEGIN PGP MESSAGE-----

            hQGMA/B9NJDVd05oAQwAqWc68igJA1kPBf5iTN1x7qcYx6urjD72A/IYyXZqaPM8
            WsIPEwu7u4KZnb7zXnm0ICY3oFj+yznDvZr5BklytCJZ45G8RBFenbUGKh0vVVsw
            zsmvGkoe9LR4hzo2QtXtmaydg3EBwkoeeJC0Vk2I8U+Wmo/JXkgmcCtTva39PmU7
            yGA2Mv0RvpmHHL/fEj52wFGsOFpyLpEXjDQamqhGLAXjbqqjPMmc7fjP/Nr5TGtA
            TTlvC1RusahxznnL3GJ2QUeHZMPahdkq1R/SzQonVgzy0MzMzZcH82e5CxazmBBL
            3f/Zn8YyjQbVveJF1zaLKk4msBrYcOqxXtQR6yoUdOVtYHSiraaxfJfOgx7XLvNS
            7rdisJ/5CzcfoP6BLh6Y9H5sVd56j8A9gxiv30QH9YrVcSJW/RurOtrKMa9nrnZs
            0gcPF4nnQOfJ9KYgfLVKBFhiK6Afx/lRKbK1E5aHshseZ9leOZpJOavYdHl9kaPd
            D4DZCy78cX0PDhHA6pQx0l4BH1yv3afgmb2zSoKcVC++rztVrNt/spnCOFB7lBrS
            HeXVpCNYQuXLGHTlDuoYviWDFdJtY6SW/9FHX+zviKOKuQiRI0jG7izHIkyy0j7C
            podcI9bilsCqxTq0sSRX
            =+0c5
            -----END PGP MESSAGE-----
          fp: 48EF05B385B1DA8713ADCA7694D3BD5F3B78E0F7
    unencrypted_suffix: _unencrypted
    version: 3.7.2

Команда sops -d enc.secrets.dev.yaml выведет нам в консоль расшифрованный файл:

secrets-dev:
    secret1: value1
    secret2: value2

Аналогичные манипуляции можно проделать для второго файла. Для in-place редактирования используейте sops -i. Инструмент простой, не требует дополнительных инфраструктурных решений и позволяет быстро внедрить шифрованные секреты в проект.

Из минусов: для редактирования секретов придется носить с собой ключи. Ниже приведем пример экспорта/импорта ключа:

gpg --armor --export-secret-key 0D955807E53158CB209E3A5645369266A697BDC7 > key-prod.gpg
gpg --allow-secret-key-import --import key-prod.gpg
gpg: key 45369266A697BDC7: "prod-habrauser <prod-habramail@prod-habramail.ru>" not changed
gpg: key 45369266A697BDC7: secret key imported
gpg: Total number processed: 1
gpg:              unchanged: 1
gpg:       secret keys read: 1
gpg:  secret keys unchanged: 1

Helm-secrets и sops

Для меня целевым инструментом является helm и его плагин helm-secrets. Репо проекта.

Обычно он используется как обертка для sops, но это только видимая часть айсберга.

При использовании helm-secrets мы получаем дополнительные фичи:

  • In-place шифрование секрета helm secrets enc secrets.dev.yaml;

  • Просмотр helm secrets view secrets.dev.yaml;

  • Редактирование helm secrets edit secrets.dev.yaml откроет расшифрованный файл в $EDITOR (у vscode есть плагин, который автоматически шифрует и расшифровывает секреты при редактировании);

  • Автоматический рендер переменных при разворачивании чарта helm secrets upgrade --install -f values.yaml -f secrets.dev.yaml project;

  • Возможность интеграции с различными системами хранения секретов (об этом чуть позже).

А вот расшифровка in-place подкачала – helm secrets dec secrets.dev.yaml – создаст расшифрованный файл secrets.dev.yaml.dec, а исходный останется без изменений.

Helm-secrets и envsubst

Частая задача – отрендерить переменные в каком-либо файле соответствующими переменными в ENV. В этом поможет замечательная утилита envsubst, которая принимает входящий поток и заменяет в нем переменные на значения из ENV. Как обычно это делают при деплое:

envsubst < ./deploy-config/secrets.dev.yaml > secrets.dev.yaml
helm upgrade --install -f values.yaml -f secrets.dev.yaml $CI_PROJECT_NAME ./deploy-chart

Тоже самое можно реализовать in-place при помощи helm-secrets с драйвером envsubst:

HELM_SECRETS_DRIVER=envsubst helm secrets upgrade --install -f values.yaml -f $CI_PROJECT_NAME ./deploy-chart

Обратите внимание, в первом случае просто helm так как мы на вход подаём уже отрендеренный конфиг, а во втором - helm secrets ибо требуется извлечение секретов.

Файл secrets.dev.yaml в данном случае выглядит приблизительно так:

secrets-dev:
    secret1: $ENV-VAR1
    secret2: $ENV-VAR2

Драйвер – это обертка на bash, он позволяет подключать любые системы хранения секретов.

Подробнее о драйверах.

Примеры реализаций оберток.

Один vals, чтоб править всеми

У драйверов для helm-secrets есть один недостаток – нельзя использовать несколько драйверов одновременно.

Таким образом мы подходим к гвоздю программы – vals. Он позволяет из одного манифеста ссылаться на различные источники секретов: sops, vault, aws, gcp. Также его можно использовать как драйвер для helm secrets.

Репозиторий проекта.

Принцип работы – в файл вставляются ссылки вида:

secrets-dev:
    secret1: ref+sops://path/to/file#/foo/bar
    secret2: ref+vault://mykv/foo#/bar?address=https://vault1.example.com:8200
    secret3: ref+gcs://BUCKET/KEY/OF/OBJECT[?generation=ID]
    secret4: ref+драйвер(хранилище):/путь/относительно/хранилища

Для интеграции с vault достаточно задать переменные VAULT_ADDR и VAULT_TOKEN и можно вытаскивать секреты ref+vault://kv/annotation#pod_annotation,

Но есть и особенности:

  • Для рендера всего чарта используйте helm secrets -d vals -f values.yaml template . Если не укажете -f values.yaml – вместо значений увидите свои ссылки.

  • Для просмотра секретов в конкретном файле используйте helm secrets -d vals view values.yaml - . Работает команда странно: сортирует все ключи по алфавиту. Кажется, что она выводит не весь файл. На родной интеграции helm + vault (не через vals) у меня так и не заработал рендер этой командой.

Использование vals вне helm выглядит следующим образом: vals eval -f values.yaml . Файл с подставленными переменными будет выведен в консоль.

Vals разделяет понятие переменных и секретов: для секретов можно использовать дополнительную маркировку secretref+драйвер, тогда команда vals eval --exclude-secret -f values.yaml отрендерит только ссылки, начинающиеся с ref+, а secretref+ останутся в виде переменных. Этот функционал можно использовать, чтобы увидеть, как в итоге собрался файл конфигурации, не показывая при этом секреты всем.

Заключение

Рассмотренные инструменты – это проекты c открытым исходным кодом, написанные на golang и вы можете добавить свой функционал по необходимости.

Для внедрения sops не требуется каких-либо сторонних инфраструктурных решений и специалистов, вы можете сделать это уже сегодня.

Использование комбинаций helm/sops/vals позволяют соблюсти требования безопасности на старте проекта и плавно переехать на целевую инфраструктуру по мере ее готовности.

Пожалуйста, поделитесь в комментариях своими советами и приемами по работе с секретами!

Комментарии (10)


  1. barkalov
    25.03.2022 18:20
    +1

    Все хорошо, но теперь нужна инфраструктура для хранения и деплоя ключей для секретов.


    1. S1M Автор
      25.03.2022 18:31

      В случае helm secrets и sops все хранится в репе и ничего дополнительно не нужно. И потом можно разворачивать инфраструктурные решения и перезжать туда.


    1. lokkersp
      25.03.2022 20:37
      +1

      вас тут немного обманули, дело в том, что sops поддерживает и KMS(AWS,GCP)-ключи, Vault.


      1. S1M Автор
        26.03.2022 04:51

        По коммитам vault завезли "16 Jul 2020" я честно говоря пропустил этот момент, т.к. инструмент начал использовать гораздо раньше. KMS опять же не целевая архитектура для корпоратов: я долгое время работал в банке и внешние системы не рассматриваю. Спасибо, что указали на неточность, я в документации по sops вычитал интересную вещь: он может запушить секреты из файла в vault: sops publish $file


        1. lokkersp
          26.03.2022 11:06

          для кого как, мы например sops используем совместно с терраформом, и не таскать ключи за собой это прям хорошо.


    1. amarao
      25.03.2022 21:06
      +2

      sops полностью автономный, и пока что, самый админолюбивый из всего, что я видел для работы с секретами.

      Во-первых он в гите, т.е. секреты соответствуют версии приложения.

      Во-вторых диффы можно смотреть без содрогания.

      В третьих, даже если нет diff view, то даже в зашифрованном виде диффы можно смотреть без содрогания.


  1. pshhpshh
    26.03.2022 00:33

    Как же хорошо было жить на азуре, где все блин на порядки проще...

    Сейчас перешел на проект с AWS и это ужас - хелмы, датадоги (хорошая штука, конечно), аппвееры, куча еще какой-то фрагментированной лабуды.

    Признаюсь: о чем статья и зачем все это нужно, если есть Azure KeyValult - не понял.


    1. S1M Автор
      26.03.2022 04:43

      Например, вы сидите в любой крупной корпоративной конторе и вам просто запрещено выносить что-либо за пределы контролируемой зоны.


  1. dolfinus
    26.03.2022 01:51

    Также стоит включить на уровне настроек репозитория или push хуков проверку на то, что в репозиторий не залили секрет в открытом виде. Например, с помощью https://github.com/Yelp/detect-secrets


  1. zartdinov
    26.03.2022 14:13

    Вроде всегда получается не хранить ничего в репозитории, просто везде использую переменные окружения