Большинство программных проектов используют секреты – обычно, как ключи к удаленному API или данные для доступа к внешнему ресурсу, например к базе данных. Вашему приложению необходимы эти ключи во время работы, поэтому вам нужно предоставить их при развертывании приложения или на этапе подготовки окружения.
В данной статье я покажу вам, как использовать git-crypt так, чтобы вы могли безопасно хранить секреты ваших приложений в репозиториях исходного кода, даже публичных.


Проблема с секретами приложений


Большинство проектов имеют различные ключи или данные для входа. Например, если ваше приложение располагается на Heroku, вы можете предоставить ключ API приложению Heroku, используя команды вроде этих:


$ heroku config:set API_KEY=my-sooper-sekrit-api-key

Запуская эту команду до того, как вы (снова) развернете приложение, вы передаете переменную окружения с именем API_KEY и значением my-sooper-sekrit-api-key. Однако продолжать отслеживать значения этих переменных вне Heroku (или где бы вы ни развертывали свое приложение) все еще проблематично.


Я всегда стараюсь настроить мои проекты так, чтобы я мог запустить развертку с нуля одной командой, без каких-либо дополнительных шагов. В нашем примере это означает, что мне нужно хранить значение my-sooper-sekrit-api-key где-то для того, чтобы мой развертываемый код мог использовать его (в данном случае, для запуска heroku config:set… командой выше).


Исходный код моего проекта всегда хранится на git и обычно располагается на github.com или bitbucket.com или на другом сервисе хостинга. Я могу хранить значение API_KEY в своем репозитории исходного кода, однако есть и недостатки:


  • Я не могу поделиться репозиторием, и не дать доступа к моим секретам. Это значит, что репозиторий моего приложения с секретами должен быть приватным.
  • Предположительно многие участники Github/bitbucket/прочего тоже получили бы доступ к моим секретам, с чем я мог бы не согласиться (в зависимости от секрета).
  • Можно легко забыть о наличии секретов в приватных репозиториях, если позже решишь сделать его публичным. Это могло бы случайно раскрыть важный секрет.

Я могу хранить секреты отдельно от исходного кода приложения, но и это имеет проблемы:


  • Необходим способ доставать секреты, независимо от того, где они находятся, во время или после развертывания, и дать моему коду развертки доступ до них.
  • Мои секреты могут не храниться так же надежно, как исходный код. К примеру, можно хранить секреты в файлах типа .env на ноутбуке, и быть уверенным, что мне не придется сверяться с git репозиторием. Однако, если я потеряю этот файл (например, если ноутбук сломается\потеряется), то секрет пропадет вместе с ним.

git-crypt


Git-crypt позволяет решить эту проблему шифрованием ваших секретов, когда загружаете их в git репозиторий, и расшифровкой, когда необходимо их извлечь. С вашей точки зрения все происходит прозрачно. То есть секреты находятся в открытом виде как для вас, так и для вашего экземпляра развертывания, но никто кроме вас не сможет читать их, даже если исходный код располагается в публичном в репозитории GitHub.


Рассмотрим пример.


1. Установка git-crypt.


Инструкция для Linux, Mac, и Windows на странице установки git-crypt.


Если вы, как и я, используете Mac с установленным Homebrew, вы можете запустить:


$ brew install git-crypt

2. Создайте новый git репозиторий.


$ mkdir myproject 
$ cd myproject 
$ git init 
$ echo "This is some text" > file.txt 
$ git add file.txt 
$ git commit -m "Initial commit"

Теперь у нас есть репозиторий с одним текстовым файлом.


3. Настройка репозитория для git-crypt.


$ git-crypt init

Вы должны увидеть следующее:


Generating key...

Перед тем, как мы сделаем что-либо еще, пожалуйста, запустите следующую команду:


$ git-crypt export-key ../git-crypt-key

Эта команда создает копию симметричного ключа git-crypt, который сгенерирован для этого репозитория. Поместим его в директорию на уровень выше этого репозитория, так чтобы можно было использовать один ключ в нескольких git репозиториях.


По стандарту, git-crypt хранит все созданные ключи в файле .git/git-crypt/keys/default, так что вы можете достичь тех же результатов запустив cp .git/git-crypt/keys/default ../git-crypt-key

Файл git-crypt-key очень важен. Это ключ, который открывает все зашифрованные файлы в нашем репозитории. Дальше мы рассмотрим как использовать его.


4. Укажите git-crypt какой файл зашифровать.


Представим, что нашему приложению нужен API ключ, и мы собираемся хранить его в файле с именем api.key.


До того, как добавим этот файл в репозиторий, мы укажем git-crypt, что хотим, чтобы каждый раз при совершении коммита, api.key был зашифрован.


Мы делаем это, используя файл .gitattributes. Его можно использовать, чтобы добавить дополнительные метаданные в git репозиторий. Это не особенность git-crypt, так что файл .gitattributes может уже существовать в вашем репозитории. В таком случае просто добавьте актуальные строки, не заменяя весь файл.


