@@ -1,222 +0,0 @@ |
||
| 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`). |
|
@@ -1,327 +0,0 @@ |
||
| 1 |
-# SSH Keys and Access Guide |
|
| 2 |
- |
|
| 3 |
-Centralized reference for SSH key management and access paths. |
|
| 4 |
- |
|
| 5 |
-## Key Storage |
|
| 6 |
- |
|
| 7 |
-### Local Keys (on your machine) |
|
| 8 |
- |
|
| 9 |
-``` |
|
| 10 |
-~/.ssh/keys/ |
|
| 11 |
- id_ed25519 → ~/.ssh/id_ed25519 (modern, preferred) |
|
| 12 |
- id_ed25519_2026-05 |
|
| 13 |
- id_ed25519_2026-05.pub |
|
| 14 |
- id_rsa (RSA legacy, 2048-bit) |
|
| 15 |
- id_rsa_old (deprecated, from 2015) |
|
| 16 |
- id_rsa_old.pub |
|
| 17 |
- is-jumper_ed25519 (specific to is-jumper entry point) |
|
| 18 |
- is-jumper_ed25519.pub |
|
| 19 |
-``` |
|
| 20 |
- |
|
| 21 |
-### Physical Hardware Key (on is-jumper) |
|
| 22 |
- |
|
| 23 |
-**⚠️ IMPORTANT**: The primary authentication for the **company network (J1/J2)** is a **physical hardware security key (card/smartcard)** mounted **only on is-jumper** (192.168.2.100). |
|
| 24 |
- |
|
| 25 |
-- **Location**: is-jumper (192.168.2.100) only |
|
| 26 |
-- **Type**: Hardware security module / smartcard (RSA 4096-bit) |
|
| 27 |
-- **Exposed via**: GPG agent at `/run/user/0/gnupg/S.gpg-agent.ssh` (on is-jumper) |
|
| 28 |
-- **Access method**: SSH agent forwarding through is-jumper |
|
| 29 |
-- **Users served**: Multiple users from the 192.168.2.0/24 network |
|
| 30 |
- |
|
| 31 |
-**You cannot access J1/J2 directly from your local machine.** The connection must go through is-jumper, which holds the only valid physical key for the company network. |
|
| 32 |
- |
|
| 33 |
-## Key Usage Matrix |
|
| 34 |
- |
|
| 35 |
-| Key | Location | Used By | Purpose | Auth Method | |
|
| 36 |
-| --- | --- | --- | --- | --- | |
|
| 37 |
-| **Physical card (RSA 4096)** | Hardware on is-jumper only | J1, J2, company network | Primary auth for nextgen network | Hardware smartcard via GPG agent | |
|
| 38 |
-| **is-jumper ED25519** | `~/.ssh/keys/is-jumper_ed25519` | Your machine → is-jumper | Access the entry point | pubkey + IdentitiesOnly | |
|
| 39 |
-| **Modern ED25519** | `~/.ssh/id_ed25519` | Local lab, final hosts | Secondary/fallback auth | pubkey | |
|
| 40 |
-| **Legacy RSA** | `~/.ssh/id_rsa` | Legacy systems | Transitional | pubkey | |
|
| 41 |
-| **Deprecated RSA** | `~/.ssh/keys/id_rsa_old` | Very old hosts | Final fallback | pubkey | |
|
| 42 |
- |
|
| 43 |
-**Key insight**: You authenticate TO is-jumper with your local ED25519 key, but once on is-jumper, the system uses the **physical hardware key** to authenticate to the company network (J1/J2). |
|
| 44 |
- |
|
| 45 |
-## Access Paths and Routing |
|
| 46 |
- |
|
| 47 |
-### 1. Local Lab Setup (192.168.2.0/24) |
|
| 48 |
- |
|
| 49 |
-**Direct access** — no jump needed: |
|
| 50 |
- |
|
| 51 |
-``` |
|
| 52 |
-is-jumper (192.168.2.100) |
|
| 53 |
-├─ User: root |
|
| 54 |
-├─ Port: 22 |
|
| 55 |
-├─ Key: ~/.ssh/keys/is-jumper_ed25519 |
|
| 56 |
-├─ IdentitiesOnly: yes |
|
| 57 |
-└─ Routing: SSH_ROUTE=local |
|
| 58 |
- |
|
| 59 |
-Local dev machines (192.168.2.110-122) |
|
| 60 |
-├─ User: bogdan |
|
| 61 |
-├─ Port: 22 |
|
| 62 |
-└─ Key: ~/.ssh/id_ed25519 |
|
| 63 |
-``` |
|
| 64 |
- |
|
| 65 |
-Access to is-jumper: |
|
| 66 |
- |
|
| 67 |
-```bash |
|
| 68 |
-# Direct connection (SSH will use is-jumper_ed25519 via IdentitiesOnly) |
|
| 69 |
-ssh is-jumper |
|
| 70 |
- |
|
| 71 |
-# Verify key in use: |
|
| 72 |
-ssh -G is-jumper | grep identityfile |
|
| 73 |
-``` |
|
| 74 |
- |
|
| 75 |
-### 2. Production: Via is-jumper → J1/J2 → Final Hosts |
|
| 76 |
- |
|
| 77 |
-**Access chain for internal company network (10.253.51.0/24)**: |
|
| 78 |
- |
|
| 79 |
-``` |
|
| 80 |
-Your machine (local) |
|
| 81 |
- ↓ (ED25519 pubkey) |
|
| 82 |
-is-jumper (192.168.2.100) [VPN client + physical card key guardian] |
|
| 83 |
- ↓ (SSH agent forwarding + physical card RSA 4096) |
|
| 84 |
-J1 (10.253.51.50:25904) or J2 (10.253.51.52:25904) [jump hosts] |
|
| 85 |
- ↓ (SSH forward) |
|
| 86 |
-Final host (voip, porta, radius, etc.) |
|
| 87 |
-``` |
|
| 88 |
- |
|
| 89 |
-**Critical**: You cannot connect to J1/J2 directly from your local machine because the physical hardware key is **only on is-jumper**. All company network access must go through is-jumper first. |
|
| 90 |
- |
|
| 91 |
-#### Step 1: is-jumper (entry point) |
|
| 92 |
- |
|
| 93 |
-Authenticate to is-jumper with your local ED25519 key: |
|
| 94 |
- |
|
| 95 |
-- **User**: root |
|
| 96 |
-- **Host**: 192.168.2.100 |
|
| 97 |
-- **Port**: 22 |
|
| 98 |
-- **Key**: `~/.ssh/keys/is-jumper_ed25519` |
|
| 99 |
-- **IdentitiesOnly**: yes (forces use of specified key only) |
|
| 100 |
- |
|
| 101 |
-```bash |
|
| 102 |
-ssh is-jumper |
|
| 103 |
-``` |
|
| 104 |
- |
|
| 105 |
-Once logged in, is-jumper has access to the physical card key via GPG agent at `/run/user/0/gnupg/S.gpg-agent.ssh`. |
|
| 106 |
- |
|
| 107 |
-#### Step 2: J1/J2 (jump hosts) — via is-jumper's physical key |
|
| 108 |
- |
|
| 109 |
-**SSH agent forwarding** connects your SSH client to is-jumper's physical card: |
|
| 110 |
- |
|
| 111 |
-- **Routing**: ProxyJump is-jumper |
|
| 112 |
-- **Authentication on J1/J2**: Physical card (RSA 4096) via `SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh` |
|
| 113 |
-- **User on J1/J2**: `bogdan.timofte` (company network default) |
|
| 114 |
-- **Port**: 25904 (VPN jump port) |
|
| 115 |
- |
|
| 116 |
-```bash |
|
| 117 |
-ssh j1 # Routes: local → is-jumper → J1 (using is-jumper's physical key) |
|
| 118 |
-ssh j2 # Routes: local → is-jumper → J2 (using is-jumper's physical key) |
|
| 119 |
-``` |
|
| 120 |
- |
|
| 121 |
-**The SSH config handles this automatically** — you don't need to manually set up agent forwarding. The wrapper or ProxyJump chain uses is-jumper's physical key to authenticate. |
|
| 122 |
- |
|
| 123 |
-**Verify J1 config**: |
|
| 124 |
- |
|
| 125 |
-```bash |
|
| 126 |
-ssh -G j1 | grep -E '^(hostname|port|user|proxyjump)' |
|
| 127 |
-``` |
|
| 128 |
- |
|
| 129 |
-#### Step 3: Final Hosts |
|
| 130 |
- |
|
| 131 |
-**From J1/J2 to actual hosts** (configured in J1's/J2's local SSH config): |
|
| 132 |
- |
|
| 133 |
-- **User**: varies by host group (bogdan, bogdan.timofte, root, etc.) |
|
| 134 |
-- **Port**: 22 (standard) or 24 (jump hosts default) or custom |
|
| 135 |
-- **Key**: `~/.ssh/id_ed25519` |
|
| 136 |
-- **ProxyJump**: j1 or j2 |
|
| 137 |
- |
|
| 138 |
-```bash |
|
| 139 |
-# Example: PortaOne database server |
|
| 140 |
-ssh porta-db # → is-jumper → J1 → 193.16.148.11 |
|
| 141 |
- |
|
| 142 |
-# Example: VoIP PBX |
|
| 143 |
-ssh voip-prov # → is-jumper → J1 → 10.253.51.139 |
|
| 144 |
- |
|
| 145 |
-# Example: Radius database |
|
| 146 |
-ssh falticeni.radius-db # → is-jumper → J1 → falticeni.radius-db:24 |
|
| 147 |
-``` |
|
| 148 |
- |
|
| 149 |
-### 3. Emergency Public Routes (j1.next-gen.ro / j2.next-gen.ro) |
|
| 150 |
- |
|
| 151 |
-If internal VPN is down, use public DNS names: |
|
| 152 |
- |
|
| 153 |
-```bash |
|
| 154 |
-# Standard (internal VPN) |
|
| 155 |
-ssh j1 # 10.253.51.50:25904 |
|
| 156 |
- |
|
| 157 |
-# Emergency (public DNS) |
|
| 158 |
-ssh j1.next-gen.ro # j1.next-gen.ro:25904 |
|
| 159 |
-ssh j2.next-gen.ro # j2.next-gen.ro:25904 |
|
| 160 |
-``` |
|
| 161 |
- |
|
| 162 |
-Both routes go through is-jumper first (no direct connection). |
|
| 163 |
- |
|
| 164 |
-## Key Migration Status |
|
| 165 |
- |
|
| 166 |
-### Check which hosts have modern keys |
|
| 167 |
- |
|
| 168 |
-```bash |
|
| 169 |
-# Test all local lab hosts |
|
| 170 |
-for h in is-baobab is-ebony is-tapia is-jumper is-mazeri is-toltec is-andrafiabe is-anjohibe is-nasturel is-mat; do |
|
| 171 |
- timeout 2 ssh -o BatchMode=yes "$h" true 2>/dev/null && echo "$h: ✓" || echo "$h: ⚠" |
|
| 172 |
-done |
|
| 173 |
-``` |
|
| 174 |
- |
|
| 175 |
-### Migrate a host to modern ED25519 key |
|
| 176 |
- |
|
| 177 |
-```bash |
|
| 178 |
-# Automatic migration (uses legacy key to install modern key) |
|
| 179 |
-tools/migrate-modern-key.sh is-baobab |
|
| 180 |
- |
|
| 181 |
-# Or migrate all |
|
| 182 |
-tools/migrate-modern-key.sh |
|
| 183 |
-``` |
|
| 184 |
- |
|
| 185 |
-See [docs/KEY_MIGRATION.md](docs/KEY_MIGRATION.md) for manual procedures. |
|
| 186 |
- |
|
| 187 |
-## SSH Config Generation |
|
| 188 |
- |
|
| 189 |
-The `~/.ssh/config` is **auto-generated** from inventory — do not edit manually. |
|
| 190 |
- |
|
| 191 |
-```bash |
|
| 192 |
-# Regenerate after inventory changes |
|
| 193 |
-python3 tools/generate-configs.py |
|
| 194 |
- |
|
| 195 |
-# Deploy to ~/.ssh/config |
|
| 196 |
-cp generated/client.conf ~/.ssh/config |
|
| 197 |
-# or use the deploy script: |
|
| 198 |
-tools/deploy-local.sh |
|
| 199 |
-``` |
|
| 200 |
- |
|
| 201 |
-**Config files generated**: |
|
| 202 |
- |
|
| 203 |
-| File | Target | Purpose | |
|
| 204 |
-|------|--------|---------| |
|
| 205 |
-| `generated/client.conf` | local client (~/.ssh/config) | local access config | |
|
| 206 |
-| `generated/is-jumper.conf` | is-jumper (via deploy) | is-jumper alias config | |
|
| 207 |
-| `generated/j1.conf` | J1 (server-side) | final host access on J1 | |
|
| 208 |
-| `generated/j2.conf` | J2 (server-side) | final host access on J2 | |
|
| 209 |
- |
|
| 210 |
-## Troubleshooting |
|
| 211 |
- |
|
| 212 |
-### "Permission denied (publickey)" on J1/J2 |
|
| 213 |
- |
|
| 214 |
-**⚠️ First check**: Do you have **access to is-jumper**? |
|
| 215 |
- |
|
| 216 |
-```bash |
|
| 217 |
-ssh is-jumper "echo access ok" |
|
| 218 |
-``` |
|
| 219 |
- |
|
| 220 |
-If this fails, the issue is your local ED25519 key or is-jumper authentication. Fix that first. |
|
| 221 |
- |
|
| 222 |
-**If is-jumper works but J1 fails**: |
|
| 223 |
- |
|
| 224 |
-The issue is likely the **physical card key is not properly exposed** or **SSH agent forwarding isn't working**. |
|
| 225 |
- |
|
| 226 |
-**Diagnosis**: |
|
| 227 |
- |
|
| 228 |
-```bash |
|
| 229 |
-# 1. Check if you can reach is-jumper |
|
| 230 |
-ssh is-jumper |
|
| 231 |
- |
|
| 232 |
-# 2. From is-jumper, verify the physical card is available |
|
| 233 |
-ssh is-jumper "ls -la /run/user/0/gnupg/S.gpg-agent.ssh" |
|
| 234 |
-# Should exist and be readable |
|
| 235 |
- |
|
| 236 |
-# 3. Check J1 connectivity with verbose output |
|
| 237 |
-ssh -vvv j1 2>&1 | grep -E "ProxyJump|Offering|Authentications|Trying" |
|
| 238 |
-``` |
|
| 239 |
- |
|
| 240 |
-**Solutions**: |
|
| 241 |
- |
|
| 242 |
-1. **is-jumper's SSH agent not running**: |
|
| 243 |
- ```bash |
|
| 244 |
- ssh is-jumper "ps aux | grep gpg-agent" |
|
| 245 |
- # If not running, restart it or contact the system administrator |
|
| 246 |
- ``` |
|
| 247 |
- |
|
| 248 |
-2. **SSH config not set up for agent forwarding**: |
|
| 249 |
- - Verify ProxyJump is configured: `ssh -G j1 | grep proxyjump` |
|
| 250 |
- - Verify `ForwardAgent yes` is set (check generated config) |
|
| 251 |
- |
|
| 252 |
-3. **Physical card key not accessible**: |
|
| 253 |
- - This is a system issue on is-jumper — contact the key administrator |
|
| 254 |
- - The card may need to be re-mounted or re-authenticated |
|
| 255 |
- |
|
| 256 |
-4. **SSH on your machine not supporting agent forwarding**: |
|
| 257 |
- ```bash |
|
| 258 |
- # Ensure SSH_AUTH_SOCK is set when you SSH to is-jumper |
|
| 259 |
- ssh -A is-jumper |
|
| 260 |
- ``` |
|
| 261 |
- |
|
| 262 |
-### "Permission denied (publickey)" on is-jumper |
|
| 263 |
- |
|
| 264 |
-**The issue**: Your local ED25519 key isn't authorized on is-jumper. |
|
| 265 |
- |
|
| 266 |
-**Verify your key is present**: |
|
| 267 |
- |
|
| 268 |
-```bash |
|
| 269 |
-ls -la ~/.ssh/keys/is-jumper_ed25519 |
|
| 270 |
-cat ~/.ssh/keys/is-jumper_ed25519.pub |
|
| 271 |
-``` |
|
| 272 |
- |
|
| 273 |
-**Check that is-jumper has your key**: |
|
| 274 |
- |
|
| 275 |
-```bash |
|
| 276 |
-ssh is-jumper "cat ~/.ssh/authorized_keys | grep -i ed25519" |
|
| 277 |
-``` |
|
| 278 |
- |
|
| 279 |
-If your key isn't there, it needs to be added by the is-jumper administrator. |
|
| 280 |
- |
|
| 281 |
-### "No route to host" (10.253.51.x) |
|
| 282 |
- |
|
| 283 |
-**Cause**: Trying to reach J1/J2 directly without going through is-jumper. |
|
| 284 |
- |
|
| 285 |
-**Check ProxyJump**: |
|
| 286 |
- |
|
| 287 |
-```bash |
|
| 288 |
-ssh -G j1 | grep proxyjump |
|
| 289 |
-# Must show: proxyjump is-jumper |
|
| 290 |
-``` |
|
| 291 |
- |
|
| 292 |
-If not, regenerate config: `python3 tools/generate-configs.py && cp generated/client.conf ~/.ssh/config` |
|
| 293 |
- |
|
| 294 |
-### SSH hangs or timeouts |
|
| 295 |
- |
|
| 296 |
-**For jump hosts** (reduce to 5-10 seconds): |
|
| 297 |
- |
|
| 298 |
-```bash |
|
| 299 |
-ssh -o ConnectTimeout=5 is-jumper |
|
| 300 |
-ssh -o ConnectTimeout=5 j1 |
|
| 301 |
-``` |
|
| 302 |
- |
|
| 303 |
-**Check network**: |
|
| 304 |
- |
|
| 305 |
-```bash |
|
| 306 |
-# is-jumper reachable? |
|
| 307 |
-ping 192.168.2.100 |
|
| 308 |
- |
|
| 309 |
-# If behind firewall, may need port 22 open to is-jumper |
|
| 310 |
-``` |
|
| 311 |
- |
|
| 312 |
-## Maintenance Checklist |
|
| 313 |
- |
|
| 314 |
-- [ ] Keep both `id_ed25519` and `id_rsa_old` until all hosts migrated |
|
| 315 |
-- [ ] After adding new hosts: run `tools/migrate-modern-key.sh <host>` |
|
| 316 |
-- [ ] After inventory changes: regenerate with `python3 tools/generate-configs.py` |
|
| 317 |
-- [ ] Periodically check: `ssh -G <alias>` to verify config without connecting |
|
| 318 |
-- [ ] Never manually edit `~/.ssh/config` — it's auto-generated |
|
| 319 |
-- [ ] Keep keys in `~/.ssh/keys/` with mode `600` |
|
| 320 |
- |
|
| 321 |
-## References |
|
| 322 |
- |
|
| 323 |
-- [Ed25519 vs RSA Security](https://ianix.com/pub/ed25519-deployment.html) |
|
| 324 |
-- [OpenSSH Manual](https://man.openbsd.org/ssh) |
|
| 325 |
-- [Project inventory structure](inventory/hosts.yaml) |
|
| 326 |
-- [Key migration procedures](docs/KEY_MIGRATION.md) |
|
| 327 |
-- [Architecture details](ARCHITECTURE.md) (legacy Romanian docs: `.doc/ssh-jump-architecture.md`) |
|
@@ -1,139 +1,659 @@ |
||
| 1 |
-# SSH Infrastructure |
|
| 1 |
+# SSH Infrastructure - Single Source of Truth |
|
| 2 | 2 |
|
| 3 |
-Source-controlled SSH routing and configuration for multi-user jump infrastructure |
|
| 4 |
-(`is-jumper` gateway → J1/J2 company network → final hosts). |
|
| 3 |
+Last updated: 2026-05-21 |
|
| 5 | 4 |
|
| 6 |
-**⚠️ New to this project?** Start with [KEYS_AND_ACCESS.md](KEYS_AND_ACCESS.md) — it |
|
| 7 |
-explains which SSH key to use where, and how the access chain works. |
|
| 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. |
|
| 8 | 9 |
|
| 9 |
-## Quick Start |
|
| 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 |
+ |
|
| 53 |
+## Repository Rules |
|
| 54 |
+ |
|
| 55 |
+Project source: |
|
| 56 |
+ |
|
| 57 |
+```text |
|
| 58 |
+/Users/bogdan/Documents/Workspaces/Bogdan/ssh-infrastructure |
|
| 59 |
+``` |
|
| 60 |
+ |
|
| 61 |
+Runtime OpenSSH state: |
|
| 62 |
+ |
|
| 63 |
+```text |
|
| 64 |
+~/.ssh/config |
|
| 65 |
+~/.ssh/known_hosts |
|
| 66 |
+~/.ssh/authorized_keys |
|
| 67 |
+~/.ssh/keys/ |
|
| 68 |
+~/.local/bin/ssh |
|
| 69 |
+~/.local/bin/scp |
|
| 70 |
+~/.local/bin/sftp |
|
| 71 |
+``` |
|
| 72 |
+ |
|
| 73 |
+Only edit source files in the repository. Do not edit generated runtime files by |
|
| 74 |
+hand. |
|
| 75 |
+ |
|
| 76 |
+Tracked source files: |
|
| 77 |
+ |
|
| 78 |
+```text |
|
| 79 |
+README.md this file, the only documentation |
|
| 80 |
+inventory/hosts.yaml upstream/company host inventory |
|
| 81 |
+inventory/hosts-local.yaml local overlay and local lab inventory |
|
| 82 |
+schema/hosts.schema.json inventory schema |
|
| 83 |
+scripts/ssh-wrapper.sh installed as ~/.local/bin/ssh |
|
| 84 |
+scripts/scp-wrapper.sh installed as ~/.local/bin/scp |
|
| 85 |
+scripts/sftp-wrapper.sh installed as ~/.local/bin/sftp |
|
| 86 |
+tools/generate-configs.py config generator |
|
| 87 |
+tools/deploy-local.sh local deploy |
|
| 88 |
+tools/sync-hosts-from-upstream.sh upstream inventory sync |
|
| 89 |
+tools/migrate-modern-key.sh legacy local key migration helper |
|
| 90 |
+.gitignore |
|
| 91 |
+``` |
|
| 92 |
+ |
|
| 93 |
+Ignored or runtime-only files: |
|
| 94 |
+ |
|
| 95 |
+```text |
|
| 96 |
+generated/ |
|
| 97 |
+SSH_SETUP_SUMMARY.md |
|
| 98 |
+authorized_keys |
|
| 99 |
+known_hosts |
|
| 100 |
+known_hosts.old |
|
| 101 |
+keys/ |
|
| 102 |
+agent/ |
|
| 103 |
+conf.d/ |
|
| 104 |
+import/ |
|
| 105 |
+*.pem *.key *.ppk *.der *.csr |
|
| 106 |
+``` |
|
| 107 |
+ |
|
| 108 |
+Git basics: |
|
| 109 |
+ |
|
| 110 |
+```bash |
|
| 111 |
+git status |
|
| 112 |
+git add README.md inventory schema scripts tools .gitignore |
|
| 113 |
+git commit -m "Describe change" |
|
| 114 |
+``` |
|
| 115 |
+ |
|
| 116 |
+Known remotes: |
|
| 117 |
+ |
|
| 118 |
+```text |
|
| 119 |
+nextgen ssh://git@192.168.2.103/home/git/repositories/bogdan/NextGen-Host-List.git |
|
| 120 |
+mazeri ssh://git@192.168.2.102/home/git/repositories/bogdan/SSH-Infrastructure.git |
|
| 121 |
+``` |
|
| 122 |
+ |
|
| 123 |
+## Architecture |
|
| 124 |
+ |
|
| 125 |
+### Network |
|
| 126 |
+ |
|
| 127 |
+```text |
|
| 128 |
+192.168.2.0/24 - local office/lab network |
|
| 129 |
+ is-jumper 192.168.2.100 - VPN client and hardware-key guardian |
|
| 130 |
+ local lab hosts |
|
| 131 |
+ |
|
| 132 |
+10.253.51.0/24 - internal company network reached from is-jumper VPN |
|
| 133 |
+ J1 10.253.51.50:25904 |
|
| 134 |
+ J2 10.253.51.52:25904 |
|
| 135 |
+ final hosts |
|
| 136 |
+``` |
|
| 137 |
+ |
|
| 138 |
+`is-jumper` is not a VPN server. It is a local host that has VPN reachability to |
|
| 139 |
+the company network and has the physical smartcard mounted. |
|
| 140 |
+ |
|
| 141 |
+### Access Chains |
|
| 142 |
+ |
|
| 143 |
+Standard final-host chain: |
|
| 144 |
+ |
|
| 145 |
+```text |
|
| 146 |
+local wrapper |
|
| 147 |
+ -> /usr/bin/ssh is-jumper |
|
| 148 |
+ -> SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh -A J1 |
|
| 149 |
+ -> ssh final-host |
|
| 150 |
+``` |
|
| 151 |
+ |
|
| 152 |
+Interactive J1/J2 login: |
|
| 153 |
+ |
|
| 154 |
+```text |
|
| 155 |
+local wrapper -> is-jumper -> J1/J2 |
|
| 156 |
+``` |
|
| 157 |
+ |
|
| 158 |
+Emergency public routes: |
|
| 159 |
+ |
|
| 160 |
+```text |
|
| 161 |
+local wrapper -> is-jumper -> j1.next-gen.ro or j2.next-gen.ro |
|
| 162 |
+``` |
|
| 163 |
+ |
|
| 164 |
+The wrapper strips custom flags before calling real SSH: |
|
| 165 |
+ |
|
| 166 |
+```text |
|
| 167 |
+-J1 use J1 VPN route, default |
|
| 168 |
+-J2 use J2 VPN route |
|
| 169 |
+-j1 use public j1 route |
|
| 170 |
+-j2 use public j2 route |
|
| 171 |
+``` |
|
| 172 |
+ |
|
| 173 |
+Do not reintroduce local port forwarding, Python relays, `IdentityAgent |
|
| 174 |
+/tmp/...`, or helper scripts that bridge the physical-card socket to the local |
|
| 175 |
+machine. Those were removed for compliance and SentinelOne noise. |
|
| 176 |
+ |
|
| 177 |
+## Keys |
|
| 178 |
+ |
|
| 179 |
+### Key Matrix |
|
| 180 |
+ |
|
| 181 |
+| Key | Location | Purpose | |
|
| 182 |
+| --- | --- | --- | |
|
| 183 |
+| Physical smartcard RSA 4096 | only on `is-jumper` | Auth from `is-jumper` to J1/J2/company network | |
|
| 184 |
+| `is-jumper_ed25519` | local `~/.ssh/keys/is-jumper_ed25519` | Auth from macOS to `is-jumper` | |
|
| 185 |
+| Modern ED25519 | local `~/.ssh/id_ed25519` or `~/.ssh/keys/id_ed25519` | Local lab and migrated hosts | |
|
| 186 |
+| Legacy RSA | local `~/.ssh/keys/id_rsa_old` | Temporary migration fallback for old local hosts | |
|
| 187 |
+ |
|
| 188 |
+Critical config values: |
|
| 189 |
+ |
|
| 190 |
+```yaml |
|
| 191 |
+entrypoints: |
|
| 192 |
+ is_jumper: |
|
| 193 |
+ hostname: 192.168.2.100 |
|
| 194 |
+ user: root |
|
| 195 |
+ identity_file: ~/.ssh/keys/is-jumper_ed25519 |
|
| 196 |
+ identities_only: true |
|
| 197 |
+ |
|
| 198 |
+jumps: |
|
| 199 |
+ j1: |
|
| 200 |
+ hostname: 10.253.51.50 |
|
| 201 |
+ user: bogdan.timofte |
|
| 202 |
+ port: 25904 |
|
| 203 |
+ j2: |
|
| 204 |
+ hostname: 10.253.51.52 |
|
| 205 |
+ user: bogdan.timofte |
|
| 206 |
+ port: 25904 |
|
| 207 |
+``` |
|
| 208 |
+ |
|
| 209 |
+If J1/J2 use `bogdan` instead of `bogdan.timofte`, final host SSH will fail with |
|
| 210 |
+an error like: |
|
| 211 |
+ |
|
| 212 |
+```text |
|
| 213 |
+bogdan@10.253.51.50: Permission denied (publickey). |
|
| 214 |
+Connection to 192.168.2.100 closed. |
|
| 215 |
+``` |
|
| 216 |
+ |
|
| 217 |
+Fix that in `inventory/hosts-local.yaml`, deploy, then verify: |
|
| 10 | 218 |
|
| 11 | 219 |
```bash |
| 12 |
-# 1. Access the local jump gateway |
|
| 13 |
-ssh is-jumper |
|
| 220 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 221 |
+tools/deploy-local.sh |
|
| 222 |
+ssh porta-sip hostname |
|
| 223 |
+``` |
|
| 224 |
+ |
|
| 225 |
+## Inventory and Generation |
|
| 14 | 226 |
|
| 15 |
-# 2. From there, reach company network via J1 or J2 |
|
| 16 |
-ssh j1 # or: ssh j2 |
|
| 227 |
+The generator reads: |
|
| 17 | 228 |
|
| 18 |
-# 3. From J1, reach final hosts |
|
| 19 |
-ssh porta-db # automatic routing: local → is-jumper → J1 → porta-db |
|
| 229 |
+```text |
|
| 230 |
+inventory/hosts.yaml |
|
| 231 |
+inventory/hosts-local.yaml if it exists |
|
| 20 | 232 |
``` |
| 21 | 233 |
|
| 22 |
-The `~/.ssh/config` is auto-generated from `inventory/hosts.yaml` — edit the |
|
| 23 |
-inventory, not the config file. |
|
| 234 |
+Important: the inventory merge is shallow. Later top-level maps from |
|
| 235 |
+`hosts-local.yaml` override upstream maps. This is useful for local lab entries |
|
| 236 |
+but dangerous for defaults. If `hosts-local.yaml` changes `defaults.jump.user`, |
|
| 237 |
+then local `jumps.j1` and `jumps.j2` must specify `user: bogdan.timofte` |
|
| 238 |
+explicitly. |
|
| 24 | 239 |
|
| 25 |
-## File Structure |
|
| 240 |
+Generated files: |
|
| 26 | 241 |
|
| 27 |
-- **Project source**: `~/Documents/Workspaces/Bogdan/ssh-infrastructure` |
|
| 28 |
-- **OpenSSH runtime**: `~/.ssh` (do not commit) |
|
| 242 |
+```text |
|
| 243 |
+generated/client.conf installed as ~/.ssh/config |
|
| 244 |
+generated/is-jumper.conf server-side helper config |
|
| 245 |
+generated/j1.conf server-side final-host config |
|
| 246 |
+generated/j2.conf server-side final-host config |
|
| 247 |
+``` |
|
| 29 | 248 |
|
| 30 |
-Keep secrets and machine-local state out of version control: |
|
| 249 |
+`generated/` is ignored by git. Recreate it any time: |
|
| 31 | 250 |
|
| 32 |
-- private keys: `~/.ssh/keys/` (not in git) |
|
| 33 |
-- `authorized_keys`, `known_hosts`, socket state (not in git) |
|
| 251 |
+```bash |
|
| 252 |
+python3 tools/generate-configs.py |
|
| 253 |
+``` |
|
| 34 | 254 |
|
| 35 |
-Deploy the local runtime with: |
|
| 255 |
+Deploy local runtime: |
|
| 36 | 256 |
|
| 37 | 257 |
```bash |
| 38 |
-# Regenerate ~/.ssh/config from inventory + install wrappers |
|
| 39 | 258 |
tools/deploy-local.sh |
| 40 | 259 |
``` |
| 41 | 260 |
|
| 42 |
-## Version control |
|
| 261 |
+Deploy does: |
|
| 43 | 262 |
|
| 44 |
-This directory is the git repository for source files only. Generated configs, |
|
| 45 |
-local state, keys, known hosts, and handoff notes stay out of version control. |
|
| 263 |
+```text |
|
| 264 |
+1. run tools/generate-configs.py |
|
| 265 |
+2. install generated/client.conf as ~/.ssh/config |
|
| 266 |
+3. install scripts/ssh-wrapper.sh as ~/.local/bin/ssh |
|
| 267 |
+4. install scripts/scp-wrapper.sh as ~/.local/bin/scp |
|
| 268 |
+5. install scripts/sftp-wrapper.sh as ~/.local/bin/sftp |
|
| 269 |
+6. remove obsolete ~/.ssh/scripts wrapper copies |
|
| 270 |
+``` |
|
| 46 | 271 |
|
| 47 |
-Track source changes with: |
|
| 272 |
+It does not touch private keys, `authorized_keys`, or `known_hosts`. |
|
| 273 |
+ |
|
| 274 |
+## Local Shell and Wrappers |
|
| 275 |
+ |
|
| 276 |
+For company aliases, `ssh` must be the wrapper: |
|
| 48 | 277 |
|
| 49 | 278 |
```bash |
| 50 |
-git status |
|
| 51 |
-git add inventory schema scripts tools .doc README.md .gitignore |
|
| 52 |
-git commit |
|
| 279 |
+which ssh |
|
| 280 |
+# /Users/bogdan/.local/bin/ssh |
|
| 281 |
+``` |
|
| 282 |
+ |
|
| 283 |
+If it shows `/usr/bin/ssh`, fix shell PATH and reload: |
|
| 284 |
+ |
|
| 285 |
+```bash |
|
| 286 |
+source ~/.zshrc |
|
| 287 |
+which ssh |
|
| 53 | 288 |
``` |
| 54 | 289 |
|
| 55 |
-## SSH Key Management |
|
| 290 |
+The current shell startup should keep `~/.local/bin` first in both interactive |
|
| 291 |
+and login shells. If editing these files, preserve this behavior: |
|
| 56 | 292 |
|
| 57 |
-Which key goes where? See [KEYS_AND_ACCESS.md](KEYS_AND_ACCESS.md) for the full matrix. |
|
| 293 |
+```zsh |
|
| 294 |
+path=("$HOME/.local/bin" ${path:#"$HOME/.local/bin"})
|
|
| 295 |
+export PATH |
|
| 296 |
+``` |
|
| 297 |
+ |
|
| 298 |
+`ssh-wrapper.sh` uses bash 3.2 compatible array expansion under `set -u`. |
|
| 299 |
+Do not replace guarded forms like: |
|
| 300 |
+ |
|
| 301 |
+```bash |
|
| 302 |
+${cmd_args[@]+"${cmd_args[@]}"}
|
|
| 303 |
+``` |
|
| 304 |
+ |
|
| 305 |
+with plain: |
|
| 306 |
+ |
|
| 307 |
+```bash |
|
| 308 |
+"${cmd_args[@]}"
|
|
| 309 |
+``` |
|
| 310 |
+ |
|
| 311 |
+On macOS bash 3.2, empty arrays plus `set -u` can fail with: |
|
| 312 |
+ |
|
| 313 |
+```text |
|
| 314 |
+cmd_args[@]: unbound variable |
|
| 315 |
+``` |
|
| 316 |
+ |
|
| 317 |
+## Sync from Upstream |
|
| 58 | 318 |
|
| 59 |
-Migrate hosts from legacy RSA to modern ED25519: |
|
| 319 |
+Pull upstream `hosts.yaml`, apply the local `is-jumper` key override, validate |
|
| 320 |
+generation, and deploy if changed: |
|
| 321 |
+ |
|
| 322 |
+```bash |
|
| 323 |
+tools/sync-hosts-from-upstream.sh |
|
| 324 |
+``` |
|
| 325 |
+ |
|
| 326 |
+Defaults: |
|
| 327 |
+ |
|
| 328 |
+```text |
|
| 329 |
+UPSTREAM_SSH_TARGET=nextgen@192.168.2.103 |
|
| 330 |
+UPSTREAM_HOSTS_PATH=/home/nextgen/projects/ssh-infrastructure/inventory/hosts.yaml |
|
| 331 |
+LOCAL_IS_JUMPER_IDENTITY_FILE=~/.ssh/keys/is-jumper_ed25519 |
|
| 332 |
+DEPLOY_AFTER_SYNC=1 |
|
| 333 |
+FORCE_DEPLOY=0 |
|
| 334 |
+``` |
|
| 335 |
+ |
|
| 336 |
+Useful overrides: |
|
| 337 |
+ |
|
| 338 |
+```bash |
|
| 339 |
+UPSTREAM_HOSTS_FILE=/tmp/hosts.yaml tools/sync-hosts-from-upstream.sh |
|
| 340 |
+DEPLOY_AFTER_SYNC=0 tools/sync-hosts-from-upstream.sh |
|
| 341 |
+FORCE_DEPLOY=1 tools/sync-hosts-from-upstream.sh |
|
| 342 |
+UPSTREAM_SSH_TARGET=user@host tools/sync-hosts-from-upstream.sh |
|
| 343 |
+``` |
|
| 344 |
+ |
|
| 345 |
+After sync, always check J1 user because the local overlay can override jump |
|
| 346 |
+defaults: |
|
| 347 |
+ |
|
| 348 |
+```bash |
|
| 349 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 350 |
+``` |
|
| 351 |
+ |
|
| 352 |
+Expected: |
|
| 353 |
+ |
|
| 354 |
+```text |
|
| 355 |
+user bogdan.timofte |
|
| 356 |
+hostname 10.253.51.50 |
|
| 357 |
+port 25904 |
|
| 358 |
+``` |
|
| 359 |
+ |
|
| 360 |
+## Adding or Changing Hosts |
|
| 361 |
+ |
|
| 362 |
+For company/Next-Gen hosts: |
|
| 363 |
+ |
|
| 364 |
+```text |
|
| 365 |
+1. Edit inventory/hosts.yaml or sync it from upstream. |
|
| 366 |
+2. Keep local-only corrections in inventory/hosts-local.yaml. |
|
| 367 |
+3. Run tools/deploy-local.sh. |
|
| 368 |
+4. Verify with ssh -G <alias>. |
|
| 369 |
+5. Verify read-only with ssh <alias> hostname. |
|
| 370 |
+6. Commit source changes only. |
|
| 371 |
+``` |
|
| 372 |
+ |
|
| 373 |
+For local lab hosts: |
|
| 374 |
+ |
|
| 375 |
+```text |
|
| 376 |
+1. Edit inventory/hosts-local.yaml. |
|
| 377 |
+2. Run tools/deploy-local.sh. |
|
| 378 |
+3. Verify with ssh <alias> hostname. |
|
| 379 |
+4. Commit the local overlay change. |
|
| 380 |
+``` |
|
| 381 |
+ |
|
| 382 |
+Common inventory defaults: |
|
| 383 |
+ |
|
| 384 |
+| Context | User | Port | |
|
| 385 |
+| --- | --- | --- | |
|
| 386 |
+| J1/J2 company jump | `bogdan.timofte` | `25904` for VPN route | |
|
| 387 |
+| Company final hosts | usually `bogdan` | usually `22` | |
|
| 388 |
+| Company inherited jump config | `bogdan.timofte` | often `24` | |
|
| 389 |
+| Local lab hosts | usually `bogdan` | usually `22` | |
|
| 390 |
+| Cisco/OLT interactive devices | inventory-specific | `22` | |
|
| 391 |
+ |
|
| 392 |
+For Cisco/OLT/password-interactive devices, set: |
|
| 393 |
+ |
|
| 394 |
+```yaml |
|
| 395 |
+auth: password_interactive |
|
| 396 |
+``` |
|
| 397 |
+ |
|
| 398 |
+The wrapper then avoids forcing `BatchMode=yes` and disables pubkey auth for |
|
| 399 |
+that final hop. |
|
| 400 |
+ |
|
| 401 |
+## Key Migration for Local Legacy Hosts |
|
| 402 |
+ |
|
| 403 |
+Modern preferred key: |
|
| 404 |
+ |
|
| 405 |
+```text |
|
| 406 |
+~/.ssh/id_ed25519.pub |
|
| 407 |
+``` |
|
| 408 |
+ |
|
| 409 |
+Legacy fallback key: |
|
| 410 |
+ |
|
| 411 |
+```text |
|
| 412 |
+~/.ssh/keys/id_rsa_old |
|
| 413 |
+``` |
|
| 414 |
+ |
|
| 415 |
+Migrate all configured local legacy hosts: |
|
| 60 | 416 |
|
| 61 | 417 |
```bash |
| 62 |
-# Migrate all legacy hosts |
|
| 63 | 418 |
tools/migrate-modern-key.sh |
| 419 |
+``` |
|
| 420 |
+ |
|
| 421 |
+Migrate one host: |
|
| 64 | 422 |
|
| 65 |
-# Migrate a specific host |
|
| 423 |
+```bash |
|
| 66 | 424 |
tools/migrate-modern-key.sh is-baobab |
| 67 | 425 |
``` |
| 68 | 426 |
|
| 69 |
-Details: [docs/KEY_MIGRATION.md](docs/KEY_MIGRATION.md) |
|
| 427 |
+Manual fallback if password access is available: |
|
| 70 | 428 |
|
| 71 |
-## Current client layout |
|
| 429 |
+```bash |
|
| 430 |
+ssh -o PubkeyAuthentication=no user@host \ |
|
| 431 |
+ "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys" \ |
|
| 432 |
+ < ~/.ssh/id_ed25519.pub |
|
| 433 |
+``` |
|
| 434 |
+ |
|
| 435 |
+Keep `id_rsa_old` until all legacy hosts are verified with the modern key. |
|
| 436 |
+ |
|
| 437 |
+## Verification Checklist |
|
| 438 |
+ |
|
| 439 |
+Run after deploy, sync, wrapper edits, or inventory changes: |
|
| 440 |
+ |
|
| 441 |
+```bash |
|
| 442 |
+which ssh |
|
| 443 |
+ssh -G is-jumper | grep -E '^(hostname|user|identityfile|identitiesonly) ' |
|
| 444 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 445 |
+ssh is-jumper hostname |
|
| 446 |
+ssh is-jumper 'SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh-add -L | sed -n 1p' |
|
| 447 |
+ssh porta-sip hostname |
|
| 448 |
+ssh pbx-bo hostname |
|
| 449 |
+``` |
|
| 450 |
+ |
|
| 451 |
+Expected signals: |
|
| 72 | 452 |
|
| 73 | 453 |
```text |
| 74 |
-~/.ssh/config |
|
| 75 |
-~/.local/bin/ssh |
|
| 76 |
-~/.local/bin/scp |
|
| 77 |
-~/.local/bin/sftp |
|
| 454 |
+which ssh -> /Users/bogdan/.local/bin/ssh |
|
| 455 |
+is-jumper hostname -> is-vpn-gw |
|
| 456 |
+j1 user -> bogdan.timofte |
|
| 457 |
+physical card check -> ssh-rsa ... cardno:6446168 |
|
| 458 |
+porta-sip hostname -> p12.voip.ro |
|
| 459 |
+pbx-bo hostname -> pbx-bo |
|
| 78 | 460 |
``` |
| 79 | 461 |
|
| 80 |
-The wrapper sources stay versioned in `scripts/` inside the project; deploy |
|
| 81 |
-installs executable copies into `~/.local/bin` and removes the obsolete |
|
| 82 |
-`~/.ssh/scripts` runtime layout from older checkouts. |
|
| 462 |
+Interactive smoke test: |
|
| 463 |
+ |
|
| 464 |
+```bash |
|
| 465 |
+printf "exit\n" | ssh porta-sip |
|
| 466 |
+printf "exit\n" | ssh pbx-bo |
|
| 467 |
+``` |
|
| 468 |
+ |
|
| 469 |
+## Troubleshooting |
|
| 470 |
+ |
|
| 471 |
+### `bogdan@10.253.51.50: Permission denied (publickey)` |
|
| 472 |
+ |
|
| 473 |
+The wrapper reached `is-jumper`, but J1 was attempted with user `bogdan`. |
|
| 474 |
+J1/J2 need `bogdan.timofte`. |
|
| 475 |
+ |
|
| 476 |
+Check: |
|
| 477 |
+ |
|
| 478 |
+```bash |
|
| 479 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 480 |
+``` |
|
| 481 |
+ |
|
| 482 |
+Fix: |
|
| 483 |
+ |
|
| 484 |
+```yaml |
|
| 485 |
+# inventory/hosts-local.yaml |
|
| 486 |
+jumps: |
|
| 487 |
+ j1: |
|
| 488 |
+ user: bogdan.timofte |
|
| 489 |
+ j2: |
|
| 490 |
+ user: bogdan.timofte |
|
| 491 |
+``` |
|
| 492 |
+ |
|
| 493 |
+Deploy: |
|
| 494 |
+ |
|
| 495 |
+```bash |
|
| 496 |
+tools/deploy-local.sh |
|
| 497 |
+ssh porta-sip hostname |
|
| 498 |
+``` |
|
| 83 | 499 |
|
| 84 |
-## Source of Truth |
|
| 500 |
+### `root@192.168.2.100: Permission denied` |
|
| 85 | 501 |
|
| 86 |
-The structured source of truth starts in: |
|
| 502 |
+The local connection to `is-jumper` is using the wrong key. |
|
| 503 |
+ |
|
| 504 |
+Check: |
|
| 505 |
+ |
|
| 506 |
+```bash |
|
| 507 |
+ssh -G is-jumper | grep -E '^(user|hostname|identityfile|identitiesonly) ' |
|
| 508 |
+ls -l ~/.ssh/keys/is-jumper_ed25519 |
|
| 509 |
+``` |
|
| 510 |
+ |
|
| 511 |
+Expected: |
|
| 87 | 512 |
|
| 88 | 513 |
```text |
| 89 |
-inventory/hosts.yaml |
|
| 90 |
-schema/hosts.schema.json |
|
| 91 |
-tools/generate-configs.py |
|
| 514 |
+user root |
|
| 515 |
+hostname 192.168.2.100 |
|
| 516 |
+identityfile ~/.ssh/keys/is-jumper_ed25519 |
|
| 517 |
+identitiesonly yes |
|
| 518 |
+``` |
|
| 519 |
+ |
|
| 520 |
+If generated config is wrong, fix `inventory/hosts-local.yaml` or |
|
| 521 |
+`inventory/hosts.yaml`, then deploy. |
|
| 522 |
+ |
|
| 523 |
+### `ssh pbx-bo` uses `/usr/bin/ssh` |
|
| 524 |
+ |
|
| 525 |
+The wrapper is not first in PATH. |
|
| 526 |
+ |
|
| 527 |
+Check: |
|
| 528 |
+ |
|
| 529 |
+```bash |
|
| 530 |
+which ssh |
|
| 92 | 531 |
``` |
| 93 | 532 |
|
| 94 |
-The `generated/*.conf` files are deploy artifacts. They are ignored by git and |
|
| 95 |
-can be recreated at any time with `tools/deploy-local.sh`. |
|
| 533 |
+Fix current shell: |
|
| 534 |
+ |
|
| 535 |
+```bash |
|
| 536 |
+source ~/.zshrc |
|
| 537 |
+``` |
|
| 96 | 538 |
|
| 97 |
-## Inventory and Config Generation |
|
| 539 |
+If needed, ensure `.zprofile` and `.zshrc` both move `~/.local/bin` to the front |
|
| 540 |
+using zsh `path`, not a guard that leaves it later in PATH. |
|
| 98 | 541 |
|
| 99 |
-The single source of truth: |
|
| 542 |
+### `cmd_args[@]: unbound variable` |
|
| 543 |
+ |
|
| 544 |
+This is a bash 3.2 plus `set -u` empty-array issue in `ssh-wrapper.sh`. |
|
| 545 |
+ |
|
| 546 |
+Use guarded array expansion: |
|
| 547 |
+ |
|
| 548 |
+```bash |
|
| 549 |
+${array[@]+"${array[@]}"}
|
|
| 550 |
+``` |
|
| 551 |
+ |
|
| 552 |
+Do not simplify it. |
|
| 553 |
+ |
|
| 554 |
+### Physical card missing on `is-jumper` |
|
| 555 |
+ |
|
| 556 |
+Check: |
|
| 557 |
+ |
|
| 558 |
+```bash |
|
| 559 |
+ssh is-jumper 'ls -l /run/user/0/gnupg/S.gpg-agent.ssh' |
|
| 560 |
+ssh is-jumper 'SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh ssh-add -L | sed -n 1p' |
|
| 561 |
+``` |
|
| 562 |
+ |
|
| 563 |
+Expected key output contains: |
|
| 100 | 564 |
|
| 101 | 565 |
```text |
| 102 |
-inventory/hosts.yaml ← edit here to add/modify hosts |
|
| 103 |
- ↓ (python3 tools/generate-configs.py) |
|
| 104 |
-generated/client.conf ← deployed to ~/.ssh/config |
|
| 105 |
-generated/is-jumper.conf ← deployed to is-jumper |
|
| 106 |
-generated/j1.conf ← deployed to J1 |
|
| 107 |
-generated/j2.conf ← deployed to J2 |
|
| 566 |
+cardno:6446168 |
|
| 108 | 567 |
``` |
| 109 | 568 |
|
| 110 |
-### User and Port Defaults |
|
| 569 |
+If missing, the issue is on `is-jumper`: gpg-agent, card mount, permissions, or |
|
| 570 |
+hardware state. |
|
| 111 | 571 |
|
| 112 |
-| Context | User | Port | Notes | |
|
| 113 |
-| --- | --- | --- | --- | |
|
| 114 |
-| Jump hosts (J1, J2) | `bogdan.timofte` | `24` (standard) or `25904` (VPN) | override in inventory | |
|
| 115 |
-| Final hosts | `bogdan` | `22` | most systems; dotted usernames cause issues | |
|
| 116 |
-| Interactive auth (Cisco, OLTs) | varies | `22` | marked with `auth: password_interactive` | |
|
| 572 |
+### Direct command works but wrapper fails |
|
| 117 | 573 |
|
| 118 |
-### Deployment |
|
| 574 |
+Compare generated command behavior: |
|
| 575 |
+ |
|
| 576 |
+```bash |
|
| 577 |
+bash -x ~/.local/bin/ssh porta-sip hostname |
|
| 578 |
+``` |
|
| 579 |
+ |
|
| 580 |
+Look for: |
|
| 581 |
+ |
|
| 582 |
+```text |
|
| 583 |
+SSH_AUTH_SOCK=/run/user/0/gnupg/S.gpg-agent.ssh |
|
| 584 |
+bogdan.timofte@10.253.51.50 |
|
| 585 |
+``` |
|
| 586 |
+ |
|
| 587 |
+If either is wrong, fix inventory/local overlay or wrapper. |
|
| 588 |
+ |
|
| 589 |
+### Generated config was edited manually |
|
| 590 |
+ |
|
| 591 |
+Discard manual runtime edits by redeploying: |
|
| 119 | 592 |
|
| 120 | 593 |
```bash |
| 121 |
-# Option 1: Full deploy (recommended) |
|
| 122 | 594 |
tools/deploy-local.sh |
| 595 |
+``` |
|
| 596 |
+ |
|
| 597 |
+Then verify: |
|
| 598 |
+ |
|
| 599 |
+```bash |
|
| 600 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 601 |
+``` |
|
| 602 |
+ |
|
| 603 |
+## Compatibility and Compliance |
|
| 604 |
+ |
|
| 605 |
+Do not reintroduce these removed patterns: |
|
| 606 |
+ |
|
| 607 |
+```text |
|
| 608 |
+j1-relay.sh |
|
| 609 |
+ssh-proxy.sh |
|
| 610 |
+ensure-ssh-agent-bridge.sh |
|
| 611 |
+ensure-ssh-jump.sh |
|
| 612 |
+local socket forwarding for the hardware card |
|
| 613 |
+Python/base64 port-forwarding relays |
|
| 614 |
+per-host local ProxyCommand bridges |
|
| 615 |
+``` |
|
| 123 | 616 |
|
| 124 |
-# Option 2: Manual steps |
|
| 125 |
-python3 tools/generate-configs.py # Regenerate configs |
|
| 126 |
-cp generated/client.conf ~/.ssh/config # Install client config |
|
| 617 |
+Current compliant model: |
|
| 618 |
+ |
|
| 619 |
+```text |
|
| 620 |
+local wrapper -> ssh is-jumper -> run normal ssh from is-jumper |
|
| 127 | 621 |
``` |
| 128 | 622 |
|
| 129 |
-### Sync from Upstream |
|
| 623 |
+Compatibility options for old final hosts belong in inventory or on jump hosts, |
|
| 624 |
+not in ad-hoc local forwarding scripts. |
|
| 130 | 625 |
|
| 131 |
-Pull latest `hosts.yaml` from nextgen and redeploy: |
|
| 626 |
+## Maintenance Notes for Agents |
|
| 627 |
+ |
|
| 628 |
+Before changing anything: |
|
| 132 | 629 |
|
| 133 | 630 |
```bash |
| 134 |
-tools/sync-hosts-from-upstream.sh |
|
| 631 |
+git status --short --branch |
|
| 632 |
+which ssh |
|
| 633 |
+ssh -G j1 | grep -E '^(hostname|user|port) ' |
|
| 634 |
+``` |
|
| 635 |
+ |
|
| 636 |
+When fixing auth: |
|
| 135 | 637 |
|
| 136 |
-# Customize: |
|
| 137 |
-DEPLOY_AFTER_SYNC=0 tools/sync-hosts-from-upstream.sh # generate only |
|
| 138 |
-UPSTREAM_SSH_TARGET=user@host tools/sync-hosts-from-upstream.sh # custom source |
|
| 638 |
+```text |
|
| 639 |
+1. Identify which hop failed from the error user@host. |
|
| 640 |
+2. is-jumper failures mean local key/config. |
|
| 641 |
+3. J1/J2 failures mean hardware card, SSH_AUTH_SOCK, or jump user. |
|
| 642 |
+4. final-host failures mean final host user/auth/port. |
|
| 643 |
+5. Apply the fix in inventory or wrapper source, not generated config. |
|
| 644 |
+6. Run tools/deploy-local.sh. |
|
| 645 |
+7. Run read-only SSH verification. |
|
| 646 |
+8. Commit the source change. |
|
| 647 |
+``` |
|
| 648 |
+ |
|
| 649 |
+Do not assume `hosts.yaml` alone is the effective config. Always remember |
|
| 650 |
+`inventory/hosts-local.yaml` is merged in by `tools/generate-configs.py`. |
|
| 651 |
+ |
|
| 652 |
+Do not trust stale docs, comments, or generated files over these commands: |
|
| 653 |
+ |
|
| 654 |
+```bash |
|
| 655 |
+ssh -G <alias> |
|
| 656 |
+tools/deploy-local.sh |
|
| 657 |
+ssh <alias> hostname |
|
| 658 |
+git diff |
|
| 139 | 659 |
``` |
@@ -1,126 +0,0 @@ |
||
| 1 |
-# SSH Key Migration: Legacy to Modern |
|
| 2 |
- |
|
| 3 |
-## Overview |
|
| 4 |
- |
|
| 5 |
-This document describes the process for migrating hosts from legacy SSH keys (`id_rsa_old`) to modern keys (`id_ed25519`). |
|
| 6 |
- |
|
| 7 |
-## Rationale |
|
| 8 |
- |
|
| 9 |
-- **Legacy key** (`id_rsa_old` from 2015): RSA 2048-bit, older algorithm support |
|
| 10 |
-- **Modern key** (`id_ed25519`): EdDSA 256-bit, better security, smaller key size, faster |
|
| 11 |
- |
|
| 12 |
-## Automatic Migration |
|
| 13 |
- |
|
| 14 |
-### Migrate all legacy hosts |
|
| 15 |
- |
|
| 16 |
-```bash |
|
| 17 |
-tools/migrate-modern-key.sh |
|
| 18 |
-``` |
|
| 19 |
- |
|
| 20 |
-This will: |
|
| 21 |
-1. Check all hosts in `inventory/hosts-local.yaml` legacy_infrastructure group |
|
| 22 |
-2. Test if modern key already works |
|
| 23 |
-3. Use legacy key to install modern key if needed |
|
| 24 |
-4. Verify modern key works after installation |
|
| 25 |
- |
|
| 26 |
-### Migrate specific host |
|
| 27 |
- |
|
| 28 |
-```bash |
|
| 29 |
-tools/migrate-modern-key.sh is-baobab |
|
| 30 |
-``` |
|
| 31 |
- |
|
| 32 |
-## Manual Migration (if script fails) |
|
| 33 |
- |
|
| 34 |
-For a specific host, e.g., `is-nasturel`: |
|
| 35 |
- |
|
| 36 |
-### 1. Interactive password auth |
|
| 37 |
- |
|
| 38 |
-If password is available: |
|
| 39 |
- |
|
| 40 |
-```bash |
|
| 41 |
-ssh -o PubkeyAuthentication=no sshd@192.168.2.144 \ |
|
| 42 |
- "mkdir -p ~/.ssh && \ |
|
| 43 |
- echo '$(cat ~/.ssh/id_ed25519.pub)' >> ~/.ssh/authorized_keys && \ |
|
| 44 |
- chmod 600 ~/.ssh/authorized_keys" |
|
| 45 |
-``` |
|
| 46 |
- |
|
| 47 |
-### 2. Through jump host (is-jumper) |
|
| 48 |
- |
|
| 49 |
-If accessible through is-jumper: |
|
| 50 |
- |
|
| 51 |
-```bash |
|
| 52 |
-ssh is-jumper \ |
|
| 53 |
- "ssh -o StrictHostKeyChecking=accept-new sshd@192.168.2.144 \ |
|
| 54 |
- 'mkdir -p ~/.ssh && echo \$(cat ~/.ssh/id_ed25519.pub) >> ~/.ssh/authorized_keys'" |
|
| 55 |
-``` |
|
| 56 |
- |
|
| 57 |
-### 3. Physical/console access |
|
| 58 |
- |
|
| 59 |
-If all else fails, use console/IPMI access to manually: |
|
| 60 |
-- Edit `/home/sshd/.ssh/authorized_keys` |
|
| 61 |
-- Add modern key: `ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...` |
|
| 62 |
- |
|
| 63 |
-## Adding New Hosts |
|
| 64 |
- |
|
| 65 |
-When adding new legacy hosts to the inventory: |
|
| 66 |
- |
|
| 67 |
-1. **In `inventory/hosts-local.yaml`**: add host to `legacy_infrastructure` group |
|
| 68 |
-2. **Run deployment**: `tools/deploy-local.sh` |
|
| 69 |
-3. **Run migration**: `tools/migrate-modern-key.sh <new-host>` |
|
| 70 |
-4. **Commit**: `git add . && git commit -m "Add/migrate <host>"` |
|
| 71 |
- |
|
| 72 |
-## Status Tracking |
|
| 73 |
- |
|
| 74 |
-Check which hosts have been migrated: |
|
| 75 |
- |
|
| 76 |
-```bash |
|
| 77 |
-# Modern key working |
|
| 78 |
-for h in $(grep -oE "is-[a-z0-9-]+" inventory/hosts-local.yaml | sort -u); do |
|
| 79 |
- timeout 1 ssh -o BatchMode=yes "$h" true 2>/dev/null && echo "$h: ✓" || echo "$h: ⚠" |
|
| 80 |
-done |
|
| 81 |
-``` |
|
| 82 |
- |
|
| 83 |
-## Key Files |
|
| 84 |
- |
|
| 85 |
-- **Modern (preferred)**: `~/.ssh/id_ed25519` |
|
| 86 |
-- **Legacy (deprecated)**: `~/.ssh/keys/id_rsa_old` |
|
| 87 |
- |
|
| 88 |
-Both should be kept until all hosts are migrated. |
|
| 89 |
- |
|
| 90 |
-## Removing Legacy Key |
|
| 91 |
- |
|
| 92 |
-Once all hosts migrated and verified: |
|
| 93 |
- |
|
| 94 |
-```bash |
|
| 95 |
-# Backup first |
|
| 96 |
-cp ~/.ssh/keys/id_rsa_old ~/.ssh/keys/id_rsa_old.backup |
|
| 97 |
- |
|
| 98 |
-# Remove from SSH config (if any explicit config) |
|
| 99 |
-grep -r "id_rsa_old" . && echo "Still in use" || echo "Safe to remove" |
|
| 100 |
- |
|
| 101 |
-# Safe to delete after backup |
|
| 102 |
-rm ~/.ssh/keys/id_rsa_old |
|
| 103 |
-``` |
|
| 104 |
- |
|
| 105 |
-## Troubleshooting |
|
| 106 |
- |
|
| 107 |
-### "Permission denied (publickey)" with modern key |
|
| 108 |
- |
|
| 109 |
-→ Host still using legacy auth only. Use `tools/migrate-modern-key.sh <host>` |
|
| 110 |
- |
|
| 111 |
-### Legacy key also fails to connect |
|
| 112 |
- |
|
| 113 |
-→ Host might be offline or firewall blocked. Check connectivity first: |
|
| 114 |
- |
|
| 115 |
-```bash |
|
| 116 |
-ping 192.168.2.91 # Replace with host IP |
|
| 117 |
-``` |
|
| 118 |
- |
|
| 119 |
-### SSH hangs or times out |
|
| 120 |
- |
|
| 121 |
-→ Reduce timeout, check firewall rules. Use `-o ConnectTimeout=2` flag. |
|
| 122 |
- |
|
| 123 |
-## References |
|
| 124 |
- |
|
| 125 |
-- [Ed25519 SSH keys - OpenSSH](https://man.openbsd.org/ssh-keygen) |
|
| 126 |
-- [Why Ed25519 is superior to RSA](https://ianix.com/pub/ed25519-deployment.html) |
|