# SSH Keys and Access Guide

Centralized reference for SSH key management and access paths.

## Key Files Location

All SSH keys are stored in `~/.ssh/keys/`:

```
~/.ssh/keys/
  id_ed25519           → ~/.ssh/id_ed25519 (modern, preferred)
  id_ed25519_2026-05
  id_ed25519_2026-05.pub
  id_rsa               (RSA legacy, 2048-bit)
  id_rsa_old           (deprecated, from 2015)
  id_rsa_old.pub
  is-jumper_ed25519    (specific to is-jumper entry point)
  is-jumper_ed25519.pub
```

## Key Usage Matrix

| Key | File | Used By | Auth Method | Hosts |
|-----|------|---------|-------------|-------|
| **Modern ED25519** | `~/.ssh/id_ed25519` | most systems | pubkey | All modern hosts, J1, J2, local lab |
| **is-jumper ED25519** | `~/.ssh/keys/is-jumper_ed25519` | is-jumper (entry point) | pubkey + IdentitiesOnly | Entry point `192.168.2.100` |
| **Legacy RSA** | `~/.ssh/id_rsa` | legacy systems | pubkey | Old hosts still on RSA auth |
| **Deprecated RSA** | `~/.ssh/keys/id_rsa_old` | final fallback | pubkey | Hosts not yet migrated (being phased out) |

## Access Paths and Routing

### 1. Local Lab Setup (192.168.2.0/24)

**Direct access** — no jump needed:

```
is-jumper (192.168.2.100)
├─ User: root
├─ Port: 22
├─ Key: ~/.ssh/keys/is-jumper_ed25519
├─ IdentitiesOnly: yes
└─ Routing: SSH_ROUTE=local

Local dev machines (192.168.2.110-122)
├─ User: bogdan
├─ Port: 22
└─ Key: ~/.ssh/id_ed25519
```

Access to is-jumper:

```bash
# Direct connection (SSH will use is-jumper_ed25519 via IdentitiesOnly)
ssh is-jumper

# Verify key in use:
ssh -G is-jumper | grep identityfile
```

### 2. Production: Via is-jumper → J1/J2 → Final Hosts

**Access chain for internal company network (10.253.51.0/24)**:

```
Your machine (local)
  ↓ (ProxyJump is-jumper)
is-jumper (192.168.2.100) [VPN client + key guardian]
  ↓ (SSH forward)
J1 (10.253.51.50:25904) or J2 (10.253.51.52:25904) [jump hosts]
  ↓ (SSH forward)
Final host (voip, porta, radius, etc.)
```

#### Step 1: is-jumper (entry point)

- **User**: root
- **Host**: 192.168.2.100
- **Port**: 22
- **Key**: `~/.ssh/keys/is-jumper_ed25519`
- **IdentitiesOnly**: yes (forces use of specified key only)

```bash
ssh is-jumper
```

#### Step 2: J1/J2 (jump hosts)

**Reached through is-jumper via ProxyJump**:

- **User**: bogdan.timofte (jump host default)
- **Hostname**: 10.253.51.50 (J1) or 10.253.51.52 (J2)
- **Port**: 25904
- **Key**: `~/.ssh/id_ed25519` (offered by SSH client)
- **ProxyJump**: is-jumper (configured in ~/.ssh/config)

```bash
ssh j1        # Routes: local → is-jumper → J1
ssh j2        # Routes: local → is-jumper → J2

# Interactive jump host session:
ssh -J is-jumper bogdan.timofte@10.253.51.50 -p 25904
```

**Verify J1 config**:

```bash
ssh -G j1 | grep -E '^(hostname|port|user|proxyjump|identityfile)'
```

#### Step 3: Final Hosts

**From J1/J2 to actual hosts** (configured in J1's/J2's local SSH config):

- **User**: varies by host group (bogdan, bogdan.timofte, root, etc.)
- **Port**: 22 (standard) or 24 (jump hosts default) or custom
- **Key**: `~/.ssh/id_ed25519`
- **ProxyJump**: j1 or j2

```bash
# Example: PortaOne database server
ssh porta-db        # → is-jumper → J1 → 193.16.148.11

# Example: VoIP PBX
ssh voip-prov       # → is-jumper → J1 → 10.253.51.139

# Example: Radius database
ssh falticeni.radius-db  # → is-jumper → J1 → falticeni.radius-db:24
```

