SSH por llave pública en las VMs de sr250
Los 6 hosts Windows de Conkafecito (sr250 y sus VMs vm-rds, vm-contpaq, vm-mpro, vm-biotime, vm-test) corren OpenSSH for Windows con el usuario Administrador y aceptan la llave oracle_conka_surface, la misma que se usa para el resto de la infraestructura de Esteban. El objetivo es no depender de password para automatización ni para acceso manual. Todos corrían OpenSSH-Win32 7.7p1 (versión de 2018) salvo vm-rds, que tiene 9.5p1.
Diagnosticar y arreglar esto en los 6 hosts destapó tres bugs distintos e independientes, cada uno con su propia firma de síntoma. Ninguno se veía en el log de eventos con LogLevel por defecto — hay que subir a LogLevel DEBUG3 para que sshd escriba la causa real del rechazo.
Bug 1 — Match Group administrators no coincide en Windows localizado
Síntoma: sshd rechaza la llave sin loguear ningún intento de publickey — ni exitoso ni fallido, solo cae directo a password. sshd -T -C user=<usuario>,host=<host>,addr=127.0.0.1 -f sshd_config (dump de la config efectiva, no requiere tocar el servicio) muestra que el AuthorizedKeysFile resuelto es el default .ssh/authorized_keys en vez de __PROGRAMDATA__/ssh/administrators_authorized_keys, aunque el bloque Match Group administrators exista en el archivo.
Causa: en Windows en español el grupo local se llama Administradores (SID S-1-5-32-544), no administrators. OpenSSH-Win32 7.7p1 no tiene el alias por SID que reconoce el literal inglés administrators en cualquier idioma — hace un match de string plano contra el nombre del grupo, y falla. Como el Match nunca aplica, sshd cae al AuthorizedKeysFile global, que apunta al home del usuario (C:\Users\<user>\.ssh\authorized_keys), archivo que normalmente no existe.
Fix: cambiar el literal a Match Group Administradores (nombre real del grupo, verificable con Get-LocalGroup | Where-Object {$_.SID -like '*-544'}). Se confirma con el mismo sshd -T -C — el AuthorizedKeysFile resuelto debe cambiar a administrators_authorized_keys.
Algunos hosts (vm-contpaq, vm-rds, vm-biotime) ya traían un workaround previo distinto: en vez de usar Match Group, tienen AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys como directiva global (fuera de cualquier Match), lo cual aplica a todos los usuarios y evita el problema de raíz — funciona, aunque es más permisivo de lo necesario porque cualquier usuario del sistema (no solo administradores) consulta ese archivo compartido.
Bug 2 — StrictModes rechaza el archivo si tiene una ACE de más
Síntoma: la llave está presente y bien formateada en administrators_authorized_keys, pero sshd la rechaza. Con LogLevel DEBUG3 el evento de OpenSSH/Operational sí aparece esta vez: Bad permissions. Try removing permissions for user: <host>\<usuario> ... on file administrators_authorized_keys.
Causa: StrictModes exige que ese archivo solo tenga permisos de NT AUTHORITY\SYSTEM y BUILTIN\Administradores — cualquier ACE adicional (aunque sea de un usuario administrador válido) hace que sshd descarte el archivo completo, no solo esa entrada. En vm-rds el archivo tenía además VM-RDS\EALCOCER:(F), agregado en algún momento previo.
Fix: icacls <archivo> /remove:g "<dominio>\<usuario>" para dejar solo SYSTEM + Administradores.
Bug 3 — BOM de Set-Content -Encoding UTF8 rompe la primera línea
Causa: Windows PowerShell 5.1 (no PowerShell Core) siempre antepone un BOM UTF-8 (EF BB BF) al usar Set-Content -Encoding UTF8 sobre un archivo, a diferencia de lo que el nombre sugiere. Si el archivo empieza con una llave (ssh-ed25519 AAAA...), el BOM queda pegado antes de ssh-, y sshd no reconoce el tipo de llave en esa primera línea — la descarta como si no existiera. Solo afecta a la primera línea del archivo; las siguientes no se ven afectadas.
Fix: escribir el archivo con [System.IO.File]::WriteAllText($path, $content, (New-Object System.Text.UTF8Encoding($false))) en vez de Set-Content -Encoding UTF8. Verificar con los primeros bytes del archivo ([System.IO.File]::ReadAllBytes($path)[0..5]) — deben ser 115 115 104 45 101 100 (ssh-ed), nunca 239 187 191 ....
Resumen por host
| Host | Versión sshd | Bug encontrado | Fix |
|---|---|---|---|
sr250 | 7.7p1 | Bug 1 | Match Group Administradores |
vm-contpaq | 7.7p1 | ninguno (ya usaba AuthorizedKeysFile global) | solo agregar la llave |
vm-rds | 9.5p1 | Bug 2 (ACE de EALCOCER de más) | quitar esa ACE |
vm-biotime | 7.7p1 | ninguno (ya usaba AuthorizedKeysFile global) | solo agregar la llave |
vm-mpro | 7.7p1 | Bug 1 (administrators_authorized_keys ni existía) | crear archivo con ACL correcto + Match Group Administradores |
vm-test | 7.7p1 | OpenSSH Server no estaba instalado; una vez instalado, Bug 1 con el Match Group ya activo pero con el literal en inglés | instalar OpenSSH.Server + mismo fix de Bug 1 |
Todos con el error de Bug 3 en algún punto del proceso — es fácil de reintroducir sin querer al editar los archivos con PowerShell, así que conviene verificar bytes crudos después de cualquier edición vía Set-Content/Add-Content sobre archivos de OpenSSH en Windows.
Método de diagnóstico
LogLevel DEBUG3ensshd_config+Restart-Service sshd— sin esto, sshd solo loguea “Failed publickey” genérico, sin la causa.sshd -T -C user=<u>,host=<h>,addr=<ip> -f sshd_config— dump de la config efectiva que aplicaría sshd para ese usuario, sin tocar el servicio ni requerir una conexión real. El método más seguro para verificar si unMatchaplica o no.- Como último recurso, correr
sshd.exe -ddd -p 22 -f sshd_configen foreground trasStop-Service sshdpara ver el log completo en stdout/stderr — riesgo real: si el bind falla o el proceso no levanta bien, el host queda sin nada escuchando en el puerto 22 y sin forma de reconectar por SSH. Pasó una vez durante este trabajo (ensr250) y requirió acceso local para recuperarlo. Preferir siempre el punto 2 antes que este. - Revertir
LogLevela su default (comentado) al terminar — queda ruido en el log si se deja en DEBUG3 indefinidamente.
Véase también
- Acceso vía NetBird y PowerShell Direct — cómo se llegó a los hosts que no respondían por SSH directo durante este mismo trabajo.