How to create a restricted SSH user for port forwarding?
Solution 1
TL;DR - go to the bottom of the answer, "Applying the restrictions"
Adding a restricted user consists of two parts: 1. Creating the user 2. Configuring the SSH daemon (sshd)
Configuring sshd
The best place to get known to the possibilities of SSH is by reading the related manual pages:
Where can SSH client perform actions?
Before you can restrict something, you need to know the features of SSH. Spitting through the manual pages yields:
- Shell commands execution
- File upload through sftp
- Port forwarding
- The client forwards an (un)used port to the server
- The server forwards his port to the client
- The server forwards a port of another host to the client (proxy-ish)
- X11 forwarding (display forwarding)
- Authentication agent forwarding
- Forwarding of a tunnel device
From the Authentication section of the manual page of sshd(8):
If the client successfully authenticates itself, a dialog for preparing the session is entered. At this time the client may request things like allocating a pseudo-tty, forwarding X11 connections, forwarding TCP connections, or forwarding the authentication agent connection over the secure channel.
After this, the client either requests a shell or execution of a command. The sides then enter session mode. In this mode, either side may send data at any time, and such data is forwarded to/from the shell or command on the server side, and the user terminal in the client side.
Options for restricting SSH features
Files and their options that alter behavior are:
-
~/.ssh/authorized_keys
- contains keys which are allowed to connect which can be given options:-
command="command"
- The command supplied by the user (if any) is ignored. Note that the client may specify TCP and/or X11 forwarding unless they are explicitly prohibited. Note that this option applies to shell, command or subsystem execution. -
no-agent-forwarding
- Forbids authentication agent forwarding when this key is used for authentication. -
no-port-forwarding
- Forbids TCP forwarding when this key is used for authentication -
no-X11-forwarding
- "Forbids X11 forwarding when this key is used for authentication." -
permitopen="host:port"
- Limit local 'ssh -L' port forwarding such that it may only connect to the specified host and port.
-
-
~/.ssh/environment
- This file is read into the environment at login (if it exists). Environment processing is disabled by default and is controlled via the PermitUserEnvironment option -
~/.ssh/rc
- Contains initialization routines to be run before the user's home directory becomes accessible. -
/etc/ssh/sshd_config
- the system-wide configuration file-
AllowAgentForwarding
- Specifies whether ssh-agent(1) forwarding is permitted. AllowTcpForwarding
-
ForceCommand
- "Forces the execution of the command specified by ForceCommand, ignoring any command supplied by the client and ~/.ssh/rc if present. The command is invoked by using the user's login shell with the -c option." -
GatewayPorts
- "Specifies whether remote hosts are allowed to connect to ports forwarded for the client. By default, sshd(8) binds remote port forwardings to the loopback address. This prevents other remote hosts from connecting to forwarded ports. GatewayPorts can be used to specify that sshd should allow remote port forwardings to bind to non-loopback addresses, thus allowing other hosts to connect." -
PermitOpen
:Specifies the destinations to which TCP port forwarding is permitted. The forwarding specification must be one of the following forms:
PermitOpen host:port PermitOpen IPv4_addr:port PermitOpen [IPv6_addr]:port
Multiple forwards may be specified by separating them with whitespace. An argument of 'any' can be used to remove all restrictions and permit any forwarding requests. By default all port forwarding requests are permitted.
-
PermitTunnel
- Specifies whether tun(4) device forwarding is allowed. The default is 'no' -
X11Forwarding
- Specifies whether X11 forwarding is permitted. The default is 'no'
-
Applying the restrictions
Modifying the system-wide configuration file /etc/ssh/sshd_config
allows the configuration be applied even if password-based authentication is applied or if the restrictions in ~/.ssh/authorized_keys
are accidentally removed. If you've modified the global defaults, you should uncomment the options accordingly.
Match User limited-user
#AllowTcpForwarding yes
#X11Forwarding no
#PermitTunnel no
#GatewayPorts no
AllowAgentForwarding no
PermitOpen localhost:62222
ForceCommand echo 'This account can only be used for [reason]'
Now add a user:
sudo useradd -m limited-user
The option ForceCommand
can be omitted if the shell is set to a non-shell like /bin/false
(or /bin/true
) as /bin/false -c [command]
won't do anything.
Now the client can only connect to port 62222 on the loopback address of the server over SSH (it will not listen on the public IP address)
Disabling AllowTcpForwarding
would also disallow the use of -R
, thus defeating the use of such a restricted account for forwarding a single port. PermitOpen localhost:62222
assumes that port 62222 on the server is never in use because the client can happily connect to it and listen on it too.
If TCP forwarding is allowed in the system-wide configuration and disabled password-based authentication, you can use per-key settings as well. Edit ~/.ssh/authorized_keys
and add the next options before the ssh-
(with a space between the options and ssh-
):
command="echo 'This account can only be used for [reason]'",no-agent-forwarding,no-X11-forwarding,permitopen="localhost:62222"
Verify
To be sure that it works as expected, some test cases need to be run. In the below commands, host
should be replaced by the actual login if it's not set in ~/.ssh/config
. Behind the command, a command is shown that should be executed on either the client or server (as specified).
# connection closed:
ssh host
# connection closed (/bin/date is not executed):
ssh host /bin/date
# administratively prohibited (2x):
ssh host -N -D 62222 # client: curl -I --socks5 localhost:62222 example.com
ssh host -N -L 8080:example.com:80 # client: curl -I localhost:8080
sftp host
# should be possible because the client should forward his SSH server
ssh host -N -R 8080:example.com:80 # server: curl -I localhost:8080
# This works, it forwards the client SSH to the server
ssh host -N -R 62222:localhost:22
# unfortunately, the client can listen on that port too. Not a big issue
ssh host -N -L 1234:localhost:62222
Conclusion
Checklist: The SSH user should not able to:
- execute shell commands - done
- access files or upload files to the server - done
- use the server as proxy (e.g. webproxy) - done
- access local services which were otherwise not publicly accessible due to a firewall - partially, the client cannot access other ports than 62222, but can listen and connect to port 62222 on the server
- kill the server - done (note that these checks are limited to the SSH server. If you've an other vulnerable service on the machine, it could allow a possible attacker to run commands, kill the server, etc. )
Solution 2
I'm sure there are many solutions to this, and many more robust than the one I'm proposing. However, this may be sufficient for your needs. In order to do it, I'm assuming the user has is able to do ssh key based authentication (putty or any unix ssh should support this).
Add a user as you normally would ('adduser' or any other tool)
Create the users .ssh dir and .ssh/authorized_keys
your_user $ sudo -Hu ssh_forwarder /bin/bash
ssh_forwarder $ cd ~
ssh_forwarder $ mkdir .ssh
ssh_forwarder $ ( umask 066 && cat > .ssh/authorized_keys ) <<EOF
no-agent-forwarding,no-X11-forwarding,command="read a; exit" ssh-rsa AAAB3NzaC1y....2cD/VN3NtHw== smoser@brickies
EOF
- Disable password access to this account.
your_user $ sudo usermod --lock ssh_forwarder
Now, the only way that user can get into your system is via access to the proper ssh key, and ssh will run "/bin/bash -c 'read a'" for them, no matter what they attempt to run. 'read a' will simply read until a newline, and then the shell will exit, so the user just has to hit 'enter' to kill the connection.
There are lots of other things you could do in 'command='. See man authorized_keys
and search for 'command' for more information.
If you do not like the fact that hitting enter kills the connection, you could use something like the following for the 'command=' entry:
command="f=./.fifo.$$ && mkfifo $f && trap \"rm -f $f\" EXIT && read a <$f && echo $a; exit;"
This just creates a temporary fifo in the users home directory, and then tries to read from it. Nothing will be writing to that file, so this will hang indefinitely. Additionally, if you want to forcefully terminate that connection, you could do something like:
your_user$ echo GOODBYE | sudo tee ~ssh_forwarder/.fifo.*
This should use very little resources, and nothing should be able to go wrong in that script that would not end in termination of the shell.
sleep 1h; echo You have been here too long. Good bye.
I didn't see off hand how you could allow the user to remote forward (ssh -R
) but limit (ssh -L
). Perhaps 'permitopen' could be used. Googling was not very helpful. It would seem that something like 'no-port-forwarding,permitremoteopen=10001' would be useful to allow ssh -R 6901:localhost:6901
.
This is a solution. It can most definitely be improved on, and any opening of remote ports should be scrutinized. If my goal was to allow my Grandmother to connect into my LAN so I could use vnc to view her screen, and access to those keys was limited to her, I personally would feel reasonably safe. If this was for an enterprise, more thorough investigation would be necessary. One thing to be aware of is ssh -N
does not request a shell at all, so the 'command=' code does not execute.
Other, possibly more secure mechanisms may include creating a custom shell for the user, and even locking that down with apparmour.
Related videos on Youtube
Lekensteyn
Arch Linux user, open-source enthusiast, programmer, Wireshark developer, TRU/e Security master student at TU/e. Interests: network protocols, Linux kernel, server administration, Android, breaking & fixing stuff.
Updated on September 18, 2022Comments
-
Lekensteyn over 1 year
ændrük suggested a reverse connection for getting an easy SSH connection with someone else (for remote help). For that to work, an additional user is needed to accept the connection. This user needs to be able to forward his port through the server (the server acts as proxy).
How do I create a restricted user that can do nothing more than the above described?
The new user must not be able to:
- execute shell commands
- access files or upload files to the server
- use the server as proxy (e.g. webproxy)
- access local services which were otherwise not publicly accessible due to a firewall
- kill the server
Summarized, how do I create a restricted SSH user which is only able to connect to the SSH server without privileges, so I can connect through that connection with his computer?
-
Admin almost 5 years
-
Admin about 3 years
-
Lekensteyn almost 13 yearsThis looks very hackish, it's not even necessary to have a shell because no command needs to be executed. See the manual page of
ssh
for the-N
option. There must be a cleaner way to accomplish this. -
smoser almost 13 yearsThats true. I'm hoping to see a cleaner solution also. I do think that authorized_keys in conjunction with no-port-forwarding would be a pretty good solution if there was a 'permitremoteopen' option.
-
Lekensteyn almost 13 yearsI did some researcg (see above). Since
ssh -N
does not execute a command at all, it's no problem thatcommand="something"
closes the session. -
Lekensteyn over 12 yearsFor this to work, the account added by
useradd
must be unlocked. That can be done by replacing the password in/etc/shadow
by a asteric (*
) or by setting a password usingsudo passwd limited-user
. -
gucki over 12 yearsIt works good, but I don't understand which configuration option blocks the sftp access. Could you explain please? :)
-
Lekensteyn over 12 yearsSFTP works because the SSH server runs the
sftp-server
command. WithForceCommand
, this command won't be executed anymore. -
wittrup over 8 yearsAny particular steps needed for this to work on a raspberry pi? I seem unable to make this work...
-
Lekensteyn over 8 years@wittrup This should also work on the RPi (Raspbian / Arch / other?). If it does not, try to open a new question on raspberrypi.stackexchange.com and provide details of what you have tried, and what the results are.
-
lauhub almost 8 years@wittrup It seems that using localhost:XXX in PermitOpen does not always work. You can use 127.0.0.1:XXX instead. See also this answer
-
Donn Lee over 7 yearsAlso useful is the
from=
option inauthorized_keys
. It allows you to limit the use of the key to sources with a particular IP address or hostname. Example,from="1.1.1.1"
orfrom="10.0.0.?,*.example.com"
. See section "AUTHORIZED_KEYS FILE FORMAT" in the man page (linux.die.net/man/8/sshd) for all the authorized_keys options. -
Simon Sapin about 7 years"AllowTcpForwarding local" forbids remote forwarding. (I don’t know if it existed when this answer was written.)
-
nh2 about 7 yearsNote that this gets broken by persistent ssh connections, so you shouldn't have something like
Host * ControlMaster auto
in your~/.ssh/config
file when connecting withssh -N
. If you do need it, usessh -N -o ControlMaster=no
-
PeteW over 6 yearsIf you're setting up a tunnelling server, use
PermitOpen server1.db.example.com:3306 server2.db.example.com:3306
(for example). Then on Windows you can doplink.exe -N -L 3307:server1.db.example.com:3306 [email protected] -i C:\Users\..path..to\my.ppk
. -
PeteW over 6 yearsIf you have several users you want to limit, use
groupadd limited-users
and in sshd conf useMatch Group limited-users
instead ofMatch User
above. Then when you add a user,useradd -m limited-user1
thenusermod -a -G limited-users limited-user1
-
eigenfield about 5 yearsAm i missing something? It seemed that
chsh ssh_forwarder -s /bin/false
is all it takes. -
Zakaria over 4 yearsThis doesn't satisfy not used as proxy if the user use dynamic port forwarding a.k.a SOCKS proxy
-
Lekensteyn over 4 years@Zakaria Have you tried it?
ssh -N -D 8080 limited-user@localhost -vvv
andcurl --socks5 localhost:8080 example.com -v
definitely fails for me on Arch Linux with the above config that includesPermitOpen
. -
Tugzrida about 4 yearsWhat I ended up using in
authorized_keys
on the server to allow a device to 'dial-out-forward' its ssh:command="echo 'This account can only be used for [reason]'",permitlisten="127.0.0.1:62222",no-agent-forwarding,no-X11-forwarding,no-pty,no-user-rc,permitopen="[100::]:1"
and thenssh host -N -R 127.0.0.1:62222:127.0.0.1:22
on the device. The device's ssh is then on the server's port 62222. The device can only do-L
forwarding to100::
, which is the IPv6 discard prefix. -
baptx about 3 yearsWhat about the
no-pty
option from~/.ssh/authorized_keys
and thePermitTTY
option from/etc/ssh/sshd_config
? It is not needed to restrict? I saw theno-pty
option mentioned here: blog.tinned-software.net/… -
Zective almost 3 yearsThis may not have been the OP's question, but it stayed unclear to me whether these settings restrict the access of someone trying to access the local machine via a socket on a remote machine created by a reverse SSH-tunnel initiated from local machine to that remote host. Can that access be restricted via the permissions on the local host, once the tunnel exists? I am asking mainly because I am looking into whether creating a new question.
-
burny almost 3 yearsThat works, too. But how can this specific ssh connection be stopped?
-
Rafael Kitover over 2 yearsThank you for this excellent and detailed post, I added ChrootDirectory /var/empty for extra paranoia, and it works fine.