Restrict User to SSH Forced Command(s)

In order to restrict executable SSH commands with authorized keys, you can use the SSH feature called forced command within the authorized_keys file.
As the command is bound to an SSH key, when the user try to execute a command, the only output will be the one of the command configured previously.

The use of a non-interactive will be helpful when the user is a daemon or can’t answers on the terminal, especially if SSH_ORIGINAL_COMMAND is used: this variable contains the original command line if a forced command is executed and it can be used to extract the original arguments.

Restrict executable SSH commands with authorized keys

On a serverA and serverB, you have a same user account.

Deals keys

You need in first time deals the user keys with servers.

Deals users keys with servers :

# ssh-keygen /home/user01/.ssh/id_rsa
# ssh-copy-id -i /home/user01/.ssh/id_rsa user01@serverB

Now your user01 can connect to the other server without enter passwd and launch all cmd (and connect to serverB) :

[user01@serverA ~]$ ssh serverB ls -la /tmp
[user01@serverA ~]$ ssh serverB df -h
[user01@serverA ~]$ ssh serverB free

Limit to only one command

On the serverB, restrict a command in the user01 authorized_keys files :

# vi /home/user01/.ssh/authorized_keys

And add the chosen command (date here) :

command="date" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCv6Ytv+ugm3dd3ALt/aJg2E7Mj083[....]= user01@serverA

On the serverA, the user01 can test all command but the only output will be for “date”, which set as the forced command :

[user01@serverA ~]$ ssh serverB date
Fri Nov 17 09:52:18 UTC 2023

[user01@serverA ~]$ ssh serverB df -h
Fri Nov 17 09:52:29 UTC 2023

[user01@serverA ~]$ ssh serverB shjkghkadfwerjhkskdf
Fri Nov 17 09:53:16 UTC 2023

[user01@serverA ~]$ ssh serverB
Fri Nov 17 09:53:27 UTC 2023
Connection to serverB closed.

Limit to whitelist command

Instead of add a single command in a .ssh/authorized_keys. You can create a whitlist command in a bash script.

Simply add the path to your scrip :

command="/usr/bin/authorized_cmd.sh" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCv6Ytv+ugm3dd3ALt/aJg2E7Mj083[....]= user01@serverA

You can add more security to your /var/lib/user01/.ssh/authorized_keys file with adding 5 useful parameters before the key:

no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command="/usr/bin/authorized-cmd.sh" ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC9EXZT3nctHP7AdcSU8c6zW/8vQZJNOULtmU6450cgJVTwi4F/rvdozM4YHsxbpHP3X/KihZb[...]+1ICBaaV/jmcBjREeUZV85BBMxw24GU5qLnWh8zhLhfBRtsG2UuGjRQ4QPHk/3klcHK/k= [email protected]

Five security layers had been added here:

no-port-forwarding: no remote access with port forwarding is possible
no-X11-forwarding: no graphical X11 forwarding is possible
no-agent-forwarding: no SSH transfer agent is possible
no-pty: no terminal connexion is possible
command="/usr/bin/authorized-cmd.sh": the whitelist commands script

Example of custom bash whitelist commands

Simple version

#!/bin/sh
/bin/echo -e "\n This are the authorized commands for ${HOSTNAME} \n"
h3lp()
{
/bin/echo -e "
Your allowed commands are:
1 date
2 free
3 df -h
4 who
h help
q quit
"
}
h3lp
printf " Please, enter a NUMBER or LETTER : "
read -r cmd
/bin/echo -e "\n\e[32m-= [ OUTPUT OF ${HOSTNAME} ] :\n\e[0m"
while [ "$cmd" != "q" ]; do
case "$cmd" in
1)
/bin/date
;;
2)
/usr/bin/free
;;
3)
/bin/df -h
;;
4)
/bin/who
;;
h)
h3lp
;;
q)
/bin/echo "Goodbye"
exit 0
;;
*)
/bin/echo -e " NO, '$cmd' is not allowed "
;;
esac
printf "\n Please, enter a NUMBER or LETTER : "
read -r cmd
/bin/echo -e "\n\e[32m-= [ OUTPUT OF ${HOSTNAME} ] :\n\e[0m"
done
exit 0

Artistic version

