# Execute on client C $ ssh G
# Execute on gateway G $ ssh S
you must run a remote ssh on gateway G that in turn contacts server S:# Execute on client C $ ssh S /bin/ls
This isn't only annoying but also can complicate automation. Imagine rewriting all your SSH-based scripts to accommodate this environment. Fortunately, SSH configuration is flexible enough to afford a neat solution, which we now present using SSH1 features and syntax. We use public-key authentication to take advantage of the options of the authorized_keys file, and ssh-agent with agent forwarding so that authentication passes on transparently to the second SSH connection (see Figure 11-15).# Execute on client C $ ssh G "ssh S /bin/ls"
Next, on gateway G, associate a forced command with your chosen key to invoke an SSH connection to server S: [Section 8.2.4, "Forced Commands "]# ~/.ssh/config on client C host S hostname G user gilligan
Now, when you invoke the command ssh S on client C, it connects to gateway G, runs the forced command automatically, and establishes a second SSH session to server S. And thanks to agent forwarding, authentication from G to S happens automatically, assuming you've loaded the appropriate key. This can be the same key you used to access gilligan@ G or a different one.[144]# ~/.ssh/authorized_keys on gateway G command="ssh -l skipper S" ...key..
[144]Note that if you want to use this setup for an interactive connection, you need to use the -t option to ssh, to force it to allocate a tty on G. It doesn't normally do that, because it doesn't have any way to know that the remote command -- in this case, another instance of ssh -- needs one.This trick not only provides a transparent connection from client C to server S, it also sidesteps the fact that the name S might not have any meaning on client C. Often in this kind of network situation, your internal network naming scheme is cut off from the outside world (e.g., split DNS with internal roots). After all, what's the point of allowing you to name hosts you can't reach? Thanks to the Host configuration keyword for SSH clients, you can create a nickname S that instructs SSH to reach that host transparently via G. [Section 7.1.3.5, "Making nicknames for hosts"]
actually runs ssh in a subprocess to connect to S and invoke a remote scp server. Section 3.8.1, "scp1 Details" Now that we've gotten ssh working from client C to server S, you'd expect that scp would work between these machines with no further effort. Well, it almost does, but it wouldn't be software if there weren't a couple of small problems to work around:$ scp ... S:file ...
Now the forced command invokes the proper scp-related command on server S. You aren't quite done, however, because this forced command unfortunately breaks our existing setup. It works fine for ssh invocations on client C that run a remote command (e.g., ssh S /bin/ls), but it fails when ssh S is invoked alone to run a remote shell. You see, SSH_ORIGINAL_COMMAND is set only if a remote command is specified, so ssh S dies because SSH_ORIGINAL_COMMAND is undefined. You can work around this problem using the Bourne shell and its parameter substitution operator :- as follows:# ~/.ssh/authorized_keys on gateway G command="ssh -l skipper S $SSH_ORIGINAL_COMMAND" ...key...
The expression ${SSH_ORIGINAL_COMMAND:-} returns the value of $SSH_ORIGINAL_COMMAND if it is set, or the empty string otherwise. (In general, ${V:-D} means "return the value of the environment variable V or the string D if V isn't set." See the sh manpage for more information.) This produces precisely the desired behavior, and ssh and scp commands both work properly now from client C to server S.# ~/.ssh/authorized_keys on gateway G command="sh -c 'ssh -l skipper S ${SSH_ORIGINAL_COMMAND:-}'" ...key...
[145]Actually, you can hack your way around this, but it's ugly and we won't go into it.Normally you could turn on agent forwarding in your client configuration file:
but this doesn't help because the -a on the command line takes precedence. Alternatively, you might try the -o option of scp, which can pass along options to ssh, such as -o ForwardAgent yes. But in this case, scp places the -a after any -o options it passes where it takes precedence, so that doesn't work either. There is a solution, though. scp has a -S option to indicate a path to the SSH client program it should use, so you create a "wrapper" script that tweaks the SSH command line as needed, and then make scp use it with -S. Place the following script in an executable file on client C, say ~/bin/ssh-wrapper :# ~/.ssh/config on client C, but this FAILS ForwardAgent yes
This runs the real ssh, removing -a from the command line if it's there. Now, give your scp command like this:#!/usr/bin/perl exec '/usr/local/bin/ssh1', map {$_ eq '-a' ? ( ) : $_} @ARGV;
and it should work.scp -S ~/bin/ssh-wrapper ... S:file ...
This connects to server S by carrying the second SSH connection (from C to S) inside a port-forwarding channel of the first (from C to G ). You can make this more transparent by creating a nickname S in your client configuration file:# Execute on client C $ ssh -L2001:S:22 G # Execute on client C in a different shell $ ssh -p 2001 localhost
Now the earlier commands become:# ~/.ssh/config on client C host S hostname localhost port 2001
Because this technique requires a separate, manual step to establish the port forwarding, it is less transparent than the one in [Section 11.5.1, "Making Transparent SSH Connections"]. However, it has some advantages. If you plan to use port or X forwarding between C and S with the first method, it's a little complicated. scp not only gives the -a switch to ssh to turn off agent forwarding, but also it gives -x and -o "ClearAllForwardings yes", turning off X and port forwarding. So you need to modify the earlier wrapper script to remove these unwanted options as well. [Section 11.5.2.2, "Authentication"] Then, for port forwarding you need to set up a chain of forwarded ports that connect to one another. For example, to forward port 2017 on client C to port 143 (the IMAP port) on server S:# Execute on client C $ ssh -L2001:S:22 G # Execute on client C in a different shell $ ssh S
This works, but it's difficult to understand, error-prone, and fragile: if you trigger the TIME_WAIT problem [Section 9.2.9.1, "The TIME_WAIT problem"], you have to edit files and redo the tunnel just to pick a new ephemeral port to replace 1234. Using the SSH-in-SSH technique instead, your port and X-forwarding options operate directly between client C and server S in the usual, straightforward manner. The preceding example becomes:# ~/.ssh/config on client C host S hostname G user gilligan # ~/.ssh/authorized_keys on gateway G command="ssh -L1234:localhost:143 skipper@S" ...key... # Execute on client C $ ssh -L2017:localhost:1234 S
This final command connects to server S, forwarding local port 2017 to the IMAP port on S.# ~/.ssh/config on client C host S hostname localhost port 2001 # Execute on client C $ ssh -L2001:S:22 G # Execute on client C in a different shell $ ssh -L2017:localhost:143 S
11.4. Kerberos and SSH | 12. Troubleshooting and FAQ |
Copyright © 2002 O'Reilly & Associates. All rights reserved.