OpenSSH can chose (or force) the 'shell' used for a specific SSH key

June 9, 2024

One of the perhaps less known and under-utilized features of OpenSSH is that you can make connections using specific authorized SSH keys use specific 'shells', although actually using this may be a little bit tricky. The basic ingredient to do this is a command= setting on the specific key in your .ssh/authorized_keys file, but of course there are some wrinkles and you may not be happy if you just set this to a shell-like program.

The first wrinkle is that sshd runs this command using your regular /etc/passwd shell, as '$SHELL -c <whatever>'. This is presumably done so that you can't evade a restricted shell by writing yourself an authorized_keys file with a more liberal command= command, such as 'command="/bin/bash" ssh-ed25519 ...'. The second wrinkle is that this command is always run with no arguments regardless of how you ran 'ssh', and it's up to the command to work out what you want to do.

If you ran 'ssh u@h echo hi', then the command will be run with a $SSH_ORIGINAL_COMMAND that contains 'echo hi'. If you just ran 'ssh u@h', there will be no $SSH_ORIGINAL_COMMAND in the command's environment. This means that if you simply use a shell as your 'command=', it will only half work. You can do 'ssh u@h' and get a shell environment, but it won't be a login shell, while 'ssh u@h echo hi' won't work at all (it will typically hang). And in both cases, '$SHELL' will be your /etc/passwd login shell. To use this to selectively change your login shell based on the SSH key you use to authenticate, you'll need a cover script.

(Why might you want such a thing? Well, suppose you like to use an alternate shell with unusual behavior, and you also want to use remote access systems, such as Emacs' TRAMP, that are tightly connected to using a standard shell on the remote end. Changing your shell via 'command=' shifts the problem to getting the remote access system to use a specific SSH key, which may be much easier than any alternatives.)

The most straightforward thing to do with a 'command=' script is to narrowly restrict what the particular SSH key can do; we use this in our rsync replication setup. As covered in both the sshd manual page and that entry, you'll need to add additional restrictions to the key to make things solid.

A more complex thing is to use the cover script to do additional access control, authorization checks, and logging before you run what you were asked to. For example, if you have a special 'break glass' system access SSH key, you might want to have it forced to a command= that loudly logs its use, perhaps complains (and errors out) if it wasn't used in exactly the way you expected (for instance, you might never intend to use the 'break glass' key to run remote commands, just to log in), and maybe even test that your regular authentication methods are down. If all of the tests pass, you can then invoke a regular shell (probably just '$SHELL', unless you have a reason to want another one). Especially energetic people could run the entire 'break glass' shell session within script(1), so you hopefully have a record of absolutely everything in the session.

(You'd want the record not so much for security as so that you can later reconstruct what you did in that frantic session when you were entirely focused on putting things back together.)

Sidebar: A plausible alternate shell cover script for command=

The following is only lightly tested and it assumes that your shell supports '-l', '-c <string>' and '-i' options, but since I bothered to work it out and test it a bit I'm going to put it down here.

#!/bin/sh
SHELL=<whatever alternate shell you want>
export SHELL
if [ -n "$SSH_ORIGINAL_COMMAND" ]; then
    exec $SHELL -c "$SSH_ORIGINAL_COMMAND"
elif [ -t 0 ]; then
    exec $SHELL -l
else
    exec $SHELL -i
fi

This handles the two most likely cases (running a command and logging in), and defaults to an interactive shell session if you're not on a PTY and didn't supply a command to run.


Comments on this page:

We've been using authprogs.pl for ages (a slightly modified version that allows regex matches)...

I just found out there's even a (newer) python version of it from the same author https://github.com/daethnir/authprogs

Gitosis uses this mechanism to perform access control on git-over-ssh.

By Opk at 2024-06-17 03:41:42:

An rrsync command is often included with rsync which is specifically for this purpose. I use it with our CI system so that the ssh key belonging to that can only be used for rsync.

The fact that the command is run with the user's original shell does make it possible to restrict the commmand based on a shell pattern. A simple zsh example for rsync would be, e.g.:

   command="${=${(M)SSH_ORIGINAL_COMMAND##rsync --server *}:?error rsync only}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty

For a more complex example, zsh can enable the extended_glob option with `(*)` which gives you a level of flexibility similar to that of regular expressions.

Written on 09 June 2024.
« Operating services versus operating an "adequate environment"
The NFS server 'subtree' export problem »

Page tools: View Source, View Normal.
Search:
Login: Password:

Last modified: Sun Jun 9 22:56:56 2024
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.