Удаление конфиденциальных данных из репозитория

Удаление конфиденциальных данных из репозитория
Update

Удаление конфиденциальных данных из репозитория — это двухэтапный процесс, который включает в себя действия как локально, так и в удаленном репозитории:

  1. Локальная очистка истории. На этом этапе используйте git-filter-repo для перезаписи истории репозитория на локальной машине. Процесс включает удаление файлов, замену текста в содержимом файлов и/или сообщениях коммитов. После этого локальная копия вашей истории уже не будет содержать конфиденциальных данных.

  2. Обновление удаленного репозитория и последующая очистка на сервере. После локальной очистки вы принудительно отправляете (git push --force) измененную историю на удаленный репозиторий. Однако этого часто недостаточно, так как удаленные платформы могут кэшировать старые версии данных или сохранять ссылки на них в запросах на слияние. Поэтому второй критически важный шаг — это обращение в службу поддержки GitVerse для окончательной очистки кешей и удаления всех оставшихся ссылок на конфиденциальные данные на стороне сервера.

Локальная очистка истории

Программные средства для очистки истории локального репозитория

Известное ПО для очистки истории локального репозитория:

  1. git-filter-repo (opens in a new tab) — современный инструмент для перезаписи истории Git, рекомендуем использовать его.

  2. git filter-branch — старый, медленный и имеет более сложный синтаксис.

По умолчанию git-filter-repo обрабатывает все ветки и теги в репозитории:

  1. Все локальные ветки
  2. Все теги
  3. Вся история коммитов, достижимая из этих веток

Что НЕ обрабатывается автоматически:

  1. Удаленные ветки (remote branches) — они не включаются по умолчанию.
  2. Ветки, которые не были получены локально (fetch).

Для обработки ВСЕХ веток включая удаленные:

# Сначала получите все удаленные ветки
git fetch --all
 
# Создайте локальные копии всех удаленных веток
git branch -r | grep -v '\->' | while read remote; do 
    git branch --track "${remote#origin/}" "$remote" 2>/dev/null || true
done
 
# Затем запустите git-filter-repo
git-filter-repo --replace-text sensitive_data_to_clean.txt

Альтернативный способ — указать конкретные ветки:

git-filter-repo --replace-text sensitive_data_to_clean.txt --refs refs/heads/ --refs refs/remotes/

Основные команды очистки локального репозитория

  1. --invert-paths и --invert-paths --path — удаление определенного файла из истории.
  2. --replace-text — замена текста в файлах и сообщениях коммитов.
  3. --message-callback — очистка текста в сообщениях коммитов с помощью Python-скрипта.
  4. --mailmap — замена email-адресов и имен авторов в метаданных коммитов.
  5. --commit-callback — гибкое изменение метаданных коммитов с помощью Python-скрипта.
  6. cat .git/filter-repo/changed-refs - проверка обработанных веток
  7. grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs — определение количества затронутых запросов на слияние.

--invert-paths и --invert-paths --path

Удаление определенного файла из истории:

git-filter-repo --invert-paths --path ПУТЬ-К-ВАШЕМУ-ФАЙЛУ-С-КОНФИДЕНЦИАЛЬНЫМИ-ДАННЫМИ

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

--replace-text

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

git-filter-repo --replace-text path/to/file/file_with_passwords.txt

Файл, который вы передаете в качестве аргумента --replace-text, должен содержать строки в формате search_string==>replacement_string. Каждая строка должна содержать искомый текст, затем два знака равенства (==>), а затем текст для замены.

Пример подготовленного файла со строками для замены:

моя_старая_функция==>моя_новая_функция
устаревшая_переменная==>актуальная_переменная
секретный_ключ_А==>скрытый_ключ_B

Если не указывать текст для замены, то вместо замены найденный текст будет удален. Пример подготовленного файла для удаления:

моя_секретная_строка_123
API_KEY_ТОКЕН
пароль_пользователя

--message-callback

Очистка текста в сообщениях коммитов. Этот метод позволяет выполнять более гибкую очистку или замену текста непосредственно в сообщениях коммитов, используя Python-скрипт:

