SSH Start to Finish Architecture – Forced Commands

Last week, we covered SSH Key options that can restrict how a private key is used to connect to a server. One of those options was the “command=” option, which allows restricting the key to calling only one command, regardless of the command issued as part of the ssh connection attempt. There are actually several ways to enforce this.

You can do the public key “command=” option we already covered. You can also use the sshd_config settings to apply a ForceCommand option. This is most useful for applying the same kind of forced command scenario we described last week via a “Match User” directive. It’s also useful for applying an sftp-only situation to a given user, so that the only thing the user can do is transfer files. The option would look like this, if that is your goal:

ForceCommand internal-sftp

The user’s shell needs to be a valid one for this to work, since the forced command is invoked via “ -c.” This means a shell of /bin/false or /bin/nologin is a no go. Since you are forcing a command, this should be less of a problem, though.

Finally, there is also a way to force this command with ssh-keygen options when signing a key via the certificate authority system for OpenSSH, but we will go into more detail on that when we get to the CA stuff.

The force command options don’t allow running the user’s ~/.ssh/rc, so that would not be a work around that the user could use to hack this system. ControlMaster overrides the public key force command if the option is set after a master session has already been established, so you may need to terminate all ssh connections for that user after making changes that enforce restrictions, moving forward.

A lot of people grumble about the force command options, because they believe that a single key is needed for each command that gets passed, but there is actually a means for handling that. There is an environment variable that gets set when an ssh session that wants to call a remote command is used to connect while force command is in use for a user. That environment variable is SSH_ORIGINAL_COMMAND, and it retains the command that was requested. This means you could have a wrapper script that is your forced command, have it check this environment variable for sanity (are all of the commands in the list provided in our whitelist, or not? If not, log a rejection and terminate. If so, log the call and execute.) The variable is unset if the user just tries to ssh in without calling a remote command, so be sure to check for that if you go this route. Assume if the variable is unset/empty that they tried to log in for an interactive session, and handle that however you feel is best. I would assume “log a rejection and terminate” is better, though, since an interactive session can’t be restricted properly without a restricted shell, that may still be jail broken if misconfigured. Your own needs may vary, though. Just be very thorough in your design, and be sure to sanitize all input before executing, and you should be fine.

Thanks for reading!