A
=> B
=> C
A
. Our computer~/.ssh/config
is roughly like this:
Host B
ServerAliveInterval 30
IdentifyFile ~/.ssh/id_rsa
User a-user
Port 10022
ForwardAgent yes
B
. Jump host/etc/ssh/sshd_config
is probably like this:
RSAAuthentication yes
PubkeyAuthentication yes
AllowTcpForwarding no
ChallengeResponseAuthentication no
PasswordAuthentication no
ForceCommand "ssh C"
A
's public key is written in authorized_keys
.C
. Target hostA
's public key is written in authorized_keys
.A
can ssh B
using password authentication.A
can ssh B
using public key authentication thanks to agent forwarding.A
cannot ssh B foo
using password authentication. 3 times challenges are rapidly failed.A
cannot ssh -t B foo
using password authentication. Argument foo
is ignored.A
cannot ssh B foo
using public key authentication. Argument foo
is ignored.A
cannot ssh -t B foo
using public key authentication. argument foo
is ignored.I wrote the following complicated ShellScript...
# Overrides native ssh command
ssh() {
# Configuration
local password="PASSWORD"
local specialhost="FUMIDAI"
# Special jump host..?
if [[ "$@" =~ $specialhost ]]; then
# Divide arguments into ssh's and additonal commands' and itself
local sshargs
sshargs=("ssh" "-t")
local reach=false
while (( $# > 0 )); do
if [ "--" = "$1" ]; then
shift
break
elif [[ "$1" =~ ^-[bcDEeFIiLlmOopQRSWw]+$ ]]; then
sshargs=("${sshargs[@]}" "$1")
shift
if (( $# > 0 )); then
sshargs=("${sshargs[@]}" "$1")
shift
fi
elif [[ "$1" =~ ^-.*$ ]]; then
sshargs=("${sshargs[@]}" "$1")
shift
elif ! $reach; then
sshargs=("${sshargs[@]}" "$1")
reach=true
shift
else
break
fi
done
# No additional commands?
if (( $# == 0 )); then
# Automatically input password and interact
expect <(echo '
set timeout -1
set password [lindex $argv 0]
set sshargs [lrange $argv 1 end]
proc are_you_sure {} {
send [gets stdin]
send "\n"
expect "password:" password_input "Please type" are_you_sure
}
proc password_input {} {
global password
send $password
send "\n"
expect "password:" password_input "Last login:" interact
}
eval spawn -noecho command $sshargs
expect "Are you sure" are_you_sure "password:" password_input "Last login:" interact
') $password "${sshargs[@]}"
# We have sub commands!
else
# Generate random boundary
local boundary=$(mktemp -u boundary---------XXXXXXXXXXXXXXXXXXXXXXXXXX)
# Is STDIN owned by terminal?
if [ -t 0 ]; then
# Automatically input password,
# input commands and execute them,
# receive base64 encoded output,
# finally exit when the last boundary appeared.
expect <(echo '
set timeout -1
set password [lindex $argv 0]
set boundary [lindex $argv 2]
set bashargs [lrange $argv 3 [expr 3 + [lindex $argv 1] - 1]]
set sshargs [lrange $argv [expr 3 + [lindex $argv 1]] end]
set boundary_count 0
proc are_you_sure {} {
send [gets stdin]
send "\n"
expect "password:" password_input "Please type" are_you_sure
}
proc password_input {} {
global password
send $password
send "\n"
expect "password:" password_input "Last login:" command_input
}
proc command_input {} {
global boundary
global bashargs
global eb
expect_background "$boundary---" observe_boundary
set eb "echo \"$boundary\"\"---\""
set en "echo \"\""
set args [join $bashargs]
send "$eb && ( $args | base64 ) 2>/dev/null && $eb && $en && $en\n"
interact
}
proc observe_boundary {} {
global boundary
global boundary_count
if { $boundary_count < 1 } {
incr boundary_count
expect_background "$boundary---" observe_boundary
} else {
exit 0
}
}
eval spawn -noecho command $sshargs
expect "Are you sure" are_you_sure "password:" password_input "Last login:" command_input
') $password $# $boundary "$@" "${sshargs[@]}" |
# Filter by boundaries
awk '
BEGIN {
count=0
skip=0
}
$0 ~ "'$boundary'---" {
++count
skip=1
}
count==1 {
if (skip != 1) {
print $0
} else {
skip=0
}
}
' |
# Retrive it! (in OSX "-D" option means decoding, not "-d")
base64 -D
# Piped, or redirected
else
# Encode STDIN
base64 |
# Prevent buffer flooding
(sleep 3 && cat && sleep 3) |
# Automatically input password,
# input commands and execute them,
# decode STDIN and process them and re-encode,
# receive base64 encoded output,
# finally exit when the last boundary appeared.
expect <(echo '
set timeout -1
set password [lindex $argv 0]
set boundary [lindex $argv 2]
set bashargs [lrange $argv 3 [expr 3 + [lindex $argv 1] - 1]]
set sshargs [lrange $argv [expr 3 + [lindex $argv 1]] end]
set boundary_count 0
proc are_you_sure {} {
send [gets stdin]
send "\n"
expect "password:" password_input "Please type" are_you_sure
}
proc password_input {} {
global password
send $password
send "\n"
expect "password:" password_input "Last login:" command_input
}
proc command_input {} {
global boundary
global bashargs
global eb
expect_background "$boundary---" observe_boundary
set eb "echo \"$boundary\"\"---\""
set en "echo \"\""
set args [join $bashargs]
send "$eb && ( base64 -d | $args | base64 ) 2>/dev/null && $eb && $en && $en\n"
fconfigure stdin -blocking 0
while {[gets stdin line] != -1} {
send "$line\n"
}
send \004
interact
}
proc observe_boundary {} {
global boundary
global boundary_count
if { $boundary_count < 1 } {
incr boundary_count
expect_background "$boundary---" observe_boundary
} else {
send "logout\n"
}
}
eval spawn -noecho command $sshargs
expect "Are you sure" are_you_sure "password:" password_input "Last login:" command_input
') $password $# $boundary "$@" "${sshargs[@]}" |
# Filter by boundaries
awk '
BEGIN {
count=0
skip=0
}
$0 ~ "'$boundary'---" {
++count
skip=1
}
count==1 {
if (skip != 1) {
print $0
} else {
skip=0
}
}
' |
# Retrive it! (in OSX "-D" option means decoding, not "-d")
base64 -D
fi
fi
# No, regular ssh
else
# Call native ssh command
command ssh "$@"
fi
}
Works:
ssh B
ssh B ls -A
ssh B cat example.tar.gz | tar -xf - -C /tmp
echo "This will be appeared in STDOUT" | ssh B cat
Doesn't work:
cat example.tar.gz | ssh B tar -xf - -C /tmp
scp
with agent forwardingMy final goal is sending local files to remote machine, so that scp
seems to be an another good choice. However, ssh
works with agent forwarding, while scp
doesn't...
scp
with agent forwarding?Very sorry for long long question. Thank you.
Outputs of cat excample.tar.gz| ssh jgn-mba tar -xf - -C /tmp
:
I trapped | tee ~/Desktop/tee.log |
after expect
to find weird result:
Yes, last boundary didn't appear...
You can't use scp
with agent forwarding because it turns it off explicitly. In most cases, this makes sense: Allowing a system you're copying a file to or from to use your keychain for the duration of the connection is usually an unnecessary risk.
Quoting from the source:
addargs(&args, "%s", ssh_program); addargs(&args, "-x"); addargs(&args, "-oForwardAgent=no"); addargs(&args, "-oPermitLocalCommand=no"); addargs(&args, "-oClearAllForwardings=yes");
You could, of course, build a patched version which didn't do this.
Another option is to build a script which proxies to ssh but leaves out the argument you don't want:
scp -S /path/to/yourscript file host:/path/to/file
...then, /path/to/yourscript
can be something like this:
#!/bin/bash
args=( )
while (( $# )); do
case $1 in
-oForwardAgent=no) : ;;
-oClearAllForwardings=yes) : ;;
*) args+=( "$1" ) ;;
esac
shift
done
exec /usr/bin/ssh "${args[@]}"