### 3. Emergency Public Routes (j1.next-gen.ro / j2.next-gen.ro)

If internal VPN is down, use public DNS names:

```bash
# Standard (internal VPN)
ssh j1              # 10.253.51.50:25904

# Emergency (public DNS)
ssh j1.next-gen.ro  # j1.next-gen.ro:25904
ssh j2.next-gen.ro  # j2.next-gen.ro:25904
```

Both routes go through is-jumper first (no direct connection).

## Key Migration Status

### Check which hosts have modern keys

```bash
# Test all local lab hosts
for h in is-baobab is-ebony is-tapia is-jumper is-mazeri is-toltec is-andrafiabe is-anjohibe is-nasturel is-mat; do
  timeout 2 ssh -o BatchMode=yes "$h" true 2>/dev/null && echo "$h: ✓" || echo "$h: ⚠"
done
```

### Migrate a host to modern ED25519 key

```bash
# Automatic migration (uses legacy key to install modern key)
tools/migrate-modern-key.sh is-baobab

# Or migrate all
tools/migrate-modern-key.sh
```

See [docs/KEY_MIGRATION.md](docs/KEY_MIGRATION.md) for manual procedures.

## SSH Config Generation

The `~/.ssh/config` is **auto-generated** from inventory — do not edit manually.

```bash
# Regenerate after inventory changes
python3 tools/generate-configs.py

# Deploy to ~/.ssh/config
cp generated/client.conf ~/.ssh/config
# or use the deploy script:
tools/deploy-local.sh
```

**Config files generated**:

| File | Target | Purpose |
|------|--------|---------|
| `generated/client.conf` | local client (~/.ssh/config) | local access config |
| `generated/is-jumper.conf` | is-jumper (via deploy) | is-jumper alias config |
| `generated/j1.conf` | J1 (server-side) | final host access on J1 |
| `generated/j2.conf` | J2 (server-side) | final host access on J2 |

## Troubleshooting

### "Permission denied (publickey)"

**Diagnosis**:

```bash
# Check which key is being offered
ssh -vvv porta-db 2>&1 | grep -E "Offering|Authentications"

# Check ProxyJump chain
ssh -vvv j1 2>&1 | grep -E "ProxyJump|Connecting"
```

**Solutions**:

1. **On is-jumper** — verify IdentitiesOnly and key:
   ```bash
   ssh -G is-jumper | grep identities
   ```
   Should show: `identitiesonly yes` and `identityfile ~/.ssh/keys/is-jumper_ed25519`

2. **On J1/J2** — verify modern key is installed:
   ```bash
   ssh is-jumper "ssh-keygen -l -f /home/bogdan.timofte/.ssh/authorized_keys"
   ```
   Should include ED25519 fingerprints.

3. **Regenerate and redeploy config**:
   ```bash
   python3 tools/generate-configs.py && cp generated/client.conf ~/.ssh/config
   ```

### "No route to host" (10.253.51.x)

**Cause**: Trying to access internal IPs directly without is-jumper.

**Solution**: Verify ProxyJump is configured:

```bash
ssh -G vo52 | grep proxyjump
# Should output: proxyjump j1
```

### SSH hangs or timeouts

**For jump hosts** (reduce to 5-10 seconds):

```bash
ssh -o ConnectTimeout=5 is-jumper
ssh -o ConnectTimeout=5 j1
```

**Check network**:

```bash
# is-jumper reachable?
ping 192.168.2.100

# If behind firewall, may need port 22 open to is-jumper
```

## Maintenance Checklist

- [ ] Keep both `id_ed25519` and `id_rsa_old` until all hosts migrated
- [ ] After adding new hosts: run `tools/migrate-modern-key.sh <host>`
- [ ] After inventory changes: regenerate with `python3 tools/generate-configs.py`
- [ ] Periodically check: `ssh -G <alias>` to verify config without connecting
- [ ] Never manually edit `~/.ssh/config` — it's auto-generated
- [ ] Keep keys in `~/.ssh/keys/` with mode `600`

## References

- [Ed25519 vs RSA Security](https://ianix.com/pub/ed25519-deployment.html)
- [OpenSSH Manual](https://man.openbsd.org/ssh)
- [Project inventory structure](inventory/hosts.yaml)
- [Key migration procedures](docs/KEY_MIGRATION.md)
- [Architecture details](ARCHITECTURE.md) (legacy Romanian docs: `.doc/ssh-jump-architecture.md`)
