Поддържайте хранилището си чисто, за да избегнете хакване

Някои разработчици обичат да кодират бързо и да изрязват ъглите и аз също съм виновен за това! Това означава, че чувствителни данни (напр. тайни в обикновен текст, ключове за интерфейс за програмиране на приложения [API], пароли и т.н.) може случайно да бъдат ангажирани към вашето git хранилище. Това може да е добре, ако разработвате локално, но това може да е проблем, когато използвате хоствана услуга като GitHub.

Прочетете Secjuice Squeeze Volume 7, който съдържа история за API ключ на Starbucks, открит в GitHub.

Заден план

Работех върху няколко git хранилища, повечето от които имаха чувствителни данни, ангажирани към тях. Те имаха API ключове, AWS ключове, пароли, каквото и да е! Като инженер по сигурността исках да поправя това. Изглеждаше много работа. Имаше множество хранилища, но имаха тези „мръсни“ ангажименти от години назад. Ще споделя как почистих мръсните ангажименти, като премахнах тайните им.

Насоки от GitHub Help

Използвах официалната документация от GitHub, за да започна. Прочетох го и изглежда достатъчно просто. Просто трябваше да използвам BFG Repo-Cleaner и да помоля разработчиците да изтрият хранилището и да го клонират отново. Парче торта! Или поне така си мислех.

https://help.github.com/en/articles/removing-sensitive-data-from-a-repository

Използване на BFG Repo-Cleaner

BFG Repo-Cleaner е програма на Java, която използва git filter клон, за да модифицира съществуващите ангажименти и да замени съдържанието. Клонът на филтъра Git е доста досаден процес (вижте помощния документ на GitHub по-горе), така че се радвам, че BFG го опростява.

По-долу е процесът, който използвах за почистване на моите ангажименти.

1) Изтеглих приложението Java в моята папка ~/Downloads.

2) Създадох ~/Documents/bfg-secrets-all.txt файл. Погрижих се да поставя това извън моите git хранилища, за да избегна случайното му извършване и нарушаване на целта на това упражнение!

Добавих по един ред за всяка тайна, която исках да изчистя. Всеки ред трябва да започва с regex: или glob: и реших да използвам регулярни изрази за простота и познатост.

regex:8cea3229-09cd-4b89-9dce-f0f9b0697406
regex:815e9bc4-d795-4961-ab8b-50ddf8a391fe

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

regex:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}

3) Премахнах старателно всички тайни от всяко хранилище. Използвах променливи на средата, AWS Key Management Sservice и dot files, за да преместя чувствителните данни от ангажираните файлове.

4) Отидох до правилата за защита на клонове в настройките на хранилището на GitHub и активирах принудителни натискания.

5) Изпълних следната команда, за да проверя за мръсни ангажименти.

java -jar ~/Downloads/bfg-1.13.0.jar --replace-text ~/Documents/bfg-secrets-all.txt

Или ще каже, че няма мръсни ангажименти, или ще отпечата списък с мръсни ангажименти. Вижте дезинфекцирания примерен изход.

Commit Tree-Dirt History
------------------------
    Earliest                                              Latest
    |                                                          |
    ..DDDDDDDDDDDDDDDDDDDDDDDDDDDDmmDmmDDDDmDDDDDDmmmmmmmmmmmmDD
    D = dirty commits (file tree fixed)
    m = modified commits (commit message or parents changed)
    . = clean commits (no changes to file tree)
                            Before     After   
    -------------------------------------------
    First modified commit | 06f9e3e4 | cc990b18
    Last dirty commit     | e587f82e | f7ded7dc

6) След това избутах всички промени.

git reflog expire --expire=now --all && git gc --prune=now --aggressive
git push origin --force --all
git push origin --force --tags

7) Помоли разработчиците да изтрият хранилището и да го клонират отново.

8) Посетих хранилището на GitHub и се уверих, че ангажиментът изглеждаше така:

- apiKey = 'placeholder';
+ apiKey = '8cea3229-09cd-4b89-9dce-f0f9b0697406';

Сега изглеждаше така:

- apiKey = 'placeholder';
+ apikey = '***REMOVED***';

9) Празнувах, защото мислех, че съм приключил.

Премахване на чувствителни данни от всички клонове

Малко по-късно преминах към различен и остарял клон. Случайно видях API ключ в обикновен текст в историята на ангажиментите. BFG Repo-Cleaner каза, че е почистил историята на ангажиментите!

Разбрах, че BFG Repo-Cleaner почиства само извлечения git клон. Има смисъл. Това е в съответствие с целия работен процес на git.

Трябваше да повторя BFG процеса за всеки клон и да помоля разработчиците да изтрият своите хранилища и да ги клонират отново.

Сега поне всички клони са почистени. Притесненията ми вече свършиха.

Премахване на чувствителни данни от заявки за изтегляне на GitHub

Посетих стара заявка за изтегляне (PR) и видях API ключ в обикновен текст в хронологията на ангажиментите. Отново! Почистих всеки клон с BFG Repo-Cleaner. Какво става?! Измами ме веднъж; засрами се. Измами ме два пъти; засрамвам се.

Оказва се, че GitHub PR са независими от git хранилището. Това изглежда очевидно, тъй като PR е външен документ, позволяващ на рецензентите да одобрят дали един клон трябва да се слее с друг. Когато PR е одобрен и обединен, GitHub изпълнява функцията git merge.

