Trusted-Host Access Control

A limited type of per-account configuration is possible if you use trusted-host authentication rather than public-key authentication. Specifically, you can permit SSH access to your account based on the client's remote username and hostname via the system files /etc/shosts.equiv and /etc/hosts.equiv, and personal files ~/.rhosts and ~/.shosts. A line like:
+client.example.com jones
permits trusted-host SSH access by the user jones@client.example.com. Since we've already covered the details of these four files, we won't repeat the information in this chapter. [Section 3.4.2.3]
Per-account configuration with trusted-host authentication is similar to using the from option of authorized_keys with public keys. Both may restrict SSH connections from particular hosts. The differences are shown in this table.
Feature
Trusted-Host
Public-Key from
Authenticate by hostname
Yes
Yes
Authenticate by IP address
Yes
Yes
Authenticate by remote username
Yes
No
Wildcards in hostnames and IP
No
Yes
Passphrase required for logins
No
Yes
Use other public-key features
No
Yes
Security
Less
More
To use trusted-host authentication for access control, all the following conditions must be true:
  • ●  Trusted-host authentication is enabled in the server, both at compile time and in the serverwide configuration file.
  • ●  Your desired client hosts aren't specifically excluded by serverwide configuration, e. g., by AllowHosts and DenyHosts.
  • ●  For SSH1, ssh1 is installed setuid root.
    Despite its capabilities, trusted-host authentication is more complex than one might expect. For example, if your carefully crafted .shosts file denies access to sandy@trusted.example. com:
    # ~/.shosts
    -trusted.example.com sandy
    
    but your .rhosts file inadvertently permits access: # ~/.rhosts
    +trusted.example.com
    
    then sandy will have SSH access to your account. Worse, even if you don't have a ~/.rhosts file, the system files /etc/hosts.equiv and /etc/shosts.equiv can still punch a trusted-host security hole into your account against your wishes. Unfortunately, using per-account configuration, there's no way to prevent this problem. Only compile-time or serverwide configuration can disable trusted-host authentication.
    Because of these issues and other serious, inherent weaknesses, we recommend against using the weak form of trusted-host authentication, Rhosts authentication, as a form of per- account configuration. (By default it is disabled, and we approve.) If you require the features of trusted-host authentication, we recommend the stronger form, called RhostsRSAuthentication (SSH1, OpenSSH) or hostbased (SSH2), which adds cryptographic verification of host keys. [Section 3.4.2.3]
Book: SSH, The Secure Shell: The Definitive Guide Section: Chapter 8. Per-Account Server Configuration
8.4 The User rc File
The shell script /etc/sshrc is invoked by the SSH server for each incoming SSH connection. Section 5.6.4 You may define a similar script in your account, ~/.ssh/rc (SSH1, OpenSSH)
or ~/.ssh2/rc (SSH2), to be invoked for every SSH connection to your account. If this file exists, /etc/sshrc isn't run.
The SSH rc file is much like a shell startup file (e.g., ~/.profile or ~/.cshrc), but it executes only when your account is accessed by SSH. It is run for both interactive logins and remote commands. Place any commands in this script that you would like executed when your account is accessed by SSH, rather than an ordinary login. For example, you can run and load your ssh-agent in this file: [Section 6.3.3]
# ~/.ssh/rc, assuming your login shell is the C shell
if ( ! $?SSH_AUTH_SOCK  ) then
  eval `ssh-agent`
  /usr/bin/tty | grep 'not a tty' > /dev/null
  if ( ! $status ) then
    ssh-add
  endif
endif
Like /etc/sshrc, your personal rc file is executed just before the shell or remote command requested by the incoming connection. Unlike /etc/sshrc, which is always processed by the Bourne shell ( /bin/sh), your rc file is processed by your account's normal login shell.

Book: SSH, The Secure Shell: The Definitive Guide Section: Chapter 8. Per-Account Server Configuration
8.5 Summary
Per-account configuration lets you instruct the SSH server to treat your account differently. Using public-key authentication, you can permit or restrict connections based on a client's key, hostname, or IP address. With forced commands, you can limit the set of programs that a client may run in your account. You can also disable unwanted features of SSH, such as port forwarding, agent forwarding, and tty allocation.
Using trusted-host authentication, you can permit or restrict particular hosts or remote users from accessing your account. This uses the files ~/.shosts or (less optimally) ~/.rhosts. However, the mechanism is less secure and less flexible than public-key authentication. 

Port Forwarding and X Forwarding

