Una deploy key por repositorio, y por que no reuso la llave de mi laptop
El servidor despliega seis proyectos por git. Tiene seis llaves distintas, todas de solo lectura, y un archivo de configuracion de SSH que hace que git no tenga que enterarse.
Mi servidor despliega seis proyectos con git pull. Tiene seis llaves SSH distintas, una por repositorio, todas de solo lectura. Es más trabajo de montar que una sola llave y vale cada minuto.
Por qué no una llave para todo
La tentación es generar una llave en el servidor, subirla a tu cuenta de GitHub como SSH key y terminar. Funciona a la primera. Y deja tres cosas mal:
Esa llave puede escribir en todo lo que tú puedas escribir. No en un repositorio: en todos, incluidos los privados de clientes y los de la organización. Un servidor comprometido no roba un proyecto, roba tu identidad de desarrollador.
No se puede revocar en pequeño. El día que retiras un proyecto del servidor, o que un VPS pasa a otras manos, la única forma de cortarle el acceso es borrar la llave — y con ella se van los otros cinco despliegues.
No sabes quién hizo qué. En el registro de GitHub todo aparece como tú.
Una deploy key es distinta: se da de alta en el repositorio, no en la cuenta, y solo sirve para ese repositorio. Si alguien se lleva el servidor, se lleva el acceso de lectura a seis repos que ya estaban desplegados ahí. Malo, pero acotado.
Y sin acceso de escritura. GitHub ofrece la casilla Allow write access al darla de alta; para desplegar no hace falta. El servidor lee y construye, no publica.
El problema de tener seis
Todas apuntan a github.com, así que SSH no tiene forma de saber cuál usar. Sin ayuda, prueba la primera, GitHub la rechaza para ese repositorio, y obtienes el mensaje más engañoso del ecosistema:
ERROR: Repository not found.
No dice «llave equivocada». Dice que el repositorio no existe — que es lo mismo que verías si te equivocaste de nombre, o si de verdad no tienes acceso. Se pierde tiempo revisando la URL.
La solución es un alias por proyecto en ~/.ssh/config:
Host github-hm
HostName github.com
User git
IdentityFile ~/.ssh/hm_deploy
IdentitiesOnly yes
Host github-electoral
HostName github.com
User git
IdentityFile ~/.ssh/electoral_deploy
IdentitiesOnly yes
IdentitiesOnly yes es la línea que hace el trabajo: sin ella, SSH ofrece todas las llaves que encuentra, GitHub acepta la primera que reconozca y acabas clonando con la llave de otro proyecto sin enterarte.
Con eso, el remoto del repositorio usa el alias en vez del dominio:
git remote add origin github-hm:manuelcbravo/hm_agency.git
Y git pull funciona sin saber que hay seis llaves.
Cómo lo monto
Tres comandos en el servidor:
ssh-keygen -t ed25519 -f ~/.ssh/hm_deploy -N '' -C 'hm-agency-deploy'
cat >> ~/.ssh/config <<'EOF'
Host github-hm
HostName github.com
User git
IdentityFile ~/.ssh/hm_deploy
IdentitiesOnly yes
EOF
cat ~/.ssh/hm_deploy.pub
Esa última salida se pega en Settings → Deploy keys → Add deploy key del repositorio, sin marcar escritura.
La llave va sin frase de paso (-N '') a propósito: un despliegue automático no puede contestar una pregunta. Lo que la protege es que solo lee, solo ese repositorio, y solo desde esa máquina.
Se comprueba sin clonar nada:
$ git ls-remote github-hm:manuelcbravo/hm_agency.git
540e82b9a2b764aa4b75f315a622cbfd285adf07 HEAD
Si contesta con un hash, está lista.
Un detalle de Windows
En mi laptop la llave personal sí tiene frase de paso, y ahí me encontré con algo que no esperaba: el ssh de Git Bash no puede usar el agente de Windows. Busca un socket de Unix en SSH_AUTH_SOCK; el agente de Windows expone una tubería con nombre. Son dos mundos distintos.
La salida es usar el cliente de Windows explícitamente:
/c/Windows/System32/OpenSSH/ssh.exe manuel@mi-servidor
Ese sí habla con el agente, y entra sin pedir la frase. El de Git Bash, con la misma llave, falla con Permission denied (publickey) — otro mensaje que apunta al lugar equivocado.