The options
| Option | What it does | What the other side sees |
|---|---|---|
restrict | No forwarding of any kind, no terminal, no ~/.ssh/rc | PTY allocation request failed on channel 0 |
command="..." | Always runs this command | The output of that command |
from="..." | Only from these addresses or names | Permission denied |
permitopen="host:port" | Limits ssh -L and -W to this destination | stdio forwarding failed or open failed |
permitlisten="[host:]port" | Limits ssh -R to this port | remote port forwarding failed |
Start with restrict and add back what the key needs. It also covers any restriction added to OpenSSH in the future, which a list of no-pty,no-port-forwarding and so on does not. Options go on one line, separated by commas, with no spaces between them.
One command only
restrict,command="/usr/local/bin/backup" ssh-ed25519 AAAA... backup-job
Now ssh user@server and ssh user@server rm -rf / both run the backup and nothing else.
A few allowed commands, with SSH_ORIGINAL_COMMAND
What the client asked for is in the variable SSH_ORIGINAL_COMMAND, and a small script can check it:
#!/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 our test, ssh user@server uptime worked, and ssh user@server "uptime; id" answered Not allowed: uptime; id. Match whole strings as above. A pattern like uptime* lets through anything that starts with the word, and never pass the variable to sh -c or eval.
Choose the allowed commands with care too. A command that can write files or start a shell, such as an editor, undoes the restriction.
Limit by address
from="203.0.113.10,198.51.100.0/24" ssh-ed25519 AAAA... office
Patterns and CIDR ranges both work, and ! excludes an address. Use IP addresses rather than host names, since a host name depends on reverse DNS. When the address does not match, the client sees Permission denied and the server log explains:
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).
A key only for one tunnel
restrict,port-forwarding,permitopen="10.0.1.10:5432",command="/usr/sbin/nologin" ssh-ed25519 AAAA... db-tunnel
This key can reach the database at that address and port. A forward to another port fails, and a command only gets This account is currently not available. To use it, add -N so that ssh asks for no command:
ssh -N -L 5432:10.0.1.10:5432 user@server
A hostname in permitopen must match exactly what the client sends, with no lookup. localhost and 127.0.0.1 count as different.
Watch out: this still allows ssh -R
port-forwarding turns on both directions, and permitopen limits only one. In our test, the key above could still open a remote forward with ssh -R. permitlisten="none" is not a way out, because the line is then invalid and the key stops working altogether. Do one of these instead:
- Set
permitlistento a single port that nothing uses, for examplepermitlisten="127.0.0.1:9". We tested that a forward on any other port is refused. - Limit the account in the server config, which applies to all its keys:
# /etc/ssh/sshd_config.d/10-tunnel.conf
Match User tunnel
AllowTcpForwarding local
For what -L, -R and -D do, see local, remote and dynamic SSH tunnels.
File transfer keys
restrict alone does not stop file transfer. In our test, sftp still worked with it, because SFTP needs no terminal or forwarding. To allow SFTP and nothing else:
restrict,command="internal-sftp" ssh-ed25519 AAAA... files
To allow rsync in one folder, use rrsync, which comes with rsync. On Debian it is at /usr/bin/rrsync, and other systems can differ. With -ro it allows reading only, and we confirmed that uploads then fail:
restrict,command="/usr/bin/rrsync -ro /srv/backup" ssh-ed25519 AAAA... backup-read
What these options do not do
- They bind only that key. Another key, or a password, on the same account has none of these limits. Give each job its own account where you can, and turn off passwords.
- The user can edit the file. If the account can write to its own
authorized_keys, a command that writes files can remove the limits. For a firm boundary, keep the file owned by root. See authorized_keys explained. - Line order matters. If the same key also appears on an unrestricted line above, that line wins.
Questions
Does command= in authorized_keys block SFTP?
Yes, when command= points to anything other than internal-sftp, because SFTP then runs that command instead. restrict alone does not block SFTP.
What is SSH_ORIGINAL_COMMAND?
An environment variable that holds the command the client asked for when a forced command runs. A wrapper script can check it and allow only a few exact commands.
Can I use a host name in from=?
Yes, but it depends on reverse DNS, which the other side may control. IP addresses and CIDR ranges are safer.