One of SSH's major benefits is transparency. A terminal session secured by SSH behaves like an ordinary, insecure one (e.g., created by telnet or rsh) once it has been established. Behind the scenes, however, SSH keeps the session secure via strong authentication, encryption, and integrity checking.
In some situations, however, transparency is hard to achieve. A network firewall might be in the way, interfering with certain network traffic you need. Corporate security policies might prohibit you from storing SSH keys on certain machines. Or you might need to use insecure network applications in an otherwise secure environment.
In this chapter, we'll discuss an important feature of SSH, called forwarding or tunneling, that addresses several concerns about transparency:
Securing other TCP/IP applications
SSH can transparently encrypt another application's data stream. This is called port
forwarding.
Securing X window applications
Using SSH, you can invoke X programs on a remote machine and have them appear, securely, on your local display. (This feature of X is insecure ordinarily.) This is called X forwarding, a special case of port forwarding for which SSH has extra support.
SSH forwarding isn't completely transparent, since it occurs at the application level, not the network level. Applications must be configured to participate in forwarding, and a few protocols are problematic to forward (FTP data channels are a notable example). But in most common situations, once a secure tunnel is set up, the participating applications appear to the user to operate normally. For complete application-level transparency, you need a network-level technique, such as IPSEC [Section 1.6.4] or a proprietary VPN
(Virtual Private Network) technology available from various vendors, in host software or dedicated routers. While VPNs provide a more complete solution, they require significantly more work and expense to set up compared to SSH forwarding.
So, when we say "transparent" in this chapter, we mean "transparent to the application, once a little configuration has been done."
page338image920
In this chapter, we discuss using SSH forwarding techniques to allow otherwise prohibited traffic across firewalls. This can be a perfectly legitimate and adequately safe practice if done properly: the firewall prevents unauthorized traffic, while SSH forwarding allows authorized users to bypass the restriction. However, don't forget you are bypassing a security restriction that is in place for a reason. Be sure to follow the guidelines we give for safe SSH forwarding. Also, take care that you aren't violating a company policy by using forwarding. Just because you can do something doesn't automatically mean that it's a good idea. If in doubt, consult with your system administrators.
Book: SSH, The Secure Shell: The Definitive Guide Section: Chapter 9. Port Forwarding and X Forwarding
9.1 What Is Forwarding?
Forwarding is a type of interaction with another network application, as shown in Figure 9- 1. SSH intercepts a service request from some other program on one side of an SSH
connection, sends it across the encrypted connection, and delivers it to the intended recipient on the other side. This process is mostly transparent to both sides of the connection: each believes it is talking directly to its partner and has no knowledge that forwarding is taking place. Even more powerfully, SSH forwarding can achieve certain types of communication that are impossible without it.
Figure 9.1. SSH forwarding
Forwarding isn't a new concept. The basic operation of a terminal connection over a network (say, using telnet) is also a kind of forwarding. In a telnet connection, you sit on one end, your remote shell is on the other, and both sides operate as if directly connected by a serial cable. Nevertheless, sitting in the middle is a cooperating telnet client and server, forwarding bytes back and forth. SSH forwarding is much the same, except SSH plays fancy tricks with the data to add security.
We have also seen another type of SSH forwarding, agent forwarding. [Section 6.3.5] This
let us create SSH connections from one computer, through a second computer, and onto a third using public-key authentication, but without installing our private key on the second machine. To accomplish this, an SSH server pretended to be an SSH agent, while transparently forwarding data to and from a remote agent. This paradigm holds true for TCP port forwarding and X forwarding, as the SSH server transparently masquerades as another network application.
page339image15560
Book: SSH, The Secure Shell: The Definitive Guide Section: Chapter 9. Port Forwarding and X Forwarding
9.2 Port Forwarding
SSH uses TCP/IP as its transport mechanism, usually TCP port 22 on the server machine, as it encrypts and decrypts the traffic passing over the connection. We will now discuss a cool feature that encrypts and decrypts TCP/IP traffic belonging to other applications, on other TCP ports, using SSH. This process, called port forwarding, is largely transparent and quite powerful. Telnet, SMTP, NNTP, IMAP, and other insecure protocols running over TCP can be made secure by forwarding the connections through SSH. Port forwarding is sometimes called tunneling because the SSH connection provides a secure "tunnel" through which another TCP/IP connection may pass.
Suppose you have a home machine H that runs an IMAP-capable email reader, and you want to connect to an IMAP server on machine S to read and send mail. Normally, this connection is insecure, with your mail account password transmitted as plaintext between your mail program and the server. With SSH port forwarding, you can transparently reroute the IMAP connection (found on server S's TCP port 143) to pass through SSH, securely encrypting the data over
[1]
the connection. protection.
The IMAP server machine must be running an SSH server for port forwarding to provide real
In short, with minimal configuration changes to your programs, SSH port forwarding protects arbitrary TCP/IP connections by redirecting them through an SSH session. Port forwarding can even pass a connection safely through a firewall if you configure things properly. Once you start securing your communications with port forwarding, you'll wonder how you ever got along without it. Here are examples of what you can do:
  • ●  Access various kinds of TCP servers (e.g., SMTP, IMAP, POP, LDAP, etc.) across a firewall that prevents direct access.
  • ●  Provide protection for your sessions with these same TCP servers, preventing disclosure or alteration of passwords and other content that would otherwise be sent in the clear as part of the session.
  • ●  Tunnel the control connection of an FTP session, to encrypt your username, password, and commands. (It isn't usually possible to protect the data channels that carry the file contents, though. [Section 11.2])
  • ●  Use your ISP's SMTP servers for sending mail, even if you're connected outside the ISP's network and the ISP forbids mail relaying from your current location. [Section 11.3.2]
    9.2.1 Local Forwarding
    In our earlier example, we had an IMAP server running on machine S, and an email reader on home machine H, and we wanted to secure the IMAP connection using SSH. Let's delve into that example in more detail.

SSH port forwarding is a general proxying mechanism for TCP only. (See Sidebar "TCP Connections" for an overview of TCP concepts.) Forwarding can't work with protocols not
[2]
built on TCP, such as the UDP-based DNS, DHCP, NFS, and NetBIOS, based protocols, such as AppleTalk or Novell's SPX/IPX.
or with non-IP-

TCP Connections
To understand port forwarding, it's important to know some details about TCP, the Transmission Control Protocol. TCP is a fundamental building block of the Internet. Built on top of IP, it is the transport mechanism for many application-level Internet protocols such as FTP, Telnet, HTTP, SMTP, POP, IMAP, and SSH itself.
TCP comes with strong guarantees. A TCP connection is a virtual, full-duplex circuit between two communicating parties, acting like a two-way pipe. Either side may write any number of bytes at any time to the pipe, and the bytes are guaranteed to arrive unaltered and in order at the other side. The mechanisms that implement these guarantees, though, are designed to counter transmission problems in the network, such as routing around failed links, or retransmitting data corrupted by noise or lost due to temporary network congestion. They aren't effective against deliberate attempts to steal a connection or alter data in transit. SSH provides this protection that TCP alone lacks.
If an application doesn't need these guarantees about data integrity and order, or doesn't want the overhead associated with them, another protocol called User Datagram Protocol (UDP) often suffices. It is packet- oriented, and has no guarantees of delivery or packet ordering. Some protocols that run over UDP are NFS, DNS, DHCP, NetBIOS, TFTP, Kerberos, SYSLOG, and NTP.
When a program establishes a TCP connection to a service, it needs two pieces of information: the IP address of the destination machine and a way to identify the desired service. TCP (and UDP) use a positive integer, called a port number, to identify a service. For example, SSH uses port 22, telnet uses port 23, and IMAP uses port 143. Port numbers allow multiple services at the same IP address.
The combination of an IP address and a port number is called a socket. For example, if you run telnet to connect to port 23 on the machine at IP address 128.220.91.4, the socket is denoted "(128.220.91.4,23)." Simply put, when you make a TCP connection, its destination is a socket. The source (client program) also has a socket on its end of the connection, and the connection as a whole is completely defined by the pair of source and destination sockets.
In order for a connection attempt to a socket to succeed, something must be "listening" on that socket. That is, a program running on the destination machine must ask TCP to accept connection requests on that port and to pass the connections on to the program. If you've ever attempted a TCP connection and received the response "connection refused," it means that the remote machine is up and running, but nothing is listening on the target socket.
How does a client program know the target port number of a listening server? Port numbers for many protocols are standardized, assigned by the Internet Assigned Numbers Authority or IANA. (IANA's complete list of port numbers is found at http://www.isi.edu/in-notes/iana/assignments/port-numbers.) For instance, the TCP port number assigned to the NNTP (Usenet news) protocol is 119. Therefore, news servers listen on port 119, and newsreaders (clients) connect to them via port 119. More specifically, if a newsreader is configured to talk to a news server at IP address 10.1.2.3, it requests a TCP connection to the socket (10.1.2.3,119).
Port numbers aren't always hardcoded into programs. Many operating systems let applications refer to protocols by name, instead of number, by defining a table of TCP names and port numbers. Programs can then look up port numbers by the protocol name. Under Unix, the table is often contained in the file /etc/ services or the NIS services map, and queries are performed using the library routines getservbyname () , getservbyport() , and related procedures. Other environments allow servers to register their listening ports dynamically via a naming service, such as the AppleTalk Name Binding Protocol or DNS's WKS and SRV records.
So far, we've discussed the port number used by a TCP server when a TCP client program wants to connect. We call this the target port number. The client also uses a port number, called the source port
page342image424 page342image584 page342image744 page342image904
number, so the server can transmit to the client. If you combine the client's IP address and its source port number, you get the client's socket.
Unlike target port numbers, source port numbers aren't standard. In most cases, in fact, neither the client nor the server cares which source port number is used by the client. Often a client will let TCP select an unused port number for the source. (The Berkeley r-commands, however, do care about source ports. [Section 3.4.2.3]) If you examine the existing TCP connections on a machine with a command such as netstat -a or lsof -i tcp , you will see connections to the well-known port numbers for common services (e. g., 23 for Telnet, 22 for SSH), with large, apparently random source port numbers on the other end. Those source ports were chosen from the range of unassigned ports by TCP on the machines initiating those connections.
Once established, a TCP connection is completely determined by the combination of its source and target sockets. Therefore, multiple TCP clients may connect to the same target socket. If the connections originate from different hosts, the IP address portions of their source sockets will differ, distinguishing the connections. If they come from two different programs running on the same host, TCP on that host ensures they have different source port numbers.
IMAP uses TCP port 143; this means that an IMAP server will be listening for connections on port 143 on the server machine. To tunnel the IMAP connection through SSH, you need to pick a local port on home machine H (between 1024 and 65535) and forward it to the remote socket (S,143). Suppose you randomly pick local port 2001. The
[3]
The -L option specifies local forwarding, in which the TCP client is on the local machine with the SSH client. The option is followed by three values separated by colons: a local port to listen on (2001), the remote machine name or IP address (S), and the remote, target port number (143).
The previous command logs you into S, as it will if you just type ssh S. However, this SSH session has also forwarded TCP port 2001 on H to port 143 on S; the forwarding remains in effect until you log out of the session. To make use of the tunnel, the final step is to tell your email reader to use the forwarded port. Normally your email program connects to port 143 on the server machine, that is, the socket (S,143). Instead, it's configured to connect to port 2001 on home machine H itself, i.e., socket (localhost,2001). So the path of the connection is now as follows:
  1. The email reader on home machine H sends data to local port 2001.
  2. The local SSH client on H reads port 2001, encrypts the data, and sends it through the SSH connection to the SSH server on S.
  3. The SSH server on S decrypts the data and sends it to the IMAP server listening on port 143 on S.
  4. Data is sent back from the IMAP server to home machine H by the same process in reverse.
Port forwarding can be specified only when you create an SSH connection. You can't add a forwarding to an existing SSH connection with any SSH implementation we know of, though there's nothing intrinsic to the SSH protocol that would prevent it, and it would sometimes be a useful feature. Instead of using the -L option to establish a local forwarding, you can use the LocalForward keyword in your client configuration file:
# SSH1, OpenSSH
LocalForward 2001 localhost:143
# SSH2 only
LocalForward "2001:localhost:143"
page342image31232 page342image31392
following command then creates the tunnel:
$ ssh -L2001:localhost:143 S
Note the small syntactic differences. In SSH1 and OpenSSH, there are two arguments: the local port number, and the remote socket expressed as host:port. In SSH2, the expression is just as on the command line, except that it must be enclosed in double quotes. If you forget the quotes, ssh2 doesn't complain, but it doesn't forward the port, either.
Our example with home machine H and IMAP server S can be set up like this:
# SSH1, OpenSSH
Host local-forwarding-example
HostName S
 LocalForward 2001 localhost:143
# Run on home machine H
$ ssh local-forwarding-example
9.2.1.1 Local forwarding and GatewayPorts
In SSH1 and OpenSSH, by default, only the host running the SSH client can connect to locally forwarded ports. This is because ssh listens only on the machine's loopback interface for connections to the forwarded port; that is, it binds the socket (localhost,2001), a.k.a. (127.0.0.1,2001), and not (H,2001). So, in the preceding example, only machine H can use the forwarding; attempts by other machines to connect to (H,2001) get "connection refused." However, ssh for SSH1 and OpenSSH has a command-line option, -g, that disables this restriction, permitting any host to connect to locally forwarded ports:
# SSH1, OpenSSH
$ ssh1 -g -L<localport>:<remotehost>:<remoteport> hostname
The client configuration keyword GatewayPorts also controls this feature; the default value is no, and giving GatewayPorts=yes does the same thing as -g:
# SSH1, OpenSSH
GatewayPorts yes
There's a reason why GatewayPorts and -g are disabled by default: they represent a security risk. [Section 9.2.4.2]
9.2.1.2 Remote forwarding
A remotely forwarded port is just like a local one, but the directions are reversed. This time the TCP client is remote, its server is local, and a forwarded connection is initiated from the remote machine.
Continuing with our example, suppose instead that you are logged into server machine S to begin with, where the IMAP server is running. You can now create a secure tunnel for remote clients to reach the IMAP server on port 143. Once again, you select a random port number to forward (say, 2001 again) and create the tunnel:
$ ssh -R2001:localhost:143 H
The -R option specifies remote forwarding. It is followed by three values, separated by colons as before but interpreted slightly differently. The remote port to be forwarded (2001) is now first, followed by the machine name or IP address (localhost) and port number (143). SSH can now forward connections from (localhost,143) to (H,2001).
Once this command has run, a secure tunnel has been constructed from the port 2001 on the remote machine H, to port 143 on the server machine S. Now any program on H can use the secure tunnel by connecting to (localhost,2001). As before, the command also runs an SSH terminal session on remote machine H, just as ssh H does.
As with local forwarding, you may establish a remote forwarding using a keyword in your client configuration file. The RemoteForward keyword is analogous to LocalForward, with the same syntactic differences between SSH1 and
SSH2:
# SSH1, OpenSSH
RemoteForward 2001 S:143
# SSH2 only
RemoteForward "2001:S:143"
For example, here's the preceding forwarding defined in an SSH2-format configuration file:
# SSH2 only
remote-forwarding-example:
 Host H
 RemoteForward "2001:S:143"
$ ssh2 remote-forwarding-example
9.2.2 Trouble with Multiple Connections
If you use LocalForward or RemoteForward in your configuration file, you might run into a subtle problem. Suppose you have set up a section in your configuration file to forward local port 2001 to an IMAP server:
# SSH1 syntax used for illustration
Host server.example.com
 LocalForward 2001 server.example.com:143
This configuration works fine if you connect once:
$ ssh server.example.com
But if you try to open a second ssh connection to server.example.com at the same time-perhaps to run a different program in another window of your workstation-the attempt will fail:
$ ssh server.example.com
Local: bind: Address already in use
Why does this happen? Because your configuration file section tries to forward port 2001 again but finds that port is already in use ("bound" for listening) by the first instance of ssh. You need some way to make the connection but omit the port forwarding.
SSH1 (but not OpenSSH) provides a solution, the client configuration keyword ClearAllForwardings. From the name, you might think it terminates existing forwardings, but it doesn't. Rather, it nullifies any forwardings specified in the current ssh command. In the previous example, you can connect without forwardings to server.example.com with:
page344image15480
You might think that the GatewayPorts feature discussed in the last section applies equally well to remote port forwardings. This would make sense as a feature, but as it happens, it isn't done. There would have to be a way for the client to communicate this parameter to the server for a given forwarding, and that feature hasn't been included in the SSH protocol. In SSH1 and SSH2, remotely forwarded ports always listen on all network interfaces and accept connections from anywhere. [Section 9.4] The OpenSSH server does accept the GatewayPorts configuration option, and it applies globally to all remote forwardings established by that server.

# SSH1 only
$ ssh1 -o ClearAllForwardings=yes server.example.com
The original tunnel, set up by the first invocation, continues to exist, but ClearAllForwardings prevents the second invocation from attempting to recreate the tunnel. To illustrate the point further, here's a rather silly command:
$ ssh1 -L2001:localhost:143 -o ClearAllForwardings=yes mymachine
The -L option specifies a forwarding, but ClearAllForwardings cancels it. This silly command is identical in
function to:
$ ssh1 mymachine
ClearAllForwardings
may also be placed in your client configuration file, of course. It seems more useful on the
command line, however, where it can be used on the fly without editing a file.
9.2.3 Comparing Local and Remote PortForwarding
The differences between local and remote forwarding can be subtle. It can get a bit confusing to know which kind of forwarding to use in a given situation. The quick rule is look for the TCP client application.
The rest of this section is devoted to dissecting the forwarding process in detail and understanding where this rule comes from.
9.2.3.1 Common elements
Local and remote forwarding can be confusing because of overloaded terminology. In a given port forwarding situation, there are two clients and two servers lying around. We have the SSH client and server programs (e.g., ssh and sshd ), plus the TCP application's client and server programs whose connection you want to protect by port forwarding.
An SSH session has a direction of establishment. That is, you run an SSH client on one machine, and it initiates a session with an SSH server on another. Likewise, a forwarded connection has a direction of establishment: you run an application client on one machine, and it initiates a session with a service on another. These two directions may or may not match. This is the difference between local and remote forwarding. Let's introduce some terminology and provide some diagrams to make sense of this. 

Security issues

Before we begin in-depth examples of forced commands, let's discuss security. On first glance, a forced command seems at least as secure a "normal" SSH connection that invokes a shell. This is because a shell can invoke any program, while a forced command can invoke only one program, the forced command itself. If a forced command is /usr/local/bin/pine, only /usr/local/bin/pine can be invoked.
Nevertheless, there's a caveat. A forced command, carelessly used, may lull you into a sense of false security, believing that you have limited the client's capabilities when you haven't. This occurs if the forced command unintentionally permits a shell escape, i.e., a way to invoke a shell from within the forced command. Using a shell escape, a client can invoke any program available to a shell. Many Unix programs have shell escapes, such as text editors (vi, Emacs), pagers (more, less), programs that invoke pagers (man), news readers (rn), mail readers (such as Pine in the previous example!), and debuggers (adb). Interactive programs are the most common culprits, but even noninteractive commands may run shell commands ( find, xargs, etc.).
When you define a forced command, you probably don't want its key used for arbitrary shell commands. Therefore, we propose the following safety rules for deciding whether a program is appropriate as a forced command:
  • ●  Avoid programs that have shell escapes. Read their documentation carefully. If you still aren't sure, get help.
  • ●  Avoid compilers, interpreters, or other programs that let the user generate and run arbitrary executable code.
  • ●  Treat very carefully any program that creates or deletes files on disk in user-specified locations. This includes not only applications (word processors, graphics programs, etc.) but also command-line utilities that move or copy files (cp, mv, rm, scp, ftp, etc.).
  • ●  Avoid programs with their setuid or setgid bits set, particularly setuid root.
  • ●  If using a script as a forced command, follow traditional rules of safe script writing. Within the
    script, limit the search path to relevant directories (omitting "."), invoke all programs by absolute path, don't blindly execute user supplied strings as commands, and don't make the script setuid
    [4]
anything.
And again, don't invoke any program that has a shell escape.
  • ●  Consider using a restricted shell to limit what the incoming client can do. For example, the restricted shell /usr/lib/rsh (not to be confused with the r-command also called "rsh") can limit the remote directories the client can enter.
  • ●  Associate the forced command with a separate, dedicated SSH key, not the one used for your logins, so you can conveniently disable the key without affecting your login capability.
  • ●  Disable unnecessary SSH features using other options we cover later. Under SSH1, you may disable port forwarding with no-port-forwarding, agent forwarding with no-agent- forwarding, and tty allocation using no-pty.
    Any program may be used as a forced command, but some may be risky choices. In the examples that follow, we cover several of these issues as they're encountered.
    8.2.4.2 Rejecting connections with a custom message
    Suppose you've permitted a friend to access your account by SSH, but now you've decided to disable the access. You can simply remove his key from your authorization file, but here's something fancier. You can
define a forced command to print a custom message for your friend, indicating that his access has been disabled. For example:
# 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!"
Any incoming SSH connection that successfully authenticates with this key causes the following message to be displayed on standard output:
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):
# SSH1, OpenSSH
command="/bin/cat $HOME/go.away" ...
key...
# SSH2 only
Key friend.pub
Command "/bin/cat $HOME/go.away"
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: Don't do this!
command="/bin/more $HOME/go.away" ...
key...
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.
8.2.4.3 Displaying a command menu
Suppose you want to provide limited access to your account, permitting the incoming SSH client to invoke only a few, specific programs. Forced commands can accomplish this. For instance, you can write a shell script that permits a known set of programs to be executed and then run the script as a forced command. A sample script, shown in Example 8-1, permits only three programs to be chosen from a menu.
Example 8.1. Menu Script
#!/bin/sh
/bin/echo "Welcome!
Your choices are:
  1. 1        See today's date
    
  2. 2        See who's logged in
    
  3. 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
When someone accesses your account by public key and invokes the forced command, the script displays:
Welcome!
Your choices are:
  1.  1        See today's date
    
  2.  2        See who's logged in
    
  3.  3        See current processes
    
q Quit
Your choice:
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.
8.2.4.4 Examining the client's original command
As we've seen, a forced command gets substituted for any other command the SSH client might send. If an SSH client attempts to invoke the program ps :
$ ssh1 server.example.com ps
but a forced command is set up to execute "/bin/who" instead:
# SSH1, OpenSSH command="/bin/who" ...key...
then ps is ignored and /bin/who runs instead. Nevertheless, the SSH server does read the original command
[5]
string sent by the client and stores it in an environment variable. For SSH1 and OpenSSH, 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.
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:
# SSH1 only
command="/bin/echo You tried to invoke $SSH_ORIGINAL_COMMAND" ...
key...
Then connect with an SSH-1 client, supplying a remote command (which will not be executed), such as:
$ ssh1 server.example.com cat /etc/passwd
Instead of executing cat, the SSH1 server simply prints:
You tried to invoke cat /etc/passwd
and exits. Similarly, for SSH2, you can set up a forced command like this:
# SSH2 only
Key mykey.pub
Command "/bin/echo You tried to invoke $SSH2_ORIGINAL_COMMAND"
and then a client command like:
$ ssh2 server.example.com cat /etc/passwd
produces:
You tried to invoke cat /etc/passwd
8.2.4.5 Restricting a client's original command
Let's try a slightly more complex example using the environment variable SSH_ORIGINAL_COMMAND. We will create a forced command that examines the environment variable and turns a requested command
the
into another of our choice. For example, suppose you want to permit a friend to invoke remote commands in your account, except for the rm (remove file) command. In other words, a command such as:
$ ssh server.example.com rm myfile
is rejected. Here's a script that checks for the presence of rm in the command string and, if present, rejects the command:
#!/bin/sh
# SSH1 only; for SSH2, use $SSH2_ORIGINAL_COMMAND.
#
case "$SSH_ORIGINAL_COMMAND" in
  *rm*)
    echo "Sorry, rejected"
    ;;
  *)
    $SSH_ORIGINAL_COMMAND
    ;;
