Newer Older
724 lines | 16.284kb
Bogdan Timofte authored 2 weeks ago
1
# SSH Infrastructure - Single Source of Truth
Bogdan Timofte authored 2 weeks ago
2

            
Bogdan Timofte authored 2 weeks ago
3
Last updated: 2026-05-21
Bogdan Timofte authored 2 weeks ago
4

            
Bogdan Timofte authored 2 weeks ago
5
This is the only project documentation file. Keep architecture, key handling,
6
sync/deploy steps, troubleshooting, and maintenance notes here. Do not add
7
separate Markdown documents for the same subject unless this README is split by
8
explicit decision.
Bogdan Timofte authored 2 weeks ago
9

            
Bogdan Timofte authored 2 weeks ago
10
## Read This First
11

            
12
This repository manages SSH access from Bogdan's macOS workstation to Next-Gen
13
company hosts through:
14

            
15
```text
16
local macOS
17
  -> is-jumper 192.168.2.100
18
  -> J1/J2 10.253.51.50/52:25904
19
  -> final hosts: porta, pbx, radius, voip, network gear
20
```
21

            
22
The key detail agents keep missing:
23

            
24
- The local machine does not hold the company hardware key.
25
- The physical RSA smartcard is mounted only on `is-jumper`.
26
- The wrapper logs into `is-jumper`, sets
27
  `SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh`, then runs SSH from there to
28
  J1/J2.
29
- J1/J2 must use user `bogdan.timofte`.
30
- `is-jumper` itself must use local key `~/.ssh/keys/is-jumper_ed25519`.
31
- `ssh` on macOS must resolve to `~/.local/bin/ssh`, not `/usr/bin/ssh`, for
32
  company aliases.
