oreilly.comSafari Books Online.Conferences.


O'Reilly Book Excerpts: SSH, The Secure Shell: The Definitive Guide

Using SCP Through a Gateway

Related Reading

SSH, The Secure Shell: The Definitive Guide
By Daniel J. Barrett, Richard E. Silverman

by Daniel J. Barrett and Richard E. Silverman

Recall that the command:

$ scp ... S:file ...

actually runs ssh in a subprocess to connect to S and invoke a remote scp server. 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:

  • Problems invoking the ssh subprocess, due to the forced command
  • Authentication difficulties due to lack of a tty

Passing along the remote command

The first problem is that the ssh command on client C sends a command to be executed on server S, that starts the scp server, but now that command is ignored in favor of our forced command. You have to find a way to relay the intended scp server command to S. To accomplish this, modify the authorized_keys file on gateway G, instructing ssh to invoke the command contained in the environment variable SSH_ORIGINAL_COMMAND: See Examining the client's original command

# ~/.ssh/authorized_keys on gateway G
command="ssh -l skipper S $SSH_ORIGINAL_COMMAND" ...key...

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="sh -c '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.


The second scp-related problem is authentication for the second SSH connection, from gateway G to server S. You can't provide a password or passphrase to the second ssh program, since it has no tty allocated. (Actually, you can hack your way around this, but it's ugly and we won't go into it.) So you need a form of authentication that doesn't require user input: either RhostsRSA, or public-key authentication with agent forwarding. RhostsRSA works as is, so if you plan to use it, you can skip to the next section. Public-key authentication, however, has a problem: scp runs ssh with the -a switch to disable agent forwarding. You need to reenable agent forwarding for this to work, and this is surprisingly tricky.

Normally you could turn on agent forwarding in your client configuration file:

# ~/.ssh/config on client C, but this FAILS
ForwardAgent yes

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 :

exec '/usr/local/bin/ssh1', map {$_ eq '-a' ? (  ) : $_} @ARGV;

This runs the real ssh, removing -a from the command line if it's there. Now, give your scp command like this:

scp -S ~/bin/ssh-wrapper ... S:file ...

and it should work.

Daniel J. Barrett has been immersed in Internet technology since 1985. Currently, he is working as a software engineer. He is the author of O'Reilly's Linux Pocket Guide, and the coauthor of two more O'Reilly books: Linux Security Cookbook, and SSH, The Secure Shell: The Definitive Guide.

Richard E. Silverman first touched a computer as a college junior in 1986, when he logged into a DEC-20, typed "MM" to send some mail, and was promptly lost to the world.

View catalog information for SSH, The Secure Shell: The Definitive Guide

Return to

Sponsored by: