@@ -0,0 +1,222 @@ |
||
| 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`). |
|
@@ -0,0 +1,27 @@ |
||
| 1 |
+# Ignore SSH agent state and dynamic socket directories |
|
| 2 |
+agent/ |
|
| 3 |
+ |
|
| 4 |
+# Ignore known hosts, authorized keys, and private keys |
|
| 5 |
+authorized_keys |
|
| 6 |
+known_hosts |
|
| 7 |
+known_hosts.old |
|
| 8 |
+keys/ |
|
| 9 |
+*.pem |
|
| 10 |
+*.key |
|
| 11 |
+*.ppk |
|
| 12 |
+*.der |
|
| 13 |
+*.csr |
|
| 14 |
+ |
|
| 15 |
+# Ignore local or temporary files |
|
| 16 |
+*.swp |
|
| 17 |
+*.tmp |
|
| 18 |
+.DS_Store |
|
| 19 |
+__pycache__/ |
|
| 20 |
+ |
|
| 21 |
+# Ignore generated deploy artifacts |
|
| 22 |
+generated/ |
|
| 23 |
+SSH_SETUP_SUMMARY.md |
|
| 24 |
+ |
|
| 25 |
+# Ignore local import/export work areas and ad-hoc overlay trees |
|
| 26 |
+import/ |
|
| 27 |
+conf.d/ |
|
@@ -0,0 +1,116 @@ |
||
| 1 |
+# SSH Infrastructure |
|
| 2 |
+ |
|
| 3 |
+Source-controlled SSH routing and wrapper configuration for the Next-Gen jump |
|
| 4 |
+infrastructure. |
|
| 5 |
+ |
|
| 6 |
+## Runtime vs project |
|
| 7 |
+ |
|
| 8 |
+- Project source: `~/Documents/Workspaces/Bogdan/ssh-infrastructure` |
|
| 9 |
+- OpenSSH runtime: `~/.ssh` |
|
| 10 |
+ |
|
| 11 |
+Keep secrets and machine-local state out of this repository: |
|
| 12 |
+ |
|
| 13 |
+- private keys: `~/.ssh/keys/` |
|
| 14 |
+- `authorized_keys` |
|
| 15 |
+- `known_hosts` |
|
| 16 |
+- socket or agent state |
|
| 17 |
+ |
|
| 18 |
+The runtime `~/.ssh/config` is a generated single-file OpenSSH config. The |
|
| 19 |
+project is the place to edit wrappers, documentation, inventory, and generator |
|
| 20 |
+code. |
|
| 21 |
+ |
|
| 22 |
+Deploy the local runtime copy with: |
|
| 23 |
+ |
|
| 24 |
+```bash |
|
| 25 |
+tools/deploy-local.sh |
|
| 26 |
+``` |
|
| 27 |
+ |
|
| 28 |
+The deploy script generates `generated/client.conf`, installs it as |
|
| 29 |
+`~/.ssh/config`, and installs the wrapper commands into `~/.local/bin`. |
|
| 30 |
+It does not touch keys, `authorized_keys`, or `known_hosts`. |
|
| 31 |
+ |
|
| 32 |
+## Version control |
|
| 33 |
+ |
|
| 34 |
+This directory is the git repository for source files only. Generated configs, |
|
| 35 |
+local state, keys, known hosts, and handoff notes stay out of version control. |
|
| 36 |
+ |
|
| 37 |
+Track source changes with: |
|
| 38 |
+ |
|
| 39 |
+```bash |
|
| 40 |
+git status |
|
| 41 |
+git add inventory schema scripts tools .doc README.md .gitignore |
|
| 42 |
+git commit |
|
| 43 |
+``` |
|
| 44 |
+ |
|
| 45 |
+## Current client layout |
|
| 46 |
+ |
|
| 47 |
+```text |
|
| 48 |
+~/.ssh/config |
|
| 49 |
+~/.local/bin/ssh |
|
| 50 |
+~/.local/bin/scp |
|
| 51 |
+~/.local/bin/sftp |
|
| 52 |
+``` |
|
| 53 |
+ |
|
| 54 |
+The wrapper sources stay versioned in `scripts/` inside the project; deploy |
|
| 55 |
+installs executable copies into `~/.local/bin` and removes the obsolete |
|
| 56 |
+`~/.ssh/scripts` runtime layout from older checkouts. |
|
| 57 |
+ |
|
| 58 |
+## Source of Truth |
|
| 59 |
+ |
|
| 60 |
+The structured source of truth starts in: |
|
| 61 |
+ |
|
| 62 |
+```text |
|
| 63 |
+inventory/hosts.yaml |
|
| 64 |
+schema/hosts.schema.json |
|
| 65 |
+tools/generate-configs.py |
|
| 66 |
+``` |
|
| 67 |
+ |
|
| 68 |
+The `generated/*.conf` files are deploy artifacts. They are ignored by git and |
|
| 69 |
+can be recreated at any time with `tools/deploy-local.sh`. |
|
| 70 |
+ |
|
| 71 |
+## Sync from upstream |
|
| 72 |
+ |
|
| 73 |
+Pull the upstream inventory, apply this machine's local `is-jumper` key override, |
|
| 74 |
+regenerate, and deploy if the inventory changed: |
|
| 75 |
+ |
|
| 76 |
+```bash |
|
| 77 |
+tools/sync-hosts-from-upstream.sh |
|
| 78 |
+``` |
|
| 79 |
+ |
|
| 80 |
+Defaults: |
|
| 81 |
+ |
|
| 82 |
+```text |
|
| 83 |
+UPSTREAM_SSH_TARGET=nextgen@192.168.2.103 |
|
| 84 |
+UPSTREAM_HOSTS_PATH=/home/nextgen/projects/ssh-infrastructure/inventory/hosts.yaml |
|
| 85 |
+LOCAL_IS_JUMPER_IDENTITY_FILE=~/.ssh/keys/is-jumper_ed25519 |
|
| 86 |
+DEPLOY_AFTER_SYNC=1 |
|
| 87 |
+``` |
|
| 88 |
+ |
|
| 89 |
+Useful overrides: |
|
| 90 |
+ |
|
| 91 |
+```bash |
|
| 92 |
+UPSTREAM_HOSTS_FILE=/tmp/hosts.yaml tools/sync-hosts-from-upstream.sh |
|
| 93 |
+DEPLOY_AFTER_SYNC=0 tools/sync-hosts-from-upstream.sh |
|
| 94 |
+FORCE_DEPLOY=1 tools/sync-hosts-from-upstream.sh |
|
| 95 |
+``` |
|
| 96 |
+ |
|
| 97 |
+Known defaults captured there: |
|
| 98 |
+ |
|
| 99 |
+- jump hosts default to user `bogdan.timofte`; |
|
| 100 |
+- jump hosts default to port `24`, with explicit overrides such as `25904`; |
|
| 101 |
+- final hosts default to user `bogdan` because most distributions do not like |
|
| 102 |
+ dotted local usernames; |
|
| 103 |
+- when imported configs disagree between `bogdan` and `root`, `bogdan` wins; |
|
| 104 |
+- final hosts default to port `22`, with explicit overrides where needed. |
|
| 105 |
+- Cisco/TACACS router entries use `auth: password_interactive`; generated SSH |
|
| 106 |
+ config marks them so the wrapper does not force `BatchMode=yes`. |
|
| 107 |
+- Imported IFS devices use the canonical hostname already known on the jump |
|
| 108 |
+ servers as client-side `HostName`, while IPs remain available as aliases for |
|
| 109 |
+ autocomplete. |
|
| 110 |
+- J1/J2 configs inherit the company-managed global compatibility block and |
|
| 111 |
+ selected `Match Host` user/port defaults from the jump servers instead of |
|
| 112 |
+ duplicating them in generated output. |
|
| 113 |
+- J1/J2 configs only emit host entries when there is an effective override to |
|
| 114 |
+ keep on the jump side; hosts that only use inherited defaults are omitted. |
|
| 115 |
+- J1/J2 generated configs are stripped down to functional SSH stanzas only, |
|
| 116 |
+ without generated comments or group annotations. |
|
@@ -0,0 +1,700 @@ |
||
| 1 |
+version: 1 |
|
| 2 |
+facts: |
|
| 3 |
+ jump_default_port: 24 |
|
| 4 |
+ jump_default_user: bogdan.timofte |
|
| 5 |
+ common_distribution_user: bogdan |
|
| 6 |
+ notes: |
|
| 7 |
+ - Most distributions do not like dots in local usernames, so most final-host installs |
|
| 8 |
+ use bogdan. |
|
| 9 |
+ - In bogdan/root import conflicts, bogdan wins. |
|
| 10 |
+ssh_options: |
|
| 11 |
+ legacy_compatibility: |
|
| 12 |
+ description: Company-managed jump global ssh_config compatibility options |
|
| 13 |
+ options: |
|
| 14 |
+ KexAlgorithms: +diffie-hellman-group1-sha1,diffie-hellman-group-exchange-sha1,diffie-hellman-group14-sha1 |
|
| 15 |
+ Ciphers: +aes128-cbc,3des-cbc,aes192-cbc,aes256-cbc |
|
| 16 |
+ HostKeyAlgorithms: +ssh-rsa |
|
| 17 |
+ PubkeyAcceptedAlgorithms: +ssh-rsa |
|
| 18 |
+ ForwardAgent: true |
|
| 19 |
+ ForwardX11: false |
|
| 20 |
+ PasswordAuthentication: true |
|
| 21 |
+ HostbasedAuthentication: false |
|
| 22 |
+ CheckHostIP: true |
|
| 23 |
+ StrictHostKeyChecking: ask |
|
| 24 |
+ Tunnel: false |
|
| 25 |
+ SendEnv: LANG LC_* GIT_* ANSIBLE_* |
|
| 26 |
+ HashKnownHosts: true |
|
| 27 |
+company_managed: |
|
| 28 |
+ jump_hosts: |
|
| 29 |
+ inherit_globals_on_targets: |
|
| 30 |
+ - j1 |
|
| 31 |
+ - j2 |
|
| 32 |
+ match_defaults: |
|
| 33 |
+ - patterns: |
|
| 34 |
+ - '*.dr0?' |
|
| 35 |
+ - '*.ar0?' |
|
| 36 |
+ - '*.cr01' |
|
| 37 |
+ - '*.br01' |
|
| 38 |
+ - '*.as??' |
|
| 39 |
+ - '*.cs0?' |
|
| 40 |
+ - '*.tv01' |
|
| 41 |
+ - '*.ds0?' |
|
| 42 |
+ - bucuresti.ines.dcm01 |
|
| 43 |
+ - bucuresti.nxdata.voip |
|
| 44 |
+ - bucuresti.dolce.tv01 |
|
| 45 |
+ - '*dasan*' |
|
| 46 |
+ user: bogdan.timofte |
|
| 47 |
+ port: 22 |
|
| 48 |
+ - patterns: |
|
| 49 |
+ - '*.olt' |
|
| 50 |
+ user: bogdan.timofte@next-gen.ro |
|
| 51 |
+ port: 22 |
|
| 52 |
+ - patterns: |
|
| 53 |
+ - '*.dhcp' |
|
| 54 |
+ - '*.shaper*' |
|
| 55 |
+ - '*.sentinel' |
|
| 56 |
+ - '*.scan' |
|
| 57 |
+ - redmine |
|
| 58 |
+ - speedtest |
|
| 59 |
+ - webdevel |
|
| 60 |
+ - scripting |
|
| 61 |
+ - zabbix |
|
| 62 |
+ - itpve-* |
|
| 63 |
+ - cacti |
|
| 64 |
+ - mx |
|
| 65 |
+ - bucuresti.radius-pppoe |
|
| 66 |
+ - flood-detector |
|
| 67 |
+ - tacacs2 |
|
| 68 |
+ - tacacs1 |
|
| 69 |
+ - ns2 |
|
| 70 |
+ - ns1 |
|
| 71 |
+ - backup1 |
|
| 72 |
+ - gitlab |
|
| 73 |
+ - nlg |
|
| 74 |
+ - nexus |
|
| 75 |
+ - dhcp-cmts |
|
| 76 |
+ - '*.radius-db' |
|
| 77 |
+ - jump1 |
|
| 78 |
+ - aggregator-buc |
|
| 79 |
+ - mappix |
|
| 80 |
+ - docker.* |
|
| 81 |
+ - cpanel |
|
| 82 |
+ - jump2 |
|
| 83 |
+ - nocpve-* |
|
| 84 |
+ - ocvpn |
|
| 85 |
+ user: bogdan.timofte |
|
| 86 |
+ port: 24 |
|
| 87 |
+defaults: |
|
| 88 |
+ jump: |
|
| 89 |
+ user: bogdan.timofte |
|
| 90 |
+ port: 24 |
|
| 91 |
+ final_host: |
|
| 92 |
+ user: bogdan |
|
| 93 |
+ port: 22 |
|
| 94 |
+ connect_timeout: 10 |
|
| 95 |
+ connection_attempts: 1 |
|
| 96 |
+entrypoints: |
|
| 97 |
+ is_jumper: |
|
| 98 |
+ aliases: |
|
| 99 |
+ - is-jumper |
|
| 100 |
+ hostname: 192.168.2.100 |
|
| 101 |
+ user: root |
|
| 102 |
+ identity_file: ~/.ssh/keys/is-jumper_ed25519 |
|
| 103 |
+ identities_only: true |
|
| 104 |
+jumps: |
|
| 105 |
+ j1: |
|
| 106 |
+ aliases: |
|
| 107 |
+ - j1 |
|
| 108 |
+ hostname: 10.253.51.50 |
|
| 109 |
+ port: 25904 |
|
| 110 |
+ role: primary_vpn |
|
| 111 |
+ j2: |
|
| 112 |
+ aliases: |
|
| 113 |
+ - j2 |
|
| 114 |
+ hostname: 10.253.51.52 |
|
| 115 |
+ port: 25904 |
|
| 116 |
+ role: failover_vpn |
|
| 117 |
+ j1_public: |
|
| 118 |
+ aliases: |
|
| 119 |
+ - j1 |
|
| 120 |
+ hostname: j1.next-gen.ro |
|
| 121 |
+ port: 25904 |
|
| 122 |
+ role: emergency_public |
|
| 123 |
+ j2_public: |
|
| 124 |
+ aliases: |
|
| 125 |
+ - j2 |
|
| 126 |
+ hostname: j2.next-gen.ro |
|
| 127 |
+ port: 25904 |
|
| 128 |
+ role: emergency_public |
|
| 129 |
+groups: |
|
| 130 |
+ voip_applications: |
|
| 131 |
+ description: PBX systems |
|
| 132 |
+ default_jump: j1 |
|
| 133 |
+ hosts: |
|
| 134 |
+ vo52: |
|
| 135 |
+ aliases: |
|
| 136 |
+ - vo52 |
|
| 137 |
+ - vo522 |
|
| 138 |
+ - vo52-new |
|
| 139 |
+ - 10.253.51.140 |
|
| 140 |
+ hostname: 10.253.51.140 |
|
| 141 |
+ user: root |
|
| 142 |
+ vo52_old: |
|
| 143 |
+ aliases: |
|
| 144 |
+ - vo52-old |
|
| 145 |
+ hostname: 193.16.148.152 |
|
| 146 |
+ user: root |
|
| 147 |
+ vo53: |
|
| 148 |
+ aliases: |
|
| 149 |
+ - vo53 |
|
| 150 |
+ - 193.16.148.153 |
|
| 151 |
+ hostname: 193.16.148.153 |
|
| 152 |
+ port: 60011 |
|
| 153 |
+ elastix: |
|
| 154 |
+ aliases: |
|
| 155 |
+ - elastix |
|
| 156 |
+ - 10.253.50.62 |
|
| 157 |
+ - 188.173.1.15 |
|
| 158 |
+ hostname: 10.253.50.62 |
|
| 159 |
+ user: root |
|
| 160 |
+ ss7: |
|
| 161 |
+ aliases: |
|
| 162 |
+ - ss7 |
|
| 163 |
+ hostname: 10.253.51.138 |
|
| 164 |
+ user: root |
|
| 165 |
+ voip_pbx_dispecerat: |
|
| 166 |
+ aliases: |
|
| 167 |
+ - voip-pbx-dispeceri |
|
| 168 |
+ - pbx-dispeceri |
|
| 169 |
+ - 10.253.51.134 |
|
| 170 |
+ hostname: 10.253.51.134 |
|
| 171 |
+ user: bogdan |
|
| 172 |
+ voip_pbx_bo: |
|
| 173 |
+ aliases: |
|
| 174 |
+ - voip-pbx-bo |
|
| 175 |
+ - pbx-bo |
|
| 176 |
+ - 10.253.51.135 |
|
| 177 |
+ hostname: 10.253.51.135 |
|
| 178 |
+ user: bogdan |
|
| 179 |
+ voip_network: |
|
| 180 |
+ description: VoIP network infrastructure |
|
| 181 |
+ default_jump: j1 |
|
| 182 |
+ hosts: |
|
| 183 |
+ sbc0: |
|
| 184 |
+ aliases: |
|
| 185 |
+ - sbc0 |
|
| 186 |
+ - 10.253.51.130 |
|
| 187 |
+ - 10.20.30.10 |
|
| 188 |
+ - 193.16.148.197 |
|
| 189 |
+ hostname: 10.253.51.130 |
|
| 190 |
+ sbc1: |
|
| 191 |
+ aliases: |
|
| 192 |
+ - sbc1 |
|
| 193 |
+ - 10.253.51.131 |
|
| 194 |
+ - 10.20.30.10 |
|
| 195 |
+ - 193.16.148.194 |
|
| 196 |
+ - 193.16.148.195 |
|
| 197 |
+ - 193.16.148.196 |
|
| 198 |
+ - 193.16.148.198 |
|
| 199 |
+ - 193.16.148.199 |
|
| 200 |
+ hostname: 10.253.51.131 |
|
| 201 |
+ sbc2: |
|
| 202 |
+ aliases: |
|
| 203 |
+ - sbc2 |
|
| 204 |
+ - 10.253.51.132 |
|
| 205 |
+ - 10.20.30.11 |
|
| 206 |
+ hostname: 10.253.51.132 |
|
| 207 |
+ voip_prov: |
|
| 208 |
+ aliases: |
|
| 209 |
+ - voip-prov |
|
| 210 |
+ - 10.253.51.139 |
|
| 211 |
+ hostname: 10.253.51.139 |
|
| 212 |
+ portabilitate: |
|
| 213 |
+ aliases: |
|
| 214 |
+ - portabilitate |
|
| 215 |
+ - bdc |
|
| 216 |
+ - 10.253.51.133 |
|
| 217 |
+ - 89.165.199.20 |
|
| 218 |
+ - 89.165.232.232 |
|
| 219 |
+ hostname: 10.253.51.133 |
|
| 220 |
+ porta: |
|
| 221 |
+ description: PortaOne MR30 legacy |
|
| 222 |
+ default_jump: j1 |
|
| 223 |
+ hosts: |
|
| 224 |
+ porta_sip: |
|
| 225 |
+ aliases: |
|
| 226 |
+ - porta-sip |
|
| 227 |
+ - p12-sip |
|
| 228 |
+ - p12 |
|
| 229 |
+ - p12.voip.ro |
|
| 230 |
+ - 193.16.148.4 |
|
| 231 |
+ hostname: 193.16.148.4 |
|
| 232 |
+ porta_web: |
|
| 233 |
+ aliases: |
|
| 234 |
+ - porta-web |
|
| 235 |
+ - porta-api |
|
| 236 |
+ - porta-slave |
|
| 237 |
+ - porta7 |
|
| 238 |
+ - telefonie.next-gen.ro |
|
| 239 |
+ - 193.16.148.7 |
|
| 240 |
+ hostname: 193.16.148.7 |
|
| 241 |
+ porta_db: |
|
| 242 |
+ aliases: |
|
| 243 |
+ - porta-db |
|
| 244 |
+ - porta-master |
|
| 245 |
+ - porta1 |
|
| 246 |
+ - 193.16.148.11 |
|
| 247 |
+ hostname: 193.16.148.11 |
|
| 248 |
+ porta_config: |
|
| 249 |
+ aliases: |
|
| 250 |
+ - porta-config |
|
| 251 |
+ - porta-configurator |
|
| 252 |
+ - 193.16.148.13 |
|
| 253 |
+ hostname: 193.16.148.13 |
|
| 254 |
+ pppoe: |
|
| 255 |
+ description: RADIUS and PPPOE systems |
|
| 256 |
+ default_jump: j1 |
|
| 257 |
+ defaults: |
|
| 258 |
+ user: bogdan.timofte |
|
| 259 |
+ port: 24 |
|
| 260 |
+ patterns: |
|
| 261 |
+ '*.radius-db': |
|
| 262 |
+ connect_timeout: 10 |
|
| 263 |
+ connection_attempts: 1 |
|
| 264 |
+ '*.radius-pppoe': |
|
| 265 |
+ connect_timeout: 10 |
|
| 266 |
+ connection_attempts: 1 |
|
| 267 |
+ hosts: |
|
| 268 |
+ radauti_radius_db: |
|
| 269 |
+ aliases: |
|
| 270 |
+ - radauti.radius-db |
|
| 271 |
+ - 94.53.112.30 |
|
| 272 |
+ - 10.132.96.121 |
|
| 273 |
+ hostname: radauti.radius-db |
|
| 274 |
+ pascani_radius_db: |
|
| 275 |
+ aliases: |
|
| 276 |
+ - pascani.radius-db |
|
| 277 |
+ - 46.214.144.7 |
|
| 278 |
+ - 10.132.0.121 |
|
| 279 |
+ hostname: pascani.radius-db |
|
| 280 |
+ falticeni_radius_db: |
|
| 281 |
+ aliases: |
|
| 282 |
+ - falticeni.radius-db |
|
| 283 |
+ - 46.214.136.7 |
|
| 284 |
+ - 10.132.64.121 |
|
| 285 |
+ hostname: falticeni.radius-db |
|
| 286 |
+ tg_frumos_radius_db: |
|
| 287 |
+ aliases: |
|
| 288 |
+ - tg_frumos.radius-db |
|
| 289 |
+ - 94.53.170.7 |
|
| 290 |
+ - 10.132.32.121 |
|
| 291 |
+ hostname: tg_frumos.radius-db |
|
| 292 |
+ buhusi_radius_db: |
|
| 293 |
+ aliases: |
|
| 294 |
+ - buhusi.radius-db |
|
| 295 |
+ - 46.214.240.7 |
|
| 296 |
+ - 10.132.128.121 |
|
| 297 |
+ hostname: buhusi.radius-db |
|
| 298 |
+ bucuresti_radius_pppoe: |
|
| 299 |
+ aliases: |
|
| 300 |
+ - bucuresti.radius-pppoe |
|
| 301 |
+ - 188.173.1.29 |
|
| 302 |
+ hostname: bucuresti.radius-pppoe |
|
| 303 |
+ legacy_public: |
|
| 304 |
+ description: Legacy public VoIP jump |
|
| 305 |
+ default_jump: j1 |
|
| 306 |
+ hosts: |
|
| 307 |
+ voce_pub: |
|
| 308 |
+ aliases: |
|
| 309 |
+ - voce-pub |
|
| 310 |
+ - voce-pub2 |
|
| 311 |
+ - 188.173.0.230 |
|
| 312 |
+ hostname: 188.173.0.230 |
|
| 313 |
+ user: bogdan |
|
| 314 |
+ port: 22 |
|
| 315 |
+ imported_jump_hosts: |
|
| 316 |
+ description: Hosts imported from J1/J2 user SSH configs |
|
| 317 |
+ default_jump: j1 |
|
| 318 |
+ defaults: |
|
| 319 |
+ user: bogdan.timofte |
|
| 320 |
+ port: 24 |
|
| 321 |
+ hosts: |
|
| 322 |
+ host_10_132_128_121: |
|
| 323 |
+ aliases: |
|
| 324 |
+ - 10.132.128.121 |
|
| 325 |
+ hostname: 10.132.128.121 |
|
| 326 |
+ host_188_173_0_163: |
|
| 327 |
+ aliases: |
|
| 328 |
+ - 188.173.0.163 |
|
| 329 |
+ hostname: 188.173.0.163 |
|
| 330 |
+ user: bogdan |
|
| 331 |
+ host_188_173_0_141: |
|
| 332 |
+ aliases: |
|
| 333 |
+ - 188.173.0.141 |
|
| 334 |
+ hostname: 188.173.0.141 |
|
| 335 |
+ user: bogdan |
|
| 336 |
+ noc: |
|
| 337 |
+ description: NOC hosts grouped by function |
|
| 338 |
+ default_jump: j1 |
|
| 339 |
+ defaults: |
|
| 340 |
+ user: bogdan.timofte |
|
| 341 |
+ port: 24 |
|
| 342 |
+ pve: |
|
| 343 |
+ description: Proxmox hosts |
|
| 344 |
+ default_jump: j1 |
|
| 345 |
+ hosts: |
|
| 346 |
+ nocpve_nxdata1: |
|
| 347 |
+ aliases: |
|
| 348 |
+ - nocpve-nxdata1 |
|
| 349 |
+ - 188.173.1.112 |
|
| 350 |
+ - 10.253.51.24 |
|
| 351 |
+ hostname: 10.253.51.24 |
|
| 352 |
+ user: root |
|
| 353 |
+ nocpve_nxdata2: |
|
| 354 |
+ aliases: |
|
| 355 |
+ - nocpve-nxdata2 |
|
| 356 |
+ - 188.173.1.116 |
|
| 357 |
+ - 10.253.51.25 |
|
| 358 |
+ hostname: 10.253.51.25 |
|
| 359 |
+ user: root |
|
| 360 |
+ nocpve_ines1: |
|
| 361 |
+ aliases: |
|
| 362 |
+ - nocpve-ines1 |
|
| 363 |
+ - 188.173.1.117 |
|
| 364 |
+ - 10.253.51.27 |
|
| 365 |
+ hostname: 10.253.51.27 |
|
| 366 |
+ user: root |
|
| 367 |
+ nocpve_ines2: |
|
| 368 |
+ aliases: |
|
| 369 |
+ - nocpve-ines2 |
|
| 370 |
+ - 188.173.1.118 |
|
| 371 |
+ - 10.253.51.28 |
|
| 372 |
+ hostname: 10.253.51.28 |
|
| 373 |
+ user: root |
|
| 374 |
+ itpve_ines1: |
|
| 375 |
+ aliases: |
|
| 376 |
+ - itpve-ines1 |
|
| 377 |
+ - 188.173.0.211 |
|
| 378 |
+ - 10.253.51.211 |
|
| 379 |
+ hostname: 10.253.51.211 |
|
| 380 |
+ user: root |
|
| 381 |
+ itpve_ines2: |
|
| 382 |
+ aliases: |
|
| 383 |
+ - itpve-ines2 |
|
| 384 |
+ - 188.173.0.212 |
|
| 385 |
+ - 10.253.51.212 |
|
| 386 |
+ hostname: 10.253.51.212 |
|
| 387 |
+ user: root |
|
| 388 |
+ itpve_ines3: |
|
| 389 |
+ aliases: |
|
| 390 |
+ - itpve-ines3 |
|
| 391 |
+ - 188.173.0.213 |
|
| 392 |
+ - 10.253.51.213 |
|
| 393 |
+ hostname: 10.253.51.213 |
|
| 394 |
+ user: root |
|
| 395 |
+ itpve_ines4: |
|
| 396 |
+ aliases: |
|
| 397 |
+ - itpve-ines4 |
|
| 398 |
+ - 188.173.0.222 |
|
| 399 |
+ - 10.253.51.222 |
|
| 400 |
+ hostname: 10.253.51.222 |
|
| 401 |
+ user: root |
|
| 402 |
+ itpve_bns1: |
|
| 403 |
+ aliases: |
|
| 404 |
+ - itpve-bns1 |
|
| 405 |
+ - 188.173.0.201 |
|
| 406 |
+ - 10.253.51.201 |
|
| 407 |
+ hostname: 10.253.51.201 |
|
| 408 |
+ user: root |
|
| 409 |
+ itpve_bns2: |
|
| 410 |
+ aliases: |
|
| 411 |
+ - itpve-bns2 |
|
| 412 |
+ - 188.173.0.202 |
|
| 413 |
+ - 10.253.51.202 |
|
| 414 |
+ hostname: 10.253.51.202 |
|
| 415 |
+ user: root |
|
| 416 |
+ itpve_bns3: |
|
| 417 |
+ aliases: |
|
| 418 |
+ - itpve-bns3 |
|
| 419 |
+ - 188.173.0.203 |
|
| 420 |
+ - 10.253.51.203 |
|
| 421 |
+ hostname: 10.253.51.203 |
|
| 422 |
+ user: root |
|
| 423 |
+ itpve_bns4: |
|
| 424 |
+ aliases: |
|
| 425 |
+ - itpve-bns4 |
|
| 426 |
+ - 188.173.0.220 |
|
| 427 |
+ - 10.253.51.204 |
|
| 428 |
+ hostname: 10.253.51.204 |
|
| 429 |
+ user: root |
|
| 430 |
+ backup: |
|
| 431 |
+ description: Backup hosts |
|
| 432 |
+ default_jump: j1 |
|
| 433 |
+ hosts: |
|
| 434 |
+ backup_bns_01: |
|
| 435 |
+ aliases: |
|
| 436 |
+ - backup-bns-01 |
|
| 437 |
+ - 188.173.1.83 |
|
| 438 |
+ hostname: 188.173.1.83 |
|
| 439 |
+ user: root |
|
| 440 |
+ huawei_olts: |
|
| 441 |
+ description: Huawei OLT access equipment with interactive password auth |
|
| 442 |
+ default_jump: j1 |
|
| 443 |
+ defaults: |
|
| 444 |
+ user: bogdan.timofte@next-gen.ro |
|
| 445 |
+ port: 22 |
|
| 446 |
+ auth: password_interactive |
|
| 447 |
+ hosts: |
|
| 448 |
+ pascani_olt: |
|
| 449 |
+ aliases: |
|
| 450 |
+ - pascani.olt |
|
| 451 |
+ hostname: pascani.olt |
|
| 452 |
+ radauti_olt: |
|
| 453 |
+ aliases: |
|
| 454 |
+ - radauti.olt |
|
| 455 |
+ - 10.132.96.50 |
|
| 456 |
+ hostname: radauti.olt |
|
| 457 |
+ cisco_routers: |
|
| 458 |
+ description: Cisco and similar managed devices with interactive password auth |
|
| 459 |
+ default_jump: j1 |
|
| 460 |
+ defaults: |
|
| 461 |
+ user: bogdan.timofte |
|
| 462 |
+ port: 22 |
|
| 463 |
+ auth: password_interactive |
|
| 464 |
+ hosts: |
|
| 465 |
+ pascani_headend_cr01: |
|
| 466 |
+ aliases: |
|
| 467 |
+ - pascani.headend.cr01 |
|
| 468 |
+ - 10.132.0.97 |
|
| 469 |
+ hostname: pascani.headend.cr01 |
|
| 470 |
+ buhusi_headend_as01: |
|
| 471 |
+ aliases: |
|
| 472 |
+ - buhusi.headend.as01 |
|
| 473 |
+ - 10.132.128.11 |
|
| 474 |
+ hostname: buhusi.headend.as01 |
|
| 475 |
+ buhusi_headend_as02: |
|
| 476 |
+ aliases: |
|
| 477 |
+ - buhusi.headend.as02 |
|
| 478 |
+ - 10.132.128.12 |
|
| 479 |
+ hostname: buhusi.headend.as02 |
|
| 480 |
+ buhusi_headend_as03: |
|
| 481 |
+ aliases: |
|
| 482 |
+ - buhusi.headend.as03 |
|
| 483 |
+ - 10.132.128.13 |
|
| 484 |
+ hostname: buhusi.headend.as03 |
|
| 485 |
+ buhusi_headend_as04: |
|
| 486 |
+ aliases: |
|
| 487 |
+ - buhusi.headend.as04 |
|
| 488 |
+ - 10.132.128.14 |
|
| 489 |
+ hostname: buhusi.headend.as04 |
|
| 490 |
+ buhusi_headend_as05: |
|
| 491 |
+ aliases: |
|
| 492 |
+ - buhusi.headend.as05 |
|
| 493 |
+ - 10.132.128.15 |
|
| 494 |
+ hostname: buhusi.headend.as05 |
|
| 495 |
+ buhusi_headend_dr01: |
|
| 496 |
+ aliases: |
|
| 497 |
+ - buhusi.headend.dr01 |
|
| 498 |
+ - 10.132.128.1 |
|
| 499 |
+ hostname: buhusi.headend.dr01 |
|
| 500 |
+ buhusi_headend_ds02: |
|
| 501 |
+ aliases: |
|
| 502 |
+ - buhusi.headend.ds02 |
|
| 503 |
+ - 10.132.128.5 |
|
| 504 |
+ hostname: buhusi.headend.ds02 |
|
| 505 |
+ falticeni_headend_dr01: |
|
| 506 |
+ aliases: |
|
| 507 |
+ - falticeni.headend.dr01 |
|
| 508 |
+ - 10.132.64.1 |
|
| 509 |
+ hostname: falticeni.headend.dr01 |
|
| 510 |
+ falticeni_headend_ds02: |
|
| 511 |
+ aliases: |
|
| 512 |
+ - falticeni.headend.ds02 |
|
| 513 |
+ - 10.132.64.5 |
|
| 514 |
+ hostname: falticeni.headend.ds02 |
|
| 515 |
+ falticeni_headend_ds04: |
|
| 516 |
+ aliases: |
|
| 517 |
+ - falticeni.headend.ds04 |
|
| 518 |
+ - 10.132.64.7 |
|
| 519 |
+ hostname: falticeni.headend.ds04 |
|
| 520 |
+ pascani_headend_as01: |
|
| 521 |
+ aliases: |
|
| 522 |
+ - pascani.headend.as01 |
|
| 523 |
+ - 10.132.0.5 |
|
| 524 |
+ hostname: pascani.headend.as01 |
|
| 525 |
+ pascani_headend_dr01: |
|
| 526 |
+ aliases: |
|
| 527 |
+ - pascani.headend.dr01 |
|
| 528 |
+ - 10.132.0.1 |
|
| 529 |
+ hostname: pascani.headend.dr01 |
|
| 530 |
+ pascani_headend_dr02: |
|
| 531 |
+ aliases: |
|
| 532 |
+ - pascani.headend.dr02 |
|
| 533 |
+ - 10.132.0.100 |
|
| 534 |
+ hostname: pascani.headend.dr02 |
|
| 535 |
+ pascani_headend_dr03: |
|
| 536 |
+ aliases: |
|
| 537 |
+ - pascani.headend.dr03 |
|
| 538 |
+ - 10.132.0.99 |
|
| 539 |
+ hostname: pascani.headend.dr03 |
|
| 540 |
+ pascani_headend_ds01: |
|
| 541 |
+ aliases: |
|
| 542 |
+ - pascani.headend.ds01 |
|
| 543 |
+ - 10.132.0.3 |
|
| 544 |
+ hostname: pascani.headend.ds01 |
|
| 545 |
+ pascani_headend_tv01: |
|
| 546 |
+ aliases: |
|
| 547 |
+ - pascani.headend.tv01 |
|
| 548 |
+ - 10.132.0.101 |
|
| 549 |
+ hostname: pascani.headend.tv01 |
|
| 550 |
+ radauti_headend_as01: |
|
| 551 |
+ aliases: |
|
| 552 |
+ - radauti.headend.as01 |
|
| 553 |
+ - 10.132.96.11 |
|
| 554 |
+ hostname: radauti.headend.as01 |
|
| 555 |
+ radauti_headend_dr01: |
|
| 556 |
+ aliases: |
|
| 557 |
+ - radauti.headend.dr01 |
|
| 558 |
+ - 172.30.255.101 |
|
| 559 |
+ hostname: radauti.headend.dr01 |
|
| 560 |
+ tg_frumos_headend_as01: |
|
| 561 |
+ aliases: |
|
| 562 |
+ - tg_frumos.headend.as01 |
|
| 563 |
+ - 10.132.32.11 |
|
| 564 |
+ hostname: tg_frumos.headend.as01 |
|
| 565 |
+ tg_frumos_headend_dr01: |
|
| 566 |
+ aliases: |
|
| 567 |
+ - tg_frumos.headend.dr01 |
|
| 568 |
+ - 10.132.32.1 |
|
| 569 |
+ hostname: tg_frumos.headend.dr01 |
|
| 570 |
+ tg_frumos_headend_ds01: |
|
| 571 |
+ aliases: |
|
| 572 |
+ - tg_frumos.headend.ds01 |
|
| 573 |
+ - 10.132.32.3 |
|
| 574 |
+ hostname: tg_frumos.headend.ds01 |
|
| 575 |
+ network_switches: |
|
| 576 |
+ description: DCN switches with interactive password auth |
|
| 577 |
+ default_jump: j1 |
|
| 578 |
+ defaults: |
|
| 579 |
+ user: bogdan.timofte |
|
| 580 |
+ port: 22 |
|
| 581 |
+ auth: password_interactive |
|
| 582 |
+ hosts: |
|
| 583 |
+ buhusi_psw_010: |
|
| 584 |
+ aliases: |
|
| 585 |
+ - buhusi-psw-010 |
|
| 586 |
+ - 10.132.128.20 |
|
| 587 |
+ hostname: buhusi-psw-010 |
|
| 588 |
+ buhusi_psw_011: |
|
| 589 |
+ aliases: |
|
| 590 |
+ - buhusi-psw-011 |
|
| 591 |
+ - 10.132.128.21 |
|
| 592 |
+ hostname: buhusi-psw-011 |
|
| 593 |
+ buhusi_psw_012: |
|
| 594 |
+ aliases: |
|
| 595 |
+ - buhusi-psw-012 |
|
| 596 |
+ - 10.132.128.22 |
|
| 597 |
+ hostname: buhusi-psw-012 |
|
| 598 |
+ buhusi_psw_013: |
|
| 599 |
+ aliases: |
|
| 600 |
+ - buhusi-psw-013 |
|
| 601 |
+ - 10.132.128.23 |
|
| 602 |
+ hostname: buhusi-psw-013 |
|
| 603 |
+ buhusi_psw_014: |
|
| 604 |
+ aliases: |
|
| 605 |
+ - buhusi-psw-014 |
|
| 606 |
+ - 10.132.128.24 |
|
| 607 |
+ hostname: buhusi-psw-014 |
|
| 608 |
+ buhusi_silistea_psw_001: |
|
| 609 |
+ aliases: |
|
| 610 |
+ - buhusi.silistea.psw-001 |
|
| 611 |
+ - 10.132.128.50 |
|
| 612 |
+ hostname: buhusi.silistea.psw-001 |
|
| 613 |
+ falticeni_psw_110: |
|
| 614 |
+ aliases: |
|
| 615 |
+ - falticeni-psw-110 |
|
| 616 |
+ - 10.132.64.20 |
|
| 617 |
+ hostname: falticeni-psw-110 |
|
| 618 |
+ radauti_headend_ag001: |
|
| 619 |
+ aliases: |
|
| 620 |
+ - radauti.headend.ag001 |
|
| 621 |
+ - 10.132.96.12 |
|
| 622 |
+ hostname: radauti.headend.ag001 |
|
| 623 |
+ mikrotik_routers: |
|
| 624 |
+ description: MikroTik CRS/CCR equipment with interactive password auth |
|
| 625 |
+ default_jump: j1 |
|
| 626 |
+ defaults: |
|
| 627 |
+ user: admin |
|
| 628 |
+ port: 24 |
|
| 629 |
+ auth: password_interactive |
|
| 630 |
+ hosts: |
|
| 631 |
+ buhusi_mikrotik_dr01: |
|
| 632 |
+ aliases: |
|
| 633 |
+ - buhusi.mikrotik.dr01 |
|
| 634 |
+ - 10.132.128.110 |
|
| 635 |
+ hostname: buhusi.mikrotik.dr01 |
|
| 636 |
+ buhusi_mikrotik_ds01: |
|
| 637 |
+ aliases: |
|
| 638 |
+ - buhusi.mikrotik.ds01 |
|
| 639 |
+ - 10.132.128.100 |
|
| 640 |
+ hostname: buhusi.mikrotik.ds01 |
|
| 641 |
+ buhusi_mikrotik_pppoe01: |
|
| 642 |
+ aliases: |
|
| 643 |
+ - buhusi.mikrotik.pppoe01 |
|
| 644 |
+ - 10.132.128.111 |
|
| 645 |
+ hostname: buhusi.mikrotik.pppoe01 |
|
| 646 |
+ buhusi_mikrotik_pppoe02: |
|
| 647 |
+ aliases: |
|
| 648 |
+ - buhusi.mikrotik.pppoe02 |
|
| 649 |
+ - 10.132.128.112 |
|
| 650 |
+ hostname: buhusi.mikrotik.pppoe02 |
|
| 651 |
+ falticeni_mikrotik_dr01: |
|
| 652 |
+ aliases: |
|
| 653 |
+ - falticeni.mikrotik.dr01 |
|
| 654 |
+ - 10.132.64.110 |
|
| 655 |
+ hostname: falticeni.mikrotik.dr01 |
|
| 656 |
+ falticeni_mikrotik_ds01: |
|
| 657 |
+ aliases: |
|
| 658 |
+ - falticeni.mikrotik.ds01 |
|
| 659 |
+ - 10.132.64.100 |
|
| 660 |
+ hostname: falticeni.mikrotik.ds01 |
|
| 661 |
+ falticeni_mikrotik_pppoe1: |
|
| 662 |
+ aliases: |
|
| 663 |
+ - falticeni.mikrotik.pppoe1 |
|
| 664 |
+ - 10.132.64.111 |
|
| 665 |
+ hostname: falticeni.mikrotik.pppoe1 |
|
| 666 |
+ falticeni_mikrotik_pppoe2: |
|
| 667 |
+ aliases: |
|
| 668 |
+ - falticeni.mikrotik.pppoe2 |
|
| 669 |
+ - 10.132.64.112 |
|
| 670 |
+ hostname: falticeni.mikrotik.pppoe2 |
|
| 671 |
+ pascani_mikrotik_pppoe1: |
|
| 672 |
+ aliases: |
|
| 673 |
+ - pascani.mikrotik.pppoe1 |
|
| 674 |
+ - 10.132.0.111 |
|
| 675 |
+ hostname: pascani.mikrotik.pppoe1 |
|
| 676 |
+ pascani_mikrotik_pppoe2: |
|
| 677 |
+ aliases: |
|
| 678 |
+ - pascani.mikrotik.pppoe2 |
|
| 679 |
+ - 10.132.0.112 |
|
| 680 |
+ hostname: pascani.mikrotik.pppoe2 |
|
| 681 |
+ radauti_mikrotik_pppoe1: |
|
| 682 |
+ aliases: |
|
| 683 |
+ - radauti.mikrotik.pppoe1 |
|
| 684 |
+ - 10.132.96.111 |
|
| 685 |
+ hostname: radauti.mikrotik.pppoe1 |
|
| 686 |
+ radauti_mikrotik_pppoe2: |
|
| 687 |
+ aliases: |
|
| 688 |
+ - radauti.mikrotik.pppoe2 |
|
| 689 |
+ - 10.132.96.112 |
|
| 690 |
+ hostname: radauti.mikrotik.pppoe2 |
|
| 691 |
+ tg_frumos_mikrotik_dr01: |
|
| 692 |
+ aliases: |
|
| 693 |
+ - tg_frumos.mikrotik.dr01 |
|
| 694 |
+ - 94.53.170.1 |
|
| 695 |
+ hostname: tg_frumos.mikrotik.dr01 |
|
| 696 |
+ tg_frumos_mikrotik_pppoe1: |
|
| 697 |
+ aliases: |
|
| 698 |
+ - tg_frumos.mikrotik.pppoe1 |
|
| 699 |
+ - 10.132.32.111 |
|
| 700 |
+ hostname: tg_frumos.mikrotik.pppoe1 |
|
@@ -0,0 +1,159 @@ |
||
| 1 |
+{
|
|
| 2 |
+ "$schema": "https://json-schema.org/draft/2020-12/schema", |
|
| 3 |
+ "$id": "https://next-gen.local/ssh-infrastructure/hosts.schema.json", |
|
| 4 |
+ "title": "SSH infrastructure inventory", |
|
| 5 |
+ "type": "object", |
|
| 6 |
+ "required": ["version", "defaults", "entrypoints", "jumps", "groups"], |
|
| 7 |
+ "additionalProperties": false, |
|
| 8 |
+ "properties": {
|
|
| 9 |
+ "version": { "const": 1 },
|
|
| 10 |
+ "facts": { "type": "object" },
|
|
| 11 |
+ "ssh_options": {
|
|
| 12 |
+ "type": "object", |
|
| 13 |
+ "additionalProperties": {
|
|
| 14 |
+ "type": "object", |
|
| 15 |
+ "required": ["options"], |
|
| 16 |
+ "additionalProperties": false, |
|
| 17 |
+ "properties": {
|
|
| 18 |
+ "description": { "type": "string" },
|
|
| 19 |
+ "options": {
|
|
| 20 |
+ "type": "object", |
|
| 21 |
+ "additionalProperties": {
|
|
| 22 |
+ "anyOf": [ |
|
| 23 |
+ { "type": "string" },
|
|
| 24 |
+ { "type": "integer" },
|
|
| 25 |
+ { "type": "boolean" }
|
|
| 26 |
+ ] |
|
| 27 |
+ } |
|
| 28 |
+ } |
|
| 29 |
+ } |
|
| 30 |
+ } |
|
| 31 |
+ }, |
|
| 32 |
+ "company_managed": {
|
|
| 33 |
+ "type": "object", |
|
| 34 |
+ "additionalProperties": false, |
|
| 35 |
+ "properties": {
|
|
| 36 |
+ "jump_hosts": {
|
|
| 37 |
+ "type": "object", |
|
| 38 |
+ "additionalProperties": false, |
|
| 39 |
+ "properties": {
|
|
| 40 |
+ "inherit_globals_on_targets": {
|
|
| 41 |
+ "type": "array", |
|
| 42 |
+ "items": { "type": "string" }
|
|
| 43 |
+ }, |
|
| 44 |
+ "match_defaults": {
|
|
| 45 |
+ "type": "array", |
|
| 46 |
+ "items": { "$ref": "#/$defs/companyManagedMatch" }
|
|
| 47 |
+ } |
|
| 48 |
+ } |
|
| 49 |
+ } |
|
| 50 |
+ } |
|
| 51 |
+ }, |
|
| 52 |
+ "defaults": {
|
|
| 53 |
+ "type": "object", |
|
| 54 |
+ "required": ["jump", "final_host"], |
|
| 55 |
+ "additionalProperties": false, |
|
| 56 |
+ "properties": {
|
|
| 57 |
+ "jump": { "$ref": "#/$defs/defaults" },
|
|
| 58 |
+ "final_host": { "$ref": "#/$defs/defaults" }
|
|
| 59 |
+ } |
|
| 60 |
+ }, |
|
| 61 |
+ "entrypoints": {
|
|
| 62 |
+ "type": "object", |
|
| 63 |
+ "additionalProperties": { "$ref": "#/$defs/host" }
|
|
| 64 |
+ }, |
|
| 65 |
+ "jumps": {
|
|
| 66 |
+ "type": "object", |
|
| 67 |
+ "additionalProperties": {
|
|
| 68 |
+ "allOf": [ |
|
| 69 |
+ { "$ref": "#/$defs/host" },
|
|
| 70 |
+ {
|
|
| 71 |
+ "type": "object", |
|
| 72 |
+ "properties": {
|
|
| 73 |
+ "role": { "type": "string" }
|
|
| 74 |
+ } |
|
| 75 |
+ } |
|
| 76 |
+ ] |
|
| 77 |
+ } |
|
| 78 |
+ }, |
|
| 79 |
+ "groups": {
|
|
| 80 |
+ "type": "object", |
|
| 81 |
+ "additionalProperties": {
|
|
| 82 |
+ "type": "object", |
|
| 83 |
+ "required": ["hosts"], |
|
| 84 |
+ "additionalProperties": false, |
|
| 85 |
+ "properties": {
|
|
| 86 |
+ "description": { "type": "string" },
|
|
| 87 |
+ "default_jump": { "type": "string" },
|
|
| 88 |
+ "defaults": { "$ref": "#/$defs/defaults" },
|
|
| 89 |
+ "patterns": {
|
|
| 90 |
+ "type": "object", |
|
| 91 |
+ "additionalProperties": { "$ref": "#/$defs/options" }
|
|
| 92 |
+ }, |
|
| 93 |
+ "hosts": {
|
|
| 94 |
+ "type": "object", |
|
| 95 |
+ "additionalProperties": { "$ref": "#/$defs/host" }
|
|
| 96 |
+ } |
|
| 97 |
+ } |
|
| 98 |
+ } |
|
| 99 |
+ } |
|
| 100 |
+ }, |
|
| 101 |
+ "$defs": {
|
|
| 102 |
+ "defaults": {
|
|
| 103 |
+ "type": "object", |
|
| 104 |
+ "additionalProperties": false, |
|
| 105 |
+ "properties": {
|
|
| 106 |
+ "user": { "type": "string" },
|
|
| 107 |
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
|
| 108 |
+ "connect_timeout": { "type": "integer", "minimum": 1 },
|
|
| 109 |
+ "connection_attempts": { "type": "integer", "minimum": 1 },
|
|
| 110 |
+ "auth": { "$ref": "#/$defs/auth" }
|
|
| 111 |
+ } |
|
| 112 |
+ }, |
|
| 113 |
+ "options": {
|
|
| 114 |
+ "type": "object", |
|
| 115 |
+ "additionalProperties": false, |
|
| 116 |
+ "properties": {
|
|
| 117 |
+ "connect_timeout": { "type": "integer", "minimum": 1 },
|
|
| 118 |
+ "connection_attempts": { "type": "integer", "minimum": 1 }
|
|
| 119 |
+ } |
|
| 120 |
+ }, |
|
| 121 |
+ "companyManagedMatch": {
|
|
| 122 |
+ "type": "object", |
|
| 123 |
+ "required": ["patterns"], |
|
| 124 |
+ "additionalProperties": false, |
|
| 125 |
+ "properties": {
|
|
| 126 |
+ "patterns": {
|
|
| 127 |
+ "type": "array", |
|
| 128 |
+ "minItems": 1, |
|
| 129 |
+ "items": { "type": "string" }
|
|
| 130 |
+ }, |
|
| 131 |
+ "user": { "type": "string" },
|
|
| 132 |
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 }
|
|
| 133 |
+ } |
|
| 134 |
+ }, |
|
| 135 |
+ "host": {
|
|
| 136 |
+ "type": "object", |
|
| 137 |
+ "required": ["aliases", "hostname"], |
|
| 138 |
+ "additionalProperties": false, |
|
| 139 |
+ "properties": {
|
|
| 140 |
+ "aliases": {
|
|
| 141 |
+ "type": "array", |
|
| 142 |
+ "minItems": 1, |
|
| 143 |
+ "items": { "type": "string" }
|
|
| 144 |
+ }, |
|
| 145 |
+ "hostname": { "type": "string" },
|
|
| 146 |
+ "user": { "type": "string" },
|
|
| 147 |
+ "port": { "type": "integer", "minimum": 1, "maximum": 65535 },
|
|
| 148 |
+ "identity_file": { "type": "string" },
|
|
| 149 |
+ "identities_only": { "type": "boolean" },
|
|
| 150 |
+ "role": { "type": "string" },
|
|
| 151 |
+ "auth": { "$ref": "#/$defs/auth" }
|
|
| 152 |
+ } |
|
| 153 |
+ }, |
|
| 154 |
+ "auth": {
|
|
| 155 |
+ "type": "string", |
|
| 156 |
+ "enum": ["key", "password_interactive"] |
|
| 157 |
+ } |
|
| 158 |
+ } |
|
| 159 |
+} |
|
@@ -0,0 +1,2 @@ |
||
| 1 |
+#!/usr/bin/env bash |
|
| 2 |
+exec /usr/bin/scp -S ~/.local/bin/ssh "$@" |
|
@@ -0,0 +1,2 @@ |
||
| 1 |
+#!/usr/bin/env bash |
|
| 2 |
+exec /usr/bin/sftp -S ~/.local/bin/ssh "$@" |
|
@@ -0,0 +1,288 @@ |
||
| 1 |
+#!/usr/bin/env bash |
|
| 2 |
+set -euo pipefail |
|
| 3 |
+ |
|
| 4 |
+real_ssh="/usr/bin/ssh" |
|
| 5 |
+ssh_config="$HOME/.ssh/config" |
|
| 6 |
+remote_agent="/run/user/0/gnupg/S.gpg-agent.ssh" |
|
| 7 |
+ |
|
| 8 |
+# Access model, May 2026 |
|
| 9 |
+# ---------------------- |
|
| 10 |
+# |
|
| 11 |
+# Cheia fizica este montata pe is-jumper. Wrapper-ul local nu mai face bridge |
|
| 12 |
+# de agent in /tmp; in schimb ruleaza clientul SSH activ pe is-jumper: |
|
| 13 |
+# |
|
| 14 |
+# local ssh wrapper |
|
| 15 |
+# -> is-jumper (192.168.2.100, acces local cu id_ed25519) |
|
| 16 |
+# -> J1/J2/j1/j2 (autentificare cu agentul fizic de pe is-jumper) |
|
| 17 |
+# -> ssh final-host |
|
| 18 |
+# |
|
| 19 |
+# Custom jump flags (stripped by wrapper, not passed to real ssh): |
|
| 20 |
+# -J1 use J1 via VPN (default for configured hosts) |
|
| 21 |
+# -J2 use J2 via VPN |
|
| 22 |
+# -j1 use j1 via public DNS (urgente, fara ruta VPN) |
|
| 23 |
+# -j2 use j2 via public DNS (urgente, fara ruta VPN) |
|
| 24 |
+# |
|
| 25 |
+# Hosturi arbitrare (fara config SSH): wrapper-ul le ruteza prin jump doar daca |
|
| 26 |
+# unul dintre flagurile de mai sus este prezent explicit. |
|
| 27 |
+ |
|
| 28 |
+target_user="" |
|
| 29 |
+target_host="" |
|
| 30 |
+target_port="" |
|
| 31 |
+target_auth="" |
|
| 32 |
+found_target=0 |
|
| 33 |
+cmd_args=() |
|
| 34 |
+ssh_config_args=() |
|
| 35 |
+jump_alias="j1" |
|
| 36 |
+custom_jump_set=0 |
|
| 37 |
+host_configured=1 |
|
| 38 |
+target_is_jump=0 |
|
| 39 |
+want_subsystem=0 |
|
| 40 |
+user_option="" |
|
| 41 |
+port_option="" |
|
| 42 |
+ |
|
| 43 |
+has_explicit_config() {
|
|
| 44 |
+ local arg |
|
| 45 |
+ |
|
| 46 |
+ for arg in "$@"; do |
|
| 47 |
+ case "$arg" in |
|
| 48 |
+ -F|-F*) |
|
| 49 |
+ return 0 |
|
| 50 |
+ ;; |
|
| 51 |
+ esac |
|
| 52 |
+ done |
|
| 53 |
+ |
|
| 54 |
+ return 1 |
|
| 55 |
+} |
|
| 56 |
+ |
|
| 57 |
+quote_cmd() {
|
|
| 58 |
+ local out="" q part |
|
| 59 |
+ |
|
| 60 |
+ for part in "$@"; do |
|
| 61 |
+ printf -v q "%q" "$part" |
|
| 62 |
+ out+="$q " |
|
| 63 |
+ done |
|
| 64 |
+ |
|
| 65 |
+ printf "%s" "$out" |
|
| 66 |
+} |
|
| 67 |
+ |
|
| 68 |
+resolve_ssh_config() {
|
|
| 69 |
+ local target=$1 |
|
| 70 |
+ local line |
|
| 71 |
+ |
|
| 72 |
+ target_user="" |
|
| 73 |
+ target_host="" |
|
| 74 |
+ target_port="" |
|
| 75 |
+ target_auth="" |
|
| 76 |
+ |
|
| 77 |
+ while IFS= read -r line; do |
|
| 78 |
+ case "$line" in |
|
| 79 |
+ user\ *) target_user=${line#user } ;;
|
|
| 80 |
+ hostname\ *) target_host=${line#hostname } ;;
|
|
| 81 |
+ port\ *) target_port=${line#port } ;;
|
|
| 82 |
+ setenv\ *NG_SSH_AUTH=password-interactive*) target_auth="password_interactive" ;; |
|
| 83 |
+ esac |
|
| 84 |
+ done < <("$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} -G "$target" 2>/dev/null)
|
|
| 85 |
+ |
|
| 86 |
+ [[ -n "$target_user" && -n "$target_host" && -n "$target_port" ]] |
|
| 87 |
+} |
|
| 88 |
+ |
|
| 89 |
+run_real_ssh() {
|
|
| 90 |
+ exec "$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} "$@"
|
|
| 91 |
+} |
|
| 92 |
+ |
|
| 93 |
+resolve_target_from_config() {
|
|
| 94 |
+ local target=$1 |
|
| 95 |
+ local default_user=${USER:-${LOGNAME:-}}
|
|
| 96 |
+ local user_override="" |
|
| 97 |
+ |
|
| 98 |
+ case "$target" in |
|
| 99 |
+ *@*) |
|
| 100 |
+ user_override=${target%@*}
|
|
| 101 |
+ target=${target#*@}
|
|
| 102 |
+ ;; |
|
| 103 |
+ esac |
|
| 104 |
+ |
|
| 105 |
+ case "$target" in |
|
| 106 |
+ is-jumper|192.168.2.100) |
|
| 107 |
+ return 1 |
|
| 108 |
+ ;; |
|
| 109 |
+ J1|J2|j1|j2) |
|
| 110 |
+ target_is_jump=1 |
|
| 111 |
+ ;; |
|
| 112 |
+ esac |
|
| 113 |
+ |
|
| 114 |
+ resolve_ssh_config "$target" || return 1 |
|
| 115 |
+ |
|
| 116 |
+ if [[ -n "$user_override" ]]; then |
|
| 117 |
+ target_user=$user_override |
|
| 118 |
+ fi |
|
| 119 |
+ if [[ -n "$user_option" ]]; then |
|
| 120 |
+ target_user=$user_option |
|
| 121 |
+ fi |
|
| 122 |
+ if [[ -n "$port_option" ]]; then |
|
| 123 |
+ target_port=$port_option |
|
| 124 |
+ fi |
|
| 125 |
+ |
|
| 126 |
+ # Unconfigured host (ssh -G returns defaults): bypass unless a custom jump |
|
| 127 |
+ # was requested explicitly. |
|
| 128 |
+ if [[ "$target_is_jump" -eq 0 && "$target_host" == "$target" && "$target_port" == "22" && "$target_user" == "$default_user" ]]; then |
|
| 129 |
+ [[ $custom_jump_set -eq 0 ]] && return 1 |
|
| 130 |
+ host_configured=0 |
|
| 131 |
+ fi |
|
| 132 |
+ |
|
| 133 |
+ [[ -n "$target_user" && -n "$target_host" && -n "$target_port" ]] |
|
| 134 |
+} |
|
| 135 |
+ |
|
| 136 |
+resolve_jump() {
|
|
| 137 |
+ local saved_user saved_host saved_port saved_auth |
|
| 138 |
+ |
|
| 139 |
+ saved_user=$target_user |
|
| 140 |
+ saved_host=$target_host |
|
| 141 |
+ saved_port=$target_port |
|
| 142 |
+ saved_auth=$target_auth |
|
| 143 |
+ |
|
| 144 |
+ resolve_ssh_config "$jump_alias" || {
|
|
| 145 |
+ printf "ssh-wrapper: cannot resolve jump alias %s\n" "$jump_alias" >&2 |
|
| 146 |
+ exit 255 |
|
| 147 |
+ } |
|
| 148 |
+ |
|
| 149 |
+ jump_user=$target_user |
|
| 150 |
+ jump_host=$target_host |
|
| 151 |
+ jump_port=$target_port |
|
| 152 |
+ |
|
| 153 |
+ target_user=$saved_user |
|
| 154 |
+ target_host=$saved_host |
|
| 155 |
+ target_port=$saved_port |
|
| 156 |
+ target_auth=$saved_auth |
|
| 157 |
+} |
|
| 158 |
+ |
|
| 159 |
+# Pre-process: extract custom jump flags and strip them from args. |
|
| 160 |
+filtered_args=() |
|
| 161 |
+for arg in "$@"; do |
|
| 162 |
+ case "$arg" in |
|
| 163 |
+ -J1) jump_alias="j1"; custom_jump_set=1 ;; |
|
| 164 |
+ -J2) jump_alias="j2"; custom_jump_set=1 ;; |
|
| 165 |
+ -j1) jump_alias="j1"; custom_jump_set=1 ;; |
|
| 166 |
+ -j2) jump_alias="j2"; custom_jump_set=1 ;; |
|
| 167 |
+ *) filtered_args+=("$arg") ;;
|
|
| 168 |
+ esac |
|
| 169 |
+done |
|
| 170 |
+set -- "${filtered_args[@]+"${filtered_args[@]}"}"
|
|
| 171 |
+ |
|
| 172 |
+if [[ -f "$ssh_config" ]] && ! has_explicit_config "$@"; then |
|
| 173 |
+ ssh_config_args=(-F "$ssh_config") |
|
| 174 |
+fi |
|
| 175 |
+ |
|
| 176 |
+skip_next=0 |
|
| 177 |
+capture_user=0 |
|
| 178 |
+capture_port=0 |
|
| 179 |
+after_double_dash=0 |
|
| 180 |
+ |
|
| 181 |
+for arg in "$@"; do |
|
| 182 |
+ if [[ $found_target -eq 1 ]]; then |
|
| 183 |
+ cmd_args+=("$arg")
|
|
| 184 |
+ continue |
|
| 185 |
+ fi |
|
| 186 |
+ |
|
| 187 |
+ if [[ $capture_user -eq 1 ]]; then |
|
| 188 |
+ user_option=$arg |
|
| 189 |
+ capture_user=0 |
|
| 190 |
+ continue |
|
| 191 |
+ fi |
|
| 192 |
+ |
|
| 193 |
+ if [[ $capture_port -eq 1 ]]; then |
|
| 194 |
+ port_option=$arg |
|
| 195 |
+ capture_port=0 |
|
| 196 |
+ continue |
|
| 197 |
+ fi |
|
| 198 |
+ |
|
| 199 |
+ if [[ $skip_next -eq 1 ]]; then |
|
| 200 |
+ skip_next=0 |
|
| 201 |
+ continue |
|
| 202 |
+ fi |
|
| 203 |
+ |
|
| 204 |
+ case "$arg" in |
|
| 205 |
+ -G|-Q|-V|-h|--help) |
|
| 206 |
+ run_real_ssh "$@" |
|
| 207 |
+ ;; |
|
| 208 |
+ --) |
|
| 209 |
+ after_double_dash=1 |
|
| 210 |
+ continue |
|
| 211 |
+ ;; |
|
| 212 |
+ esac |
|
| 213 |
+ |
|
| 214 |
+ if [[ $after_double_dash -eq 0 ]]; then |
|
| 215 |
+ case "$arg" in |
|
| 216 |
+ -s) |
|
| 217 |
+ want_subsystem=1 |
|
| 218 |
+ continue |
|
| 219 |
+ ;; |
|
| 220 |
+ -l) |
|
| 221 |
+ capture_user=1 |
|
| 222 |
+ continue |
|
| 223 |
+ ;; |
|
| 224 |
+ -l*) |
|
| 225 |
+ user_option=${arg#-l}
|
|
| 226 |
+ continue |
|
| 227 |
+ ;; |
|
| 228 |
+ -p) |
|
| 229 |
+ capture_port=1 |
|
| 230 |
+ continue |
|
| 231 |
+ ;; |
|
| 232 |
+ -p*) |
|
| 233 |
+ port_option=${arg#-p}
|
|
| 234 |
+ continue |
|
| 235 |
+ ;; |
|
| 236 |
+ -b|-c|-D|-E|-e|-F|-I|-i|-J|-L|-m|-O|-o|-Q|-R|-S|-W|-w) |
|
| 237 |
+ skip_next=1 |
|
| 238 |
+ continue |
|
| 239 |
+ ;; |
|
| 240 |
+ -b*|-c*|-D*|-E*|-e*|-F*|-I*|-i*|-J*|-L*|-m*|-O*|-o*|-Q*|-R*|-S*|-W*|-w*) |
|
| 241 |
+ continue |
|
| 242 |
+ ;; |
|
| 243 |
+ -*) |
|
| 244 |
+ continue |
|
| 245 |
+ ;; |
|
| 246 |
+ esac |
|
| 247 |
+ fi |
|
| 248 |
+ |
|
| 249 |
+ if ! resolve_target_from_config "$arg"; then |
|
| 250 |
+ run_real_ssh "$@" |
|
| 251 |
+ fi |
|
| 252 |
+ |
|
| 253 |
+ found_target=1 |
|
| 254 |
+done |
|
| 255 |
+ |
|
| 256 |
+if [[ $found_target -eq 0 ]]; then |
|
| 257 |
+ run_real_ssh "$@" |
|
| 258 |
+fi |
|
| 259 |
+ |
|
| 260 |
+tty_flag="-tt" |
|
| 261 |
+if [[ ${#cmd_args[@]} -gt 0 || $want_subsystem -eq 1 ]]; then
|
|
| 262 |
+ tty_flag="-T" |
|
| 263 |
+fi |
|
| 264 |
+ |
|
| 265 |
+if [[ $target_is_jump -eq 1 ]]; then |
|
| 266 |
+ jump_cmd=(ssh "$tty_flag" -A -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p "$target_port" "$target_user@$target_host") |
|
| 267 |
+ jump_cmd+=(${cmd_args[@]+"${cmd_args[@]}"})
|
|
| 268 |
+else |
|
| 269 |
+ resolve_jump |
|
| 270 |
+ |
|
| 271 |
+ final_cmd=(ssh "$tty_flag" -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -o ProxyJump=none -o ProxyCommand=none) |
|
| 272 |
+ if [[ "$target_auth" == "password_interactive" ]]; then |
|
| 273 |
+ final_cmd+=( -o BatchMode=no -o PreferredAuthentications=keyboard-interactive,password -o PubkeyAuthentication=no ) |
|
| 274 |
+ elif [[ $host_configured -eq 1 ]]; then |
|
| 275 |
+ final_cmd+=( -o BatchMode=yes ) |
|
| 276 |
+ fi |
|
| 277 |
+ if [[ $want_subsystem -eq 1 ]]; then |
|
| 278 |
+ final_cmd+=( -s ) |
|
| 279 |
+ fi |
|
| 280 |
+ final_cmd+=( -p "$target_port" "$target_user@$target_host" ) |
|
| 281 |
+ final_cmd+=(${cmd_args[@]+"${cmd_args[@]}"})
|
|
| 282 |
+ |
|
| 283 |
+ jump_cmd=(ssh "$tty_flag" -A -o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new -p "$jump_port" "$jump_user@$jump_host" "$(quote_cmd "${final_cmd[@]}")")
|
|
| 284 |
+fi |
|
| 285 |
+ |
|
| 286 |
+is_jumper_cmd="SSH_AUTH_SOCK=$remote_agent exec $(quote_cmd "${jump_cmd[@]}")"
|
|
| 287 |
+ |
|
| 288 |
+exec "$real_ssh" ${ssh_config_args[@]+"${ssh_config_args[@]}"} "$tty_flag" -o BatchMode=yes -o ConnectTimeout=10 is-jumper "$is_jumper_cmd"
|
|
@@ -0,0 +1,23 @@ |
||
| 1 |
+#!/usr/bin/env bash |
|
| 2 |
+set -euo pipefail |
|
| 3 |
+ |
|
| 4 |
+project_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
|
| 5 |
+ssh_root=${SSH_ROOT:-"$HOME/.ssh"}
|
|
| 6 |
+local_bin_root=${LOCAL_BIN_ROOT:-"$HOME/.local/bin"}
|
|
| 7 |
+ |
|
| 8 |
+install -d -m 700 "$ssh_root" |
|
| 9 |
+install -d -m 700 "$local_bin_root" |
|
| 10 |
+ |
|
| 11 |
+"$project_root/tools/generate-configs.py" |
|
| 12 |
+ |
|
| 13 |
+install -m 600 "$project_root/generated/client.conf" "$ssh_root/config" |
|
| 14 |
+ |
|
| 15 |
+rm -f "$local_bin_root/ssh" "$local_bin_root/scp" "$local_bin_root/sftp" |
|
| 16 |
+install -m 700 "$project_root/scripts/ssh-wrapper.sh" "$local_bin_root/ssh" |
|
| 17 |
+install -m 755 "$project_root/scripts/scp-wrapper.sh" "$local_bin_root/scp" |
|
| 18 |
+install -m 755 "$project_root/scripts/sftp-wrapper.sh" "$local_bin_root/sftp" |
|
| 19 |
+ |
|
| 20 |
+rm -f "$ssh_root/scripts/ssh-wrapper.sh" "$ssh_root/scripts/scp-wrapper.sh" "$ssh_root/scripts/sftp-wrapper.sh" |
|
| 21 |
+rmdir --ignore-fail-on-non-empty "$ssh_root/scripts" 2>/dev/null || true |
|
| 22 |
+ |
|
| 23 |
+printf 'deployed SSH config to %s and wrappers to %s\n' "$ssh_root" "$local_bin_root" |
|
@@ -0,0 +1,246 @@ |
||
| 1 |
+#!/usr/bin/env python3 |
|
| 2 |
+import argparse |
|
| 3 |
+import fnmatch |
|
| 4 |
+from pathlib import Path |
|
| 5 |
+ |
|
| 6 |
+import yaml |
|
| 7 |
+ |
|
| 8 |
+ |
|
| 9 |
+ROOT = Path(__file__).resolve().parents[1] |
|
| 10 |
+ |
|
| 11 |
+ |
|
| 12 |
+def load_inventory(path: Path) -> dict: |
|
| 13 |
+ with path.open("r", encoding="utf-8") as handle:
|
|
| 14 |
+ data = yaml.safe_load(handle) |
|
| 15 |
+ if data.get("version") != 1:
|
|
| 16 |
+ raise SystemExit("unsupported inventory version")
|
|
| 17 |
+ return data |
|
| 18 |
+ |
|
| 19 |
+ |
|
| 20 |
+def fmt_bool(value: bool) -> str: |
|
| 21 |
+ return "yes" if value else "no" |
|
| 22 |
+ |
|
| 23 |
+ |
|
| 24 |
+def fmt_option(value) -> str: |
|
| 25 |
+ if isinstance(value, bool): |
|
| 26 |
+ return fmt_bool(value) |
|
| 27 |
+ return str(value) |
|
| 28 |
+ |
|
| 29 |
+ |
|
| 30 |
+def aliases_match_rule(aliases, rule): |
|
| 31 |
+ patterns = rule.get("patterns", [])
|
|
| 32 |
+ if not patterns: |
|
| 33 |
+ return False |
|
| 34 |
+ return all(any(fnmatch.fnmatch(str(alias), pattern) for pattern in patterns) for alias in aliases) |
|
| 35 |
+ |
|
| 36 |
+ |
|
| 37 |
+def company_managed_rule(data, target, aliases, user, port): |
|
| 38 |
+ managed = data.get("company_managed", {}).get("jump_hosts", {})
|
|
| 39 |
+ if target not in managed.get("inherit_globals_on_targets", []):
|
|
| 40 |
+ return None |
|
| 41 |
+ |
|
| 42 |
+ for rule in managed.get("match_defaults", []):
|
|
| 43 |
+ if rule.get("user") != user or rule.get("port") != port:
|
|
| 44 |
+ continue |
|
| 45 |
+ if aliases_match_rule(aliases, rule): |
|
| 46 |
+ return rule |
|
| 47 |
+ return None |
|
| 48 |
+ |
|
| 49 |
+ |
|
| 50 |
+def aliases_for_host(host): |
|
| 51 |
+ aliases = [str(alias) for alias in host["aliases"]] |
|
| 52 |
+ if host["hostname"] not in aliases: |
|
| 53 |
+ aliases.append(host["hostname"]) |
|
| 54 |
+ return aliases |
|
| 55 |
+ |
|
| 56 |
+ |
|
| 57 |
+def host_block(aliases, hostname, user=None, port=None, extra=None): |
|
| 58 |
+ lines = [f"Host {' '.join(str(alias) for alias in aliases)}", f" HostName {hostname}"]
|
|
| 59 |
+ if user: |
|
| 60 |
+ lines.append(f" User {user}")
|
|
| 61 |
+ if port: |
|
| 62 |
+ lines.append(f" Port {port}")
|
|
| 63 |
+ auth = (extra or {}).pop("auth", None)
|
|
| 64 |
+ if auth == "password_interactive": |
|
| 65 |
+ lines.append(" SetEnv NG_SSH_AUTH=password-interactive")
|
|
| 66 |
+ lines.append(" BatchMode no")
|
|
| 67 |
+ lines.append(" PreferredAuthentications keyboard-interactive,password")
|
|
| 68 |
+ lines.append(" PubkeyAuthentication no")
|
|
| 69 |
+ for key, value in (extra or {}).items():
|
|
| 70 |
+ lines.append(f" {key} {value}")
|
|
| 71 |
+ lines.append("")
|
|
| 72 |
+ return lines |
|
| 73 |
+ |
|
| 74 |
+ |
|
| 75 |
+def pattern_block(pattern, options): |
|
| 76 |
+ lines = [f"Host {pattern}"]
|
|
| 77 |
+ if "connect_timeout" in options: |
|
| 78 |
+ lines.append(f" ConnectTimeout {options['connect_timeout']}")
|
|
| 79 |
+ if "connection_attempts" in options: |
|
| 80 |
+ lines.append(f" ConnectionAttempts {options['connection_attempts']}")
|
|
| 81 |
+ lines.append("")
|
|
| 82 |
+ return lines |
|
| 83 |
+ |
|
| 84 |
+ |
|
| 85 |
+def generated_header(target, include_comments=True): |
|
| 86 |
+ if not include_comments: |
|
| 87 |
+ return [] |
|
| 88 |
+ return [ |
|
| 89 |
+ "# Generated by tools/generate-configs.py.", |
|
| 90 |
+ "# Do not edit this file directly; edit inventory/hosts.yaml.", |
|
| 91 |
+ f"# Target: {target}",
|
|
| 92 |
+ "", |
|
| 93 |
+ ] |
|
| 94 |
+ |
|
| 95 |
+ |
|
| 96 |
+def emit_global_options(data, include_comments=True): |
|
| 97 |
+ blocks = data.get("ssh_options", {})
|
|
| 98 |
+ if not blocks: |
|
| 99 |
+ return [] |
|
| 100 |
+ |
|
| 101 |
+ lines = [] |
|
| 102 |
+ if include_comments: |
|
| 103 |
+ lines.extend(["# Global SSH compatibility options", ""]) |
|
| 104 |
+ for name, block in blocks.items(): |
|
| 105 |
+ if include_comments: |
|
| 106 |
+ lines.append(f"# {name}: {block.get('description', '')}")
|
|
| 107 |
+ lines.append("Host *")
|
|
| 108 |
+ for key, value in block.get("options", {}).items():
|
|
| 109 |
+ lines.append(f" {key} {fmt_option(value)}")
|
|
| 110 |
+ lines.append("")
|
|
| 111 |
+ return lines |
|
| 112 |
+ |
|
| 113 |
+ |
|
| 114 |
+def inherit_globals(data, target): |
|
| 115 |
+ managed = data.get("company_managed", {}).get("jump_hosts", {})
|
|
| 116 |
+ return target in managed.get("inherit_globals_on_targets", [])
|
|
| 117 |
+ |
|
| 118 |
+ |
|
| 119 |
+def merged(defaults, group_defaults, host): |
|
| 120 |
+ result = dict(defaults) |
|
| 121 |
+ result.update(group_defaults or {})
|
|
| 122 |
+ result.update(host) |
|
| 123 |
+ return result |
|
| 124 |
+ |
|
| 125 |
+ |
|
| 126 |
+def host_differs_from_defaults(host, defaults): |
|
| 127 |
+ for key in ("user", "port", "auth"):
|
|
| 128 |
+ if key in host and host[key] != defaults.get(key): |
|
| 129 |
+ return True |
|
| 130 |
+ return False |
|
| 131 |
+ |
|
| 132 |
+ |
|
| 133 |
+def should_emit_host_on_target(data, target, group_defaults, host): |
|
| 134 |
+ if target not in ("j1", "j2"):
|
|
| 135 |
+ return True |
|
| 136 |
+ |
|
| 137 |
+ baseline = merged(data["defaults"]["final_host"], group_defaults, {})
|
|
| 138 |
+ return host_differs_from_defaults(host, baseline) |
|
| 139 |
+ |
|
| 140 |
+ |
|
| 141 |
+def emit_entrypoints(data, include_comments=True): |
|
| 142 |
+ lines = ["# Entrypoints", ""] if include_comments else [] |
|
| 143 |
+ for host in data["entrypoints"].values(): |
|
| 144 |
+ extra = {}
|
|
| 145 |
+ if host.get("identity_file"):
|
|
| 146 |
+ extra["IdentityFile"] = host["identity_file"] |
|
| 147 |
+ if "identities_only" in host: |
|
| 148 |
+ extra["IdentitiesOnly"] = fmt_bool(host["identities_only"]) |
|
| 149 |
+ lines.extend(host_block(aliases_for_host(host), host["hostname"], host.get("user"), host.get("port"), extra))
|
|
| 150 |
+ return lines |
|
| 151 |
+ |
|
| 152 |
+ |
|
| 153 |
+def emit_jumps(data, include_comments=True): |
|
| 154 |
+ lines = ["# Jump hosts", ""] if include_comments else [] |
|
| 155 |
+ defaults = data["defaults"]["jump"] |
|
| 156 |
+ for jump in data["jumps"].values(): |
|
| 157 |
+ item = merged(defaults, {}, jump)
|
|
| 158 |
+ lines.extend(host_block(aliases_for_host(item), item["hostname"], item.get("user"), item.get("port")))
|
|
| 159 |
+ return lines |
|
| 160 |
+ |
|
| 161 |
+ |
|
| 162 |
+def emit_hosts_for_group(data, group, target, defaults): |
|
| 163 |
+ group_defaults = group.get("defaults", {})
|
|
| 164 |
+ lines = [] |
|
| 165 |
+ for host in group.get("hosts", {}).values():
|
|
| 166 |
+ if not should_emit_host_on_target(data, target, group_defaults, host): |
|
| 167 |
+ continue |
|
| 168 |
+ item = merged(defaults, group_defaults, host) |
|
| 169 |
+ aliases = aliases_for_host(item) |
|
| 170 |
+ extra = {}
|
|
| 171 |
+ if item.get("auth"):
|
|
| 172 |
+ extra["auth"] = item["auth"] |
|
| 173 |
+ user = item.get("user")
|
|
| 174 |
+ port = item.get("port")
|
|
| 175 |
+ if company_managed_rule(data, target, aliases, user, port): |
|
| 176 |
+ user = None |
|
| 177 |
+ port = None |
|
| 178 |
+ lines.extend(host_block(aliases, item["hostname"], user, port, extra)) |
|
| 179 |
+ for pattern, options in group.get("patterns", {}).items():
|
|
| 180 |
+ lines.extend(pattern_block(pattern, options)) |
|
| 181 |
+ return lines |
|
| 182 |
+ |
|
| 183 |
+ |
|
| 184 |
+def emit_groups(data, target=None, include_comments=True): |
|
| 185 |
+ lines = [] |
|
| 186 |
+ defaults = data["defaults"]["final_host"] |
|
| 187 |
+ metadata_keys = {"description", "default_jump", "defaults", "patterns", "hosts"}
|
|
| 188 |
+ queue = [(name, group) for name, group in data["groups"].items()] |
|
| 189 |
+ |
|
| 190 |
+ while queue: |
|
| 191 |
+ group_name, group = queue.pop(0) |
|
| 192 |
+ group_lines = emit_hosts_for_group(data, group, target, defaults) |
|
| 193 |
+ if group_lines: |
|
| 194 |
+ if include_comments: |
|
| 195 |
+ lines.extend([f"# Group: {group_name}", f"# Description: {group.get('description', '')}", ""])
|
|
| 196 |
+ lines.extend(group_lines) |
|
| 197 |
+ |
|
| 198 |
+ for child_name, child in group.items(): |
|
| 199 |
+ if child_name in metadata_keys or not isinstance(child, dict): |
|
| 200 |
+ continue |
|
| 201 |
+ if "hosts" in child: |
|
| 202 |
+ queue.append((f"{group_name}.{child_name}", child))
|
|
| 203 |
+ return lines |
|
| 204 |
+ |
|
| 205 |
+ |
|
| 206 |
+def write(path: Path, lines): |
|
| 207 |
+ path.parent.mkdir(parents=True, exist_ok=True) |
|
| 208 |
+ path.write_text("\n".join(lines).rstrip() + "\n", encoding="utf-8")
|
|
| 209 |
+ |
|
| 210 |
+ |
|
| 211 |
+def generate(data, output_dir: Path): |
|
| 212 |
+ final_groups = emit_groups(data) |
|
| 213 |
+ |
|
| 214 |
+ client = generated_header("client")
|
|
| 215 |
+ client.extend(emit_global_options(data)) |
|
| 216 |
+ client.extend(emit_entrypoints(data)) |
|
| 217 |
+ client.extend(emit_jumps(data)) |
|
| 218 |
+ client.extend(final_groups) |
|
| 219 |
+ write(output_dir / "client.conf", client) |
|
| 220 |
+ |
|
| 221 |
+ is_jumper = generated_header("is-jumper")
|
|
| 222 |
+ is_jumper.extend(emit_global_options(data)) |
|
| 223 |
+ is_jumper.extend(emit_jumps(data)) |
|
| 224 |
+ write(output_dir / "is-jumper.conf", is_jumper) |
|
| 225 |
+ |
|
| 226 |
+ for target in ("j1", "j2"):
|
|
| 227 |
+ lines = generated_header(target, include_comments=False) |
|
| 228 |
+ if inherit_globals(data, target): |
|
| 229 |
+ pass |
|
| 230 |
+ else: |
|
| 231 |
+ lines.extend(emit_global_options(data, include_comments=False)) |
|
| 232 |
+ lines.extend(emit_groups(data, target, include_comments=False)) |
|
| 233 |
+ write(output_dir / f"{target}.conf", lines)
|
|
| 234 |
+ |
|
| 235 |
+ |
|
| 236 |
+def main(): |
|
| 237 |
+ parser = argparse.ArgumentParser() |
|
| 238 |
+ parser.add_argument("--inventory", default=ROOT / "inventory" / "hosts.yaml", type=Path)
|
|
| 239 |
+ parser.add_argument("--output-dir", default=ROOT / "generated", type=Path)
|
|
| 240 |
+ args = parser.parse_args() |
|
| 241 |
+ |
|
| 242 |
+ generate(load_inventory(args.inventory), args.output_dir) |
|
| 243 |
+ |
|
| 244 |
+ |
|
| 245 |
+if __name__ == "__main__": |
|
| 246 |
+ main() |
|
@@ -0,0 +1,125 @@ |
||
| 1 |
+#!/usr/bin/env bash |
|
| 2 |
+set -euo pipefail |
|
| 3 |
+ |
|
| 4 |
+project_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
|
| 5 |
+ |
|
| 6 |
+inventory_path=${INVENTORY_PATH:-"$project_root/inventory/hosts.yaml"}
|
|
| 7 |
+upstream_hosts_file=${UPSTREAM_HOSTS_FILE:-}
|
|
| 8 |
+upstream_ssh_target=${UPSTREAM_SSH_TARGET:-nextgen@192.168.2.103}
|
|
| 9 |
+upstream_hosts_path=${UPSTREAM_HOSTS_PATH:-/home/nextgen/projects/ssh-infrastructure/inventory/hosts.yaml}
|
|
| 10 |
+local_is_jumper_identity_file=${LOCAL_IS_JUMPER_IDENTITY_FILE:-}
|
|
| 11 |
+if [[ -z "$local_is_jumper_identity_file" ]]; then |
|
| 12 |
+ local_is_jumper_identity_file='~/.ssh/keys/is-jumper_ed25519' |
|
| 13 |
+fi |
|
| 14 |
+deploy_after_sync=${DEPLOY_AFTER_SYNC:-1}
|
|
| 15 |
+force_deploy=${FORCE_DEPLOY:-0}
|
|
| 16 |
+ |
|
| 17 |
+tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/ssh-infra-sync.XXXXXX")
|
|
| 18 |
+trap 'rm -rf "$tmpdir"' EXIT |
|
| 19 |
+ |
|
| 20 |
+tmp_hosts="$tmpdir/hosts.yaml" |
|
| 21 |
+tmp_generated="$tmpdir/generated" |
|
| 22 |
+ |
|
| 23 |
+if [[ -n "$upstream_hosts_file" ]]; then |
|
| 24 |
+ cp "$upstream_hosts_file" "$tmp_hosts" |
|
| 25 |
+else |
|
| 26 |
+ /usr/bin/scp -q "${upstream_ssh_target}:${upstream_hosts_path}" "$tmp_hosts"
|
|
| 27 |
+fi |
|
| 28 |
+ |
|
| 29 |
+python3 - "$tmp_hosts" "$local_is_jumper_identity_file" <<'PY' |
|
| 30 |
+from pathlib import Path |
|
| 31 |
+import sys |
|
| 32 |
+ |
|
| 33 |
+import yaml |
|
| 34 |
+ |
|
| 35 |
+path = Path(sys.argv[1]) |
|
| 36 |
+identity_file = sys.argv[2] |
|
| 37 |
+text = path.read_text(encoding="utf-8") |
|
| 38 |
+ |
|
| 39 |
+with path.open("r", encoding="utf-8") as handle:
|
|
| 40 |
+ data = yaml.safe_load(handle) |
|
| 41 |
+ |
|
| 42 |
+is_jumper = data.get("entrypoints", {}).get("is_jumper", {})
|
|
| 43 |
+if ( |
|
| 44 |
+ is_jumper.get("identity_file") == identity_file
|
|
| 45 |
+ and is_jumper.get("identities_only") is True
|
|
| 46 |
+): |
|
| 47 |
+ raise SystemExit(0) |
|
| 48 |
+ |
|
| 49 |
+lines = text.splitlines(keepends=True) |
|
| 50 |
+ |
|
| 51 |
+entrypoints_idx = None |
|
| 52 |
+for idx, line in enumerate(lines): |
|
| 53 |
+ if line.strip() == "entrypoints:" and not line.startswith((" ", "\t")):
|
|
| 54 |
+ entrypoints_idx = idx |
|
| 55 |
+ break |
|
| 56 |
+if entrypoints_idx is None: |
|
| 57 |
+ raise SystemExit("missing entrypoints section")
|
|
| 58 |
+ |
|
| 59 |
+is_jumper_idx = None |
|
| 60 |
+for idx in range(entrypoints_idx + 1, len(lines)): |
|
| 61 |
+ line = lines[idx] |
|
| 62 |
+ if line and not line.startswith((" ", "\t")) and line.strip():
|
|
| 63 |
+ break |
|
| 64 |
+ if line.startswith(" is_jumper:"):
|
|
| 65 |
+ is_jumper_idx = idx |
|
| 66 |
+ break |
|
| 67 |
+if is_jumper_idx is None: |
|
| 68 |
+ raise SystemExit("missing entrypoints.is_jumper section")
|
|
| 69 |
+ |
|
| 70 |
+block_end = len(lines) |
|
| 71 |
+for idx in range(is_jumper_idx + 1, len(lines)): |
|
| 72 |
+ line = lines[idx] |
|
| 73 |
+ if line.strip() and not line.startswith(" "):
|
|
| 74 |
+ block_end = idx |
|
| 75 |
+ break |
|
| 76 |
+ |
|
| 77 |
+identity_line = f" identity_file: {identity_file}\n"
|
|
| 78 |
+identities_only_line = " identities_only: true\n" |
|
| 79 |
+identity_idx = None |
|
| 80 |
+identities_only_idx = None |
|
| 81 |
+for idx in range(is_jumper_idx + 1, block_end): |
|
| 82 |
+ stripped = lines[idx].strip() |
|
| 83 |
+ if stripped.startswith("identity_file:"):
|
|
| 84 |
+ identity_idx = idx |
|
| 85 |
+ elif stripped.startswith("identities_only:"):
|
|
| 86 |
+ identities_only_idx = idx |
|
| 87 |
+ |
|
| 88 |
+if identity_idx is None: |
|
| 89 |
+ insert_at = identities_only_idx if identities_only_idx is not None else block_end |
|
| 90 |
+ lines.insert(insert_at, identity_line) |
|
| 91 |
+ if identities_only_idx is not None: |
|
| 92 |
+ identities_only_idx += 1 |
|
| 93 |
+ block_end += 1 |
|
| 94 |
+else: |
|
| 95 |
+ lines[identity_idx] = identity_line |
|
| 96 |
+ |
|
| 97 |
+if identities_only_idx is None: |
|
| 98 |
+ insert_at = (identity_idx + 1) if identity_idx is not None else block_end |
|
| 99 |
+ lines.insert(insert_at, identities_only_line) |
|
| 100 |
+else: |
|
| 101 |
+ lines[identities_only_idx] = identities_only_line |
|
| 102 |
+ |
|
| 103 |
+path.write_text("".join(lines), encoding="utf-8")
|
|
| 104 |
+PY |
|
| 105 |
+ |
|
| 106 |
+python3 "$project_root/tools/generate-configs.py" \ |
|
| 107 |
+ --inventory "$tmp_hosts" \ |
|
| 108 |
+ --output-dir "$tmp_generated" |
|
| 109 |
+ |
|
| 110 |
+if cmp -s "$tmp_hosts" "$inventory_path"; then |
|
| 111 |
+ printf 'inventory unchanged: %s\n' "$inventory_path" |
|
| 112 |
+ if [[ "$deploy_after_sync" == "1" && "$force_deploy" == "1" ]]; then |
|
| 113 |
+ "$project_root/tools/deploy-local.sh" |
|
| 114 |
+ fi |
|
| 115 |
+ exit 0 |
|
| 116 |
+fi |
|
| 117 |
+ |
|
| 118 |
+install -m 644 "$tmp_hosts" "$inventory_path" |
|
| 119 |
+printf 'updated inventory from upstream: %s\n' "$inventory_path" |
|
| 120 |
+ |
|
| 121 |
+if [[ "$deploy_after_sync" == "1" ]]; then |
|
| 122 |
+ "$project_root/tools/deploy-local.sh" |
|
| 123 |
+else |
|
| 124 |
+ printf 'skipped deploy because DEPLOY_AFTER_SYNC=%s\n' "$deploy_after_sync" |
|
| 125 |
+fi |
|