esac
Save this script in ~/rm-checker, and define a forced command to use it: # SSH1 only
command="$HOME/rm-checker" ...key...
Our script is just an example: it isn't secure. It can be easily bypassed by a clever command sequence to
remove a file:
$ ssh server.example.com '/bin/ln -s /bin/r? ./killer && ./killer myfile'
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.
8.2.4.6 Logging a client's original command
Another cool use of the "original command" environment variables is to keep a log of commands that are run using a given key. For example:
# SSH1 only command="log-and-run" ...key...
where log-and-run is the following script. It appends a line to a log file, containing a timestamp and the command attempted:
#!/bin/sh
if [ -n "$SSH_ORIGINAL_COMMAND" ]
then
echo "`/bin/date`: $SSH_ORIGINAL_COMMAND" >> $HOME/ssh-command-log
  exec $SSH_ORIGINAL_COMMAND
fi
8.2.4.7 Forced commands and secure copy (scp)
We've seen what happens when ssh encounters a key with a forced command. But what does scp do in this situation? Does the forced command run, or does the copy operation take place?
In this case, the forced command executes, and the original operation (file copy) is ignored. Depending on your needs, this behavior might be good or bad. In general, we do not recommend using scp with any key that has a forced command. Instead, use two keys, one for ordinary logins and file copying and the other for the forced command.
Now that we've thoroughly examined forced commands, let's move on to other features of per-account configuration.
8.2.5 Restricting Access by Host or Domain
Public-key authentication requires two pieces of information: the corresponding private key and its passphrase (if any). Without either piece, authentication can't succeed. Per-account configuration lets you add a third requirement for additional security: a restriction on the client's hostname or IP address. This is done with the from option. For example:
# SSH1, OpenSSH from="client.example.com" ...key...
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] 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
AllowUsers benjamin@remote.org
Using per-account configuration, the user benjamin can configure the identical setting within his authorized_keys file, for a particular key only:
# SSH1, OpenSSH
# File ~benjamin/.ssh/authorized_keys from="remote.org" ...
key...
Of course, the serverwide setting takes precedence. If the system administrator had denied this access using the DenyUsers keyword:
# SSH1, OpenSSH
DenyUsers benjamin@remote.org
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
from="som?pla?e.org"
Matches somXplaYe.org but not foo.someXplaYe.org or foo.somplace.org
It can also match the client IP address, with or without wildcards (though this is not mentioned in the manpage):
from="192.220.18.5"
from="192.2??.18.*"
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="!saruman.ring.org,*.ring.org"
while this one again denies saruman.ring.org but allows all other clients: from="!saruman.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], 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] 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.
8.2.5.1 Simulating "from" with SSH2
Although SSH2 doesn't support the from option, you can create your own host-based access control in SSH2 using a forced command. The trick is to examine the environment variable $SSH2_CLIENT [Section 7.4.4.1] and create a script that performs the following steps:
1. From $SSH2_CLIENT, extract the IP address of the incoming client, which is the first value in the string.
2. Accept or reject the connection based on that IP address and any logic you like.
For example, suppose you want to permit connections from IP address 24.128.97.204 and reject them from 128.220.85.3. The following script does the trick when installed as a forced command:
#!/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
Name the script (say) ~/ssh2from and install it as an SSH2 forced command, and you're done:
# SSH2 only
Key mykey.pub
Command "$HOME/ssh2from"
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:
#!/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
8.2.6 Setting Environment Variables
The environment option instructs the SSH1 server to set an environment variable when a client connects
via the given key. For example, the authorized_keys line: # SSH1, OpenSSH
environment="EDITOR=emacs" ...key...
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="MYVARIABLE=this value has whitespace in it" ...
key...
or even a double quote, if you escape it with a forward slash:
# SSH1, OpenSSH
environment="MYVARIABLE=I have a quote\" in my middle" ...
key...
Also, a single line in authorized_keys may have multiple environment variables set: # SSH1, OpenSSH
environment="EDITOR=emacs",environment="MYVARIABLE=26" ...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="SPECIAL=1" ...key... environment="SPECIAL=2" ...key...
Now, in your account's shell configuration file, you can examine $SPECIAL and trigger actions specific to each key:
# 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
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.
8.2.6.1 Example: CVS and $LOGNAME
As a more advanced example of the environment option, suppose a team of open source software developers around the Internet is developing a computer program. The team decides to practice good software engineering and store its code with CVS, the Concurrent Versions System version control tool. Lacking the funds to set up a server machine, the team places the CVS repository into the computer account of one of the team members, benjamin, since he has lots of available disk space. Benjamin's account is on the SSH server machine cvs.repo.com.
The other developers don't have accounts on cvs.repo.com, so benjamin places their public keys into his authorized_keys file so they can do check-ins. Now there's a problem. When a developer changes a file and checks the new version into the repository, a log entry is made by CVS, identifying the author of the change. But everyone is connecting through the benjamin account, so CVS always identifies the author as "benjamin," no matter who checked in the changes. This is bad from a software engineering standpoint: the
[6]
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:
# SSH1, OpenSSH environment="LOGNAME=dan" ...key... environment="LOGNAME=richard" ...key... ...
Now, when a given key is used for a CVS check-in, CVS identifies the author of the change by the
[7]
The idle-timeout option tells the SSH1 server to disconnect a session that has been idle for a certain time limit. This is just like the IdleTimeout keyword for serverwide configuration but is set by you within your account, instead of by the system administrator. [Section 5.4.3.3]
Suppose you let your friend Jamie access your account by SSH-1. Jamie works in an untrusted environment, however, and you are worried that he might walk away from his computer while connected to your account, and someone else might come by and use his session. One way to reduce the risk is to set an idle timeout on Jamie's key, automatically disconnecting the SSH-1 session after a given period of idle time. If the client stops sending output for a while, Jamie has probably walked away, and the session is terminated.
Timeouts are set in with the idle-timeout option. For example, to set the idle timeout to 60 seconds: # SSH1, OpenSSH
idle-timeout=60s ...key...
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.
author of each change should be clearly identified.
associated, unique LOGNAME value. Problem solved!
8.2.7 Setting Idle Timeout
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
IdleTimeout 5m
but your file sets it to 10 minutes for your account:
# SSH1, OpenSSH idle-timeout=10m ...key...
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.
8.2.8 Disabling Forwarding
Although you're permitting SSH-1 access to your account, you might not want your account to be used as a springboard to other machines by port forwarding. [Section 9.2] To prevent this, use the no-port- forwarding option for that key:
# SSH1, OpenSSH no-port-forwarding ...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] This is done with the no-agent- forwarding option:
# SSH1, OpenSSH no-agent-forwarding ...key...
8.2.9 Disabling TTY Allocation
Normally when you log in via SSH-1, the server allocates a pseudo-terminal (henceforth, tty) for the login session: [Section 7.4.5.5]
page330image13576
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.
# A tty is allocated for this client
$ ssh1 server.example.com
The server even sets an environment variable, SSH_TTY, with the name of the tty allocated. For example:
# After logging in via SSH-1
$ echo $SSH_TTY
/dev/pts/1
When you run a noninteractive command, however, the SSH server doesn't allocate a tty to set SSH_TTY:
# No tty is allocated
$ ssh1 server.example.com /bin/ls
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:
# SSH1, OpenSSH no-pty ...key...
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:
Warning: Remote host failed or refused to allocate a pseudo-tty.
SSH_SMSG_FAILURE: invalid SSH state
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:
# SSH1, OpenSSH
command="echo SSH_TTY is [$SSH_TTY]" ...
key...
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 server.example.com
SSH_TTY is [/dev/pts/2]
$ ssh1 server.example.com anything
SSH_TTY is []
Next, add the no-pty option:
# SSH1, OpenSSH
no-pty,command="echo SSH_TTY is [$SSH_TTY]" ...
key...

and try connecting interactively. The connection (properly) fails and SSH_TTY has no value:
$ ssh1 server.example.com
Warning: Remote host failed or refused to allocate a pseudo-tty.
SSH_TTY is []
Connection to server.example.com closed.
Even if a client requests a tty specifically (with ssh -t), the no-pty option forbids its allocation.
# 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.
[2]
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.
[3]
The name may be changed with the keyword AuthorizationFile in the serverwide configuration file. [Section
5.4.1.6] 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.
[4]
Modern Unix implementations often ignore the setuid bit on scripts for security reasons.
[5]
Older versions of OpenSSH didn't set SSH_ORIGINAL_COMMAND.
[6]
In an industrial setting, each developer would have an account on the CVS repository machine, so the problem
would not exist.
[7]
Incidentally, the authors used this technique while collaborating on this book.