Конфликт локальных SSH ключей для общего домена GitLab
Обычно компании используют свои домены второго уровня для репозиториев (например, company.gitlab.com), и проблем с SSH доступом не возникает. Но в моем случае проект клиента оказался на том же gitlab.com, что и мой личный. Здесь я впервые столкнулся с "конфликтом" SSH ключей для одного домена.
Алиасы, названия ключей и проектов изменены.
Проблема
Проект клиента успешно склонировался, я настроил его и некоторое время работал. Но когда я вернулся к своему проекту, то получил неожиданную ошибку:
master@mi-pro:~/dev/www/me/app1$ git pull remote: remote: ======================================================================== remote: remote: ERROR: The project you were looking for could not be found or you don't have permission to view it. remote: remote: ======================================================================== remote: fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists
Первым делом я проверил загруженные в SSH-агент ключи – все на месте:
master@mi-pro:~$ ssh-add -l 3072 SHA256:ABCD2 client1_gitlab.com (RSA) 3072 SHA256:ABCD1 my_gitlab.com (RSA)
В чем же дело? Проблема в том, что SSH-клиент, подключаясь к gitlab.com (порт 22), предлагает серверу все доступные ключи из агента по очереди. GitLab принимает первый ключ, который есть в системе (в моем случае это оказался последний добавленный – client1_gitlab_rsa). Для моего аккаунта этот ключ чужой, отсюда и ошибка доступа.
Ситуация стала окончательно ясна, когда я заглянул в конфиги Git:
master@mi-pro:~$ cat ~/dev/www/me/app1/.git/config | grep url
url = git@gitlab.com:me/app1.git
master@mi-pro:~$ cat ~/dev/www/client1/app1/.git/config | grep url
url = git@gitlab.com:client1/app1.git
Оба URL начинаются одинаково: git@gitlab.com:. Для Git это один и тот же удаленный сервер, но SSH использует для подключения не тот ключ.
Главной задачей для меня стала необходимость сделать мой основной SSH-ключ по умолчанию, поскольку мои проекты лежат не только в одном каталоге. А ключи клиентов подгружать отдельно.
Варианты решения
Есть несколько способов решить эту проблему. Опишу два наиболее оптимальных, на мой взгляд.
Вариант 1. Алиас хоста в ~/.ssh/config
Работая только со своим GitLab, у меня не было необходимости явно определять свой ключ в ~/.ssh/config. Но здесь этот файл стал удобным решением. Мы явно укажем, какой ключ для какого домена использовать, а также добавим параметр IdentitiesOnly yes, который запрещает SSH использовать ключи из агента, заставляя его применять только то, что указано в конфиге.
master@mi-pro:~$ nano ~/.ssh/config
...
# Мой ключ (по умолчанию для gitlab.com)
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/my_gitlab_rsa
IdentitiesOnly yes
# Ключ клиента (используем псевдоним хоста)
Host gitlab-client1
HostName gitlab.com
User git
IdentityFile ~/.ssh/client1_gitlab_rsa
IdentitiesOnly yes
После этого важно изменить URL в конфиге проекта клиента, заменив gitlab.com на наш новый алиас gitlab-client1:
master@mi-pro:~$ nano ~/dev/www/client1/app1/.git/config
[remote "origin"]
url = git@gitlab-client1:client1/app.git
fetch = +refs/heads/*:refs/remotes/origin/*
Теперь Git, видя алиас gitlab-client1, обратится к SSH-конфигу, который подставит правильный ключ.
Вариант 2. Контекстная конфигурация через ~/.gitconfig
Это подход позволяет автоматически подставлять настройки в зависимости от того, в какой директории вы находитесь. Для этого используется механизм includeIf в Git.
Создадим отдельные конфиги для каждого контекста:
master@mi-pro:~$ nano ~/.gitconfig-me
[core]
sshCommand = ssh -i ~/.ssh/my_gitlab_rsa -F /dev/null
master@mi-pro:~$ nano ~/.gitconfig-client1
[core]
sshCommand = ssh -i ~/.ssh/client1_gitlab_rsa -F /dev/null
master@mi-pro:~$ ls -la | grep .gitconfig
-rw-rw-r-- 1 master master 66 Mar 17 13:19 .gitconfig
-rw-rw-r-- 1 master master 0 Mar 17 17:32 .gitconfig-client1
-rw-rw-r-- 1 master master 0 Mar 17 17:32 .gitconfig-me
Флаг
-F /dev/nullговорит SSH не использовать общий конфиг, чтобы избежать неожиданных пересечений.
Далее, в главном ~/.gitconfig пропишем правила подключения этих файлов в зависимости от пути к репозиторию.
master@mi-pro:~$ nano ~/.gitconfig
[user]
name = Your Name
email = main@email.com
[includeIf "gitdir:~/dev/www/me/"]
path = ~/.gitconfig-me
[includeIf "gitdir:~/dev/www/client1/"]
path = ~/.gitconfig-client1
Теперь, когда вы будете выполнять Git-команды в любой папке проектов, Git автоматически подставит команду ssh с вашим личным ключом. Для проектов клиента – с ключом клиента. URL в .git/config проектов менять не нужно, они остаются стандартными (git@gitlab.com:...).
Мой выбор
Я выбрал для себя Вариант 1, так как мой основной ключ SSH не должен зависеть от каталога проекта, плюс я привык контролировать настройки удаленных репозиториев в файле .git/config.
Однако Вариант 2 намного удобнее, если вы работаете с десятками проектов или не хотите вручную править их конфиги. Git сам "поймет", где вы находитесь, и сделает все за вас.
Также я изначально попробовал более короткий вариант через хук в ~/.bashrc:
PROMPT_COMMAND='update_git_ssh_command'
update_git_ssh_command() {
if git rev-parse --git-dir > /dev/null 2>&1; then
if [[ "$PWD" == *"dev/www/client1"* ]]; then
export GIT_SSH_COMMAND="ssh -i ~/.ssh/client1_gitlab_rsa -F /dev/null"
else
export GIT_SSH_COMMAND="ssh -i ~/.ssh/my_gitlab_rsa -F /dev/null"
fi
else
unset GIT_SSH_COMMAND
fi
}
Но у него есть оверхед в плане производительности и возможных аномалий в консоли Linux, поэтому лучше использовать более предсказуемые варианты.
Источники и ссылки
- ssh_config(5) – Linux manual page
- FILES > ~/.gitconfig – Git Documentation
- CONFIGURATION FILE > Includes – Git Documentation