Смотрите сами: вот проект, вот история коммитов.
Список контрибьюторов с главной страницы репозитория:
Ссылки на аватарках ведут на странички профилей реальных людей.
Всё на месте. Кроме плашечки "Verified" как здесь:
Знатоки Git и GPG, не торопитесь проматывать ленту: эта статья не про необходимость подписывать свои коммиты. Она про неявные допущения, которые мы делаем, пользуясь "интуитивно-понятными" монстрами GitHub и GitLab и доверяя им контроль доступа к нашим репозиториям.
Напоминаю, что если ты встретил ошибку/опечатку/неточность, которая не влияет на смысл статьи, а лишь на внешний облик:
- выдели текст;
- жми Ctrl+Enter;
В комментариях этому не место.
Опечатки
Об опечатке в посте легко сообщить автору, выделив часть текста и нажав Ctrl+Enter или Cmd+Enter. Появится форма с выделенной цитатой и полем для вашего комментария. Когда вы нажмете кнопку «Отправить», сообщение уйдет автору поста и в дальнейшем будет видно ваших диалогах.
Отправлять сообщения могут только зарегистрированные пользователи. Выделить можно любую часть текста на странице поста, но в цитату войдут только первые 220 символов. А максимальная длина комментария — 500 знаков.
(с) Правила
Однако об ошибках и неточностях, влияющих на смысл статьи стоит писать в комментариях — чтобы сообщить читателям, которые прочитали статью до авторских исправлений (которые могут пройти незаметно).
Напоминаю, что ошибки автора и комментарии к ним — отличный повод начать дискуссию и учиться друг у друга, а не холиварить.
UPD: Чтобы еще раз подчеркнуть этот момент, цитирую себя же из комментариев:
Моя статья не о том, как защитить свой репозиторий от чужого вмешательства.
Я пишу о том, как сложно защитить себя от того, что моё имя будет использовано другим человеком в неизвестном мне репозитории для обмана неизвестных мне людей.
© https://habr.com/ru/post/515550/#comment_21990276
Далее в статье я буду говорить о GitHub, однако изначально эту проблему я обнаружил на нашем внутреннем GitLab, а для этой статьи повторил то же самое на GitHub. Так что выводы применимы к обоим сервисам (и, возможно, не только к ним).
В чем проблема и зачем писать об этом статью?
Один из проектов в нашей компании прямо сейчас мигрирует из SVN в Git, и за последнюю неделю я несколько раз (и от разных людей) слышал один и тот же вопрос: "Зачем подписывать коммиты, если GitHub и так предоставляет контроль доступа?"
Короткий ответ: GitHub проверяет, что в репозиторий коммитят только определенные люди, но он не проверяет, что конкретно лежит в ваших коммитах.
Однако подобное доверие может сыграть с нами злую шутку. Например, репозиторий на GitHub с участием известных людей может стать еще одним звеном в цепи фишинговой кампании, толкающей жертву к потере средств.
Дополнительная проблема в том, что обезопасить себя (как жертву) от таких уловок очень сложно без вмешательства GitHub.
Механизм подставы
Многие из нас, когда впервые коммитили в Git-репозиторий, сталкивались со следующим сообщением:
$ git commit
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
fatal: unable to auto-detect email address (got 'user@pc.(none)')
Несмотря на то, что все уже поняли, в чем дело, я продолжу.
Git для коммита требует два поля: моё имя и почту. При этом, писать туда можно всё, что угодно: Git не проводит верификацию почты, это поле появилось по историческим причинам и нужно для связи с разработчиком.
$ git log --oneline
61c133f | Несчастная кошка порезала лапу # <-- будем смотреть на этот коммит
d825484 | Сидит, и ни шагу не может ступить.
1d87db4 | Скорей, чтобы вылечить кошкину лапу
e5e09ae | Воздушные шарики надо купить!
dfa34b5 |
db4191f | И сразу столпился народ на дороге —
2d3468f | Шумит, и кричит, и на кошку глядит.
5990103 | А кошка отчасти идет по дороге,
43cc6a9 | Отчасти по воздуху плавно летит!
$ git cat-file -p 61c133f
tree 93edb4a15b5b20f6d35fee611b70d07d581fc8f4
parent d825484462dbe091daa6108538807e37ff49a4b2
author Bjarne Stroustrup <bjarne@stroustrup.com> 1597692753 +0300
committer Bjarne Stroustrup <bjarne@stroustrup.com> 1597692753 +0300
| Несчастная кошка порезала лапу —
GitHub, в свою очередь, во время пуша удостоверяется, что у пользователя есть право пушить в этот репозиторий и в эту ветку. Причина здесь проста: GitHub реализует свои функции поверх такого же гита, которым пользуемся и мы с тобой на своем десктопе.
В этом и заключается тонкий момент. Принимая от пользователя коммиты, GitHub читает поле email
и если такой пользователь зарегистрирован на сайте, он считается контрибьютором репозитория. Для гита ведь вполне естественной является ситуация, когда я пишу код, коммичу, собираю свои коммиты в патч и посылаю другому пользователю. А он их проверит и запушит в основной репозиторий.
GitHub читает поле email
и если такой пользователь зарегистрирован на сайте, он считается контрибьютором репозитория
Так вот зачем...
… подписывать коммиты!" — воскликнет внимательный читатель.
К сожалению, нет.
Прямо сейчас я сходил в гугл и поискал статьи по теме. Вот одна из них: Spoofing Git Commits. Автор описывает тот же механизм (если я путано объяснил, можно почитать там) и приходит к выводу, что единственный способ защиты — подписывать свои коммиты. Но это неверно.
Как GitHub реагирует на подписанные коммиты?
В общем, лучше объясню словами:
- GitHub получает коммит с
email = lala@mail.com
, подписанный GPG; - он находит пользователя с почтой
lala@mail.com
; - он берет из профиля пользователя публичный ключ;
- если подпись коммита подходит к публичному ключу -> рисуем "Verified";
- если подпись не подходит к публичному ключу -> рисуем "Не Verified";
- если у пользователя нет ключа и нет подписи коммита -> ничего не рисуем;
- если у пользователя есть ключ и нет подписи коммита -> НИЧЕГО НЕ РИСУЕМ;
- если у пользователя нет ключа и есть подпись -> не знаю, что будет, но самое важное тут — строчка выше.
Когда обычный пользователь приходит в репозиторий и видит плашечки "Unverified", он насторожится.
Насторожится ли он, зайдя в репозиторий и не найдя там ни одной плашечки? Не думаю.
Если возникли подозрения, можно кое-что проверить
Судя по всему, активность пользователя (которая показывается на страничке его профиля) заполняется в базу во время пушей/работы с интерфейсом в браузере, а не парсится на лету из всех репозиториев на сайте.
Поэтому мои фейковые коммиты не отображаются на страничке активности.
Как пример — страничка Линуса:
Подводя итог
Подписывая коммит, я доказываю репозиторию и окружающим, что человек, выполнивший git commit
— это я, и никто другой.
Не подписывая коммит, я никому ничего не доказываю.
Когда ты видишь мой неподписанный коммит, у тебя не возникает причин думать, что этот код закоммичен не мной. Поэтому компульсивное подписывание каждого своего коммита не защищает меня от ситуации, когда кто-то другой пишет в коммитах мою почту и уверяет инвесторов, что я пишу код в его проект.
Ничто, кроме отсутствия аккаунта на GitHub. Или факта, что я — неуловимый Джо.
В любом случае, я знаю по именам шестерых людей, с которыми подобная ситуация уже произошла: их имена и почтовые адреса вписали в репозиторий на GitHub, чтобы завлечь читателей под кат.
Противоречу сам себе
Ничто не защитит меня от того, что кто-то создаст на GitHub репозиторий и положит туда код, под которым GitHub будет писать моё имя.
Вообще-то на GitHub есть потенциальная возможность защитить себя: в настройках профиля можно включить опцию Keep my email address private. Подробности в их доках.
Скриншот оттуда:
Чтобы почувствовать относительную безопасность, нужно:
- зарегистрировать аккаунт на никому не известную почту;
- включить оба чекбокса на скриншоте.
Зачем первый пункт? Специально для этого в список контрибьюторов на КДПВ я включил boomburum. Как можно удостовериться на странице его профиля на GitHub, он не засветил свою почту ни в одном коммите. Я эту почту просто угадал.
Еще один итог
Ты на работе пишешь в ваш внутренний GitLab, но на сервере нет хуков, которые отвергали бы любые пуши с коммитами, которые не были бы подписаны заранее известными ключами?
Будь готов к тому, что однажды к тебе придут и спросят, зачем ты вставил в процессинг платежей бекдор, через который утекла пара миллионов.
<шутка>Если только собираешься вставить такой бекдор — ты знаешь, что делать.</шутка> Не делай так, ведь админы твоего репозитория наверняка хранят все логи и записи аудита из GitLab за последние несколько лет.
Вместо заключения
Напишу пару слов о том, как я создал этот репозиторий с прекрасным стихотворением Даниила Хармса.
Всё, что я опишу ниже лежит в репозитории на GitHub: здесь.
- Кладем стихотворение в файл
./poem.txt
; Пишем скрипт
#!/bin/bash # Скрипт прервется, если встретит непроинициализированную переменную # либо какая-то функция/команда вернет код ответа, отличный от нуля set -eu ########## Подготавливаем окружение ############################################ FILE="${1:-./poem.txt}" OUT_DIR="${2:-./poetry}" OUT_FILE_NAME="README.md" OUT_FILE="${OUT_DIR}/${OUT_FILE_NAME}" export GIT_DIR="${OUT_DIR}/.git" export GIT_WORK_TREE="$OUT_DIR" if [[ -d "$OUT_DIR" ]]; then echo "Directory ${OUT_DIR} exists already" >&2 exit 1 fi if [[ ! -s "$FILE" ]]; then echo "File ${FILE} doesn't exist or empty" >&2 exit 2 fi mkdir -p "$OUT_DIR" touch "$OUT_FILE" git init # Убеждаемся, что гит не будет подписывать коммиты git config --local commit.gpgsign no ########## Список будущих контрибьюторов ####################################### declare -A AUTHORS=( ) AUTHORS["Linus Torvalds"]=torvalds@linux-foundation.org AUTHORS["Vitalik Buterin"]=v@buterin.com AUTHORS["Bjarne Stroustrup"]=bjarne@stroustrup.com AUTHORS["Brendan Gregg"]=brendan.d.gregg@gmail.com AUTHORS["Guido van Rossum"]=guido@python.org AUTHORS["Aleksey Boomburum"]=boomburum@gmail.com AUTHORS["Ivan Vasilev"]=slavniyteo@gmail.com # Массив имен разработчиков, чтобы идти по ним в цикле # В функции get-next-author-name declare -a AUTHOR_NAMES=( "${!AUTHORS[@]}" ) ########## Функции ############################################################# get-next-author-name() { local GLOBAL_AUTHOR_INDEX="$1" local AUTHOR_INDEX="$(($GLOBAL_AUTHOR_INDEX % "${#AUTHOR_NAMES[@]}"))" echo "${AUTHOR_NAMES["$AUTHOR_INDEX"]}" } commit-one-line() { local AUTHOR="$1" local LINE="$2" local AUTHOR_EMAIL="${AUTHORS["$AUTHOR"]}" # Вставляем строку в начало файла echo "$LINE" | cat - "$OUT_FILE" > temp mv temp "$OUT_FILE" git add "$OUT_FILE_NAME" # Добавляем '| ' в начало, # чтобы избежать проблем с пустым сообщением коммита git -c user.name="$AUTHOR" -c user.email="$AUTHOR_EMAIL" commit -m "| $LINE" } ########## Основной цикл ####################################################### LINE_IDX=0 while IFS= read -r LINE; do LINE_IDX="$((LINE_IDX + 1))" AUTHOR="$(get-next-author-name "$LINE_IDX")" commit-one-line "$AUTHOR" "$LINE" done <<<"$(tac "$FILE")" # Читаем файл от последней строки к первой ################################################################################
UPD: Скрипт обновил после комментария capslocky (использую
git -c user.name=$name
вместоgit config ...
;
UPD: Пофиксилread LINE
, который немного ломался, когда встречал обратные слеши.
- Запускаем скрипт и грустим от того, что лог репозитория на GitHub не очень подходит для чтения стихотворений;
- Радуемся, как круто выглядит в tig:
PVoLan
Кажется, что описанный механизм справедлив только для публичных репозиториев, где не используется проверка по паролю/SSH-ключу, и где email в конфиге — единственный способ определить автора?
Прямо сейчас я коммичу в два проекта двум разным компаниям (правда, это на gitlab, не на github). В одну компанию я имею доступ на свой личный аккаунт, в другую — на фирменный username@companyname.com. При этом у меня в git config и там и там прописан мой личный email (не менялся с момента установки гита и работает глобально на все проекты, где я работаю). Никаких проблем с отображением себя в репозитории второй компании не наблюдаю — те коммиты, которые имеют мой личный емейл в локальной истории гита, имеют подпись с фирменным email и ссылкой на фирменный аккаунт в истории gitlab.
SlavniyTeo Автор
Изначально такое поведение обнаружил в корпоративном on-prem GitLab Community. Коммитил и пушил в приватный репозиторий. Когда в коммите прописана почта, на которую регистрировался мой коллега — поведение полностью соответствует описанному в статье.
Можете повторить этот трюк в своих корпоративных репозиториях (можно и в своем личном репозитории, главное чтобы почта принадлежала кому-то, зарегистрированному на сервере).
Могу предположить, что эта почта добавлена в список дополнительных почт в профиле GitLab.
PVoLan
Я правильно понимаю, что речь идет о репозитории, собранном на локальном корпоративном сервере? Не про gitlab.com?
Точно нет. Аккаунт username@companyname.com не содержит никаких данных про мою личную почту — создавал недавно, ничего такого не вносил.
Более того, в настройках гита на моей локальной машине нигде не указывается username@companyname.com. Единственный фактор, по которому меня может идентифицировать gitlab в момент пуша — это SSH-ключ (его настройки периодически слетают, и я часто теряю доступ к репозиторию из-за этого).
SlavniyTeo Автор
Всё верно, on-prem (on-premise) значит, что сервер поднят на наших собственных серверах.
Все свои эксперименты я проводил через HTTPS. Можете проверить по HTTPS на своих серверах?
Если то, о чем Вы пишете, работает только по SSH, то Вы всё еще не защищены от подлога другими людьми по HTTPS.
PVoLan
Мммм, не могу прямо сейчас проверить https, нет под рукой подходящей конфигурации, но я использую общедоступный gitlab.com, можете протестить самостоятельно. Возможно, смогу сам еще протестить попозже.
Тем не менее, когда-то давно я на github пользовался доступом по https. И хорошо помню, что он требовал ввода пароля пользователя на каждый пуш (по крайней мере, при доступе в приватный репозиторий).
SlavniyTeo Автор
Об этом я писал в статье:
Каждый раз, когда я пушил свои коммиты, я вводил логин/пароль (или делегировал эту процедуру
libsecret
). Сайт проверял, что у меня есть право пуша в репозиторий, а потом показывал то, что я использовал как КДПВ.