Usa nomi che portano informazione
Scegli uno schema e rispettalo, per esempio ruolo, ambiente e numero:
web-prod-1
web-prod-2
db-staging-1
acme-web-prod-1
Un nome così ti dice su cosa stai per intervenire e funziona con i pattern. Un nome come server3 o un semplice indirizzo IP non fa né l'una né l'altra cosa. Aggiungi un prefisso di progetto o di cliente se ne segui più di uno.
Usa i pattern, non righe ripetute
Host *-prod-*
User deploy
IdentityFile ~/.ssh/prod_ed25519
IdentitiesOnly yes
Host web-prod-1
HostName 203.0.113.11
Host web-prod-2
HostName 203.0.113.12
Ricorda che vince il primo valore e che si applicano tutti i blocchi corrispondenti, quindi l'ordine conta. Vedi la guida al file di configurazione ssh. Controlla qualsiasi host con ssh -G web-prod-1.
Se i tuoi nomi si risolvono nel DNS, puoi eliminare le righe per singolo host:
Host web-*
HostName %h.example.com
Un file per progetto
# top of ~/.ssh/config
Include ~/.ssh/config.d/*
~/.ssh/config.d/
acme
internal
lab
Ogni file contiene gli host di un progetto, così puoi condividerlo con un collega, tenerlo nel repository di quel progetto o eliminarlo quando il progetto finisce. Tieni Include in cima al file principale e i default di Host * sotto.
Gruppi
ssh non ha gruppi in senso stretto. I pattern sono il modo semplice, quindi sono i nomi a fare il lavoro. I tag, da OpenSSH 9.4, raggruppano host i cui nomi non seguono uno schema comune:
Host legacy-api billing-1
Tag prod
Match tagged prod
User deploy
Per eseguire un comando in sola lettura su un gruppo, un semplice ciclo di shell va bene su piccola scala:
for host in web-prod-1 web-prod-2; do
printf '%s: ' "$host"
ssh -n -o BatchMode=yes -o ConnectTimeout=5 "$host" uptime
done
-n tiene ssh lontano dall'input standard, così lo stesso ciclo funziona anche quando i nomi arrivano da un file. BatchMode fa fallire invece di attendere a un prompt e ConnectTimeout evita che un server morto blocchi tutti gli altri. Per l'esecuzione in parallelo e i rilasci prudenti, vedi come eseguire un comando su più server.
Una chiave per scopo
Usare una chiave per scopo, come lavoro, un cliente o la produzione, significa che perderne una non apre tutto. Significa anche che puoi togliere l'accesso a un cliente rimuovendo una sola chiave.
Host acme-*
IdentityFile ~/.ssh/acme_ed25519
IdentitiesOnly yes
Senza IdentitiesOnly yes, ssh offre anche le chiavi presenti nel tuo agent, il che porta a Too many authentication failures. Non mettere IdentityFile in Host * se vuoi questa separazione: quel valore viene aggiunto all'elenco e non lo sostituisce, quindi viene provata anche l'altra chiave.
Una chiave per server di solito costa più fatica di quanto valga. Se l'elenco di persone e server cambia di continuo, guarda invece i certificati SSH.
known_hosts con hash: un compromesso
HashKnownHosts yes salva i nomi host in ~/.ssh/known_hosts come hash, così chi ruba il file non ottiene un elenco pronto dei tuoi server. Debian e Ubuntu lo attivano in /etc/ssh/ssh_config. OpenSSH originale lo lascia disattivato.
- Cosa guadagni: un file rubato non elenca i tuoi server in chiaro. Gli hash si possono comunque confrontare con un'ipotesi, quindi questo rallenta un attaccante e non lo ferma.
- Cosa perdi: non puoi leggere o cercare il file a occhio, e il completamento della shell basato su di esso smette di funzionare.
Gli strumenti funzionano comunque con le voci con hash:
ssh-keygen -F web-prod-1 # find a host
ssh-keygen -R web-prod-1 # remove a host
ssh-keygen -H # hash an existing file
ssh-keygen -H conserva l'originale, con i nomi in chiaro, come known_hosts.old. Cancella quel file se lo hai fatto per riservatezza. Nota che anche la tua configurazione ssh, la cronologia della shell e l'inventario contengono i nomi, quindi l'hash da solo nasconde meno di quanto sembri.
Tieni un inventario
Oltre la dozzina di server, tieni un solo elenco che dica cosa esiste: nome, indirizzo, ruolo, ambiente, responsabile. Basta un file di testo in un repository Git privato. Non metterci password né chiavi. L'elenco è ciò che controlli quando qualcuno se ne va, o quando ti chiedi quali server sono ancora in uso.
Un inventario Ansible è un buon formato per questo anche se non usi ancora Ansible:
[web]
web-prod-1
web-prod-2
[db]
db-prod-1 ansible_user=postgres
Se hai già un elenco così, puoi generare da esso i file di config.d, invece di mantenere due elenchi a mano. Gli stessi file servono anche a un'app che importa gli host da ~/.ssh/config, compresi i file indicati da Include.
Quando conviene uno strumento come Ansible
ssh è ottimo per raggiungere un server. Non mantiene i server uguali tra loro. Passa ad Ansible o a uno strumento simile quando fai la stessa modifica su più di pochi server, quando devi ripetere una configurazione o quando ti serve una traccia di cosa è cambiato.
ansible all -i inventory.ini -m ping
ansible web -i inventory.ini -a 'uptime'
Ansible usa di default il comando ssh, quindi la tua configurazione, i jump host e le chiavi continuano a funzionare. Il bersaglio ha bisogno solo di SSH e Python. Controlla cosa farà un playbook con --check prima di eseguirlo in produzione, e leggi come evitare errori sui server di produzione.
Domande
Come organizzo molti host in ~/.ssh/config?
Dai agli host nomi che indicano ruolo e ambiente, metti le impostazioni comuni in pattern come Host web-* e tieni un file per progetto in ~/.ssh/config.d/, caricato con Include.
Come eseguo un comando su molti server SSH?
Per pochi server basta un ciclo della shell con ssh -n -o BatchMode=yes. Per le modifiche che ripeti, usa Ansible, che passa dalla tua configurazione ssh.
Conviene una sola chiave SSH per tutti i server?
Usa una chiave per scopo, come lavoro, un cliente o produzione, con IdentitiesOnly yes. Una chiave per server di solito costa più fatica di quanto valga.