#!/bin/sh
/bin/echo -e "
\e[31m
╔══════════════════════════════════════════════╗
║ ║
║ This are the authorized commands for ║
║ ${HOSTNAME} ║
║ ║
╚══════════════════════════════════════════════╝\e[0m"
h3lp()
{
/bin/echo -e "
______________________________________________
| |
| Your allowed commands are: |
|______________________________________________|
| |
| 1 date |
| 2 free |
| 3 df -h |
| 4 who |
| h help |
| q quit |
|______________________________________________|\n"
}
h3lp
printf " Please, enter a NUMBER or LETTER : "
read -r cmd
/bin/echo -e "
.................................................
\e[32m-= [ OUTPUT OF ${HOSTNAME} ] :\n\e[0m"
while [ "$cmd" != "q" ]; do
case "$cmd" in
1)
/bin/date
;;
2)
/usr/bin/free
;;
3)
/bin/df -h
;;
4)
/bin/who
;;
h)
h3lp
;;
q)
/bin/echo "Goodbye"
exit 0
;;
*)
/bin/echo -e " NO, '$cmd' is not allowed "
;;
esac
/bin/echo -e "\n ――――――――――――――――――――――――――――――――――――――――――――――――― \n"
printf " Please, enter a NUMBER or LETTER : "
read -r cmd
/bin/echo -e "
.................................................
\e[32m-= [ OUTPUT OF ${HOSTNAME} ] :\n\e[0m"
done
exit 0

The use of SSH ORIGINAL COMMAND

This script is non-interactive, helpful when the user is a daemon or can’t answers on the terminal.

The goal is to use the SSH_ORIGINAL_COMMAND environment variable which contains the original command line if a forced command is executed. It can be used to extract the original arguments.

Manual:

SSH_ORIGINAL_COMMAND
This variable contains the original command line if a forced command is executed. It can be used to extract the original arguments.

The script in /usr/bin/authorized-cmd.sh with SSH_ORIGINAL_COMMAND contains:

#!/bin/sh
#
# You can have only one forced command in ~/.ssh/authorized_keys.
# Use this wrapper to allow several commands.
case "$SSH_ORIGINAL_COMMAND" in
"/usr/share/BackupPC/bin/BackupPC_dump -v -f my-awesome.company.sub.org")
/usr/share/BackupPC/bin/BackupPC_dump -v -f my-awesome.company.sub.org
;;
"/usr/share/BackupPC/bin/BackupPC_dump -v -i my-awesome.company.sub.org")
/usr/share/BackupPC/bin/BackupPC_dump -v -i my-awesome.company.sub.org
;;
*)
exit 1
;;
esac

You can add some log files:

#!/bin/sh

echo -e "$(date +%Y-%m-%d-%H-%M-%S) - Backup triggered by BackupPC" >> /tmp/file01.log
echo -e "$(date +%Y-%m-%d-%H-%M-%S) - The command executed by BackupPC is the following: \n$SSH_ORIGINAL_COMMAND" >> /tmp/file01.log

case "$SSH_ORIGINAL_COMMAND" in

## The backupPC commands and what they suppose to do

"sudo /usr/bin/aCommand1 --option1 --option2 -arguments")
echo "$(date +%Y-%m-%d-%H-%M-%S) - Backup Type: INCR" >> /tmp/file01.log ; sudo /usr/bin/aCommand1 --option1 --option2 -arguments
;;
"sudo /usr/bin/aCommand2 --option1 --option2 -arguments")
echo "$(date +%Y-%m-%d-%H-%M-%S) - Backup Type: FULL" >> /tmp/file01.log ; sudo /usr/bin/aCommand2 --option1 --option2 -arguments
;;

## If a other command is launched, a message will be display and the action logged
*)
echo "$(date +%Y-%m-%d-%H-%M-%S) - Operation not permitted by YUR COMPANY rules" | tee -a /tmp/file02.log
exit 1
;;
esac

Documentation

https://superuser.com/questions/641275/make-linux-server-allow-rsync-scp-sftp-but-not-a-terminal-login
https://superuser.com/questions/1507366/securing-ssh-original-command-on-a-ssh-proxy-server
https://www.ibm.com/docs/en/zos/2.2.0?topic=socrlp-environment-variables
https://unix.stackexchange.com/questions/324727/openssh-prevent-globbing-on-ssh-original-command
https://linuxcommand.org/lc3_man_pages/ssh1.html

> Partager <