BFG Repo-Cleaner е проектиран за git хранилища, а не за заявки за изтегляне на GitHub. В резултат на това трябваше да премахна всички тези PR и техните ангажименти ръчно.

След втория PR и 40 ангажимента по-късно осъзнах, че ръчната проверка на стотици PR и хиляди ангажименти за тайни би била трудна и податлива на човешка грешка.

Реших, че имам нужда от автоматизиран начин за намиране на всички PR-и и ангажименти, които имат мръсни ангажименти. Реших да използвам GitHub API.

Не мога да споделя сценария, който написах, поради съображения за авторски права. Описвам мисловния процес, който използвах, за да създам сценария.

1) Създадох персонален токен за достъп.

2) Създадох скрипт Node.js за тестване на токен.

mkdir myscript
npm init -y
npm install github-api
touch index.js
/* index.js */
'use strict'
const GitHub = require('github-api');
const gh = new GitHub({ token });

3) Използвах GitHub организация за всички хранилища. Актуализирах скрипта, за да получа всички хранилища. Скриптът изброява всички хранилища.

gh.getOrganization(orgName);
const repos = org.getRepos();
console.log(repos);

4) Избрах едно хранилище.

const repo = gh.getRepo(repos[0].owner.login, repos[0].name);

5) Получих всички негови PR-и.

const prs = repo.listPullRequests(options);

6) Избрах един PR.

const pr = prs[0];

7) Получих всички файлове, създадени, модифицирани и изтрити в PR.

const files = repo.listPullRequestFiles(repo, pr.number);

8) Прочетох файла bfg-secrets-all.txt, който използвах по-рано.

9) Търсих разликите във всеки файл, използвайки файла bfg-secrets-all.txt, който използвах по-рано, и създадох CSV изход.

10) Бих актуализирал кода, за да преминава през всяко хранилище, PR и файл.

11) Свързах се с Поддръжката на GitHub, за да изтрия или целия PR, или препратките за проследяване, и можете да използвате API на GitHub, за да получите и тази информация (вижте по-долу за примерен скрипт).

Отново проверка на хранилищата

Изчаках няколко седмици, след като почистих хранилищата и стартирах отново BFG Repo-Cleaner върху хранилищата. Открих, че някои хранилища отново имат чувствителни данни. Научих, че програмист е забравил да изтрие хранилището и е натиснал ангажимент, използвайки непочистеното хранилище.

Добра идея е да проверите хранилищата след изтичане на времето, за да сте сигурни, че са чисти.

Предотвратяване на ангажиране на разработчиците

Изглежда, че това може да е безкрайна битка: чистя, програмист случайно записва тайна, намирам я случайно, чистя отново и цикълът се повтаря. Исках процес, който да помогне за предотвратяването на това на първо място.

Реших да използвам git куки, за да проверя комит, преди да се ангажира. Реших да проверя куката на git за предварително ангажиране.

1) Създадох изпълним скрипт за предварително ангажиране.

touch .git/hooks/pre-commit
chmod +x .git/hoooks/pre-commit
# pre-commit
#!/bin/sh
if $(grep -rqE "\w{8}-\w{4}-\w{4}-\w{4}-\w{12}"  *) ; then
  echo 'Found a matching secret'
  exit 1
fi

2) Създадох файл с тайна, за да го тествам.

echo 8cea3229-09cd-4b89-9dce-f0f9b0697406 > secres.txt
git commit -a -m 'Testing'

Получих следния изход и файлът не беше ангажиран.

Found a matching secret

3) Имах нужда от начин да направя тази част от хранилището. В момента ще работи само на моята машина. Използвах, че всяко хранилище е за проект Node.js. Добавих скрипт след инсталиране, за да гарантирам, че git hook скриптът ще работи на всяка машина на разработчика.

Актуализирах файла package.json.

{
  "scripts": {
    "postinstall": "git config core.hooksPath .githooks"
  }
}

4) Копирах git hook скрипта в директория, която може да бъде ангажирана, и го ангажирах; не можете да ангажирате файлове в директорията .git.

mkdir .githooks
mv .git/hooks/pre-commit .githooks
git add .githooks/pre-commit
git commit -m "Added pre-commit hook script."

5) Всички разработчици трябва да изтеглят най-новия код и да изпълнят командата npm install на своята машина.

6) Друг подход е да се позволи на npm install да копира куката в директорията .git/hooks.

{
  "scripts": {
    "postinstall": "cp .githooks/* .git/hooks"
  }
}

Заключение

Записването на чувствителни данни и тайни с обикновен текст в хранилище на GitHub може да отслаби позицията ви за сигурност и са необходими усилия, за да ги изчистите след факта.

Можете да използвате BFG Repo-Cleaner, за да изчистите тайните във вашата хронология на ангажименти. Уверете се, че сте почистили всеки клон, принудително натиснете промените и стартирайте BFG отново след изтичане на времето, за да сте сигурни, че чувствителните данни не са въведени отново.

Може да намерите чувствителни данни в заявките за изтегляне на GitHull след използване на BFG. Можете да използвате GitHub API, за да намерите заявки за изтегляне с чувствителни данни. Изпратете тези констатации на GitHub Support и ги помолете да изтрият заявките за изтегляне или техните референции за проследяване.

Можете да използвате кукички за предварително ангажиране на git, за да предотвратите предаването на чувствителни данни.