Come limitare una chiave SSH a un solo comando o indirizzo

Metti le opzioni davanti alla chiave in authorized_keys: restrict,command="/usr/local/bin/backup",from="203.0.113.10" ssh-ed25519 AAAA.... Con quella riga la chiave esegue sempre e solo quel comando, qualunque cosa chieda il client, solo da quell'indirizzo, senza terminale e senza inoltro. I limiti valgono solo per quella chiave, non per le altre chiavi o per le password dello stesso account.

Aggiornata il . Tutte le opzioni, lo script wrapper, rrsync e SFTP sono stati provati con OpenSSH 10.0 su Debian 13, in un container usa e getta.

Le opzioni

OpzioneCosa faCosa vede l'altra parte
restrictNessun inoltro di alcun tipo, nessun terminale, niente ~/.ssh/rcPTY allocation request failed on channel 0
command="..."Esegue sempre questo comandoL'output di quel comando
from="..."Solo da questi indirizzi o nomiPermission denied
permitopen="host:port"Limita ssh -L e -W a questa destinazionestdio forwarding failed oppure open failed
permitlisten="[host:]port"Limita ssh -R a questa portaremote port forwarding failed

Parti da restrict e riaggiungi solo ciò che serve alla chiave. Copre anche le restrizioni che OpenSSH aggiungerà in futuro, cosa che un elenco come no-pty,no-port-forwarding non fa. Le opzioni stanno su una sola riga, separate da virgole, senza spazi in mezzo.

Un solo comando

restrict,command="/usr/local/bin/backup" ssh-ed25519 AAAA... backup-job

Ora sia ssh user@server sia ssh user@server rm -rf / eseguono il backup e nient'altro.

Pochi comandi permessi, con SSH_ORIGINAL_COMMAND

Quello che il client ha chiesto sta nella variabile SSH_ORIGINAL_COMMAND, e un piccolo script può controllarla:

#!/bin/sh
# /usr/local/bin/ssh-allow
case "$SSH_ORIGINAL_COMMAND" in
  "uptime"|"df -h")
    exec $SSH_ORIGINAL_COMMAND
    ;;
  *)
    echo "Not allowed: $SSH_ORIGINAL_COMMAND" >&2
    exit 1
    ;;
esac
restrict,command="/usr/local/bin/ssh-allow" ssh-ed25519 AAAA... monitoring

Nella nostra prova, ssh user@server uptime ha funzionato, mentre ssh user@server "uptime; id" ha risposto Not allowed: uptime; id. Confronta stringhe intere, come sopra. Un pattern come uptime* lascia passare qualsiasi cosa inizi con quella parola, e non passare mai la variabile a sh -c o a eval.

Scegli con cura anche i comandi permessi. Un comando che può scrivere file o aprire una shell, come un editor, annulla la restrizione.

Limitare per indirizzo

from="203.0.113.10,198.51.100.0/24" ssh-ed25519 AAAA... office

Funzionano sia i pattern sia gli intervalli CIDR, e ! esclude un indirizzo. Usa indirizzi IP e non nomi host, perché un nome host dipende dal DNS inverso. Quando l'indirizzo non corrisponde, il client vede Permission denied e il log del server ne spiega il motivo:

Authentication tried for user with correct key but not from a permitted host (host=198.51.100.7, ip=198.51.100.7, required=203.0.113.10).

Una chiave solo per un tunnel

restrict,port-forwarding,permitopen="10.0.1.10:5432",command="/usr/sbin/nologin" ssh-ed25519 AAAA... db-tunnel

Questa chiave può raggiungere il database a quell'indirizzo e a quella porta. Un inoltro verso un'altra porta fallisce, e un comando ottiene solo This account is currently not available. Per usarla, aggiungi -N così che ssh non chieda alcun comando:

ssh -N -L 5432:10.0.1.10:5432 user@server

Un nome host in permitopen deve corrispondere esattamente a quello che invia il client, senza alcuna risoluzione. localhost e 127.0.0.1 contano come due cose diverse.

Attenzione: questa riga permette ancora ssh -R

port-forwarding attiva entrambe le direzioni, e permitopen ne limita una sola. Nella nostra prova, la chiave qui sopra poteva ancora aprire un inoltro remoto con ssh -R. permitlisten="none" non è una soluzione, perché la riga diventa non valida e la chiave smette di funzionare del tutto. Fai invece una di queste due cose:

  • Imposta permitlisten su una sola porta che nessuno usa, per esempio permitlisten="127.0.0.1:9". Abbiamo verificato che un inoltro su qualsiasi altra porta viene rifiutato.
  • Limita l'account nella configurazione del server, così vale per tutte le sue chiavi:
# /etc/ssh/sshd_config.d/10-tunnel.conf
Match User tunnel
    AllowTcpForwarding local

Per cosa fanno -L, -R e -D, vedi tunnel SSH locali, remoti e dinamici.

Chiavi per il trasferimento di file

restrict da solo non blocca il trasferimento di file. Nella nostra prova, sftp funzionava ancora, perché SFTP non richiede né terminale né inoltro. Per permettere SFTP e nient'altro:

restrict,command="internal-sftp" ssh-ed25519 AAAA... files

Per permettere rsync in una sola cartella, usa rrsync, che arriva insieme a rsync. Su Debian si trova in /usr/bin/rrsync, altri sistemi possono differire. Con -ro permette solo la lettura, e abbiamo confermato che i caricamenti poi falliscono:

restrict,command="/usr/bin/rrsync -ro /srv/backup" ssh-ed25519 AAAA... backup-read

Cosa non fanno queste opzioni

  • Vincolano solo quella chiave. Un'altra chiave, o una password, sullo stesso account non ha nessuno di questi limiti. Dove puoi, dai a ogni lavoro il suo account e disattiva le password.
  • L'utente può modificare il file. Se l'account può scrivere nel proprio authorized_keys, un comando che scrive file può togliere i limiti. Per un confine solido, lascia il file di proprietà di root. Vedi authorized_keys spiegato.
  • L'ordine delle righe conta. Se la stessa chiave compare anche in una riga senza restrizioni più in alto, vince quella riga.

Domande

command= in authorized_keys blocca SFTP?

Sì, se command= indica qualcosa di diverso da internal-sftp, perché SFTP esegue quel comando al suo posto. restrict da solo non blocca SFTP.

Cos'è SSH_ORIGINAL_COMMAND?

Una variabile d'ambiente con il comando chiesto dal client quando gira un comando forzato. Uno script può controllarla e permettere solo pochi comandi esatti.

Posso usare un nome host in from=?

Sì, ma dipende dal DNS inverso, che l'altra parte può controllare. Indirizzi IP e intervalli CIDR sono più sicuri.

Fonti

Altro in Mettere in sicurezza un server

Tutte le guide