git-filter-repo --message-callback '
import re
 
secret_to_find = r"секрет" # Переменная для текста с конфиденциальными данными
replacement_text = b"Сообщение очищено от конфиденциальных данных" # Сообщение на месте замены
 
if re.search(secret_to_find, message.decode("utf-8")):
    return replacement_text
return message
'

В данном примере, чтобы конфиденциальный текст был стерт без замены, переменной replacement_text следует присвоить пустую байтовую строку replacement_text = b"".

--mailmap

Замена email-адресов и имен авторов в метаданных коммитов. Этот метод позволяет изменить информацию об авторах коммитов во всей истории репозитория:

git-filter-repo --mailmap <(echo "Новое Имя <new-email@example.com> <old-email@personal.com>")
⚠️
  1. Не используйте <(echo ...) в Windows — это работает только в bash, но можно также создать отдельный файл .mailmap со списком замен и передать его в качестве аргумента.
  2. В mailmap-файле обязательно используйте угловые скобки для email-адреса. Формат строки mailmap: Новое Имя <новый-email@example.com> <старый-email@personal.com>.

Пример файла mailmap.txt с несколькими заменами (обязательно используйте угловые скобки):

Личная почта <public@example.com> <sensitive_personal@example1.com>
Рабочая почта <new@company.com> <old@company.com>
Анонимный Пользователь <anon@example.com> <sensitive_personal@example2.com>

Пример запуска команды git-filter-repo --mailmap с файлом mailmap.txt (файл размещен в папке уровнем выше):

git-filter-repo --mailmap ../mailmap.txt

--commit-callback

Гибкое изменение метаданных коммитов с помощью Python-скрипта. Этот метод позволяет программно изменять любые данные коммита, включая email-адреса, имена, даты:

git-filter-repo --commit-callback '
# Замена email-адреса в метаданных коммита
if commit.author_email == b"sensitive_personal@example.com":
    commit.author_email = b"public@email.com"
if commit.committer_email == b"sensitive_personal@example.com":
    commit.committer_email = b"public@email.com"
 
# Замена имен авторов
if commit.author_name == b"Личное Имя":
    commit.author_name = b"Публичное Имя"
'

С помощью --commit-callback можно также изменять:

  1. Даты коммитов.
  2. Сообщения.
  3. Другие метаданные. Все строковые значения должны быть в байтовом формате (с префиксом b).
  1. Пример изменения дат коммитов:

    git-filter-repo --commit-callback '
    import time
    from datetime import datetime
     
    # Изменяем все коммиты на текущее время
    current_timestamp = str(int(time.time())).encode("ascii")
    commit.author_date = current_timestamp + b" +0000"
    commit.committer_date = current_timestamp + b" +0000"
    '
  2. Пример изменения сообщений коммитов:

    git-filter-repo --commit-callback '
    import re
     
    # Удаляем номера задач из сообщений
    message_str = commit.message.decode("utf-8")
    # Удаляем паттерны вида "#1234" или "TASK-5678"
    clean_message = re.sub(r"#\d+|TASK-\d+", "", message_str).strip()
    commit.message = clean_message.encode("utf-8")
    '

Проверка обработанных веток

После выполнения git-filter-repo можно проверить, какие ветки были изменены в процессе очистки:

cat .git/filter-repo/changed-refs

Эта команда покажет все ссылки (refs), которые были изменены, включая:

  1. Локальные ветки (refs/heads/).
  2. Удаленные ветки (refs/remotes/).
  3. Теги (refs/tags/).
  4. Запросы на слияние (refs/pull/).

Если файл .git/filter-repo/changed-refs не существует, это означает, что git-filter-repo не обнаружил изменений или команда была выполнена без создания истории изменений.

Для подсчета количества измененных веток используйте:

# Подсчет всех измененных ссылок
wc -l .git/filter-repo/changed-refs
 
# Подсчет только локальных веток
grep -c '^refs/heads/' .git/filter-repo/changed-refs
 
# Подсчет только удаленных веток
grep -c '^refs/remotes/' .git/filter-repo/changed-refs

Проверка затронутых запросов на слияние

После перезаписи истории git-filter-repo вы можете узнать, сколько запросов на слияние потенциально затронуты, используя команду grep. Это важно, так как GitVerse может продолжать ссылаться на старые, содержащие конфиденциальные данные, коммиты.

grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs

Эта команда выводит количество строк, соответствующих шаблону ^refs/pull/.*/head$, в файле .git/filter-repo/changed-refs. Каждая такая строка указывает на то, что связанный с ней запрос на слияние может ссылаться на старую историю до очистки.

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

grep '^refs/pull/.*/head$' .git/filter-repo/changed-refs
⚠️

Если при очистке не был создан файл .git/filter-repo/changed-refs, то команда вернет сообщение grep: .git/filter-repo/changed-refs: No such file or directory.

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

Обновление удаленного репозитория и последующая очистка на сервере

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

Принудительная отправка изменений

Команда позволяет перезаписать всю историю удаленного репозитория вашей локальной, уже очищенной версией:

git push --force --mirror origin

где:

  • --force — флаг принудительной отправки изменений для перезаписи истории изменений на удаленном сервер.

  • --mirror — флаг гарантирует, что отправятся все ссылки (ветки, теги и другие специальные ссылки, такие как refs/pull/) из вашего локального репозитория в удаленный. Это обеспечивает точное соответствие удаленного репозитория вашей очищенной локальной копии, включая удаление ссылок на старые, содержащие конфиденциальные данные коммиты;

  • origin — указывает на удаленный репозиторий, куда вы отправляете изменения. Обычно это основной удаленный репозиторий по умолчанию.

⚠️

Перед выполнением этой команды обязательно согласуйте действия со всеми участниками репозитория. Все соавторы должны удалить свои локальные клоны репозитория и создать их заново, либо выполнить жесткий сброс (git reset --hard) и синхронизировать свои ветки с новой историей.

Если у вас включена защита веток, то команда git push --force --mirror может быть отклонена. В этом случае вам придется временно отключить защиту веток, выполнить отправку изменений, а затем снова включить защиту.

Пример очистки репозитория от конфиденциальных данных

Подготовительные действия

  1. Установите git-filter-repo, например:
sudo apt update
sudo apt install git-filter-repo
  1. Сохраните все изменения. Если есть непомеченные (unstaged) или незакомиченные (uncommitted) изменения, команда git-filter-repo не сработает корректно.

  2. Сделайте резервную копию вашего репозитория. Это критически важно, так как git-filter-repo необратимо изменяет историю.

Идентификация и подготовка конфиденциальных данных

Предположим, вы случайно закоммитили API-ключ MY_SECRET_API_KEY_123 в файл config.js и также упомянули его в сообщении коммита.

Создайте файл для замены/удаления текста, например, sensitive_data_to_clean.txt:

MY_SECRET_API_KEY_123==>***API_KEY_REMOVED***

Если вы хотите просто удалить, то:

MY_SECRET_API_KEY_123

Очистка файлов и сообщений коммитов локально

Выполните git-filter-repo для удаления API-ключа из содержимого файлов и сообщений коммитов:

git-filter-repo --replace-text path/to/sensitive_data_to_clean.txt

Если бы API-ключ был в отдельном файле, который нужно полностью удалить:

git-filter-repo --invert-paths --path path/to/your/config.js

Если бы вы хотели только удалить ключ из сообщений коммитов:

git-filter-repo --message-callback '
import re
secret_key = r"MY_SECRET_API_KEY_123"
replacement = b"***KEY_REMOVED_FROM_FROM_MESSAGE***"
decoded_message = message.decode("utf-8")
new_message = re.sub(secret_key, replacement.decode("utf-8"), decoded_message)
return new_message.encode("utf-8")
'

Проверка затронутых запросов на слияние

Проверьте, сколько запросов на слияние ссылаются на старые коммиты, содержащие API-ключ:

grep -c '^refs/pull/.*/head$' .git/filter-repo/changed-refs

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

Принудительная отправка изменений в удаленный репозиторий

git push --force --mirror origin
⚠️

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

Связь со службой поддержки GitVerse (при необходимости)

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