|
Bogdan Timofte
authored
2 weeks ago
|
1
|
# Arhitectura SSH Curenta
|
|
|
2
|
|
|
|
3
|
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.
|
|
|
4
|
|
|
|
5
|
## Structura Locala
|
|
|
6
|
|
|
|
7
|
```text
|
|
|
8
|
/home/nextgen/.ssh/
|
|
|
9
|
config <- single-file generat din inventory/hosts.yaml
|
|
|
10
|
known_hosts
|
|
|
11
|
known_hosts.old
|
|
|
12
|
authorized_keys
|
|
|
13
|
keys/
|
|
|
14
|
id_ed25519
|
|
|
15
|
id_ed25519.pub
|
|
|
16
|
elastix-root.pub
|
|
|
17
|
.doc/
|
|
|
18
|
ssh-jump-architecture.md
|
|
|
19
|
|
|
|
20
|
~/.local/bin/
|
|
|
21
|
ssh
|
|
|
22
|
scp
|
|
|
23
|
sftp
|
|
|
24
|
```
|
|
|
25
|
|
|
|
26
|
Reguli:
|
|
|
27
|
|
|
|
28
|
- Proiectul versionat sta in `~/projects/ssh-infrastructure`; `~/.ssh` ramane runtime OpenSSH.
|
|
|
29
|
- Cheile de identitate stau in `~/.ssh/keys`.
|
|
|
30
|
- Hosturile SSH sunt definite in `inventory/hosts.yaml`, apoi generate in `generated/*.conf`.
|
|
|
31
|
- `~/.ssh/config` este instalat din `generated/client.conf` prin `tools/deploy-local.sh`; nu se editeaza manual.
|
|
|
32
|
- Scripturile sursa stau versionate in `scripts/` in repo.
|
|
|
33
|
- `~/.local/bin/` contine copiile executabile instalate de `tools/deploy-local.sh`; nu se editeaza manual.
|
|
|
34
|
- `authorized_keys`, `known_hosts` si `config` raman in radacina `.ssh`, pentru compatibilitate cu OpenSSH.
|
|
|
35
|
- `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.
|
|
|
36
|
|
|
|
37
|
## Wrappers locale
|
|
|
38
|
|
|
|
39
|
Scripturile sursa sunt in `scripts/` in repo; `~/.local/bin/` contine copiile executabile instalate local.
|
|
|
40
|
|
|
|
41
|
| Comanda | Script | Mecanism |
|
|
|
42
|
| --- | --- | --- |
|
|
|
43
|
| `ssh` | `ssh-wrapper.sh` | parseaza args, rezolva hostul via `ssh -G`, construieste sesiunea nested |
|
|
|
44
|
| `scp` | `scp-wrapper.sh` | apeleaza `/usr/bin/scp -S ~/.local/bin/ssh` — rutarea e delegata wrapper-ului ssh |
|
|
|
45
|
| `sftp` | `sftp-wrapper.sh` | apeleaza `/usr/bin/sftp -S ~/.local/bin/ssh` — idem |
|
|
|
46
|
|
|
|
47
|
- Compatibilitatea SSH pentru hop-ul final este responsabilitatea serverelor `J1`/`J2`; wrapperele locale nu reproduc algoritmi, KEX, cipher-uri sau `OPENSSL_CONF`.
|
|
|
48
|
- Daca PATH-ul include `~/.local/bin` inaintea `/usr/bin`, comenzile `ssh`/`scp`/`sftp` folosesc automat aceste wrappers.
|
|
|
49
|
|
|
|
50
|
## Arhitectura rețelei
|
|
|
51
|
|
|
|
52
|
```
|
|
|
53
|
192.168.2.0/24 — rețea locală de birou
|
|
|
54
|
is-toltec (workstation-ul local)
|
|
|
55
|
is-jumper 192.168.2.100 (gardian cheie + client VPN)
|
|
|
56
|
|
|
|
57
|
10.253.51.0/24 — rețea internă companie (acces via VPN de pe is-jumper)
|
|
|
58
|
J1 10.253.51.50:25904
|
|
|
59
|
J2 10.253.51.52:25904
|
|
|
60
|
hosturi finale (voip, porta, radius etc.)
|
|
|
61
|
```
|
|
|
62
|
|
|
|
63
|
**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`.
|
|
|
64
|
|
|
|
65
|
**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.
|
|
|
66
|
|
|
|
67
|
## Lanțul de acces
|
|
|
68
|
|
|
|
69
|
Calea standard (VPN activ, J1):
|
|
|
70
|
|
|
|
71
|
```text
|
|
|
72
|
local
|
|
|
73
|
-> is-jumper (192.168.2.100, executor SSH + cheie fizica)
|
|
|
74
|
-> J1 (10.253.51.50:25904)
|
|
|
75
|
-> host final
|
|
|
76
|
```
|
|
|
77
|
|
|
|
78
|
Calea publică (urgențe, fără rută VPN, j1/j2):
|
|
|
79
|
|
|
|
80
|
```text
|
|
|
81
|
local
|
|
|
82
|
-> is-jumper (192.168.2.100, executor SSH + cheie fizica)
|
|
|
83
|
-> j1.next-gen.ro:25904
|
|
|
84
|
-> host final
|
|
|
85
|
```
|
|
|
86
|
|
|
|
87
|
Sesiuni interactive pe J1/J2:
|
|
|
88
|
|
|
|
89
|
```text
|
|
|
90
|
local -> is-jumper -> J1
|
|
|
91
|
local -> is-jumper -> J2
|
|
|
92
|
```
|
|
|
93
|
|
|
|
94
|
Nu mai exista bridge local de agent (`/tmp/is-jumper-agent.sock`). Cheia fizica ramane folosita local pe `is-jumper`.
|
|
|
95
|
|
|
|
96
|
Jumps disponibile via flaguri wrapper:
|
|
|
97
|
|
|
|
98
|
| Flag | Jump | Rută | Când |
|
|
|
99
|
| --- | --- | --- | --- |
|
|
|
100
|
| implicit / `-J1` | J1 (10.253.51.50) | client SSH rulat pe is-jumper | standard |
|
|
|
101
|
| `-J2` | J2 (10.253.51.52) | client SSH rulat pe is-jumper | failover VPN |
|
|
|
102
|
| `-j1` | j1.next-gen.ro | client SSH rulat pe is-jumper | urgențe |
|
|
|
103
|
| `-j2` | j2.next-gen.ro | client SSH rulat pe is-jumper | urgențe |
|
|
|
104
|
|
|
|
105
|
## Scripturi
|
|
|
106
|
|
|
|
107
|
### ssh-wrapper.sh
|
|
|
108
|
|
|
|
109
|
Parseaza argumentele `ssh`, rezolva hostul via `ssh -G`, construieste sesiunea nested activa pe `is-jumper`:
|
|
|
110
|
|
|
|
111
|
```
|
|
|
112
|
exec /usr/bin/ssh is-jumper "SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh -A <jump_host> '<final_ssh_cmd>'"
|
|
|
113
|
```
|
|
|
114
|
|
|
|
115
|
Bypass automat (pass-through direct) pentru:
|
|
|
116
|
- `is-jumper` / `192.168.2.100`
|
|
|
117
|
- Flaguri de interogare: `-G`, `-Q`, `-V`, `--help`
|
|
|
118
|
|
|
|
119
|
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`.
|
|
|
120
|
|
|
|
121
|
### scp-wrapper.sh / sftp-wrapper.sh
|
|
|
122
|
|
|
|
123
|
Wrappers subtiri care injecteaza `-S ~/.local/bin/ssh` la apelul sistemului:
|
|
|
124
|
|
|
|
125
|
```bash
|
|
|
126
|
/usr/bin/scp -S ~/.local/bin/ssh "$@"
|
|
|
127
|
/usr/bin/sftp -S ~/.local/bin/ssh "$@"
|
|
|
128
|
```
|
|
|
129
|
|
|
|
130
|
Rutarea este delegata integral catre `ssh-wrapper.sh`; `scp`/`sftp` raman transparente pentru utilizator.
|
|
|
131
|
|
|
|
132
|
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.
|
|
|
133
|
|
|
|
134
|
## Config SSH
|
|
|
135
|
|
|
|
136
|
`~/.ssh/config` este un single-file generat din `inventory/hosts.yaml`:
|
|
|
137
|
|
|
|
138
|
```sshconfig
|
|
|
139
|
# Generated by tools/generate-configs.py.
|
|
|
140
|
# Do not edit this file directly; edit inventory/hosts.yaml.
|
|
|
141
|
```
|
|
|
142
|
|
|
|
143
|
Generatorul produce:
|
|
|
144
|
|
|
|
145
|
| Fisier | Destinatie | Rol |
|
|
|
146
|
| --- | --- | --- |
|
|
|
147
|
| `generated/client.conf` | client local | config runtime pentru `~/.ssh/config` |
|
|
|
148
|
| `generated/is-jumper.conf` | is-jumper | config pentru jump aliases |
|
|
|
149
|
| `generated/j1.conf` | J1 | hosturi finale |
|
|
|
150
|
| `generated/j2.conf` | J2 | hosturi finale |
|
|
|
151
|
|
|
|
152
|
Reguli de configurare:
|
|
|
153
|
|
|
|
154
|
- hosturile finale definesc doar `HostName`, `User` si `Port`;
|
|
|
155
|
- pe client, hosturile importate din IFS folosesc drept `HostName` numele
|
|
|
156
|
canonic deja cunoscut pe jump, iar IP-ul ramane alias pentru autocomplete;
|
|
|
157
|
- `is-jumper` defineste si cheia locala de acces (`IdentityFile`, `IdentitiesOnly`);
|
|
|
158
|
- wrapperul construieste ruta nested prin `is-jumper -> J1` implicit;
|
|
|
159
|
- optiunile comune per-grup (ConnectTimeout etc.) stau in `inventory/hosts.yaml`;
|
|
|
160
|
- `generated/j1.conf` si `generated/j2.conf` mostenesc blocul global company-managed
|
|
|
161
|
si omit `User`/`Port` cand toate aliasurile unui host sunt deja acoperite de
|
|
|
162
|
regulile `Match Host` de pe jump;
|
|
|
163
|
- `generated/j1.conf` si `generated/j2.conf` nu mai includ hosturile care folosesc
|
|
|
164
|
doar defaulturi mostenite; raman doar override-urile efective si exceptiile
|
|
|
165
|
per-pattern care trebuie pastrate pe jump;
|
|
|
166
|
- `generated/j1.conf` si `generated/j2.conf` sunt generate fara comentarii,
|
|
|
167
|
doar cu stanza-urile SSH necesare;
|
|
|
168
|
- hosturile Radius folosesc acelasi flux nested prin J1 ca restul hosturilor finale.
|
|
|
169
|
|
|
|
170
|
Aliasuri principale:
|
|
|
171
|
|
|
|
172
|
| Alias | Rol | Rutare |
|
|
|
173
|
| --- | --- | --- |
|
|
|
174
|
| `is-jumper` | VPN gateway + gardian cheie fizica | direct (IP 192.168.2.100) |
|
|
|
175
|
| `J1`, `J2` | jump hosts, login interactiv | wrapper prin is-jumper |
|
|
|
176
|
| `voip-prov`, `voip-prov-root` | host VoIP provider | wrapper nested prin J1 |
|
|
|
177
|
| `porta-sip`, `porta-web`, `porta-db`, `porta-configurator` | PortaOne MR30 | wrapper nested prin J1 |
|
|
|
178
|
| `elastix` | host vechi | wrapper nested prin J1 |
|
|
|
179
|
| `*.radius-db`, `*.radius-pppoe` | baze Radius regionale | wrapper nested prin J1 |
|
|
|
180
|
|
|
|
181
|
`J1` si `J2` nu mai folosesc `ProxyJump`, `IdentityAgent` sau `RemoteCommand` in configul local; wrapper-ul orchestreaza explicit conexiunea prin `is-jumper`.
|
|
|
182
|
|
|
|
183
|
## PortaOne
|
|
|
184
|
|
|
|
185
|
Aliasuri PortaOne:
|
|
|
186
|
|
|
|
187
|
- `porta-sip`, `p12-sip`, `p12`, `p12.voip.ro` -> `193.16.148.4`
|
|
|
188
|
- `porta-web`, `porta-api`, `porta-slave`, `porta7`, `telefonie.next-gen.ro` -> `193.16.148.7`
|
|
|
189
|
- `porta-db`, `porta-master`, `porta1` -> `193.16.148.11`
|
|
|
190
|
- `porta-configurator`, `porta-config` -> `193.16.148.13`
|
|
|
191
|
|
|
|
192
|
Aceste hosturi folosesc ruta nested din wrapper; eventualele optiuni de compatibilitate ale hop-ului final sunt asigurate pe `J1`/`J2`.
|
|
|
193
|
|
|
|
194
|
## Verificari
|
|
|
195
|
|
|
|
196
|
Verificare configuratie fara conectare:
|
|
|
197
|
|
|
|
198
|
```bash
|
|
|
199
|
ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly)'
|
|
|
200
|
ssh -G porta-db | grep -E '^(hostname|user|port)'
|
|
|
201
|
ssh -G falticeni.radius-db | grep -E '^(hostname|user|port|connecttimeout)'
|
|
|
202
|
```
|
|
|
203
|
|
|
|
204
|
Verificare read-only cu conectare:
|
|
|
205
|
|
|
|
206
|
```bash
|
|
|
207
|
ssh porta-db hostname
|
|
|
208
|
ssh porta-sip 'service porta-sip status'
|
|
|
209
|
ssh voip-prov hostname
|
|
|
210
|
scp porta-db:/etc/hosts /tmp/test-scp
|
|
|
211
|
sftp porta-db <<< 'ls /'
|
|
|
212
|
```
|
|
|
213
|
|
|
|
214
|
## Mentenanta
|
|
|
215
|
|
|
|
216
|
- Cand adaugi o cheie noua, pune-o in `~/.ssh/keys` si seteaza permisiuni `600` pentru cheia privata.
|
|
|
217
|
- Cand adaugi un host nou: adauga-l in `inventory/hosts.yaml`, ruleaza generatorul si apoi `tools/deploy-local.sh`.
|
|
|
218
|
- Cand adaugi un domeniu nou: adauga un grup nou in `inventory/hosts.yaml`.
|
|
|
219
|
- Cand adaugi un wrapper nou: adauga scriptul in `scripts/` in repo si lasa `tools/deploy-local.sh` sa il instaleze in `~/.local/bin/`.
|
|
|
220
|
- Nu readuce `ProxyJump`, `IdentityAgent /tmp/is-jumper-agent.sock`, helperul Python sau port-forwarding per-host.
|
|
|
221
|
- Nu activa `ForwardAgent yes` pe hosturile finale fara un motiv explicit.
|
|
|
222
|
- Dupa modificari la wrappers sau config, verifica: `ssh -G <alias>` si cel putin un alias read-only (`ssh porta-db hostname`).
|