# Metodología de Acceso SSH

> Última actualización: 2026-04-28
> Estado: **PROPUESTA — Fase 2 (no ejecutada aún)**

Este doc define cómo vamos a acceder a los VPS de forma segura y reproducible, con las siguientes restricciones:

1. Eliminar autenticación por password (riesgo R3).
2. Mantener acceso usable desde Claude Code (Bash tool ejecuta `ssh ...` directamente).
3. Soportar múltiples operadores (Coke, Juan, asistente IA en sesiones distintas).
4. Soportar futuros VPS (cluster crece) sin tener que rehacer todo.

---

## 1. Modelo de identidades

Vamos a tener **3 tipos de keys** en circulación:

| Identidad | Quién la usa | Cuándo |
|---|---|---|
| **`bewpro_admin_<persona>_ed25519`** | Cada operador humano (Coke, Juan, etc.) | Login interactivo manual |
| **`bewpro_claude_local_ed25519`** | Claude Code en máquina local de Coke | Operaciones automatizadas desde el chat |
| **`bewpro_deploy_ed25519`** | Scripts en VPS1 que hacen `ssh` a VPS2 | Provisioning cross-VPS |

**Ventajas**:
- Si rotamos una sola key (ej: Coke pierde su laptop), no afecta a las demás.
- El log de `auth.log` muestra qué identidad entró, no solo "root".
- Podemos revocar Claude sin tocar a humanos.

**Desventajas**:
- Más entradas en `~/.ssh/authorized_keys` de cada VPS (manageable — máx ~10 keys).

---

## 2. Cliente local (máquina de Coke)

### 2.1 Generación de keys

```bash
# Identidad humana de Coke (interactivo, con passphrase)
ssh-keygen -t ed25519 \
  -f ~/.ssh/bewpro_admin_coke_ed25519 \
  -C "coke@lacompaniadigital.com — bewpro admin" \
  # cuando pida passphrase: SI poner una

# Identidad para Claude Code (sin passphrase para no bloquear automatización)
ssh-keygen -t ed25519 \
  -f ~/.ssh/bewpro_claude_local_ed25519 \
  -C "claude-code on coke laptop — bewpro automation" \
  -N ""
  # -N "" = sin passphrase
```

Resultado:
- `~/.ssh/bewpro_admin_coke_ed25519` (privada, con passphrase)
- `~/.ssh/bewpro_admin_coke_ed25519.pub`
- `~/.ssh/bewpro_claude_local_ed25519` (privada, sin passphrase — solo para Claude)
- `~/.ssh/bewpro_claude_local_ed25519.pub`

> **Por qué Claude sin passphrase**: el Bash tool no puede responder prompts interactivos de `ssh-add`. Si la key tiene passphrase, cada `ssh vps1` pide tipear, y la sesión se cuelga. La protección equivalente es:
> - La key vive solo en la máquina local de Coke (no se sincroniza a iCloud/Dropbox).
> - Si Coke pierde control de la laptop, **revocar inmediatamente** esa pubkey de los `authorized_keys` de los VPS.
> - Auditar uso desde `auth.log` del VPS.

### 2.2 `~/.ssh/config`

```
# ────────── BewPro Production ──────────
Host vps1
    HostName 72.61.45.136
    Port 22
    User root
    IdentityFile ~/.ssh/bewpro_admin_coke_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

Host vps2
    HostName 179.43.124.219
    Port 5633
    User root
    IdentityFile ~/.ssh/bewpro_admin_coke_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3

# Variantes para Claude (cuando el agente IA opera)
Host vps1-claude
    HostName 72.61.45.136
    Port 22
    User root
    IdentityFile ~/.ssh/bewpro_claude_local_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
    LogLevel ERROR

Host vps2-claude
    HostName 179.43.124.219
    Port 5633
    User root
    IdentityFile ~/.ssh/bewpro_claude_local_ed25519
    IdentitiesOnly yes
    ServerAliveInterval 60
    ServerAliveCountMax 3
    LogLevel ERROR
```

Con esto:
- Coke ejecuta `ssh vps1` → entra con su key (pide passphrase una vez por sesión).
- Claude ejecuta `ssh vps1-claude "comando"` → entra sin passphrase.
- Ambas keys están registradas separadamente en los VPS.

### 2.3 (Opcional pero recomendado) ssh-agent para Coke

Para no tipear passphrase 40 veces al día:
```bash
# En ~/.zshrc:
eval "$(ssh-agent -s)" >/dev/null
ssh-add --apple-use-keychain ~/.ssh/bewpro_admin_coke_ed25519 2>/dev/null
```

(macOS-specific: `--apple-use-keychain` integra con Keychain.)

---

## 3. Aprovisionamiento de los VPS

### 3.1 Estado de partida (hoy)

- VPS1: SSH password root, puerto 22.
- VPS2: SSH password root, puerto 5633.
- Ambos: probablemente `PasswordAuthentication=yes`, `PermitRootLogin=yes`.

### 3.2 Pasos para cada VPS

**Importante: hacer en este orden, mantener una segunda sesión SSH abierta como failsafe.**