У нас же .gitattributes нет, поэтому его необходимо создать. Файл должен содержать строки вида:


[file pattern] attr1=value1 attr2=value2

Для git-crypt, шаблон должен соответствовать всем файлам, которые должны быть зашифрованы. Атрибуты всегда одинаковы: filter и diff, которые мы установили в настройках git-crypt.


Так, наш .gitattributes должен содержать:


api.key filter=git-crypt diff=git-crypt

Создайте такой файл, добавьте и загрузите его в свой репозиторий:


$ echo "api.key filter=git-crypt diff=git-crypt" > .gitattributes 
$ git add .gitattributes 
$ git commit -m "Tell git-crypt to encrypt api.key"

Я использовал конкретное имя файла api.key в .gitattributes, но это может быть и любой шаблон, включающий в себя файлы для шифрования, так что я мог использовать, например, *.key. В качестве альтернативы, просто добавьте строку для каждого файла, который хотите зашифровать.


Легко совершить ошибку при создании .gitattributes, если вы пытаетесь защитить сразу несколько файлов с единственным шаблоном. Поэтому я настоятельно рекомендую прочесть раздел README из git-crypt, в котором выделено несколько распространенных ошибок.


5. Добавим секрет.


Теперь, когда мы указали git-crypt, что хотим зашифровать api.key, давайте добавим этот файл в репозиторий.


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

$ echo "dummy value" > api.key

Мы еще не добавили api.key в git, но можем проверить, что cделает git-crypt, запустив команду:


$ git-crypt status

Вывод должен быть следующим:


   encrypted: api.key 
not encrypted: .gitattributes 
not encrypted: file.txt

Итак, даже если api.key еще не был загружен в репозиторий, такое сообщение говорит о том, что при совершении коммита git-crypt зашифрует его.


Давайте выполним добавление и коммит файла:


$ git add api.key 
$ git commit -m "Added the API key file"

6. Подтвердим шифрование секрета.


Мы указали git-crypt файл для шифрования, добавили api.key в репозиторий, однако, если мы посмотрим, то не увидим разницы:


$ cat api.key 
dummy value

Причина в том, что git-crypt шифрует и дешифрует файлы прозрачно, как во время загрузки, так и во время извлечения. Поэтому api.key выглядит как обычный текстовый файл в открытом виде.


$ file api.key 
api.key: ASCII text

Один из способов подтвердить, что ваши файлы действительно зашифрованы – загрузить их в репозиторий GitHub. Когда вы будете просматривать api.key через интерфейс GitHub, то увидите зашифрованный двоичный файл, а не текст.


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


$ git-crypt lock

Теперь, если мы прочитаем api.key, все будет выглядеть по другому:


$ file api.key 
api.key: data 

$ cat api.key 
GITCRYPTROܮ7y\R*^

Вы увидите отличающийся от моего мусор, но ясно, что файл зашифрован. Это то, что будет храниться на GitHub.


Чтобы вернуться к открытому виду файла api.key, выполните:


$ git-crypt unlock ../git-crypt-key

../git-crypt-key — это файл, который мы сохранили ранее с помощью git-crypt export-key...


Итак, что мы узнали?


Давайте сделаем быстрый обзор того, что уже известно


  • Инициализация git-crypt в git репозитории через git-crypt init
  • Использование шаблонов в .gitattributes, чтобы указать git-crypt какие файлы шифровать
  • git-crypt lock зашифрует все указанные файлы в нашем репозитории
  • git-crypt unlock [путь до файла ключа] расшифрует зашифрованные файлы

git-crypt-key очень важен. Без него будет невозможно дешифровать какие бы то ни было зашифрованные файлы в вашем репозитории. Кто угодно, получив копию этого файла, получит доступ ко всем зашифрованным секретам в репозитории. Поэтому необходимо держать его в безопасности.


Повторное использование ключа git-crypt


Мы использовали git-crypt init и git-crypt export-key, чтобы создать файл git-crypt-key. Но если бы нам нужно было использовать отдельные ключи для каждого репозитория, то это не очень-то и улучшило бы нашу систему управления секретами.


К счастью, можно довольно легко использовать один и тот же ключ git-crypt для нескольких git репозиториев.


Чтобы использовать уже существующий ключ, просто запустите git-crypt unlock вместо git-crypt init, когда настраиваете ваш репозиторий для git-crypt, вроде этого:


$ mkdir my-other-project   # At the same directory level as `myproject` 
$ cd my-other-project 
$ git init 
$ echo "Something" > file.txt 
$ git add file.txt 
$ git commit -m "initial commit" 
$ git-crypt unlock ../git-crypt-key

Если вы запустите git-crypt unlock не добавив ни одного файла в git репозиторий, то увидите сообщение вроде такого:

fatal: You are on a branch yet to be born 
Error: 'git checkout' failed 
git-crypt has been set up but existing encrypted files have not been decrypted

Это по-прежнему неплохо работает, но слегка сбивает с толку, поэтому я убедился, что добавил и совершил коммит как минимум одного файла, до запуска git-crypt unlock...

