company_banner

Как убрать из Git-репозитория файлы с конфиденциальной информацией

Автор оригинала: Ondrej Polesny
  • Перевод
Файлы проиндексированы, написано сообщение коммита, данные отправлены на сервер… И вдруг хочется повернуть время вспять. В коммит попал файл, которого там быть не должно. Когда такое случается, приходит время обращаться к поисковику.

Каждый разработчик когда-то по ошибке коммитил в общедоступный репозиторий файлы с конфиденциальной информацией. Как справиться с такой проблемой? Как сделать так, чтобы ничего подобного больше не случилось бы?

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


Удаление файлов с конфиденциальной информацией из Git-репозитория (изображение большого размера)

Минимизация ущерба


Итак, вы случайно закоммитили файл с конфиденциальной информацией. Назовём этот файл .env. Сразу после того, как это случилось, надо задать себе пару вопросов:

  • Отправлен ли коммит в удалённый репозиторий?
  • Является ли удалённый репозиторий общедоступным?

▍Коммит пока не отправлен в удалённый репозиторий


Если вы пока не отправили коммит в репозиторий, то, в общем-то, возникшая ситуация никакой угрозы не несёт. Для того чтобы всё исправить, достаточно просто вернуться к предыдущему коммиту:

git reset HEAD^ --soft

Файлы останутся в рабочей копии репозитория, вы сможете внести в проект необходимые изменения.

Если же вы хотите сохранить коммит и вам нужно просто удалить из него определённые файлы, тогда поступите так:

git rm .env --cached
git commit --amend

Параметр --amend можно использовать только для работы с самым свежим коммитом. Если вы, после неудачного коммита, добавили ещё несколько, воспользуйтесь такой командой:

git rebase -i HEAD~{на сколько коммитов нужно вернуться?}

Это позволит исправить неправильный коммит и поможет не потерять изменения, внесённые в проект остальными коммитами.

▍Коммит отправлен в удалённый репозиторий


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

Если ваш репозиторий является приватным, и при этом он не доступен ботам или людям, которым вы не доверяете, вы можете просто внести поправки в последний коммит, воспользовавшись парой вышеприведённых команд.

Если вы отправили в репозиторий, после проблемного коммита, и другие коммиты, это не помешает вам убрать файлы с конфиденциальными данными из истории Git, воспользовавшись командой git filter-branch или инструментом BFG Repo-Cleaner.

Вот пример использования git filter-branch:

git filter-branch --force --index-filter "git rm --cached --ignore-unmatch .env" --prune-empty --tag-name-filter cat -- --all

Но, делая это, учитывайте два важных аспекта подобных изменений, вносимых в репозиторий:

  • Вы меняете историю Git. Если на текущее состояние репозитория полагаются другие люди, если от этого состояния зависят какие-то ветки того же репозитория, его форки, открытые PR, то это нарушит их работу. В подобных случаях относитесь к репозиторию как к общедоступному и постарайтесь не вносить изменения в его историю.
  • Вам нужно будет очистить кеш. Вам понадобится обратиться в службу поддержки платформы, на которой хранится ваш репозиторий, и попросить очистить его кеш. Несмотря на то, что вы исправили проблемный коммит или переписали историю репозитория, старый коммит, содержащий конфиденциальные данные, останется в кеше. Для того чтобы к нему обратиться, нужно будет знать его ID, но к нему можно будет получить доступ до тех пор, пока кеш не очистят.

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


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

Даже если вы удалили эти данные из репозитория, вы ничего не сможете сделать с ботами и с форками репозитория. Как же поступить?

  • Деактивируйте все ключи или пароли. Это надо сделать в первую очередь. После того, как вы деактивируете ключи, конфиденциальные сведения, ушедшие в общий доступ, оказываются бесполезными.
  • Настройте файл .gitignore. Сделайте в .gitignore записи о файлах с конфиденциальной информацией для того чтобы Git не отслеживал бы состояние этих файлов.
  • Подготовьте коммит, в котором нет файлов с конфиденциальной информацией.
  • Отправьте изменения в репозиторий, снабдите коммит пояснениями о возникшей ситуации. Не пытайтесь скрыть ошибку. Все программисты, работающие над проектом, включая вас, по достоинству оценят наличие в репозитории коммита с разъяснениями ситуации и с описанием того, что именно было исправлено с помощью данного коммита.

Рекомендации по хранению конфиденциальных файлов в проектах, в которых для контроля версий применяется Git


Для того чтобы не допустить утечек конфиденциальной информации стоит придерживаться следующих рекомендаций.

▍Храните секретные данные в файле .env (или в другом подобном файле)


Ключи к API и другие подобные сведения стоит хранить в единственном файле .env. При таком подходе, если Git не отслеживает состояние файла .env, вы, добавив в этот файл новый ключ, не отправите его случайно в репозиторий.

Ещё одно преимущество такого подхода заключается в том, что так у вас будет доступ ко всем ключам через глобальную переменную process.

▍Используйте, если это возможно, ключи API


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

