SSH-ключ, который не проходит аутентификацию в GitHub или GitLab, — одна из самых гуглимых проблем разработчиков, и в половине случаев с ключом всё в порядке — просто диагноз неверен. Так что прежде чем что-то перегенерировать, запустите один тест и научитесь читать его ответ. А затем проходите виновников в порядке того, как часто они и правда причина.
«You’ve successfully authenticated, but GitHub does not provide shell access»
Если вам вернулась эта строка — стоп: ваш ключ уже работает. GitHub и GitLab не выдают вам оболочку; они говорят только на git поверх SSH, поэтому аутентифицируют вас и тут же вешают трубку, а «does not provide shell access» — это они так и сообщают, а не ошибка. У GitLab формулировка немного другая (Welcome to GitLab, @you!), но смысл тот же. Если ваш ключ работает, а git push всё равно просит пароль, то ключ никогда не был проблемой — переходите к виновнику «HTTPS вместо SSH» ниже, это и есть обычная настоящая причина.
Шаг 0: тест, который скажет, сломано ли вообще
Спросите git-хост напрямую, узнаёт ли он ваш ключ:
ssh -T git@github.com # GitHub
ssh -T git@gitlab.com # GitLab
Теперь прочитайте ответ внимательно, потому что сообщение об успехе выглядит как ошибка:
Hi <username>! You've successfully authenticated, but GitHub does not
provide shell access.
Это успех. «Does not provide shell access» — не сбой: GitHub и GitLab не дают вам оболочку, они говорят только на git поверх SSH, поэтому аутентифицируют вас и вешают трубку. Люди видят «does not provide» и начинают чинить работающую настройку. Если вы видите своё имя пользователя в этой строке — ваш ключ работает; проблема где-то ещё (переходите к виновнику HTTPS вместо SSH ниже — это обычная настоящая причина).
Настоящий сбой выглядит вот так:
git@github.com: Permission denied (publickey).
Вот его мы и будем исправлять. Обратите внимание на итог: Permission denied (publickey) означает, что сервер не принял ни один из предложенных вами ключей. Идём по списку вниз.
Виновник 1: ключ не добавлен в ваш аккаунт
Самая частая настоящая причина. Ключ на вашем ноутбуке ничего не делает, пока его публичная половина не зарегистрирована на git-хосте. Скопируйте публичный ключ:
cat ~/.ssh/id_ed25519.pub # note the .pub — this is the safe half to share
Вставьте его в GitHub → Settings → SSH and GPG keys → New SSH key или GitLab → Preferences → SSH Keys.
Одно правило без исключений: вставляйте файл .pub, никогда не файл без .pub. Публичный ключ — это одна строка, начинающаяся с ssh-ed25519 AAAA… (или ssh-rsa AAAA…). Если то, что вы собираетесь вставить, начинается с -----BEGIN OPENSSH PRIVATE KEY----- — стоп: это ваш приватный ключ, секрет, который и есть ваша личность. Вставив его в веб-форму, вы отдаёте свои учётные данные всякому, кто может прочитать эту страницу. Делитесь публичной половиной; берегите приватную.
(Ещё нет ключа? Сгенерируйте — Ed25519 почти во всех случаях (на английском): ssh-keygen -t ed25519 -C "you@device".)
Виновник 2: предлагается не тот ключ
Ваш ключ зарегистрирован, но SSH предлагает другой — частое дело, когда вы носите несколько ключей и агент перебирает их в порядке, который сервер отвергает. Посмотрите, какой именно ключ отправляется:
ssh -vT git@github.com
Ищите строки Offering public key:. Если зарегистрированного ключа среди них нет или принимается не тот, закрепите нужный ключ для каждого хоста в ~/.ssh/config:
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
IdentitiesOnly yes — ключевая строка: она велит SSH предлагать только указанный вами IdentityFile и перестать разбрасывать по серверу каждый ключ из вашего агента (это заодно лечит Too many authentication failures). Сделайте то же с Host gitlab.com для GitLab. Подробнее о структуре файла — в гайде по ssh_config (на английском).
Виновник 3: агент не запущен или ключ не загружен
Если у вашего ключа есть парольная фраза, а агент её не держит, SSH может молча его пропустить. Проверьте, что есть у агента:
ssh-add -l
The agent has no identities (или Could not open a connection to your authentication agent) означает, что его нужно запустить и загрузить ключ:
eval "$(ssh-agent -s)" # start the agent in this shell
ssh-add ~/.ssh/id_ed25519 # load the key (prompts for the passphrase once)
Теперь ssh-add -l должна показать отпечаток вашего ключа. (О том, что агент есть на самом деле и как он держит ключи, см. что такое SSH-агент (на английском).)
Виновник 4: у вас удалённый репозиторий по HTTPS, а не по SSH
Это виновник за фразой «ключ работает (ssh -T прошёл), а git push всё равно просит пароль». Ваш репозиторий настроен на HTTPS, который вовсе игнорирует SSH-ключи и хочет токен. Проверьте:
git remote -v
Если видите https://github.com/you/repo.git — вот и проблема: HTTPS никогда не касается вашего SSH-ключа. Переключите удалённый репозиторий на SSH:
git remote set-url origin git@github.com:you/repo.git # GitHub
git remote set-url origin git@gitlab.com:you/repo.git # GitLab
Снова выполните git remote -v, чтобы убедиться, что теперь он начинается с git@. Одно это исправление закрывает огромную долю жалоб «SSH-ключ не работает», где ключ с самого начала был в порядке.
Виновник 5: права на ~/.ssh
SSH отказывается использовать ключи и конфиг, которые могли бы подменить другие. Если подробный вывод упоминает неправильные режимы доступа или ключи игнорируются без явной причины:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519 # private key
chmod 644 ~/.ssh/id_ed25519.pub # public key
chmod 600 ~/.ssh/config
В Windows это всплывает после копирования ключей с синхронизируемого диска или между машинами; исправьте через свойства файла «Безопасность» или держите ключи вне облачно-синхронизируемых папок.
Виновник 6: запрос парольной фразы каждый раз
Если каждая операция git требует парольную фразу вашего ключа, агент её не кэширует. Загрузите её один раз через ssh-add (на английском) (виновник 3) и закрепите между сессиями, добавив в ~/.ssh/config:
Host github.com gitlab.com
AddKeysToAgent yes
В macOS ssh-add --apple-use-keychain ~/.ssh/id_ed25519 сохраняет парольную фразу в Keychain, так что она переживает перезагрузки.
Виновник 7: два аккаунта, один хост
Рабочий и личный аккаунты GitHub не могут делить один ключ — данный публичный ключ принадлежит ровно одному аккаунту. Дайте каждому свой ключ и псевдоним Host, который его выбирает:
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/work_ed25519
IdentitiesOnly yes
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/personal_ed25519
IdentitiesOnly yes
Затем клонируйте или задавайте удалённый репозиторий, используя как хост псевдоним: git@github-work:org/repo.git. SSH сопоставляет github-work, выбирает рабочий ключ и под капотом переписывает подключение на github.com.
Виновник 8: SSO организации не авторизован (GitHub)
К своим репозиториям вы аутентифицируетесь нормально, но получаете Permission denied на репозиториях организации за SAML SSO. Ключ существует, но не авторизован для этой организации. Зайдите в Settings → SSH and GPG keys, найдите ключ, нажмите Configure SSO и авторизуйте его для организации. У GitLab есть параллельный случай, когда просроченный ключ молча отвергается — проверьте дату истечения ключа в SSH Keys, если ранее работавший ключ вдруг перестал.
Когда это всё ещё Permission denied (publickey)
Если вы прошли список и всё равно упираетесь в эту строку, значит вы сузили проблему до настоящего отказа в приёме ключа — системный цикл для этой самой ошибки (чтение ssh -vvv, список методов сервера, предложенные против принятых ключей) описан в permission denied (publickey): быстрое системное исправление (на английском), а более широкая карта ошибок SSH — в разборе ошибок SSH. Но именно для git-хостов порядок выше ловит почти всё: зарегистрируйте публичный ключ, предложите правильный, загрузите его в агент и убедитесь, что вы действительно на удалённом репозитории по SSH.
Где клиент вовсе убирает возню с настройкой
Большая часть этого трения — это учёт: какой ключ какому хосту принадлежит, загружен ли он, SSH ли вообще удалённый репозиторий. Termalin держит ключ вместе с хостом, которому тот принадлежит, разблокирует ваши ключи один раз через встроенный агент, чтобы не перенабирать парольные фразы (а отменённый запрос никогда молча не пропускает ключ), и — поскольку он говорит на стандартном протоколе ssh-agent — git и ваш CLI могут подписывать через этот же агент. Отпечатки видны по каждому ключу, так что зарегистрировать нужную публичную половину в GitHub или GitLab — это копипаст, а не охота по пяти папкам ~/.ssh. Жонглирование аккаунтами и псевдонимами выше превращается в настройку для каждого хоста, а не в головоломку с файлом конфигурации.
Termalin — бесплатный кроссплатформенный SSH-клиент, который управляет вашими ключами через агент с одной разблокировкой, и git может подписывать через него, — скачайте его или прочитайте, как он безопасно обращается с ключами.