Повторное использование ключа git-crypt удобно, но это значит, что если кто-либо получит ваш ключ, все ваши зашифрованные секреты раскроются.


Это такой же вид компромисса, как и при использовании менеджеров паролей, вроде LastPass или 1password. Вместо того, чтобы управлять несколькими секретами (паролями), каждый со своим риском взлома, вы храните их все в одном защищенном хранилище, и используете один мастер пароль для разблокировки.


Когда НЕ стоит использовать git-crypt


Git-crypt это прекрасный способ хранить нужные вашим приложениям секреты прямо в git репозитории, рядом с исходным кодом. Однако, как и другие меры безопасности, это не всегда будет уместно или целесообразно.


Чтобы определить, является ли это решение подходящим для вашего проекта, стоит учесть пару вещей:


  • git-crypt разработан для ситуаций, когда большинство файлов в вашем git репозитории могут оставаться в открытом виде, а зашифровать нужно лишь малую часть из них, в которых есть секреты. Если вам нужно зашифровать большую часть репозитория, возможно вам лучше подойдет другое решение.
  • Нет простого способа отозвать доступ к секретам в репозитории, если вы уже дали кому-то ключ-файл, как и нет простого способа изменить (т.е. заменить) ключ-файл (и даже его изменение не особо помогает, если только не поменять все действующие секреты в репозитории).
  • git-crypt шифрует только содержимое файлов. Таким образов, он не подходит, если так же важны метаданные вашего репозитория (т.е. имена файлов, дата изменения, сообщения коммита и т.д.).
  • Некоторые GUI-приложения git могут работать с git-crypt ненадежно. (Хотя конкретный случай Atlassian SourceTree, указанный в README, уже исправлен).

Больше информации в этом разделе README git-crypt.

Лучший способ использовать git-crypt


Вместо того, чтобы управлять ключ-файлом git-crypt вручную, можно включить в проект git-crypt вместе с gpg, и тогда вы сможете использовать приватный ключ gpg для дешифровки вашего git репозитория. Это так же позволит вам добавлять различных соавторов в репозиторий без обмена секретами между группами. Однако это требует гораздо более сложной настройки, что мы оставим для другой статьи.




Переводчик: DanayDemenir
Редактор: DariaRogoz

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


  1. vainkop
    29.07.2021 17:43

    Имел достаточно негативный опыт работы с git crypt в команде.

    Какой-нибудь Mozilla SOPS гораздо лучше и более DevOps yaml friendly, если цель шифрование yaml/json секретов.


    1. stas_tibekin Автор
      29.07.2021 19:53
      +1

      А какой негативный опыт был с git-crypt? Опишите?


  1. quarckster
    30.07.2021 08:35

    Не раскрыт момент использования git-crypt с CI системами. Вам всё равно придётся хранить ключ для расшифровки где-то ещё, например, в переменных окружения. Если секретов мало, то полезность git-crypt и mozilla sops стремится к нулю.


    1. stas_tibekin Автор
      30.07.2021 08:41

      Хорошее замечание, попробуем подыскать перевод, где это будет представлено в полном объеме


    1. IkaR49
      30.07.2021 12:11
      +2

      В конце сказано про приватные ключи gpg, оно не подойдет?

      И в любом случае было бы интересно про такую настройку почитать.


      1. stas_tibekin Автор
        30.07.2021 12:21

        Подойдет) Но я так понимаю, хочется почитать же реальный опыт на эту тему.


        1. IkaR49
          30.07.2021 13:54

          Да, очень. Мы в отделе, конечно, вряд ли выпустим свои коды в опенсорс, но улучшить защиту - почему бы и нет?


          1. stas_tibekin Автор
            30.07.2021 14:34

            Согласен)


      1. snp
        30.07.2021 21:48
        +1

        Ключ для расшифровки (git-crypt export-key …) шифруется GPG'ем для заданных получателей командой git-crypt add-gpg-user USERID и коммитится в репозиторий, например:


        % git-crypt add-gpg-user 522291A78747D8A3 D3A45B56CB5598D6
        [master 8890b9f] Add 2 git-crypt collaborators
         3 files changed, 4 insertions(+)
         create mode 100644 .git-crypt/.gitattributes
         create mode 100644 .git-crypt/keys/default/0/01BDB4F92C70B6388A0E2E8A522291A78747D8A3.gpg
         create mode 100644 .git-crypt/keys/default/0/864BAD7E6FF3126D217041C8D3A45B56CB5598D6.gpg

        Добавляем все USERID, которым нужен доступ, в т.ч. себя.


        Далее просто запускаем git-crypt unlock без указания файла с ключём и он автоматически расшифрует его, если доступен соответствующий приватный ключ:


        % git-crypt lock
        % file k1.key
        k1.key: data
        % git-crypt unlock
        % file k1.key
        k1.key: ASCII text

        И экспортировать ключ (git-crypt export-key …) уже не нужно.


        1. stas_tibekin Автор
          02.08.2021 08:19
          +1

          Спасибо)