1 contributor
# Arhitectura SSH Curenta
Acest document descrie configuratia activa din `/home/nextgen/.ssh`. Sursa versionata a proiectului este in `/home/nextgen/projects/ssh-infrastructure`; `/home/nextgen/.ssh` ramane runtime OpenSSH si nu contine repo-ul git. Ultima actualizare: 2026-05-15. Jurnalul istoric `ssh-jump-attempts.md` este obsolete si poate fi consultat doar din git.
## Structura Locala
```text
/home/nextgen/.ssh/
config <- single-file generat din inventory/hosts.yaml
known_hosts
known_hosts.old
authorized_keys
keys/
id_ed25519
id_ed25519.pub
elastix-root.pub
.doc/
ssh-jump-architecture.md
~/.local/bin/
ssh
scp
sftp
```
Reguli:
- Proiectul versionat sta in `~/projects/ssh-infrastructure`; `~/.ssh` ramane runtime OpenSSH.
- Cheile de identitate stau in `~/.ssh/keys`.
- Hosturile SSH sunt definite in `inventory/hosts.yaml`, apoi generate in `generated/*.conf`.
- `~/.ssh/config` este instalat din `generated/client.conf` prin `tools/deploy-local.sh`; nu se editeaza manual.
- Scripturile sursa stau versionate in `scripts/` in repo.
- `~/.local/bin/` contine copiile executabile instalate de `tools/deploy-local.sh`; nu se editeaza manual.
- `authorized_keys`, `known_hosts` si `config` raman in radacina `.ssh`, pentru compatibilitate cu OpenSSH.
- `j1-relay.sh`, `ssh-proxy.sh`, `ensure-ssh-agent-bridge.sh` si `ensure-ssh-jump.sh` au fost eliminate; `ssh-wrapper.sh` ruleaza clientul SSH activ pe `is-jumper`, unde se afla cheia fizica.
## Wrappers locale
Scripturile sursa sunt in `scripts/` in repo; `~/.local/bin/` contine copiile executabile instalate local.
| Comanda | Script | Mecanism |
| --- | --- | --- |
| `ssh` | `ssh-wrapper.sh` | parseaza args, rezolva hostul via `ssh -G`, construieste sesiunea nested |
| `scp` | `scp-wrapper.sh` | apeleaza `/usr/bin/scp -S ~/.local/bin/ssh` — rutarea e delegata wrapper-ului ssh |
| `sftp` | `sftp-wrapper.sh` | apeleaza `/usr/bin/sftp -S ~/.local/bin/ssh` — idem |
- Compatibilitatea SSH pentru hop-ul final este responsabilitatea serverelor `J1`/`J2`; wrapperele locale nu reproduc algoritmi, KEX, cipher-uri sau `OPENSSL_CONF`.
- Daca PATH-ul include `~/.local/bin` inaintea `/usr/bin`, comenzile `ssh`/`scp`/`sftp` folosesc automat aceste wrappers.
## Arhitectura rețelei
```
192.168.2.0/24 — rețea locală de birou
is-toltec (workstation-ul local)
is-jumper 192.168.2.100 (gardian cheie + client VPN)
10.253.51.0/24 — rețea internă companie (acces via VPN de pe is-jumper)
J1 10.253.51.50:25904
J2 10.253.51.52:25904
hosturi finale (voip, porta, radius etc.)
```
**is-jumper** (192.168.2.100) este pe rețeaua locală de birou — accesibil direct din is-toltec, **fără VPN**. Este un **client** VPN (nu server): prin el se obține acces la `10.253.51.0/24`. Este **gardianul cheii fizice** (card RSA 4096) deoarece deservește mai mulți utilizatori din `192.168.2.0/24`.
**Cheia fizică** (cardul) este montată exclusiv pe is-jumper și expusă prin GPG agent la `/run/user/0/gnupg/S.gpg-agent.ssh`. Wrapper-ul nu mai forwardează acest socket pe mașina locală; în schimb intră pe `is-jumper`, setează `SSH_AUTH_SOCK` la socketul local de acolo și rulează de pe `is-jumper` clientul SSH către J1/J2/j1/j2.
## Lanțul de acces
Calea standard (VPN activ, J1):
```text
local
-> is-jumper (192.168.2.100, executor SSH + cheie fizica)
-> J1 (10.253.51.50:25904)
-> host final
```
Calea publică (urgențe, fără rută VPN, j1/j2):
```text
local
-> is-jumper (192.168.2.100, executor SSH + cheie fizica)
-> j1.next-gen.ro:25904
-> host final
```
Sesiuni interactive pe J1/J2:
```text
local -> is-jumper -> J1
local -> is-jumper -> J2
```
Nu mai exista bridge local de agent (`/tmp/is-jumper-agent.sock`). Cheia fizica ramane folosita local pe `is-jumper`.
Jumps disponibile via flaguri wrapper:
| Flag | Jump | Rută | Când |
| --- | --- | --- | --- |
| implicit / `-J1` | J1 (10.253.51.50) | client SSH rulat pe is-jumper | standard |
| `-J2` | J2 (10.253.51.52) | client SSH rulat pe is-jumper | failover VPN |
| `-j1` | j1.next-gen.ro | client SSH rulat pe is-jumper | urgențe |
| `-j2` | j2.next-gen.ro | client SSH rulat pe is-jumper | urgențe |
## Scripturi
### ssh-wrapper.sh
Parseaza argumentele `ssh`, rezolva hostul via `ssh -G`, construieste sesiunea nested activa pe `is-jumper`:
```
exec /usr/bin/ssh is-jumper "SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh -A <jump_host> '<final_ssh_cmd>'"
```
Bypass automat (pass-through direct) pentru:
- `is-jumper` / `192.168.2.100`
- Flaguri de interogare: `-G`, `-Q`, `-V`, `--help`
Pentru J1/J2/j1/j2, wrapper-ul conecteaza intai local la `is-jumper`, apoi ruleaza acolo `ssh -A` cu `SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh`. Pentru hosturile finale, comanda de pe jump ruleaza inca un `ssh` catre hostul final. Pentru `scp`/`sftp`, wrapper-ul pastreaza forma de subsystem `-s ... sftp`.
### scp-wrapper.sh / sftp-wrapper.sh
Wrappers subtiri care injecteaza `-S ~/.local/bin/ssh` la apelul sistemului:
```bash
/usr/bin/scp -S ~/.local/bin/ssh "$@"
/usr/bin/sftp -S ~/.local/bin/ssh "$@"
```
Rutarea este delegata integral catre `ssh-wrapper.sh`; `scp`/`sftp` raman transparente pentru utilizator.
Nu mai exista scripturi active bazate pe Python, ProxyJump sau port-forwarding externe; vechiul helper Python a fost eliminat dupa ce a fost semnalat de Sentinel.
## Config SSH
`~/.ssh/config` este un single-file generat din `inventory/hosts.yaml`:
```sshconfig
# Generated by tools/generate-configs.py.
# Do not edit this file directly; edit inventory/hosts.yaml.
```
Generatorul produce:
| Fisier | Destinatie | Rol |
| --- | --- | --- |
| `generated/client.conf` | client local | config runtime pentru `~/.ssh/config` |
| `generated/is-jumper.conf` | is-jumper | config pentru jump aliases |
| `generated/j1.conf` | J1 | hosturi finale |
| `generated/j2.conf` | J2 | hosturi finale |
Reguli de configurare:
- hosturile finale definesc doar `HostName`, `User` si `Port`;
- pe client, hosturile importate din IFS folosesc drept `HostName` numele
canonic deja cunoscut pe jump, iar IP-ul ramane alias pentru autocomplete;
- `is-jumper` defineste si cheia locala de acces (`IdentityFile`, `IdentitiesOnly`);
- wrapperul construieste ruta nested prin `is-jumper -> J1` implicit;
- optiunile comune per-grup (ConnectTimeout etc.) stau in `inventory/hosts.yaml`;
- `generated/j1.conf` si `generated/j2.conf` mostenesc blocul global company-managed
si omit `User`/`Port` cand toate aliasurile unui host sunt deja acoperite de
regulile `Match Host` de pe jump;
- `generated/j1.conf` si `generated/j2.conf` nu mai includ hosturile care folosesc
doar defaulturi mostenite; raman doar override-urile efective si exceptiile
per-pattern care trebuie pastrate pe jump;
- `generated/j1.conf` si `generated/j2.conf` sunt generate fara comentarii,
doar cu stanza-urile SSH necesare;
- hosturile Radius folosesc acelasi flux nested prin J1 ca restul hosturilor finale.
Aliasuri principale:
| Alias | Rol | Rutare |
| --- | --- | --- |
| `is-jumper` | VPN gateway + gardian cheie fizica | direct (IP 192.168.2.100) |
| `J1`, `J2` | jump hosts, login interactiv | wrapper prin is-jumper |
| `voip-prov`, `voip-prov-root` | host VoIP provider | wrapper nested prin J1 |
| `porta-sip`, `porta-web`, `porta-db`, `porta-configurator` | PortaOne MR30 | wrapper nested prin J1 |
| `elastix` | host vechi | wrapper nested prin J1 |
| `*.radius-db`, `*.radius-pppoe` | baze Radius regionale | wrapper nested prin J1 |
`J1` si `J2` nu mai folosesc `ProxyJump`, `IdentityAgent` sau `RemoteCommand` in configul local; wrapper-ul orchestreaza explicit conexiunea prin `is-jumper`.
## PortaOne
Aliasuri PortaOne:
- `porta-sip`, `p12-sip`, `p12`, `p12.voip.ro` -> `193.16.148.4`
- `porta-web`, `porta-api`, `porta-slave`, `porta7`, `telefonie.next-gen.ro` -> `193.16.148.7`
- `porta-db`, `porta-master`, `porta1` -> `193.16.148.11`
- `porta-configurator`, `porta-config` -> `193.16.148.13`
Aceste hosturi folosesc ruta nested din wrapper; eventualele optiuni de compatibilitate ale hop-ului final sunt asigurate pe `J1`/`J2`.
## Verificari
Verificare configuratie fara conectare:
```bash
ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly)'
ssh -G porta-db | grep -E '^(hostname|user|port)'
ssh -G falticeni.radius-db | grep -E '^(hostname|user|port|connecttimeout)'
```
Verificare read-only cu conectare:
```bash
ssh porta-db hostname
ssh porta-sip 'service porta-sip status'
ssh voip-prov hostname
scp porta-db:/etc/hosts /tmp/test-scp
sftp porta-db <<< 'ls /'
```
## Mentenanta
- Cand adaugi o cheie noua, pune-o in `~/.ssh/keys` si seteaza permisiuni `600` pentru cheia privata.
- Cand adaugi un host nou: adauga-l in `inventory/hosts.yaml`, ruleaza generatorul si apoi `tools/deploy-local.sh`.
- Cand adaugi un domeniu nou: adauga un grup nou in `inventory/hosts.yaml`.
- Cand adaugi un wrapper nou: adauga scriptul in `scripts/` in repo si lasa `tools/deploy-local.sh` sa il instaleze in `~/.local/bin/`.
- Nu readuce `ProxyJump`, `IdentityAgent /tmp/is-jumper-agent.sock`, helperul Python sau port-forwarding per-host.
- Nu activa `ForwardAgent yes` pe hosturile finale fara un motiv explicit.
- Dupa modificari la wrappers sau config, verifica: `ssh -G <alias>` si cel putin un alias read-only (`ssh porta-db hostname`).