-
Notifications
You must be signed in to change notification settings - Fork 2
/
sshproxy.sh
executable file
·353 lines (275 loc) · 7.97 KB
/
sshproxy.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
#!/bin/bash
# SSH Proxy (sshproxy), Copyright (c) 2019, The Regents of the University of California,
# through Lawrence Berkeley National Laboratory (subject to receipt of any required
# approvals from the U.S. Dept. of Energy). All rights reserved.
#
# If you have questions about your rights to use or distribute this software,
# please contact Berkeley Lab's Intellectual Property Office at [email protected].
#
# NOTICE. This Software was developed under funding from the U.S. Department of Energy
# and the U.S. Government consequently retains certain rights. As such, the U.S.
# Government has been granted for itself and others acting on its behalf a paid-up,
# nonexclusive, irrevocable, worldwide license in the Software to reproduce, distribute
# copies to the public, prepare derivative works, and perform publicly and display
# publicly, and to permit other to do so.
#
# See LICENSE for full text.
progname=$(basename $0)
version="1.1.0"
# Save tty state for trap function below
original_tty_state=$(stty -g)
tmpkey=''
tmpcert=''
tmppub=''
pw=''
# Default values
id=nersc # Name of key file
user=$USER # Username
sshdir=~/.ssh # SSH directory
scope="default" # Default scope
url="https://sshproxy.nersc.gov" # hostname for reaching proxy
#############
# Functions
#############
# Error(error string, ...)
#
# prints out error string. Joins multiple arguments with ": "
Error () {
# Slightly complicated print statement so that output consists of
# arguments joined with ": "
printf "$progname: %s" "$1" 1>&2
shift
printf ': %s' "$@" 1>&2
printf "\n" 1>&2
}
# Bail(exit code, error string, ...)
#
# prints out error string and exits with given exit code
Bail () {
# get exit code
exitcode=$1
shift
Error "$@"
# restore terminal to original state, in case we're interrupted
# while reading password
stty $original_tty_state
# Go bye-bye
exit $exitcode
}
# Cleanup()
#
# Cleans up temp files on exit
Cleanup () {
for f in "$tmpkey" "$tmpcert" "$tmppub"
do
if [[ "$f" != "" && -e "$f" ]]; then
/bin/rm -f "$f"
fi
done
}
# Abort ()
#
# Trap on errors otherwise unhandled, does cleanup and exit(1)
Abort () {
Bail 255 "Exited on interrupt/error"
}
Usage () {
if [[ $# -ne 0 ]]; then
printf "$progname: %s\n\n", "$*"
fi
printf "Usage: $progname [-u <user>] [-o <filename>] [-s <scope>] [-c <account>] [-p] [-a] [-x <proxy-url>] [-U <server URL>] [-v] [-h]\n"
printf "\n"
printf "\t -u <user>\tSpecify remote (NERSC) username\n"
printf "\t\t\t(default: $user)\n"
printf "\t -o <filename>\tSpecify pathname for private key\n"
printf "\t\t\t(default: $sshdir/$id)\n"
printf "\t -s <scope>\tSpecify scope (default: '$scope')\n"
printf "\t -p\t\tGet keys in PuTTY compatible (ppk) format\n"
printf "\t -a\t\tAdd key to ssh-agent (with expiration)\n"
printf "\t -c <account>\tSpecify a collaboration account (no default)\n"
printf "\t -x <URL>\tUse socks proxy to connect to sshproxy server.\n"
printf "\t\t\t(format: <protocol>://<host>[:port], see curl manpage\n"
printf "\t\t\tsection on "--proxy" for details)\n"
printf "\t -U <URL>\tSpecify alternate URL for sshproxy server\n"
printf "\t\t\t(generally only used for testing purposes)\n"
printf "\t -v \t\tPrint version number and exit\n"
printf "\t -h \t\tPrint this usage message and exit\n"
printf "\n"
exit 0
}
#############
# Actual code starts here...
#############
# Make sure we cleanup on exit
trap Cleanup exit
trap Abort int kill term hup pipe abrt
# for command-line arguments. In reality, not all of these get used,
# but here for completeness
opt_scope='' # -s
opt_url='' # -U
opt_user='' # -u
opt_out='' # -o
opt_agent=0 # -a
opt_version='' # -v
opt_putty='' # -p
opt_socks='' # -x
# Process getopts. See Usage() above for description of arguments
while getopts "aphvs:k:U:u:o:x:c:" opt; do
case ${opt} in
h )
Usage
;;
v )
printf "$progname v$version\n"
exit 0
;;
s )
opt_scope=$OPTARG
scope=$opt_scope
;;
U )
url=$OPTARG
;;
u )
user=$OPTARG
;;
o )
opt_out=$OPTARG
;;
a )
opt_agent=1
;;
p )
opt_putty="?putty"
;;
x )
opt_socks="--proxy $OPTARG"
;;
c )
opt_collab=$OPTARG
;;
\? )
Usage "Unknown argument"
;;
: )
Usage "Invalid option: $OPTARG requires an argument"
;;
esac
done
# If user has specified a keyfile, then use that.
# Otherwise, if user has specified a scope, use that for the keyfile name
# And if it's the default, then use the "id" defined above ("nersc")
data=''
if [[ "$opt_collab" != "" ]] ; then
if [[ "$opt_scope" == "" ]] ; then
scope="collab"
opt_scope=$opt_collab
fi
data='{"target_user": "'$opt_collab'"}'
fi
if [[ $opt_out != "" ]]; then
idfile=$opt_out
elif [[ "$opt_scope" != "" ]]; then
idfile="$sshdir/$opt_scope"
else
idfile="$sshdir/$id"
fi
certfile="$idfile-cert.pub"
pubfile="$idfile.pub"
# Have user enter password+OTP. Curl can do this, but does not
# provide any control over the prompt
#
# N.B. INPWPROMPT variable is used in Bail() above for when password
# prompt is interrupted by ctrl-c. Otherwise terminal gets left in
# a weird state.
read -r -p "Enter the password+OTP for ${user}: " -s pw
# read -p doesn't output a newline after entry
printf "\n"
# Make temp files. We want them in the same target directory as the
# final keys
tmpdir=$(dirname $idfile)
tmpdir="$tmpdir"
tmpkey="$(mktemp $tmpdir/key.XXXXXX)"
tmpcert="$(mktemp $tmpdir/cert.XXXXXX)"
tmppub="$(mktemp $tmpdir/pub.XXXXXX)"
# And get the key/cert
curl -s -S -X POST $opt_socks $url/create_pair/$scope/$opt_putty \
-d "$data" -o $tmpkey -K - <<< "-u \"${user}:${pw}\""
# Check for error
err=$?
if [[ $err -ne 0 ]] ; then
Bail 1 "Failed." "Curl returned" $err
fi
# Get the first line of the file to check for errors from the
# server
read x < $tmpkey
# Check whether password failed
if [[ "$x" =~ "Authentication failed. Failed login" ]]; then
Error "The sshproxy server said: $x"
Bail 2 "This usually means you did not enter the correct password or OTP"
fi
# Check whether the file appears to contain a valid key
if [[ "$x" == "PuTTY-User-Key-File-2: ssh-rsa" ]]; then
mv $tmpkey $idfile.ppk
printf "Successfully obtained PuTTY Key file %s\n" "$idfile.ppk"
exit
fi
if [[ "$x" != "-----BEGIN RSA PRIVATE KEY-----" ]]; then
Error "Did not get in a proper ssh private key. Output was:"
cat $tmpkey 1>&2
Bail 3 "Hopefully that's informative"
fi
# The private key and certificate are all in one file.
# Extract the cert into its own file, and move into place
grep ssh-rsa $tmpkey > $tmpcert \
&& ssh-keygen -y -f $tmpkey > $tmppub \
&& chmod 600 $tmpkey* \
&& /bin/mv $tmpkey $idfile \
&& /bin/mv $tmppub $pubfile \
&& /bin/mv $tmpcert $certfile
if [[ $? -ne 0 ]]; then
Bail 4 "An error occured after successfully downloading keys (!!!)"
fi
# A few shenanigans to clean up line formatting
valid=$(ssh-keygen -L -f $certfile | grep Valid)
shopt -s extglob
valid=${valid/+( )/}
valid=${valid/Valid/valid}
if [[ $opt_agent -ne 0 ]]; then
# extract expiration date from "valid" line (above)
expiry=${valid/valid*to /}
distro=$(uname)
dateError=0
case $distro in
Darwin|*BSD*)
# Convert the date to epoch
expepoch=$(date -j -f '%FT%T' $expiry +%s)
# get current epoch time
epoch=$(date -j +%s)
;;
Linux|GNU|CYGWIN*)
# Convert the date to epoch
expepoch=$(date -d $expiry +%s)
# get current epoch time
epoch=$(date +%s)
;;
*)
Error "Unrecognized OS; I don't know how to convert a date to epoch time"
dateError=1
;;
esac
# compute the interval between expiration and now
# (minus one second just to be sure)
interval=$(( $expepoch - $epoch - 1 ))
# add the interval to ssh-agent
if [[ $dateError -gt 0 ]]; then
Error "Can't add key to ssh-agent."
elif [[ $interval -gt 0 ]]; then
ssh-add -t $interval $idfile
else
Error "cert $certfile is either expired or otherwise invalid. Expiration read as: $expiry"
fi
fi
# And give the user some feedback
printf "Successfully obtained ssh key %s\n" "$idfile"
printf "Key $idfile is %s\n" "$valid"