SSH-Schlüssel auf einen Befehl oder eine Adresse beschränken

Setzen Sie die Optionen vor den Schlüssel in authorized_keys: restrict,command="/usr/local/bin/backup",from="203.0.113.10" ssh-ed25519 AAAA.... Mit dieser Zeile führt der Schlüssel immer genau diesen einen Befehl aus, egal was der Client verlangt, nur von dieser Adresse, ohne Terminal und ohne Weiterleitung. Die Beschränkungen gelten nur für diesen Schlüssel, nicht für andere Schlüssel oder Passwörter desselben Kontos.

Ein Schlüssel pro Aufgabe lässt sich leichter einschränken. Wardterm erzeugt ihn in Sekunden und legt ihn für Sie auf dem Server ab. Zeigen

Aktualisiert am . Alle Optionen, das Wrapper-Skript, rrsync und SFTP liefen mit OpenSSH 10.0 unter Debian 13 in einem Wegwerf-Container.

Die Optionen

OptionWas sie tutWas die Gegenseite sieht
restrictKeine Weiterleitung jeder Art, kein Terminal, kein ~/.ssh/rcPTY allocation request failed on channel 0
command="..."Führt immer diesen Befehl ausDie Ausgabe dieses Befehls
from="..."Nur von diesen Adressen oder NamenPermission denied
permitopen="host:port"Beschränkt ssh -L und -W auf dieses Zielstdio forwarding failed oder open failed
permitlisten="[host:]port"Beschränkt ssh -R auf diesen Portremote port forwarding failed

Beginnen Sie mit restrict und fügen Sie nur hinzu, was der Schlüssel braucht. Das deckt auch Beschränkungen ab, die OpenSSH künftig neu einführt, anders als eine Liste wie no-pty,no-port-forwarding. Die Optionen stehen in einer Zeile, durch Kommas getrennt und ohne Leerzeichen dazwischen.

Nur ein Befehl

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

Jetzt führen sowohl ssh user@server als auch ssh user@server rm -rf / das Backup aus und sonst nichts.

Einige erlaubte Befehle mit SSH_ORIGINAL_COMMAND

Was der Client verlangt hat, steht in der Variable SSH_ORIGINAL_COMMAND, und ein kleines Skript kann sie prüfen:

#!/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

In unserem Test funktionierte ssh user@server uptime, und ssh user@server "uptime; id" antwortete mit Not allowed: uptime; id. Vergleichen Sie ganze Zeichenketten wie oben. Ein Muster wie uptime* lässt alles durch, was mit diesem Wort beginnt, und übergeben Sie die Variable niemals an sh -c oder eval.

Wählen Sie auch die erlaubten Befehle mit Sorgfalt. Ein Befehl, der Dateien schreiben oder eine Shell starten kann, etwa ein Editor, hebt die Beschränkung wieder auf.

Nach Adresse beschränken

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

Muster und CIDR-Bereiche funktionieren beide, und mit ! schließen Sie eine Adresse aus. Verwenden Sie IP-Adressen statt Hostnamen, denn ein Hostname hängt vom Reverse-DNS ab. Passt die Adresse nicht, sieht der Client Permission denied, und das Server-Log nennt den Grund:

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).

Ein Schlüssel nur für einen Tunnel

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

Dieser Schlüssel erreicht die Datenbank an dieser Adresse und diesem Port. Eine Weiterleitung auf einen anderen Port schlägt fehl, und ein Befehl liefert nur This account is currently not available. Zur Nutzung fügen Sie -N hinzu, damit ssh keinen Befehl anfordert:

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

Ein Hostname in permitopen muss genau dem entsprechen, was der Client sendet, ohne Namensauflösung. localhost und 127.0.0.1 gelten als verschieden.

Achtung: Das erlaubt trotzdem ssh -R

port-forwarding schaltet beide Richtungen frei, und permitopen beschränkt nur eine. In unserem Test konnte der Schlüssel oben trotzdem mit ssh -R eine Remote-Weiterleitung öffnen. permitlisten="none" ist kein Ausweg, denn dann ist die Zeile ungültig und der Schlüssel funktioniert gar nicht mehr. Tun Sie stattdessen eines von beidem:

  • Setzen Sie permitlisten auf einen einzelnen Port, den nichts benutzt, zum Beispiel permitlisten="127.0.0.1:9". Wir haben getestet, dass eine Weiterleitung auf jeden anderen Port abgelehnt wird.
  • Beschränken Sie das Konto in der Server-Konfiguration, was für alle seine Schlüssel gilt:
# /etc/ssh/sshd_config.d/10-tunnel.conf
Match User tunnel
    AllowTcpForwarding local

Was -L, -R und -D bewirken, erklärt lokale, entfernte und dynamische SSH-Tunnel.

Schlüssel für Dateiübertragung

restrict allein verhindert keine Dateiübertragung. In unserem Test funktionierte sftp damit weiterhin, weil SFTP weder Terminal noch Weiterleitung braucht. So erlauben Sie SFTP und sonst nichts:

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

Um rsync in einem Ordner zu erlauben, nutzen Sie rrsync, das mit rsync mitkommt. Unter Debian liegt es in /usr/bin/rrsync, auf anderen Systemen kann das abweichen. Mit -ro ist nur Lesen erlaubt, und wir haben bestätigt, dass Uploads dann scheitern:

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

Was diese Optionen nicht leisten

  • Sie binden nur diesen einen Schlüssel. Ein anderer Schlüssel oder ein Passwort desselben Kontos hat keine dieser Beschränkungen. Geben Sie wenn möglich jeder Aufgabe ein eigenes Konto und schalten Sie Passwörter ab.
  • Der Benutzer kann die Datei bearbeiten. Darf das Konto seine eigene authorized_keys schreiben, kann ein Befehl, der Dateien schreibt, die Beschränkungen entfernen. Für eine feste Grenze sollte die Datei root gehören. Siehe authorized_keys erklärt.
  • Die Reihenfolge der Zeilen zählt. Steht derselbe Schlüssel weiter oben noch einmal ohne Beschränkung, gilt diese Zeile.

Fragen

Blockiert command= in authorized_keys SFTP?

Ja, wenn command= auf etwas anderes als internal-sftp zeigt, denn SFTP führt dann stattdessen diesen Befehl aus. restrict allein blockiert SFTP nicht.

Was ist SSH_ORIGINAL_COMMAND?

Eine Umgebungsvariable mit dem Befehl, den der Client angefordert hat, wenn ein erzwungener Befehl läuft. Ein Skript kann sie prüfen und nur wenige exakte Befehle zulassen.

Kann ich in from= einen Hostnamen verwenden?

Ja, aber das hängt vom Reverse-DNS ab, das die Gegenseite steuern kann. IP-Adressen und CIDR-Bereiche sind sicherer.

Quellen

Mehr zu Server absichern

Alle Anleitungen