[114]When editing authorized_keys, be sure to use a text editor capable of handling long lines. The modulus of a key may be several hundred characters long. Some text editors can't display long lines, won't edit them properly, automatically insert line breaks, or wreak other sorts of havoc upon your nice public keys. (Aaargh. Don't get us started talking about brain-damaged text editors.) Use a modern editor, and turn off automatic line breaking. We use GNU Emacs.An option may take two forms. It may be a keyword, such as:
or it may be a keyword followed by an equals sign and a value, such as:# SSH1, OpenSSH: Turn off port forwarding no-port-forwarding
Multiple options may be given together, separated by commas, with no whitespace between the options:# SSH1, OpenSSH: Set idle timeout to five minutes idle-timeout=5m
If you mistakenly include whitespace:# SSH1, OpenSSH no-port-forwarding,idle-timeout=5m
your connection by this key won't work properly. If you connect with debugging turned on (ssh1 -v), you will see a "syntax error" message from the SSH server. Many SSH users aren't aware of options or neglect to use them. This is a pity because options provide extra security and convenience. The more you know about the clients that access your account, the more options you can use to control that access.# THIS IS ILLEGAL: whitespace between the options no-port-forwarding, idle-timeout=5m
[115]The name may be changed with the keyword AuthorizationFile in the serverwide configuration file. [Section 5.4.1.6, "Per-account authorization files"] Also, the ssh2 manpage claims that AuthorizationFile can be set in the client configuration file, but as of SSH2 2.2.0 this setting has no effect. Since sshd2 doesn't read the client configuration file, this is unsurprising.Public keys are indicated using the Key keyword. Key is followed by whitespace, and then the name of a file containing a public key. Relative filenames refer to files in ~/.ssh2. For example:
means that an SSH-2 public key is contained in ~/.ssh2/myself.pub. Your authorization file must contain at least one Key line for public-key authentication to occur. Each Key line may optionally be followed immediately by a Command keyword and its value. Command specifies a forced command, i.e., a command to be executed whenever the key immediately above is used for access. We discuss forced commands later in great detail. [Section 8.2.4, "Forced Commands "] For now, all you need to know is this: a forced command begins with the keyword Command, is followed by whitespace, and ends with a shell command line. For example:# SSH2 only Key myself.pub
Remember that a Command line by itself is an error. The following examples are illegal:# SSH2 only Key somekey.pub Command "/bin/echo All logins are disabled"
# THIS IS ILLEGAL: no Key line Command "/bin/echo This line is bad." # THIS IS ILLEGAL: no Key line precedes the second Command Key somekey.pub Command "/bin/echo All logins are disabled" Command "/bin/echo This line is bad."
# SSH2 only PgpKeyName my-key Command "/bin/echo PGP authentication was detected"
A descriptive comment (optional)
host=192.168.10.1 ssh-dss AAAAB3NzaC1kc3MA... My OpenSSH key
A forced command transfers this control from the client to the server. Instead of the client's deciding which command will run, the owner of the server account decides. In Figure 8-2, the client has requested the command /bin/ls, but the server-side forced command runs /bin/who instead.# Invoke a remote login shell $ ssh server.example.com # Invoke a remote directory listing $ ssh server.example.com /bin/ls
In SSH2, a forced command appears on the line immediately following the desired Key, using the Command keyword. The previous example would be represented:# SSH1, OpenSSH command="/usr/local/bin/pine" ...secretary's public key...
You may associate at most one forced command with a given key. To associate multiple commands with a key, put them in a script on the remote machine and run the script as the forced command. (We demonstrate this in [Section 8.2.4.3, "Displaying a command menu "].)# SSH2 only Key secretary.pub Command "/usr/local/bin/pine"
[116]Modern Unix implementations often ignore the setuid bit on scripts for security reasons.
Any incoming SSH connection that successfully authenticates with this key causes the following message to be displayed on standard output:# SSH1, OpenSSH command="/bin/echo Sorry, buddy, but you've been terminated!" ...key... # SSH2 only Key friend.pub Command "/bin/echo Sorry, buddy, but you've been terminated!"
and then the connection closes. If you'd like to print a longer message, which might be awkward to include in your authorization file, you can store it in a separate file (say, ~/go.away) and display it using an appropriate program (e.g., cat):Sorry, buddy, but you've been terminated!
Since the message is long, you might be tempted to display it one screenful at a time with a pager program such as more or less. Don't do it!# SSH1, OpenSSH command="/bin/cat $HOME/go.away" ...key... # SSH2 only Key friend.pub Command "/bin/cat $HOME/go.away"
This forced command opens an unwanted hole into your account: the more program, like most Unix pager programs, has a shell escape. Instead of restricting access to your account, this forced command permits unlimited access.# SSH1: Don't do this! command="/bin/more $HOME/go.away" ...key...
#!/bin/sh /bin/echo "Welcome! Your choices are: 1 See today's date 2 See who's logged in 3 See current processes q Quit" /bin/echo "Your choice: \c" read ans while [ "$ans" != "q" ] do case "$ans" in 1) /bin/date ;; 2) /bin/who ;; 3) /usr/ucb/w ;; q) /bin/echo "Goodbye" exit 0 ;; *) /bin/echo "Invalid choice '$ans': please try again" ;; esac /bin/echo "Your choice: \c" read ans done exit 0
The user may then type 1, 2, 3, or q to run the associated program. Any other input is ignored, so no other programs can be executed. Such scripts must be written carefully to avoid security holes. In particular, none of the permitted programs should provide a means to escape to a shell, or else the user may execute any command in your account.Welcome! Your choices are: 1 See today's date 2 See who's logged in 3 See current processes q Quit Your choice:
but a forced command is set up to execute "/bin/who" instead:$ ssh1 server.example.com ps
then ps is ignored and /bin/who runs instead. Nevertheless, the SSH server does read the original command string sent by the client and stores it in an environment variable. For SSH1 and OpenSSH,[117] the environment variable is SSH_ORIGINAL_COMMAND, and for SSH2, it's SSH2_ORIGINAL_COMMAND. So in the our example, the value of SSH_ORIGINAL_COMMAND would be ps.# SSH1, OpenSSH command="/bin/who" ...key...
[117]Older versions of OpenSSH didn't set SSH_ORIGINAL_COMMAND.A quick way to see these variables in action is to print their values with forced commands. For SSH1, create a forced command like the following:
Then connect with an SSH-1 client, supplying a remote command (which will not be executed), such as:# SSH1 only command="/bin/echo You tried to invoke $SSH_ORIGINAL_COMMAND" ...key...
Instead of executing cat, the SSH1 server simply prints:$ ssh1 server.example.com cat /etc/passwd
and exits. Similarly, for SSH2, you can set up a forced command like this:You tried to invoke cat /etc/passwd
and then a client command like:# SSH2 only Key mykey.pub Command "/bin/echo You tried to invoke $SSH2_ORIGINAL_COMMAND"
produces:$ ssh2 server.example.com cat /etc/passwd
You tried to invoke cat /etc/passwd
is rejected. Here's a script that checks for the presence of rm in the command string and, if present, rejects the command:$ ssh server.example.com rm myfile
Save this script in ~/rm-checker, and define a forced command to use it:#!/bin/sh # SSH1 only; for SSH2, use $SSH2_ORIGINAL_COMMAND. # case "$SSH_ORIGINAL_COMMAND" in *rm*) echo "Sorry, rejected" ;; *) $SSH_ORIGINAL_COMMAND ;; esac
Our script is just an example: it isn't secure. It can be easily bypassed by a clever command sequence to remove a file:# SSH1 only command="$HOME/rm-checker" ...key...
which creates a link to /bin/rm with a different name (killer) and then performs the removal. Nevertheless, the concept is still valid: you can examine SSH_ORIGINAL_COMMAND to select another command to execute instead.$ ssh server.example.com '/bin/ln -s /bin/r? ./killer && ./killer myfile'
where log-and-run is the following script. It appends a line to a log file, containing a timestamp and the command attempted:# SSH1 only command="log-and-run" ...key...
#!/bin/sh if [ -n "$SSH_ORIGINAL_COMMAND" ] then echo "`/bin/date`: $SSH_ORIGINAL_COMMAND" >> $HOME/ssh-command-log exec $SSH_ORIGINAL_COMMAND fi
enforces that any SSH-1 connection must come from client.example.com, or else it is rejected. Therefore, if your private key file is somehow stolen, and your passphrase cracked, an attacker might still be stymied if he can't connect from the authorized client machine. If the concept of "from" sounds familiar, you've got a good memory: it's the same access control provided by the AllowUsers keyword for serverwide configuration. [Section 5.5.2.1, "Account access control"] The authorized_keys option, however, is set by you within your account and applies to a single key, while AllowUsers is specified by the system administrator and applies to all connections to an account. Here's an example to demonstrate the difference. Suppose you want to permit connections from remote.org to enter the benjamin account. As system administrator, you can configure this within /etc/sshd_config :# SSH1, OpenSSH from="client.example.com" ...key...
# SSH1, OpenSSH
AllowUsers [email protected]
Using per-account configuration, the user benjamin can configure the
identical setting within his authorized_keys
file, for a particular key only:
Of course, the serverwide setting takes precedence. If the system administrator had denied this access using the DenyUsers keyword:# SSH1, OpenSSH # File ~benjamin/.ssh/authorized_keys from="remote.org" ...key...
# SSH1, OpenSSH
DenyUsers [email protected]
then user benjamin can't override this restriction using the
from option in
authorized_keys.
Just like AllowUsers, the from
option can use the wildcard characters *,
matching any string, and ?, matching any one character:
from="*.someplace.org" Matches any host in the someplace.org domain
It can also match the client IP address, with or without wildcards (though this is not mentioned in the manpage):from="som?pla?e.org" Matches somXplaYe.org but not foo.someXplaYe.org or foo.somplace.org
There may also be multiple patterns, this time separated by commas (AllowUsers employs spaces). No whitespace is allowed. You may also negate a pattern by prefixing it with an exclamation point (!). The exact matching rules are: every pattern in the list is compared to either the client's canonical hostname or its IP address. If the pattern contains only numerals, dots, and wildcards, it is matched against the address, otherwise, the hostname. The connection is accepted if and only if the client matches at least one positive pattern and no negated patterns. So for example, the following rule denies connections from saruman.ring.org, allows connections from other hosts in the domain ring.org, and denies everything else:from="192.220.18.5" from="192.2??.18.*"
while this one again denies saruman.ring.org but allows all other clients:from="!saruman.ring.org,*.ring.org"
SSH1 unfortunately doesn't let you specify arbitrary IP networks using an address and mask, nor by address/number of bits. libwrap does [Section 9.4, "Forwarding Security: TCP-wrappers and libwrap"], but its restrictions apply to all connections, not on a per-key basis. Remember that access control by hostname may be problematic, due to issues with name resolution and security. [Section 3.4.2.3, "Trusted-host authentication (Rhosts and RhostsRSA)"] Fortunately, the from option is just an auxiliary feature of SSH-1 public-key authentication, which provides stronger security than would an entirely hostname-based solution.from="!saruman.ring.org,*"
Name the script (say) ~/ssh2from and install it as an SSH2 forced command, and you're done:#!/bin/sh IP=`echo $SSH2_CLIENT | /bin/awk '{print $1}'` case "$IP" in 24.128.97.204) exec $SHELL ;; 128.220.85.3) echo "Rejected" exit 1 ;; esac
This technique works reliably only for IP addresses, not hostnames. If you trust your name service, however, you can conceivably convert the IP address found in $SSH2_CLIENT to a hostname. On Linux you can use /usr/bin/host for this purpose and, say, accept connections only from client.example.com or the domain niceguy.org:# SSH2 only Key mykey.pub Command "$HOME/ssh2from"
#!/bin/sh IP=`echo $SSH2_CLIENT | /bin/awk '{print $1}'` HOSTNAME=`/usr/bin/host $IP | /bin/awk '{print $5}'` case "$HOSTNAME" in client.example.com) exec $SHELL ;; *.niceguy.org) exec $SHELL ;; *) echo "Rejected" exit 1 ;; esac
sets the environment variable EDITOR to the value emacs, thereby setting the client's default editor for the login session. The syntax following environment= is a quoted string containing a variable, an equals sign, and a value. All characters between the quotes are significant, i.e., the value may contain whitespace:# SSH1, OpenSSH environment="EDITOR=emacs" ...key...
or even a double quote, if you escape it with a forward slash:# SSH1, OpenSSH environment="MYVARIABLE=this value has whitespace in it" ...key...
Also, a single line in authorized_keys may have multiple environment variables set:# SSH1, OpenSSH environment="MYVARIABLE=I have a quote\" in my middle" ...key...
Why set an environment variable for a key? This feature lets you tailor your account to respond differently based on which key is used. For example, suppose you create two keys, each of which sets a different value for an environment variable, say, SPECIAL:# SSH1, OpenSSH environment="EDITOR=emacs",environment="MYVARIABLE=26" ...key...
Now, in your account's shell configuration file, you can examine $SPECIAL and trigger actions specific to each key:# SSH1, OpenSSH environment="SPECIAL=1" ...key... environment="SPECIAL=2" ...key...
Here, we print a custom welcome message for each key user, set an appropriate shell prompt, and in Jane's case, invoke a custom initialization script, ~/.janerc. Thus, the environment option provides a convenient communication channel between authorized_keys and the remote shell.# In your .login file switch ($SPECIAL) case 1: echo 'Hello Bob!' set prompt = 'bob> ' breaksw case 2: echo 'Hello Jane!' set prompt = jane> ' source ~/.janerc breaksw endsw
[118]In an industrial setting, each developer would have an account on the CVS repository machine, so the problem would not exist.You can eliminate this problem by modifying benjamin's file, preceding each developer's key with an environment option. CVS examines the LOGNAME environment variable to get the author's name, so you set LOGNAME differently for each developer's key:
Now, when a given key is used for a CVS check-in, CVS identifies the author of the change by the associated, unique LOGNAME value. Problem solved![119]# SSH1, OpenSSH environment="LOGNAME=dan" ...key... environment="LOGNAME=richard" ...key... ...
[119]Incidentally, the authors used this technique while collaborating on this book.
idle-timeout uses the same notation for time as the IdleTimeout keyword: an integer, optionally followed by a letter indicating the units. For example, 60s is 60 seconds, 15m is fifteen minutes, 2h is two hours, and so forth. If no letter appears, the default unit is seconds. The idle-timeout option overrides any serverwide value set with the Idle Timeout keyword. For example, if the serverwide idle timeout is five minutes:# SSH1, OpenSSH idle-timeout=60s ...key...
but your file sets it to 10 minutes for your account:# SSH1, OpenSSH IdleTimeout 5m
then any connection using this key has an idle timeout of 10 minutes, regardless of the serverwide setting. This feature has more uses than disconnecting absent typists. Suppose you're using an SSH-1 key for an automated process, such as backups. An idle timeout value kills the process automatically if it hangs due to an error.# SSH1, OpenSSH idle-timeout=10m ...key...
Likewise, you can disable agent forwarding if you don't want remote users to travel through your account and onto other computers using the given key. [Section 6.3.5, "Agent Forwarding"] This is done with the no-agent-forwarding option:# SSH1, OpenSSH no-port-forwarding ...key...
# SSH1, OpenSSH no-agent-forwarding ...key...
WARNING: These aren't strong restrictions. As long as you allow shell access, just about anything can be done over the connection. The user need employ only a pair of custom programs that talk to each other across the connection and directly implement port forwarding, agent forwarding, or anything else you thought you were preventing. To be more than just a reminder or mild deterrent, these options must be used together with carefully restricted access on the server side, such as forced commands or a restricted shell on the target account.
The server even sets an environment variable, SSH_TTY, with the name of the tty allocated. For example:# A tty is allocated for this client $ ssh1 server.example.com
When you run a noninteractive command, however, the SSH server doesn't allocate a tty to set SSH_TTY:# After logging in via SSH-1 $ echo $SSH_TTY /dev/pts/1
Suppose you want to give someone SSH-1 access for invoking noninteractive commands but not for running an interactive login session. You've seen how forced commands can limit access to a particular program, but as an added safety precaution, you can also disable tty allocation with the no-pty option:# No tty is allocated $ ssh1 server.example.com /bin/ls
Noninteractive commands will now work normally, but requests for interactive sessions are refused by the SSH1 server. If you try to establish an interactive session, your client prints a warning message, such as:# SSH1, OpenSSH no-pty ...key...
or it appears to hang or fail altogether. Just for fun, let's observe the effect of no-pty on the SSH_TTY environment variable with a simple experiment. Set up a public key and precede it with the following forced command:Warning: Remote host failed or refused to allocate a pseudo-tty. SSH_SMSG_FAILURE: invalid SSH state
Now try connecting noninteractively and interactively, and watch the output. The interactive command gives SSH_TTY a value, but the noninteractive one doesn't:# SSH1, OpenSSH command="echo SSH_TTY is [$SSH_TTY]" ...key...
Next, add the no-pty option:$ ssh1 server.example.com SSH_TTY is [/dev/pts/2] $ ssh1 server.example.com anything SSH_TTY is []
and try connecting interactively. The connection (properly) fails and SSH_TTY has no value:# SSH1, OpenSSH no-pty,command="echo SSH_TTY is [$SSH_TTY]" ...key...
Even if a client requests a tty specifically (with ssh -t), the no-pty option forbids its allocation.$ ssh1 server.example.com Warning: Remote host failed or refused to allocate a pseudo-tty. SSH_TTY is [] Connection to server.example.com closed.
# SSH1, OpenSSH $ ssh -t server.example.com emacs Warning: Remote host failed or refused to allocate a pseudo-tty. emacs: standard input is not a tty Connection to server.example.com closed.
8. Per-Account Server Configuration | 8.3. Trusted-Host Access Control |
Copyright © 2002 O'Reilly & Associates. All rights reserved.