33

            
34
Fast health check:
35

            
36
```bash
37
which ssh
38
ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly) '
39
ssh -G j1 | grep -E '^(hostname|user|port) '
40
ssh is-jumper hostname
41
ssh porta-sip hostname
42
```
43

            
44
Expected highlights:
45

            
46
```text
47
/Users/bogdan/.local/bin/ssh
48
identityfile ~/.ssh/keys/is-jumper_ed25519
49
user bogdan.timofte
50
p12.voip.ro
51
```
52

            
Bogdan Timofte authored 2 weeks ago
53
## Sources of Truth
54

            
55
There are two separate host tables, with separate ownership:
56

            
57
| Table | File / Location | Owner | What Belongs There |
58
| --- | --- | --- | --- |
59
| Local table | `inventory/hosts-local.yaml` in this repo | Us / Bogdan local workstation | Local lab hosts, local defaults, local key paths, and local overrides required for this Mac |
60
| NextGen table | `nextgen@192.168.2.103:/home/nextgen/projects/ssh-infrastructure/inventory/hosts.yaml` | NextGen / upstream | Company-managed NextGen host list: porta, pbx, radius, voip, network gear, and upstream defaults |
61

            
62
Operational rule:
63

            
64
```text
65
inventory/hosts-local.yaml is our local source of truth.
66
inventory/hosts.yaml is a local copy of the NextGen upstream table.
67
```
68

            
69
Do not put local-only fixes into the upstream table unless they are true for
70
NextGen as well. Keep Mac/local requirements in `inventory/hosts-local.yaml`.
71

            
72
The effective local config is generated from both files:
73

            
74
```text
75
inventory/hosts.yaml        <- copied/synced from nextgen upstream
76
inventory/hosts-local.yaml  <- maintained locally by us
77
  -> tools/generate-configs.py
78
  -> generated/client.conf
79
  -> ~/.ssh/config
80
```
81

            
82
Critical local overrides currently required:
83

            
84
```yaml
85
entrypoints:
86
  is_jumper:
87
    identity_file: ~/.ssh/keys/is-jumper_ed25519
88
    identities_only: true
89

            
90
jumps:
91
  j1:
92
    user: bogdan.timofte
93
  j2:
94
    user: bogdan.timofte
95
```
96

            
97
The sync script updates only the local copy of the upstream table:
98

            
99
```bash
100
tools/sync-hosts-from-upstream.sh
101
```
102

            
103
After every sync, verify the local overlay still produces the right effective
104
config:
105

            
106
```bash
107
ssh -G j1 | grep -E '^(hostname|user|port) '
108
ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly) '
109
```
110

            
111
Expected:
112

            
113
```text
114
user bogdan.timofte
115
identityfile ~/.ssh/keys/is-jumper_ed25519
116
```
117

            
Bogdan Timofte authored 2 weeks ago
118
## Repository Rules
119

            
120
Project source:
121

            
122
```text
123
/Users/bogdan/Documents/Workspaces/Bogdan/ssh-infrastructure
124
```
125

            
126
Runtime OpenSSH state:
127

            
128
```text
129
~/.ssh/config
130
~/.ssh/known_hosts
131
~/.ssh/authorized_keys
132
~/.ssh/keys/
133
~/.local/bin/ssh
134
~/.local/bin/scp
135
~/.local/bin/sftp
136
```
137

            
138
Only edit source files in the repository. Do not edit generated runtime files by
139
hand.
140

            
141
Tracked source files:
142

            
143
```text
144
README.md                         this file, the only documentation
145
inventory/hosts.yaml              upstream/company host inventory
146
inventory/hosts-local.yaml        local overlay and local lab inventory
147
schema/hosts.schema.json          inventory schema
148
scripts/ssh-wrapper.sh            installed as ~/.local/bin/ssh
149
scripts/scp-wrapper.sh            installed as ~/.local/bin/scp
150
scripts/sftp-wrapper.sh           installed as ~/.local/bin/sftp
151
tools/generate-configs.py         config generator
152
tools/deploy-local.sh             local deploy
153
tools/sync-hosts-from-upstream.sh upstream inventory sync
154
tools/migrate-modern-key.sh       legacy local key migration helper
155
.gitignore
156
```
157

            
158
Ignored or runtime-only files:
159

            
160
```text
161
generated/
162
SSH_SETUP_SUMMARY.md
163
authorized_keys
164
known_hosts
165
known_hosts.old
166
keys/
167
agent/
168
conf.d/
169
import/
170
*.pem *.key *.ppk *.der *.csr
171
```
172

            
173
Git basics:
174

            
175
```bash
176
git status
177
git add README.md inventory schema scripts tools .gitignore
178
git commit -m "Describe change"
179
```
180

            
181
Known remotes:
182

            
183
```text
184
nextgen  ssh://git@192.168.2.103/home/git/repositories/bogdan/NextGen-Host-List.git
185
mazeri   ssh://git@192.168.2.102/home/git/repositories/bogdan/SSH-Infrastructure.git
186
```
187

            
188
## Architecture
189

            
190
### Network
191

            
192
```text
193
192.168.2.0/24 - local office/lab network
194
  is-jumper 192.168.2.100 - VPN client and hardware-key guardian
195
  local lab hosts
196

            
197
10.253.51.0/24 - internal company network reached from is-jumper VPN
198
  J1 10.253.51.50:25904
199
  J2 10.253.51.52:25904
200
  final hosts
201
```
202

            
203
`is-jumper` is not a VPN server. It is a local host that has VPN reachability to
204
the company network and has the physical smartcard mounted.
205

            
206
### Access Chains
207

            
208
Standard final-host chain:
209

            
210
```text
211
local wrapper
212
  -> /usr/bin/ssh is-jumper
213
  -> SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh -A J1
214
  -> ssh final-host
215
```
216

            
217
Interactive J1/J2 login:
218

            
219
```text
220
local wrapper -> is-jumper -> J1/J2
221
```
222

            
223
Emergency public routes:
224

            
225
```text
226
local wrapper -> is-jumper -> j1.next-gen.ro or j2.next-gen.ro
227
```
228

            
229
The wrapper strips custom flags before calling real SSH:
230

            
231
```text
232
-J1  use J1 VPN route, default
233
-J2  use J2 VPN route
234
-j1  use public j1 route
235
-j2  use public j2 route
236
```
237

            
238
Do not reintroduce local port forwarding, Python relays, `IdentityAgent
239
/tmp/...`, or helper scripts that bridge the physical-card socket to the local
240
machine. Those were removed for compliance and SentinelOne noise.
241

            
242
## Keys
243

            
244
### Key Matrix
245

            
246
| Key | Location | Purpose |
247
| --- | --- | --- |
248
| Physical smartcard RSA 4096 | only on `is-jumper` | Auth from `is-jumper` to J1/J2/company network |
249
| `is-jumper_ed25519` | local `~/.ssh/keys/is-jumper_ed25519` | Auth from macOS to `is-jumper` |
250
| Modern ED25519 | local `~/.ssh/id_ed25519` or `~/.ssh/keys/id_ed25519` | Local lab and migrated hosts |
251
| Legacy RSA | local `~/.ssh/keys/id_rsa_old` | Temporary migration fallback for old local hosts |
252

            
253
Critical config values:
254

            
255
```yaml
256
entrypoints:
257
  is_jumper:
258
    hostname: 192.168.2.100
259
    user: root
260
    identity_file: ~/.ssh/keys/is-jumper_ed25519
261
    identities_only: true
262

            
263
jumps:
264
  j1:
265
    hostname: 10.253.51.50
266
    user: bogdan.timofte
267
    port: 25904
268
  j2:
269
    hostname: 10.253.51.52
270
    user: bogdan.timofte
271
    port: 25904
272
```
273

            
274
If J1/J2 use `bogdan` instead of `bogdan.timofte`, final host SSH will fail with
275
an error like:
276

            
277
```text
278
bogdan@10.253.51.50: Permission denied (publickey).
279
Connection to 192.168.2.100 closed.
280
```
281

            
282
Fix that in `inventory/hosts-local.yaml`, deploy, then verify:
Bogdan Timofte authored 2 weeks ago
283

            
Bogdan Timofte authored 2 weeks ago
284
```bash
Bogdan Timofte authored 2 weeks ago
285
ssh -G j1 | grep -E '^(hostname|user|port) '
286
tools/deploy-local.sh
287
ssh porta-sip hostname
288
```
289

            
290
## Inventory and Generation
Bogdan Timofte authored 2 weeks ago
291

            
Bogdan Timofte authored 2 weeks ago
292
The generator reads:
Bogdan Timofte authored 2 weeks ago
293

            
Bogdan Timofte authored 2 weeks ago
294
```text
295
inventory/hosts.yaml
296
inventory/hosts-local.yaml if it exists
Bogdan Timofte authored 2 weeks ago
297
```
298

            
Bogdan Timofte authored 2 weeks ago
299
Important: the inventory merge is shallow. Later top-level maps from
300
`hosts-local.yaml` override upstream maps. This is useful for local lab entries
301
but dangerous for defaults. If `hosts-local.yaml` changes `defaults.jump.user`,
302
then local `jumps.j1` and `jumps.j2` must specify `user: bogdan.timofte`
303
explicitly.
Bogdan Timofte authored 2 weeks ago
304

            
Bogdan Timofte authored 2 weeks ago
305
Generated files:
Bogdan Timofte authored 2 weeks ago
306

            
Bogdan Timofte authored 2 weeks ago
307
```text
308
generated/client.conf      installed as ~/.ssh/config
309
generated/is-jumper.conf   server-side helper config
310
generated/j1.conf          server-side final-host config
311
generated/j2.conf          server-side final-host config
312
```
Bogdan Timofte authored 2 weeks ago
313

            
Bogdan Timofte authored 2 weeks ago
314
`generated/` is ignored by git. Recreate it any time:
Bogdan Timofte authored 2 weeks ago
315

            
Bogdan Timofte authored 2 weeks ago
316
```bash
317
python3 tools/generate-configs.py
318
```
Bogdan Timofte authored 2 weeks ago
319

            
Bogdan Timofte authored 2 weeks ago
320
Deploy local runtime:
Bogdan Timofte authored 2 weeks ago
321

            
322
```bash
323
tools/deploy-local.sh
324
```
325

            
Bogdan Timofte authored 2 weeks ago
326
Deploy does:
Bogdan Timofte authored 2 weeks ago
327

            
Bogdan Timofte authored 2 weeks ago
328
```text
329
1. run tools/generate-configs.py
330
2. install generated/client.conf as ~/.ssh/config
331
3. install scripts/ssh-wrapper.sh as ~/.local/bin/ssh
332
4. install scripts/scp-wrapper.sh as ~/.local/bin/scp
333
5. install scripts/sftp-wrapper.sh as ~/.local/bin/sftp
334
6. remove obsolete ~/.ssh/scripts wrapper copies
335
```
Bogdan Timofte authored 2 weeks ago
336

            
Bogdan Timofte authored 2 weeks ago
337
It does not touch private keys, `authorized_keys`, or `known_hosts`.
338

            
339
## Local Shell and Wrappers
340

            
341
For company aliases, `ssh` must be the wrapper:
Bogdan Timofte authored 2 weeks ago
342

            
343
```bash
Bogdan Timofte authored 2 weeks ago
344
which ssh
345
# /Users/bogdan/.local/bin/ssh
346
```
347

            
348
If it shows `/usr/bin/ssh`, fix shell PATH and reload:
349

            
350
```bash
351
source ~/.zshrc
352
which ssh
Bogdan Timofte authored 2 weeks ago
353
```
354

            
Bogdan Timofte authored 2 weeks ago
355
The current shell startup should keep `~/.local/bin` first in both interactive
356
and login shells. If editing these files, preserve this behavior:
Bogdan Timofte authored 2 weeks ago
357

            
Bogdan Timofte authored 2 weeks ago
358
```zsh
359
path=("$HOME/.local/bin" ${path:#"$HOME/.local/bin"})
360
export PATH
361
```
362

            
363
`ssh-wrapper.sh` uses bash 3.2 compatible array expansion under `set -u`.
364
Do not replace guarded forms like:
365

            
366
```bash
367
${cmd_args[@]+"${cmd_args[@]}"}
368
```
369

            
370
with plain:
371

            
372
```bash
373
"${cmd_args[@]}"
374
```
375

            
376
On macOS bash 3.2, empty arrays plus `set -u` can fail with:
377

            
378
```text
379
cmd_args[@]: unbound variable
380
```
381

            
382
## Sync from Upstream
Bogdan Timofte authored 2 weeks ago
383

            
Bogdan Timofte authored 2 weeks ago
384
Pull upstream `hosts.yaml`, apply the local `is-jumper` key override, validate
385
generation, and deploy if changed:
386

            
387
```bash
388
tools/sync-hosts-from-upstream.sh
389
```
390

            
391
Defaults:
392

            
393
```text
394
UPSTREAM_SSH_TARGET=nextgen@192.168.2.103
395
UPSTREAM_HOSTS_PATH=/home/nextgen/projects/ssh-infrastructure/inventory/hosts.yaml
396
LOCAL_IS_JUMPER_IDENTITY_FILE=~/.ssh/keys/is-jumper_ed25519
397
DEPLOY_AFTER_SYNC=1
398
FORCE_DEPLOY=0
399
```
400

            
401
Useful overrides:
402

            
403
```bash
404
UPSTREAM_HOSTS_FILE=/tmp/hosts.yaml tools/sync-hosts-from-upstream.sh
405
DEPLOY_AFTER_SYNC=0 tools/sync-hosts-from-upstream.sh
406
FORCE_DEPLOY=1 tools/sync-hosts-from-upstream.sh
407
UPSTREAM_SSH_TARGET=user@host tools/sync-hosts-from-upstream.sh
408
```
409

            
410
After sync, always check J1 user because the local overlay can override jump
411
defaults:
412

            
413
```bash
414
ssh -G j1 | grep -E '^(hostname|user|port) '
415
```
416

            
417
Expected:
418

            
419
```text
420
user bogdan.timofte
421
hostname 10.253.51.50
422
port 25904
423
```
424

            
425
## Adding or Changing Hosts
426

            
427
For company/Next-Gen hosts:
428

            
429
```text
430
1. Edit inventory/hosts.yaml or sync it from upstream.
431
2. Keep local-only corrections in inventory/hosts-local.yaml.
432
3. Run tools/deploy-local.sh.
433
4. Verify with ssh -G <alias>.
434
5. Verify read-only with ssh <alias> hostname.
435
6. Commit source changes only.
436
```
437

            
438
For local lab hosts:
439

            
440
```text
441
1. Edit inventory/hosts-local.yaml.
442
2. Run tools/deploy-local.sh.
443
3. Verify with ssh <alias> hostname.
444
4. Commit the local overlay change.
445
```
446

            
447
Common inventory defaults:
448

            
449
| Context | User | Port |
450
| --- | --- | --- |
451
| J1/J2 company jump | `bogdan.timofte` | `25904` for VPN route |
452
| Company final hosts | usually `bogdan` | usually `22` |
453
| Company inherited jump config | `bogdan.timofte` | often `24` |
454
| Local lab hosts | usually `bogdan` | usually `22` |
455
| Cisco/OLT interactive devices | inventory-specific | `22` |
456

            
457
For Cisco/OLT/password-interactive devices, set:
458

            
459
```yaml
460
auth: password_interactive
461
```
462

            
463
The wrapper then avoids forcing `BatchMode=yes` and disables pubkey auth for
464
that final hop.
465

            
466
## Key Migration for Local Legacy Hosts
467

            
468
Modern preferred key:
469

            
470
```text
471
~/.ssh/id_ed25519.pub
472
```
473

            
474
Legacy fallback key:
475

            
476
```text
477
~/.ssh/keys/id_rsa_old
478
```
479

            
480
Migrate all configured local legacy hosts:
Bogdan Timofte authored 2 weeks ago
481

            
482
```bash
483
tools/migrate-modern-key.sh
Bogdan Timofte authored 2 weeks ago
484
```
485

            
486
Migrate one host:
Bogdan Timofte authored 2 weeks ago
487

            
Bogdan Timofte authored 2 weeks ago
488
```bash
Bogdan Timofte authored 2 weeks ago
489
tools/migrate-modern-key.sh is-baobab
490
```
491

            
Bogdan Timofte authored 2 weeks ago
492
Manual fallback if password access is available:
Bogdan Timofte authored 2 weeks ago
493

            
Bogdan Timofte authored 2 weeks ago
494
```bash
495
ssh -o PubkeyAuthentication=no user@host \
496
  "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" \
497
  < ~/.ssh/id_ed25519.pub
498
```
499

            
500
Keep `id_rsa_old` until all legacy hosts are verified with the modern key.
501

            
502
## Verification Checklist
503

            
504
Run after deploy, sync, wrapper edits, or inventory changes:
505

            
506
```bash
507
which ssh
508
ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly) '
509
ssh -G j1 | grep -E '^(hostname|user|port) '
510
ssh is-jumper hostname
511
ssh is-jumper 'SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh-add -L | sed -n 1p'
512
ssh porta-sip hostname
513
ssh pbx-bo hostname
514
```
515

            
516
Expected signals:
Bogdan Timofte authored 2 weeks ago
517

            
518
```text
Bogdan Timofte authored 2 weeks ago
519
which ssh                         -> /Users/bogdan/.local/bin/ssh
520
is-jumper hostname                -> is-vpn-gw
521
j1 user                           -> bogdan.timofte
522
physical card check               -> ssh-rsa ... cardno:6446168
523
porta-sip hostname                -> p12.voip.ro
524
pbx-bo hostname                   -> pbx-bo
Bogdan Timofte authored 2 weeks ago
525
```
526

            
Bogdan Timofte authored 2 weeks ago
527
Interactive smoke test:
528

            
529
```bash
530
printf "exit\n" | ssh porta-sip
531
printf "exit\n" | ssh pbx-bo
532
```
533

            
534
## Troubleshooting
535

            
536
### `bogdan@10.253.51.50: Permission denied (publickey)`
537

            
538
The wrapper reached `is-jumper`, but J1 was attempted with user `bogdan`.
539
J1/J2 need `bogdan.timofte`.
540

            
541
Check:
542

            
543
```bash
544
ssh -G j1 | grep -E '^(hostname|user|port) '
545
```
546

            
547
Fix:
548

            
549
```yaml
550
# inventory/hosts-local.yaml
551
jumps:
552
  j1:
553
    user: bogdan.timofte
554
  j2:
555
    user: bogdan.timofte
556
```
557

            
558
Deploy:
559

            
560
```bash
561
tools/deploy-local.sh
562
ssh porta-sip hostname
563
```
Bogdan Timofte authored 2 weeks ago
564

            
Bogdan Timofte authored 2 weeks ago
565
### `root@192.168.2.100: Permission denied`
Bogdan Timofte authored 2 weeks ago
566

            
Bogdan Timofte authored 2 weeks ago
567
The local connection to `is-jumper` is using the wrong key.
568

            
569
Check:
570

            
571
```bash
572
ssh -G is-jumper | grep -E '^(user|hostname|identityfile|identitiesonly) '
573
ls -l ~/.ssh/keys/is-jumper_ed25519
574
```
575

            
576
Expected:
Bogdan Timofte authored 2 weeks ago
577

            
578
```text
Bogdan Timofte authored 2 weeks ago
579
user root
580
hostname 192.168.2.100
581
identityfile ~/.ssh/keys/is-jumper_ed25519
582
identitiesonly yes
583
```
584

            
585
If generated config is wrong, fix `inventory/hosts-local.yaml` or
586
`inventory/hosts.yaml`, then deploy.
587

            
588
### `ssh pbx-bo` uses `/usr/bin/ssh`
589

            
590
The wrapper is not first in PATH.
591

            
592
Check:
593

            
594
```bash
595
which ssh
Bogdan Timofte authored 2 weeks ago
596
```
597

            
Bogdan Timofte authored 2 weeks ago
598
Fix current shell:
599

            
600
```bash
601
source ~/.zshrc
602
```
Bogdan Timofte authored 2 weeks ago
603

            
Bogdan Timofte authored 2 weeks ago
604
If needed, ensure `.zprofile` and `.zshrc` both move `~/.local/bin` to the front
605
using zsh `path`, not a guard that leaves it later in PATH.
Bogdan Timofte authored 2 weeks ago
606

            
Bogdan Timofte authored 2 weeks ago
607
### `cmd_args[@]: unbound variable`
608

            
609
This is a bash 3.2 plus `set -u` empty-array issue in `ssh-wrapper.sh`.
610

            
611
Use guarded array expansion:
612

            
613
```bash
614
${array[@]+"${array[@]}"}
615
```
616

            
617
Do not simplify it.
618

            
619
### Physical card missing on `is-jumper`
620

            
621
Check:
622

            
623
```bash
624
ssh is-jumper 'ls -l /run/user/0/gnupg/S.gpg-agent.ssh'
625
ssh is-jumper 'SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh-add -L | sed -n 1p'
626
```
627

            
628
Expected key output contains:
Bogdan Timofte authored 2 weeks ago
629

            
Bogdan Timofte authored 2 weeks ago
630
```text
Bogdan Timofte authored 2 weeks ago
631
cardno:6446168
Bogdan Timofte authored 2 weeks ago
632
```
633

            
Bogdan Timofte authored 2 weeks ago
634
If missing, the issue is on `is-jumper`: gpg-agent, card mount, permissions, or
635
hardware state.
Bogdan Timofte authored 2 weeks ago
636

            
Bogdan Timofte authored 2 weeks ago
637
### Direct command works but wrapper fails
Bogdan Timofte authored 2 weeks ago
638

            
Bogdan Timofte authored 2 weeks ago
639
Compare generated command behavior:
640

            
641
```bash
642
bash -x ~/.local/bin/ssh porta-sip hostname
643
```
644

            
645
Look for:
646

            
647
```text
648
SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh
649
bogdan.timofte@10.253.51.50
650
```
651

            
652
If either is wrong, fix inventory/local overlay or wrapper.
653

            
654
### Generated config was edited manually
655

            
656
Discard manual runtime edits by redeploying:
Bogdan Timofte authored 2 weeks ago
657

            
658
```bash
Bogdan Timofte authored 2 weeks ago
659
tools/deploy-local.sh
Bogdan Timofte authored 2 weeks ago
660
```
661

            
662
Then verify:
663

            
664
```bash
665
ssh -G j1 | grep -E '^(hostname|user|port) '
666
```
667

            
668
## Compatibility and Compliance
669

            
670
Do not reintroduce these removed patterns:
671

            
672
```text
673
j1-relay.sh
674
ssh-proxy.sh
675
ensure-ssh-agent-bridge.sh
676
ensure-ssh-jump.sh
677
local socket forwarding for the hardware card
678
Python/base64 port-forwarding relays
679
per-host local ProxyCommand bridges
680
```
Bogdan Timofte authored 2 weeks ago
681

            
Bogdan Timofte authored 2 weeks ago
682
Current compliant model:
683

            
684
```text
685
local wrapper -> ssh is-jumper -> run normal ssh from is-jumper
Bogdan Timofte authored 2 weeks ago
686
```
687

            
Bogdan Timofte authored 2 weeks ago
688
Compatibility options for old final hosts belong in inventory or on jump hosts,
689
not in ad-hoc local forwarding scripts.
Bogdan Timofte authored 2 weeks ago
690

            
Bogdan Timofte authored 2 weeks ago
691
## Maintenance Notes for Agents
692

            
693
Before changing anything:
Bogdan Timofte authored 2 weeks ago
694

            
695
```bash
Bogdan Timofte authored 2 weeks ago
696
git status --short --branch
697
which ssh
698
ssh -G j1 | grep -E '^(hostname|user|port) '
699
```
700

            
701
When fixing auth:
Bogdan Timofte authored 2 weeks ago
702

            
Bogdan Timofte authored 2 weeks ago
703
```text
704
1. Identify which hop failed from the error user@host.
705
2. is-jumper failures mean local key/config.
706
3. J1/J2 failures mean hardware card, SSH_AUTH_SOCK, or jump user.
707
4. final-host failures mean final host user/auth/port.
708
5. Apply the fix in inventory or wrapper source, not generated config.
709
6. Run tools/deploy-local.sh.
710
7. Run read-only SSH verification.
711
8. Commit the source change.
712
```
713

            
714
Do not assume `hosts.yaml` alone is the effective config. Always remember
715
`inventory/hosts-local.yaml` is merged in by `tools/generate-configs.py`.
716

            
717
Do not trust stale docs, comments, or generated files over these commands:
718

            
719
```bash
720
ssh -G <alias>
721
tools/deploy-local.sh
722
ssh <alias> hostname
723
git diff
Bogdan Timofte authored 2 weeks ago
724
```