▍Храните ключи API, пользуясь средствами вашего инструмента для сборки проектов


Ключи API обычно нужны при сборке приложений. Инструменты для сборки проектов, вроде Netlify, позволяют держать ключи в защищённых хранилищах. Такие ключи автоматически внедряются в приложение с использованием глобальной переменной process.


Управление переменными окружения

▍Добавьте запись о файле .env в файл .gitignore


Сделайте так, чтобы Git не отслеживал бы файлы, содержащие конфиденциальную информацию.

▍Подготовьте шаблонный файл .env.template


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

▍Не меняйте историю Git в удалённых репозиториях


Постарайтесь строго придерживаться этого правила. Если вы следовали вышеприведённым рекомендациям, то историю Git вам менять и не потребуется.

Итоги


Надеюсь, мой материал поможет вам в безопасной работе с конфиденциальными данными.

А вам случалось отправлять в общедоступный репозиторий что-то такое, что туда попадать не должно?



RUVDS.com
RUVDS – хостинг VDS/VPS серверов

Комментарии 14

    +2

    Что-то алгоритм какой-то странный. Уточнения:


    Во-первых, добавлять файл в .gitignore и делать git rm --cached придётся во всех ветках, не вполне понятно почему иногда об этом упомянуто, а иногда нет.


    Во-вторых, вместо простого git rebase -i HEAD~{...}, возможно, лучше cделать так:


    git commit --fixup HEAD~{...}
    git rebase -i --autosquash HEAD~{...}

    В-третьих, не вполне понятно почему для уже опубликованной истории предлагается делать git filter-branch, если git rebase -i в большинстве случаев прекрасно работает (а меньшинство случаев, скорее всего, пойдёт по ветке "Consider the API key compromised")


    В-четвёртых, в "нижней" ветке забыли сделать git push --force-with-lease

      –1
      Это не поможет избавиться от коммита физически. rebase не меняет существующие коммиты, он просто создаёт новые и переносит на них метку ветки. И старые коммиты останутся доступными по хэшу.
        0

        Как будто git filter-branch их физически удаляет...

      0
      Бороться надо всегда с причиной.

      Такой проблемы в принципе нет, если с самого начала работы над проектом убедиться, что в .gitignore добавлены файлы вида .env и ему подобные.

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

      А всё остальное, это уже следствие написания кода не приходя в сознание.
        0
        Основная проблема хранения настроек, что нельзя положить один общий файл для всех и перекрывать несколько свойств у каждого.
        Очень хочу возможность запрет на commit определенных файлов, при том что они продолжали бы получать обновления с сервера.
          0
          Основная проблема хранения настроек, что нельзя положить один общий файл для всех и перекрывать несколько свойств у каждого.

          А кто мешает научить программу работать с несколькими файлами конфигурации?


          Тот же ASP.NET Core, к слову, по умолчанию читает свою конфигурацию аж из 4х источников — из переменных окружения, из appsettings.json, из appsettings.{env}.json, и (в dev-режиме) из особых user secrets, которые вообще в папке проекта не хранятся.


          Ну или вернёмся к node js и env-файлам. Никто же не мешает сделать вот так:


          // config.mjs
          export const foo = process.env.FOO ?? 'default foo value';
          export const bar = process.env.BAR ?? 'default bar value';
            0
            >А кто мешает научить программу работать с несколькими файлами конфигурации?
            Есть много инструментов которые не очень дружат с нескольким файлами конфигурации.

            Не должна система хранения заставлять добавлять лишние строки в проект.
              0

              Но git — это не просто система хранения, а система хранения исходников. Понятие "исходный код" как бы подразумевает, что он не обязан использоваться любыми инструментами напрямую.

        +1

        Попробуйте не коммитить в репу мусор, а других карайте. А ещё есть сервисы хранения секретов, и без авторизованного доступа секреты не достать.

          +1
          Добавлю три копейки. При работе с AWS никогда не храните ключи в исходниках и любых файлах проекта, которые вообще в принципе могут быть залиты в публичный репозиторий. По статистике боты пытаются логиниться с утекшими ключами спустя секунды после коммита в публичный репозиторий на github. Всегда настраивайте profiles и их используйте при удаленной работе.
            0
            Есть еще вот такая тулза, позволяет удалять текстовые файлы с определенным содержимым, расширением, либо превышающие какой-то заданный размер:
            rtyley.github.io/bfg-repo-cleaner
              –1
              Каждый разработчик когда-то по ошибке коммитил в общедоступный репозиторий файлы с конфиденциальной информацией.

              Ни разу такого не делал. Всякие ключи храню в переменных окружения в памяти (и только в памяти). Код по необходимости достаёт их оттуда и использует. Закомитить их в git невозможно в принципе, поскольку как ты закомитишь туда блок ОЗУ неопределённого размера и адреса?
                0
                как вариант можно хранить ключи в репозитории зашифрованными, н-р. через ansible-vault
                0
                сами разработчики гит рекомендуют использовать git filter-repo вместо git filter-branch

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое