Usuwanie haseł z repozytorium Git

Usuwanie haseł z repozytorium Git

Wstęp

Wyobraź sobie, drogi czytelniku, że piszesz kolejną funkcjonalność dla swojej aplikacji i w ramach „szybkich” testów konfiguracji programu postanawiasz na chwilkę umieścić w swoim kodzie hasło dostępowe do jakiegoś serwera. Po stwierdzeniu, że wszystko działa, postanawiasz wrzucić swoje zmiany do repozytorium Git. Szybki commit i push.

Ups! Właśnie do ciebie dotarło, że wraz z twoimi zmianami, do serwera powędrowało twoje hasło, zapisane gdzieś w kodzie! Natychmiastowo usuwasz hasło z plików źródłowych, tworzysz nowy commit i ponownie: push. Uff… już po sprawie…

Czy aby na pewno?

Każda osoba mająca dostęp do repozytorium, może bez problemu przeglądać historię zmian, commit po commicie. Może więc w prosty sposób odnaleźć commity zawierające przypadkowo opublikowane hasła, a następnie zdobyte informacje wykorzystać do swoich niecnych celów. Zupełnie tak, jak przedstawiłem to w poprzednich dwóch wpisach („Kultura pracy a bezpieczeństwo w projekcie” oraz „Jak pracować z hasłami?„).

Czy jest więc jakiś sposób, by usunąć wrażliwe dane z repozytorium?

Na szczęście: tak! W niniejszym artykule przedstawię dwie metody, które są w stanie zmienić historię repozytorium, umożliwiając nam tym samym naprawienie naszych pomyłek. Są to:

  • Polecenie Git filter-branch
  • Narzędzie BFG Repo-Cleaner

Ostrzeżenie

Przed przejściem do modyfikacji historii repozytorium, zalecane jest zamknięcie wszystkich otwartych pull requestów i wykonanie wszystkich planowanych merge’y. Metody opisane poniżej ingerują w istniejące commity zmieniając ich wartości SHA, co może wpłynąć na otwarte pull requesty.

Polecenie filter-branch

Git udostępnia polecenie filter-branch, które umożliwia nadpisywanie historii repozytorium wedle naszej woli. W tym przypadku chcemy odchudzić nasze repozytorium o plik, który zawiera opublikowane przez nas poufne informacje. W tym celu musimy wykonać następujące kroki:

  1. Jeżeli jeszcze tego nie zrobiliśmy, klonujemy nasze repozytorium, które chcemy zmodyfikować:

    git clone https://serwer-gir.com/nazwa-uzytkownika/repozytorium
  2. Przechodzimy do lokalizacji ze sklonowanym repozytorium:

    cd repozytorium
  3. Wykonujemy poniższe polecenie filter-branch. Spowoduje to przetworzenie całej historii repozytorium i usunięcie wskazanego pliku ze wszystkich commitów. Warto rozważyć więc zrobienie lokalnej kopii tego pliku, by nie utracić na dobre reszty jego zawartości. Należy zwrócić uwagę, że jeżeli w którymś momencie w historii plik ten zmienił nazwę lub lokalizację, musimy wykonać poniższe polecenie również dla starych ścieżek dla tego pliku.

    Jeżeli po usunięciu pliku, któreś z commitów nagle okazałyby się puste (nie zawierałyby już żadnych zmian), to zostaną one usunięte. Należy też dodać, że istniejące tagi również zostaną nadpisane.

    Polecenie:

    git filter-branch --force --index-filter \
    'git rm --cached --ignore-unmatch scieżka-do-pliku' \
    --prune-empty --tag-name-filter cat -- --all

    Uważaj na cudzysłów. Wydanie polecenia może zakończyć się błędem. Być może będziesz zmuszony zmienić go na apostrofy '!

  4. Gdy wszystkie wrażliwe dane zostaną usunięte oraz wszystkie inne wymagane zmiany dokonane, musimy je wrzucić na serwer. W tym celu musimy wykonać operację push z flagą `–force` dla wszystkich branchy:

    git push origin --force --all
  5. Musimy również spushować zmiany w tagach:

    git push origin --force --tags
  6. Kluczowym jest poinformowanie swoich współpracowników, którzy pracują na branchach utworzonych przed modyfikacją historii repozytorium, by nie merge’owali swoich zmian, lecz wykonali operację rebase. Jakikolwiek merge brancha z poprzednią historią repozytorium może przywrócić usunięte wrażliwe dane.

  7. Na koniec, gdy jesteśmy pewni, że nasze zmiany niczego nie popsuły i wszystko działa poprawnie, możemy usunąć wszytkie niepotrzebne referencje i przedawnić stare wpisy w reflogu:

    git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin
    git reflog expire --expire=now --all
    git gc --prune=now

Narzędzie BFG Repo-Cleaner

Alternatywą do polecenia filter-branch jest narzędzie open-source o nazwie BFG Repo-Cleaner, które umożliwia wykonanie wybranych operacji na historii repozytorium w sposób prostszy i szybszy niż polecenie Gita.

Należy pamiętać, że po każdej zmianie historii repozytorium, musimy wykonać polecenie push z flagą --force. Ponadto, podobnie jak w przypadku branch-filter, musimy pilnować, by nasi współpracownicy przypadkiem nie wykonali merge’a branchy zawierających oryginalną historię repozytorium, ponieważ może to przywrócić wszystkie dane, które próbowaliśmy usunąć. Zamiast merge’a, powinien zostać wykonany rebase.

BFG Repo-Cleaner można pobrać z tej strony (klik!). Do jego działania wymagane jest środowisko uruchomieniowe Java w wersji 7 lub wyższej. Pobrany plik .jar można uruchomić następującym poleceniem:

java -jar bfg.jar <komenda> <parametry> <repozytorium>

Gdzie:

  • <komenda> jest jedną z komend wspieranych przez program. Pełną listę można uzyskać poprzez uruchomienie programu bez żadnych parametrów:

    java -jar bfg.jar
  • <parametry> to wymagane przez konkretną komendę parametry,

  • <repozytorium> to root folder lokalnego repozytorium, które chcemy modyfikować.

Poniżej przedstawię dwa najbardziej przydatne w naszym przypadku polecenia:

  • delete-files
  • replace-text

Polecenie delete-files

Polecenie delete-files umożliwia usunięcie z historii repozytorium wskazanego pliku. Modyfikowane są wszystkie commity zawierające jakiekolwiek ślady naszego pliku. Wyjątkiem jest ostatni commit, który pozostanie w stanie nienaruszonym.

Przykład użycia:

bfg --delete-files <plik> <repozytorium>

Gdzie:

  • <plik> to ścieżka do pliku, który ma zostać usunięty,
  • <repozytorium> to root folder lokalnego repozytorium, które chcemy modyfikować.

Polecenie replace-text

By pozbyć się wrażliwych danych z historii repozytorium, nie trzeba usuwać całych plików, które te dane zawierały. BFG Repo-Cleaner udostępnia polecenie replace-text, które podmienia wskazane słowa na frazę ***REMOVED*** we wszystkich plikach w repozytorium. Przykład użycia:

bfg –replace-text <plik-z-hasłami> <repozytorium>

Gdzie:

  • <plik-z-hasłami> to ścieżka do pliku zawierającego wszystkie słowa, które mają zostać usunięte. Słowa te powinny być oddzielone od siebie znakiem nowej linii.
  • <repozytorium> to root folder lokalnego repozytorium, które chcemy modyfikować.

Podsumowanie

Jeżeli jednak ze zmodyfikowanego repozytorium korzysta więcej niż jeden użytkownik, musimy zwrócić szczególną uwagę na proces merge’owania zmian. Jeżeli branch naszego współpracownika wciąż zawiera dane, które usunęliśmy, zamiast operacji merge’owania powinniśmy wykonać rebase. W przeciwnym wypadku istnieje spore ryzyko, że usunięte dane ponownie wrócą do historii.

Modyfikacja historii repozytorium nie naprawia problemu całkowicie. Musimy mieć na uwadze fakt, że mimo wszystko wrażliwe dane wyciekły i – w miarę możliwości – nie powinniśmy dalej z nich korzystać. Jeżeli były to hasła – powinny być one natychmiastowo zmienione. Nigdy nie wiemy, czy nawet po roku od wystąpienia tego incydentu ktoś nie posiada u siebie na komputerze starej kopii repozytorium, gdzie wszystkie tajne dane wciąż są dostępne.