How secure is SSH ForceCommand on a jump host?

I have the following setup in my network:

Internet <--> Bastion <--> Local Network

I have several users and each user is assigned to a specific machine. Or in other words: Each user must have only access to one of those servers. E.g.: User1 --> Machine1, User2 --> Machine2 and so on.

Those users will connect from the outside of my network and I have considered many options how to forward their connects via my bastion host to my network.

Eventually I opted for Match Blocks and forcecommand.

So, my /etc/ssh/sshd_config on bastion looks like this:

Match User User1
        ForceCommand ssh User1@Machine1 $SSH_ORIGINAL_COMMAND

User1 connects to bastion host which automatically establishes a connection with Machine1.

As far as I understood ForceCommand, User1 won't have any real access to the bastion host, because all of his operations will be handled by the match block first, hence rerouted to Machine1. However is this really true? Is this already enough to be a secure setup? The user is jailed on Machine1 anyway, so he won't have many possibilities there.


The way I use a bastion host is using ProxyCommand and the -W flag as in this example:

ssh -o ProxyCommand='ssh -W %h:%p user@bastion' user@machine

I use this approach for security reasons. The communication between client and target machine is encrypted and authenticated end-to-end, which means it remains secured even if the bastion is compromised. A compromised bastion host would provide no less security than using ssh end-to-end without a bastion would.

It also eliminates the need to use any agent forwarding. The client can use key based authentication first to access the bastion and then again to access the target host without either of those being provided with an agent connection that could be used to abuse the private key present on the client.

It also limits the code I am dependent on on the bastion host. I don't need to execute any command at all on the bastion. -W implies the no command flag as well as one single port forwarding, this port forwarding is all the bastion host need to permit.

With this approach in mind my recommendation would be to lock down the bastion host as much as possible allowing only commands of the above structure to be used.

The ~/.ssh/authorized_keys file on the bastion could be owned by root (as could all the directories on the path from the root of the file system to it), this reduces the risk it could be modified even if somebody managed to break in as unprivileged user on the bastion host.

In authorized_keys the client's privileges can be limited by using the options command, no-agent-forwarding, no-pty, no-user-rc, no-X11-forwarding, as well as using permitopen to limit port forwardings to only allow access to port 22 on the host that this user is allowed access to.

In principle this approach would be secure even if multiple users share the a single user name on the bastion. But you get slightly more separation by using separate user names on the bastion.


You can easily circumvent ForceCommand since it kicks in when you shell has started. This essentially means that your shell rc file is processed first and then ForceCommand if you allow it to get there. Simple exec sh in your shell rc file will spawn up another shell and keep ForceCommand waiting until you exit this shell.

So bottom line; if user can somehow edit his shell rc (say .bashrc) via ftp, sftp, scp or some other way, then ForceCommand is not really something to rely on.


I imagine that's fine most of the time, but the problem with security is the things noone has gotten around to thinking about yet. There are no guarantees.

Like for instance for a long time noone had thought too hard about the way functions could be created from environment variables in bash, but recently people realised that could be subverted, and one of the effects of that was that ForceCommand could be gotten around (at least as implemented in the authorized_keys file) if the users' shell was bash. Bash got fixed, and hopefully your version is up to date, but stuff like this happens.

I'm not entirely sure whether defining ForceCommand is effectively the same as defining those commands in the authorized_keys files. I haven't looked that closely.