```bash
# (1) Subir las pubkeys con la password todavía válida
ssh-copy-id -i ~/.ssh/bewpro_admin_coke_ed25519.pub root@72.61.45.136
ssh-copy-id -i ~/.ssh/bewpro_claude_local_ed25519.pub root@72.61.45.136

# Para VPS2 con puerto custom:
ssh-copy-id -i ~/.ssh/bewpro_admin_coke_ed25519.pub -p 5633 root@179.43.124.219
ssh-copy-id -i ~/.ssh/bewpro_claude_local_ed25519.pub -p 5633 root@179.43.124.219

# (2) Probar acceso por key (sin password)
ssh vps1 "whoami && hostname"          # debe responder root + hostname
ssh vps1-claude "whoami && hostname"   # idem

# (3) Solo si (2) funciona: rotar password root del VPS
ssh vps1
# Adentro:
passwd          # poner password nueva, larga, aleatoria — guardar en gestor de pw
exit

# (4) Hardening sshd_config
ssh vps1
# Editar /etc/ssh/sshd_config (con backup):
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d)
# Cambiar (o agregar):
#   PasswordAuthentication no
#   PermitRootLogin prohibit-password    # solo por key, no por password
#   PubkeyAuthentication yes
#   ChallengeResponseAuthentication no
#   UsePAM yes
#   X11Forwarding no
#   MaxAuthTries 3
#   ClientAliveInterval 300
#   ClientAliveCountMax 2

# Validar sintaxis ANTES de restartear:
sshd -t   # no output = OK

# (5) Restartear sshd CON SESIÓN PARALELA ABIERTA
# (En otra terminal local, mantener ssh vps1 abierto.)
systemctl restart sshd

# (6) Probar acceso desde una tercera sesión nueva
# Si funciona → cerrar las anteriores. Si NO → desde la sesión paralela revertir el config.
```

### 3.3 Failsafe

- Si después de restartear sshd se rompe el acceso, la sesión paralela aún está conectada y puede revertir el config.
- Última opción: rescue mode de Hostinger / Donweb (entrada por consola web del proveedor).

---

## 4. Acceso desde Claude Code

Una vez configurado todo:

```bash
# Comandos read-only (ej: ver qué cron está corriendo)
ssh vps1-claude "crontab -l"
ssh vps1-claude "ls -la /root/scripts/"

# Tail de logs
ssh vps1-claude "tail -100 /var/log/bewpro-provision.log"

# Escapar paths con caracteres especiales:
ssh vps1-claude 'whmapi1 listaccts | grep "user:" | wc -l'

# Operaciones write — siempre confirmar con Coke antes
# Ejemplo: rotación de un secret
ssh vps1-claude 'sed -i "s/AIRTABLE_TOKEN=.*/AIRTABLE_TOKEN=NEW_VALUE/" /root/scripts/.airtable.env'
```

### Reglas para Claude

1. **Siempre `ssh vps1-claude`** o `vps2-claude`, nunca `vps1` directamente — separamos auditoría humano/IA.
2. **Antes de cualquier comando destructivo** (rm, sed -i, kill, systemctl restart), confirmar con el usuario.
3. **Después de un comando con `>` o `tee`**, hacer un `cat` para verificar que el contenido es el esperado.
4. **No leer ni transmitir secretos completos** en respuestas. Si es necesario inspeccionar `.airtable.env`, mostrar solo prefijos (primeros 8 chars) o estructura.

---

## 5. Rotación

| Evento | Acción |
|---|---|
| Coke pierde laptop / sospecha de compromiso | (1) En máquina nueva, generar key nueva. (2) Desde la del operador alternativo (Juan), `ssh vps1` y borrar la línea de la key vieja en `~/.ssh/authorized_keys`. (3) Reemplazar con la pubkey nueva. |
| Claude muestra comportamiento anómalo / quiero revocar acceso IA | `ssh vps1` → editar `~/.ssh/authorized_keys` → borrar línea con `bewpro_claude_local_ed25519`. |
| Rotación periódica (cada 6 meses) | Generar key nueva + agregar a authorized_keys + remover vieja en una sola sesión. |

---

## 6. Multi-VPS futuro

Cuando agreguemos VPS3, VPS4, ...:

1. Repetir sección 3.2 sobre el VPS nuevo.
2. Agregar `Host vps3` y `vps3-claude` en `~/.ssh/config`.
3. Si vamos a más de 5 VPS, considerar:
   - Bastion host (jump server) → todas las conexiones pasan por uno solo, key rotation centralizada.
   - SSH CA (certificate authority) → emitir certificados firmados con TTL en lugar de pubkeys persistentes.

---

## 7. Validación post-Fase 2

Al terminar, debería ser cierto que:

- [ ] `ssh root@72.61.45.136` con password → **rechazado**.
- [ ] `ssh -p 5633 root@179.43.124.219` con password → **rechazado**.
- [ ] `ssh vps1` desde laptop de Coke → entra (pide passphrase de la key una vez).
- [ ] `ssh vps1-claude "whoami"` desde Claude Code → responde `root` sin prompt.
- [ ] `cat /etc/ssh/sshd_config | grep -E '^(PasswordAuthentication|PermitRootLogin)'` → muestra `no` y `prohibit-password`.
- [ ] Las passwords expuestas en este chat: rotadas.
- [ ] El inventario `01-secrets-inventory.md` actualizado con "Last rotated: 2026-04-28" en SSH.

---

## 8. Consideraciones que podríamos discutir

Estas no son acciones, son **decisiones abiertas** que conviene definir:

- **¿`PermitRootLogin` o usar usuario sudo?** Más seguro: deshabilitar root login y usar `bewpro-admin` con sudo. Más complejo: cambia todos los scripts que asumen root. Por ahora dejaría root login con `prohibit-password`.
- **¿Cambiar puerto SSH de VPS1 a uno custom?** VPS2 ya está en 5633. Hacer lo mismo en VPS1 reduce ruido de bots. Tradeoff: hay que actualizar todos los scripts y documentación.
- **¿2FA por TOTP en SSH?** Posible con `pam_google_authenticator`. Adds friction pero protege contra key compromise. Solo lo aplicaría a humanos, no a la key de Claude.
- **¿Bastion host único?** Vale la pena cuando lleguemos a 4+ VPS.
