Analytics

Линус Торвальдс, Бьёрн Страуструп и Брендан Грегг контрибьютят в мой хобби-проект. Зачем?

Смотрите сами: вот проект, вот история коммитов.


      Линус Торвальдс, Бьёрн Страуструп и Брендан Грегг контрибьютят в мой хобби-проект. Зачем?

Список контрибьюторов с главной страницы репозитория:


      Линус Торвальдс, Бьёрн Страуструп и Брендан Грегг контрибьютят в мой хобби-проект. Зачем?

Ссылки на аватарках ведут на странички профилей реальных людей.

Всё на месте. Кроме плашечки "Verified" как здесь:


      Линус Торвальдс, Бьёрн Страуструп и Брендан Грегг контрибьютят в мой хобби-проект. Зачем?

Знатоки Git и GPG, не торопитесь проматывать ленту: эта статья не про необходимость подписывать свои коммиты. Она про неявные допущения, которые мы делаем, пользуясь "интуитивно-понятными" монстрами GitHub и GitLab и доверяя им контроль доступа к нашим репозиториям.

Перед началом чтения

Напоминаю, что если ты встретил ошибку/опечатку/неточность, которая не влияет на смысл статьи, а лишь на внешний облик:

  1. выдели текст;
  2. жми 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: здесь.

    1. Кладем стихотворение в файл ./poem.txt;
    2. Пишем скрипт

      #!/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, который немного ломался, когда встречал обратные слеши.

    3. Запускаем скрипт и грустим от того, что лог репозитория на GitHub не очень подходит для чтения стихотворений;
    4. Радуемся, как круто выглядит в tig:

    
      Линус Торвальдс, Бьёрн Страуструп и Брендан Грегг контрибьютят в мой хобби-проект. Зачем?

    Related posts

    Google Analytics и GDPR: а нужно ли согласие пользователя?

    admin

    Неблагодарный opensource: разработчик самого быстрого веб сервера по версии techempower удалил его репозиторий

    admin

    Что делать, если забыт код от замка чемодана?

    admin

    Leave a Comment