-
Notifications
You must be signed in to change notification settings - Fork 4
/
session-tool.sh
executable file
·1498 lines (1387 loc) · 56.1 KB
/
session-tool.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
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
SESSION_TOOL_VERSION=1.6.5
PUBURL="https://raw.githubusercontent.com/basefarm/aws-session-tool/master/session-tool.sh"
# Bash utility to manage AWS sessions, please see usage per command or
# https://github.com/basefarm/aws-session-tool
#
# Prerequisits:
#
# A working aws credentials profile, default name: awsops
#
# openssl Used to encrypt/decrypt session state to file
# Only needed if you use the --store or --restore
# options to get_session command
# date On Max OSX it uses the nativ date command
# On Linux it assumes a GNU date compatible version
# aws The aws CLI must be avialable and in the PATH
# curl Used only for getting console URL
# python Used for normalizing JSON
# json.tool Python library for parsing JSON
#
# test, grep, egrep, awk and sed.
#
if test -n "$ZSH_VERSION"; then
PROFILE_SHELL=zsh
export AWS_SESSION_TOOL=$0:A
# Pushx the current parameters into an array
_pushp () {
local i j k
for i in ${(z)AWS_PARAMETERS} ; do
if [ "${(P)i}" != "" ]; then
case $1 in
store* | STORE* ) j="STORED_AWS_PARAMETER_${i}" ;;
temp* | TEMP* ) j="TEMPORARY_AWS_PARAMETER_${i}" ;;
* ) echo "WARN: you can only push to arrays STORED_AWS_PARAMETERS and TEMP_AWS_PARAMETERS"
return 1 ;;
esac
export ${j}="${(P)i}"
fi
done
return 0
}
# Pop an array into the current parameters, skipping the listed parameters
_popp () {
local i j k
for i in ${(z)AWS_PARAMETERS} ; do
if ! [[ "$* " = *"${i} "* ]] ; then
case $1 in
store* | STORE* ) j="STORED_AWS_PARAMETER_${i}" ;;
temp* | TEMP* ) j="TEMPORARY_AWS_PARAMETER_${i}" ;;
* ) echo "WARN: you can only pop from arrays STORED_AWS_PARAMETERS and TEMP_AWS_PARAMETERS"
return 1 ;;
esac
[ "${(P)j}" != "" ] && export ${i}="${(P)j}"
fi
done
return 0
}
#
# Clean up the user environment, only the AWS env variables
#
_aws_reset_vars () {
local i
for i in ${(z)AWS_PARAMETERS} AWS_SECURITY_TOKEN ; do
unset $i
done
return 0
}
#
# Clean up the user environment and remove every trace of an aws session
#
_aws_reset () {
local i j k
for i in ${(z)AWS_PARAMETERS} AWS_SECURITY_TOKEN ; do
j="STORED_AWS_PARAMETER_${i}"
k="TEMPORARY_AWS_PARAMETER_${i}"
unset $i $j $k
done
return 0
}
# Display a set of parameters
_dumpp () {
echo "# Parameter set:"
for i in ${(z)AWS_PARAMETERS} ; do
case $1 in
store* | STORE* )
printf "# %30s : %s\n" "${i}" "${STORED_AWS_PARAMETERS[$i]}" ;;
temp* | TEMP* )
printf "# %30s : %s\n" "${i}" "${TEMP_AWS_PARAMETERS[$i]}" ;;
"" | current )
printf "# %30s : %s\n" "${i}" "${(P)i}" ;;
esac
done
return 0
}
elif test -n "$BASH_VERSION"; then
PROFILE_SHELL=bash
export AWS_SESSION_TOOL="$BASH_SOURCE"
# Pushx the current parameters into an array
_pushp () {
local i j k
for i in ${AWS_PARAMETERS} ; do
if [ "${!i}" != "" ]; then
case $1 in
store* | STORE* ) j="STORED_AWS_PARAMETER_${i}" ;;
temp* | TEMP* ) j="TEMPORARY_AWS_PARAMETER_${i}" ;;
* ) echo "WARN: you can only push to arrays STORED_AWS_PARAMETERS and TEMP_AWS_PARAMETERS"
return 1 ;;
esac
export ${j}="${!i}"
fi
done
return 0
}
# Pop an array into the current parameters, skipping the listed parameters
_popp () {
local i j k
for i in ${AWS_PARAMETERS} ; do
if ! [[ "$* " = *"${i} "* ]] ; then
case $1 in
store* | STORE* ) j="STORED_AWS_PARAMETER_${i}" ;;
temp* | TEMP* ) j="TEMPORARY_AWS_PARAMETER_${i}" ;;
* ) echo "WARN: you can only pop from arrays STORED_AWS_PARAMETERS and TEMP_AWS_PARAMETERS"
return 1 ;;
esac
[ "${!j}" != "" ] && export ${i}="${!j}"
fi
done
return 0
}
#
# Clean up the user environment, only the AWS env variables
#
_aws_reset_vars () {
local i
for i in ${AWS_PARAMETERS} AWS_SECURITY_TOKEN ; do
unset $i
done
return 0
}
#
# Clean up the user environment and remove every trace of an aws session
#
_aws_reset () {
local i j k
for i in ${AWS_PARAMETERS} AWS_SECURITY_TOKEN ; do
j="STORED_AWS_PARAMETER_${i}"
k="TEMPORARY_AWS_PARAMETER_${i}"
unset $i $j $k
done
return 0
}
# Display a set of parameters
_dumpp () {
echo "# Parameter set:"
for i in ${AWS_PARAMETERS} ; do
case $1 in
store* | STORE* )
printf "# %30s : %s\n" "${i}" "${STORED_AWS_PARAMETERS[$i]}" ;;
temp* | TEMP* )
printf "# %30s : %s\n" "${i}" "${TEMP_AWS_PARAMETERS[$i]}" ;;
"" | current )
printf "# %30s : %s\n" "${i}" "${!i}" ;;
esac
done
return 0
}
else
echo >&2 "ERROR: Shell is not bash/zsh, probably csh or tcsh. session_tools will not work."
return -1
fi
# Defaults:
DEFAULT_PROFILE='awsops'
#
# Verify all prerequisites, and initialize arrays etc
#
_vergte() {
printf '%s\n%s' "$2" "$1" | sort -C -V
}
_prereq () {
type curl >/dev/null 2>&1 || { [[ $- =~ i ]] && echo >&2 "ERROR: curl is not found. session_tools will not work." ; }
case $OSTYPE in
darwin* ) _OPENSSL="/usr/bin/openssl";;
msys* ) _OPENSSL="";;
linux* | cygwin* ) _OPENSSL="openssl";;
*) [[ $- =~ i ]] && echo >&2 "ERROR: Unknown ostype: $OSTYPE" ;;
esac
if [ -z "$_PYTHON" ]; then
_PYTHON="python"
type $_PYTHON >/dev/null 2>&1
if [ $? -eq 1 ]; then
type python3 >/dev/null 2>&1
if [ $? -eq 0 ]; then
_PYTHON="python3"
fi
fi
fi
if [ "$_OPENSSL" != "" ]; then
type $_OPENSSL >/dev/null 2>&1 || echo >&2 "ERROR: openssl is not found. session_tools will not work."
fi
type date >/dev/null 2>&1 || echo >&2 "ERROR: date is not found. session_tools will not work."
type aws >/dev/null 2>&1 || echo >&2 "ERROR: aws is not found. session_tools will not work."
type $_PYTHON >/dev/null 2>&1 || echo >&2 "ERROR: $_PYTHON is not found. session_tools will not work."
$_PYTHON -c "import json.tool" >/dev/null 2>&1 || echo >&2 "ERROR: $_PYTHON json.tool is not found. session_tools will not work."
type grep >/dev/null 2>&1 || echo >&2 "ERROR: grep is not found. session_tools will not work."
type egrep >/dev/null 2>&1 || echo >&2 "ERROR: egrep is not found. session_tools will not work."
type awk >/dev/null 2>&1 || echo >&2 "ERROR: awk is not found. session_tools will not work."
type sed >/dev/null 2>&1 || echo >&2 "ERROR: sed is not found. session_tools will not work."
type sort >/dev/null 2>&1 || echo >&2 "ERROR: sort is not found. session_tools will not work."
type readlink >/dev/null 2>&1 || echo >&2 "ERROR: readlink is not found. session_tool will not work."
if [ "$_OPENSSL" != "" ]; then
# Check for pbkdf2 key derivation support
_OPENSSL_ARGS=""
ossl=$($_OPENSSL version)
ossl_dist=$(echo $ossl | awk '{print $1}')
ossl_ver=$(echo $ossl | awk '{print $2}')
if [ "$ossl_dist" = "OpenSSL" ]; then
# Check for OpenSSL version 1.1.1 or newer
if _vergte "$ossl_ver" "1.1.1"; then
_OPENSSL_ARGS="-pbkdf2 -iter 50000"
fi
elif [ "$ossl_dist" = "LibreSSL" ]; then
# Check for LibreSSL version 2.9.1 or newer
if _vergte "$ossl_ver" "2.9.1"; then
_OPENSSL_ARGS="-pbkdf2 -iter 50000"
fi
else
echo >&2 "ERROR: Unknown OpenSSL implementation: $ossl. session_tools may not work."
fi
fi
export AWS_PARAMETERS="AWS_PROFILE AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN AWS_USER AWS_USERNAME AWS_SERIAL AWS_EXPIRATION AWS_EXPIRATION_LOCAL AWS_EXPIRATION_S AWS_ROLE_ALIAS"
return 0
}
_upgrade_check() {
# Check if upgrade needed
local current_second file_second check_file
check_file="${HOME}/.aws/session-tool-update.txt"
if [ ! -d "${HOME}/.aws" ]; then
mkdir "${HOME}/.aws"
chmod 744 "${HOME}/.aws"
fi
if [ ! -e $check_file ]; then
touch $check_file
fi
current_second=$(date +%s)
case $OSTYPE in
darwin*)
eval `/usr/bin/stat -s -t %s $check_file`
file_second=$st_mtime;;
*)
file_second=$(stat --format=%Y $check_file);;
esac
if [ $(( $current_second - $file_second )) -gt 604800 ]; then # One week
PUBVERSION="$(if ! curl --max-time 2 --silent "${PUBURL}"; then echo 'SESSION_TOOL_VERSION=TIMEOUT' ; fi| grep ^SESSION_TOOL_VERSION= | head -n 1 | cut -d '=' -f 2)"
test "${PUBVERSION}" != "${SESSION_TOOL_VERSION}" && test "${PUBVERSION}" != "TIMEOUT" && echo >&2 "WARN: Your version of session-tool is outdated! You have ${SESSION_TOOL_VERSION}, the latest is ${PUBVERSION}"
touch $check_file
fi
return 0
}
_string_to_sec () {
case $OSTYPE in
darwin*)
local _STD_TIME=$(echo "$1" | /usr/bin/sed -E 's/([+|-])([0-9]{2}):([0-9]{2})$/\1\2\3/;s/Z$/+0000/')
local _S=$(/bin/date -j -u -f '%Y-%m-%dT%H:%M:%S%z' $_STD_TIME +%s)
local _LOCAL=$(/bin/date -j -r $_S);;
*)
local _S=$(date -d $1 +%s)
local _LOCAL=$(date -d $1);;
esac
echo "$_S,$_LOCAL"
return 0
}
_sec_to_local () {
case $OSTYPE in
darwin*)
_LOCAL=$(/bin/date -j -r $1);;
*)
_LOCAL=$(date --date="@$1");;
esac
echo "$_LOCAL"
return 0
}
# Function to check age of Access keys
_age_check () {
if [ "$AWS_PROFILE" = "" ]; then
_echoerr "ERROR(_age_check): AWS_PROFILE is not set"
return 1
fi
local CREATED=$(aws iam list-access-keys --profile $AWS_PROFILE --output json | $_PYTHON -mjson.tool | awk -F\" '{if ($2 == "CreateDate") print $4}')
if [ $? -ne 0 ]; then
echo "WARNING: Unable to check access key age."
return 2
fi
local TS=$(_string_to_sec $CREATED)
local SEC=$(echo $TS | awk -F, '{print $1}')
local LOCAL=$(echo $TS | awk -F, '{print $2}')
local NOW=$(date +%s)
local WARN_AGE=$( expr 3600 \* 24 \* 60 )
local ALLOWED_AGE=$( expr 3600 \* 24 \* 90 )
local ALLOWED_AGE_SEC=$( expr $SEC + $ALLOWED_AGE )
local ALLOWED_AGE_LOCAL=$(_sec_to_local $ALLOWED_AGE_SEC)
local AGE=$( expr $NOW - $WARN_AGE )
RED=$(tput setaf 1)
NC=$(tput sgr0)
if [ "$SEC" -lt "$AGE" ]; then
echo -e "${RED}WARNING:${NC} Your Access key is older than 60 days."
echo "The key will expire on: $ALLOWED_AGE_LOCAL"
echo "To rotate, run:"
echo " rotate_credentials -n -p ${AWS_PROFILE}"
return 1
fi
return 0
}
# Command for creating a session
get_session() {
#TODO: Create function and add to get_session to disable git check.
local OPTIND ; local PROFILE="${AWS_PROFILE:-$(aws configure get default.session_tool_default_profile)}"
local STORE=false; local RESTORE=false; local DOWNLOAD=false; local VERIFY=false
local UPLOAD=false ; local STOREONLY=false; local IMPORT; local BUCKET; local EXPORT=false
# Ugly hack to support people who want to store their sessions retroactively
if test "$*" = "-s" ; then STOREONLY=true ; fi
# extract options and their arguments into variables. Help and List are dealt with directly
while getopts ":cdhlp:rsuvi:b:e" opt ; do
case "$opt" in
h ) _get_session_usage ; return 0 ;;
c ) _aws_reset ; return 0 ;;
l )
local now=$(date +%s)
for f in `ls ~/.aws/*.aes`; do
local expiry_s=$(expr $(date -r $f '+%s') + 43200 )
case $OSTYPE in
darwin* ) local expiry_l=$(/bin/date -r $expiry_s '+%H:%M:%S %Y-%m-%d');;
* ) local expiry_l=$(date -d @${expiry_s} '+%H:%M:%S %Y-%m-%d');;
esac
local profile=$(basename $f .aes)
if [ $expiry_s -lt $now ]; then
echo "$profile $expiry_l (EXPIRED)"
else
echo "$profile $expiry_l"
fi
done
return 0 ;;
p ) PROFILE=$OPTARG ;;
s ) STORE=true ;;
r ) RESTORE=true ;;
d ) DOWNLOAD=true ;;
u ) UPLOAD=true ;;
v ) VERIFY=true ;;
i ) IMPORT=$OPTARG ;;
b ) BUCKET=$OPTARG ;;
e ) EXPORT=true ;;
\? ) echo "Invalid option: -$OPTARG" >&2 ;;
: ) echo "Option -$OPTARG requires an argument." >&2 ; return 1 ;;
esac
done
if [ ! -z "${IMPORT}" ]; then
# Import the key found in the file or the variable it self
if [ ! -e "${IMPORT}" ]; then
_KEY_ID="$(echo "${IMPORT}" | awk -F, '{print $1}')"
_KEY_SECRET="$(echo "${IMPORT}" | awk -F, '{print $2}')"
if [ -z "${_KEY_ID}" -o -z "${_KEY_SECRET}" ]; then
_echoerr "ERROR: The file '${IMPORT}' does not exist and it does not contain a valid api key-pair."
return 1
fi
if [ "$PROFILE_SHELL" != "zsh" ]; then
# Remove "this" command from history, as it contains a clear text secret access key
# Not supported zsh
history -d $((HISTCMD-1))
fi
else
_KEY_ID="$(tail -1 "${IMPORT}" | awk -F, '{print $1}')"
_KEY_SECRET="$(tail -1 "${IMPORT}" | awk -F, '{print $2}')"
if [ -z "${_KEY_ID}" -o -z "${_KEY_SECRET}" ]; then
_echoerr "ERROR: The file '${IMPORT}' does not contain a valid api key-pair."
return 1
fi
fi
if [ -z "${PROFILE}" ]; then
# As this is import, possibly for the first time, let's assume the user wants the awsops profile
PROFILE=${DEFAULT_PROFILE}
fi
if [ -z "${BUCKET}" ]; then
BUCKET="$(aws configure get session-tool_bucketname --profile ${PROFILE} 2>/dev/null)"
if [ -z "${BUCKET}" ]; then
_echoerr "ERROR: No roles bucket provided and your profile (${PROFILE}) does not contain one."
return 1
fi
fi
# Assuming the user want to use this profile right away (like supplying -d in addtion to -i)
# Need to clear existing credentials from the shell, to not confuse the process
unset AWS_PROFILE
unset AWS_ACCESS_KEY_ID
unset AWS_SESSION_TOKEN
unset AWS_SECRET_ACCESS_KEY
aws configure --profile "${PROFILE}" set aws_access_key_id "${_KEY_ID}"
aws configure --profile "${PROFILE}" set aws_secret_access_key "${_KEY_SECRET}"
# TODO: This will set the default profile for session tool. If using a multiple profiles
# the user might not want to change his default profile...
aws configure set default.session_tool_default_profile "${PROFILE}"
aws configure set session-tool_bucketname "${BUCKET}" --profile "${PROFILE}"
export AWS_PROFILE="${PROFILE}"
fi
if ${EXPORT} ; then
if test -z "${PROFILE}" ; then
if aws configure list | grep -q '<not set>' ; then
_echoerr "ERROR: No profile specified and no default profile configured."
return 1
else
${PROFILE}="$(aws configure list | grep ' profile ' | awk '{print $2}')"
fi
fi
if aws configure list --profile $PROFILE &>/dev/null ; then
export AWS_PROFILE="${PROFILE}"
else
_echoerr "ERROR: The specified profile ${PROFILE} cannot be found."
return 1
fi
_KEY_ID=$(aws configure --profile "${PROFILE}" get aws_access_key_id)
_KEY_SECRET=$(aws configure --profile "${PROFILE}" get aws_secret_access_key)
BUCKET="$(aws configure get session-tool_bucketname --profile ${PROFILE} 2>/dev/null)"
if [ -z "${BUCKET}" ]; then
_echoerr "ERROR: No roles bucket provided and your profile (${PROFILE}) does not contain one."
return 1
fi
echo "get_session -p ${PROFILE} -i ${_KEY_ID},${_KEY_SECRET} -b ${BUCKET} -d"
return 0
fi
if ! ${STOREONLY} ; then
shift $((OPTIND-1))
if test -z "${PROFILE}" ; then
if aws configure list | grep -q '<not set>' ; then
_echoerr "ERROR: No profile specified and no default profile configured."
return 1
else
${PROFILE}="$(aws configure list | grep ' profile ' | awk '{print $2}')"
fi
fi
if aws configure list --profile $PROFILE &>/dev/null ; then
export AWS_PROFILE="${PROFILE}"
else
_echoerr "ERROR: The specified profile ${PROFILE} cannot be found."
return 1
fi
# Fetch roles from specified bucket - if it is configured....
if ${DOWNLOAD} ; then
if ${UPLOAD} ; then
_echoerr "ERROR: uploading and downloading are mutually exclusive..."
return 1
fi
local ROLEBUCKET
if ! ROLEBUCKET="$(aws configure get session-tool_bucketname --profile ${AWS_PROFILE})" ; then
_echoerr "ERROR: No bucket configure to download roles from. Please configure with: aws configure set session-tool_bucketname <BUCKETNAME> --profile ${AWS_PROFILE}"
return 1
fi
local ROLESFILE
if ! ROLESFILE="$(aws configure get session-tool_rolesfile --profile ${AWS_PROFILE})" ; then
if ! aws s3 ls "${ROLEBUCKET}/session-tool_roles.cfg" | grep -q session-tool_roles.cfg ; then
_echoerr "ERROR: There is no rolesfile configured and no session-tool_roles.cfg in ${ROLEBUCKET}. Maybe ${ROLEBUCKET} is not the right bucket, or you need to configure session-tool_rolesfile?"
return 1
else
ROLESFILE="session-tool_roles.cfg"
fi
fi
if ! aws s3 ls "${ROLEBUCKET}/${ROLESFILE}" --profile ${AWS_PROFILE} | grep -q ${ROLESFILE} ; then
_echoerr "ERROR: There is no ${ROLESFILE} in ${ROLEBUCKET}. Maybe ${ROLEBUCKET} or ${ROLESFILE} is misconfigured?"
return 1
fi
if ! out="$(aws s3 cp "s3://${ROLEBUCKET}/${ROLESFILE}" ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg --profile ${AWS_PROFILE} 2>&1)" ; then
_echoerr "ERROR: ${out}"
_echoerr " Unable to download s3://${ROLEBUCKET}/${ROLESFILE} into ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg"
return 1
else
echo "# Roles downloaded"
return 0
fi
fi
if ${UPLOAD} ; then
if ${DOWNLOAD} ; then
_echoerr "ERROR: uploading and downloading are mutually exclusive..."
return 1
else
local ROLEBUCKET
if ! ROLEBUCKET="$(aws configure get session-tool_bucketname --profile ${AWS_PROFILE})" ; then
_echoerr "ERROR: No bucket configure to upload roles to. Please configure with: aws configure set session-tool_bucketname <BUCKETNAME> --profile ${AWS_PROFILE}"
return 1
fi
if ! ROLESFILE="$(aws configure get session-tool_rolesfile --profile ${AWS_PROFILE})" ; then
_echoerr "ERROR: please configure the rolesfile to upload: aws configure set session-tool_rolesfile <ROLESFILE> --profile ${AWS_PROFILE}"
return 1
fi
if ! test -r ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg ; then
_echoerr "ERROR: missing file to upload ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg"
return 1
fi
# User must assume the role that grants write before running the upload
aws s3 cp ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg "s3://${ROLEBUCKET}/${ROLESFILE}"
echo "# Roles uploaded"
return 0
fi
fi
# Verify session
if $VERIFY; then
if [ $# -gt 0 ]; then
_echoerr "ERROR: Please don't combine verify with other operations."
return 1
fi
if test "${AWS_SESSION_TOKEN}" = "" ; then
_echoerr "ERROR: No session token found, so there is nothing to validate."
return 1
fi
_pushp TEMP_AWS_PARAMETERS
_popp STORED_AWS_PARAMETERS
local response="$(aws sts get-caller-identity 2>&1)"
_popp TEMP_AWS_PARAMETERS
if echo "${response}" | grep -q "security token included in the request is expired" ; then
_echoerr "ERROR: Your session has expired"
return 1
else
return 0
fi
fi
# Restore session
if $RESTORE; then
if $STORE; then
_echoerr "ERROR: You can not both store and restore state in the same run."
return 1
fi
if [ "$_OPENSSL" = "" ]; then
_echoerr "ERROR: Store/restore not supported on GIT bash for Windows"
return 1
fi
if [ $# -gt 0 ]; then
_echoerr "ERROR: You can only combine restore with the profile option."
return 1
fi
if [ ! -e ~/.aws/${AWS_PROFILE}.aes ]; then
_echoerr "ERROR: No saved session found for profile $AWS_PROFILE."
return 1
fi
if [ "$PROFILE_SHELL" = "bash" ]; then
local CREDENTIALS=$($_OPENSSL aes-256-cbc $_OPENSSL_ARGS -d -in ~/.aws/${AWS_PROFILE}.aes)
elif [ "$PROFILE_SHELL" = "zsh" ]; then
local CREDENTIALS=$($_OPENSSL aes-256-cbc $=_OPENSSL_ARGS -d -in ~/.aws/${AWS_PROFILE}.aes)
else
_echoerr "ERROR: Unknown/undefined shell: '$PROFILE_SHELL'"
return 1
fi
if echo "$CREDENTIALS" | egrep -qv "^AWS_"; then
_echoerr "ERROR: Unable to restore your credentials."
return 1
fi
_pushp TEMP_AWS_PARAMETERS
_aws_reset_vars
eval "$CREDENTIALS"
if ! _session_ok; then
_aws_reset_vars
_popp TEMP_AWS_PARAMETERS
return 1
fi
local NOW=$(date +%s)
local EXP_HOUR=$(($AWS_EXPIRATION_S + 3600))
if [ $EXP_HOUR -lt $NOW ]; then
echo "WARNING: Your $AWS_PROFILE stored session will expire at $AWS_EXPIRATION_LOCAL."
fi
local CREDS=$(echo "$CREDENTIALS" | sed 's/^/export /')
eval "$CREDS"
_pushp STORED_AWS_PARAMETERS
return 0
fi
# Making new session, with optional MFA
if [ -z "$AWS_USER" ]; then
_init_aws
fi
if [ "${AWS_PROFILE}" != "${AWS_PROFILE_STORED}" ] ; then
_init_aws
fi
_age_check
_pushp TEMP_AWS_PARAMETERS
local CREDTXT
# If there is an MFA, then it should be numeric and used for the sts get-session-token call
if [ -n "$1" ]; then
# Verify the MFA token code, AWS currently only support 6 numbers
local re='^[0-9][0-9][0-9][0-9][0-9][0-9]$'
if ! [[ "$1" =~ $re ]]; then
_echoerr "ERROR: MFA token code can only consist of 6 numbers."
return 1
fi
local MFA=$1
local JSON=$(aws --output json --profile $AWS_PROFILE sts get-session-token --serial-number=$AWS_SERIAL --token-code $MFA )
else
local JSON=$(aws --output json --profile $AWS_PROFILE sts get-session-token )
# When not using MFA, it is usually by mistake so we issue a warning
_echoerr "# Warning: you did not input an MFA token. Proceed at your own risk."
fi
if [ -z "$JSON" ]; then
_echoerr "ERROR: Unable to obtain session"
return 1
fi
local JSON_NORM=$(echo $JSON | $_PYTHON -mjson.tool)
AWS_SECRET_ACCESS_KEY=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "SecretAccessKey") print $4}')
AWS_SESSION_TOKEN=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "SessionToken") print $4}')
AWS_ACCESS_KEY_ID=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "AccessKeyId") print $4}')
AWS_EXPIRATION=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "Expiration") print $4}')
if [ -z "$AWS_SESSION_TOKEN" ]; then
_echoerr "ERROR: Unable to obtain session"
_popp TEMP_AWS_PARAMETERS
return 1
fi
# Unset ROLE specific variables, as this session is now "plain"
unset AWS_ROLE_ALIAS
local TS=$(_string_to_sec $AWS_EXPIRATION)
export AWS_EXPIRATION_S=$(echo $TS | awk -F, '{print $1}')
export AWS_EXPIRATION_LOCAL=$(echo $TS | awk -F, '{print $2}')
export AWS_ACCESS_KEY_ID AWS_EXPIRATION AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
_pushp STORED_AWS_PARAMETERS
fi
# Store if requested
if $STORE ; then
if [ "$_OPENSSL" = "" ]; then
_echoerr "ERROR: Store/restore not supported on GIT bash for Windows"
return 1
fi
touch ~/.aws/${AWS_PROFILE}.aes
chmod 600 ~/.aws/${AWS_PROFILE}.aes
if [ "$PROFILE_SHELL" = "bash" ]; then
$_OPENSSL enc -aes-256-cbc $_OPENSSL_ARGS -salt -out ~/.aws/${AWS_PROFILE}.aes <<-EOF
AWS_USER='$AWS_USER'
AWS_USERNAME='$AWS_USERNAME'
AWS_SERIAL='$AWS_SERIAL'
AWS_PROFILE='$AWS_PROFILE'
AWS_SECRET_ACCESS_KEY='$AWS_SECRET_ACCESS_KEY'
AWS_SESSION_TOKEN='$AWS_SESSION_TOKEN'
AWS_ACCESS_KEY_ID='$AWS_ACCESS_KEY_ID'
AWS_EXPIRATION='$AWS_EXPIRATION'
AWS_EXPIRATION_S='$AWS_EXPIRATION_S'
AWS_EXPIRATION_LOCAL='$AWS_EXPIRATION_LOCAL'
EOF
elif [ "$PROFILE_SHELL" = "zsh" ]; then
$_OPENSSL enc -aes-256-cbc $=_OPENSSL_ARGS -salt -out ~/.aws/${AWS_PROFILE}.aes <<-EOF
AWS_USER='$AWS_USER'
AWS_USERNAME='$AWS_USERNAME'
AWS_SERIAL='$AWS_SERIAL'
AWS_PROFILE='$AWS_PROFILE'
AWS_SECRET_ACCESS_KEY='$AWS_SECRET_ACCESS_KEY'
AWS_SESSION_TOKEN='$AWS_SESSION_TOKEN'
AWS_ACCESS_KEY_ID='$AWS_ACCESS_KEY_ID'
AWS_EXPIRATION='$AWS_EXPIRATION'
AWS_EXPIRATION_S='$AWS_EXPIRATION_S'
AWS_EXPIRATION_LOCAL='$AWS_EXPIRATION_LOCAL'
EOF
else
_echoerr "ERROR: Unknown/undefined shell: '$PROFILE_SHELL'"
return 1
fi
fi
return 0
}
assume_role () {
local OPTIND
# extract options and their arguments into variables. Help and List are dealt with directly
while getopts ":hl" opt ; do
case "$opt" in
h ) _assume_role_usage ; return 0 ;;
l ) _list_roles ; return 0 ;;
\? ) echo "Invalid option: -$OPTARG" >&2 ;;
: ) echo "Option -$OPTARG requires an argument." >&2 ; return 1 ;;
esac
done
shift $((OPTIND-1))
if ! _check_exists_rolefiles ; then return 1 ; fi
_sts_assume_role $* ; return $?
}
get_console_url () {
local OPTIND
# extract options and their arguments into variables. Help and List are dealt with directly
local CONSOLE=$(_rawurlencode "https://console.aws.amazon.com/")
local OPEN_BROWSER=0
local OPEN_BROWSER_DEFAULT_PROFILE=0
while getopts ":hlodu:" opt ; do
case "$opt" in
h ) _get_console_url_usage ; return 0 ;;
l ) _list_roles ; return 0 ;;
o ) OPEN_BROWSER=1 ;;
d ) OPEN_BROWSER_DEFAULT_PROFILE=1 ;;
u ) CONSOLE=$(_rawurlencode "$OPTARG");;
\? ) echo "Invalid option: -$OPTARG" >&2
return 1 ;;
: ) echo "Option -$OPTARG requires an argument." >&2 ; return 1 ;;
esac
done
shift $((OPTIND-1))
if ! _check_exists_rolefiles ; then return 1 ; fi
if _sts_assume_role $* ; then
local SESSION="{\"sessionId\":\"${AWS_ACCESS_KEY_ID}\",\"sessionKey\":\"${AWS_SECRET_ACCESS_KEY}\",\"sessionToken\":\"${AWS_SESSION_TOKEN}\"}"
local ENCODED_SESSION=$(_rawurlencode ${SESSION})
local URL="https://signin.aws.amazon.com/federation?Action=getSigninToken&Session=${ENCODED_SESSION}"
local SIGNIN_TOKEN=$(curl --silent ${URL} | $_PYTHON -mjson.tool | grep SigninToken | awk -F\" '{print $4}')
local CONSOLE_URI="https://signin.aws.amazon.com/federation?Action=login&Issuer=&Destination=${CONSOLE}&SigninToken=${SIGNIN_TOKEN}"
if [ "$OPEN_BROWSER" = "1" -o "$OPEN_BROWSER_DEFAULT_PROFILE" = "1" ]; then
local CHROME="$(aws configure get session-tool_chrome --profile ${AWS_PROFILE} 2>/dev/null)"
local PROFILE_OPT="--profile-directory=${AWS_ROLE_ALIAS}"
if [ "$OPEN_BROWSER_DEFAULT_PROFILE" = "1" ]; then
PROFILE_OPT="--profile-directory=Default"
fi
case $OSTYPE in
darwin* )
if [ "$CHROME" = "" ]; then
CHROME="/Applications/Google Chrome.app"
fi
open -n -a "$CHROME" --args --no-first-run --no-default-browser-check $PROFILE_OPT "${CONSOLE_URI}" ;;
linux* )
if [ "$CHROME" = "" ]; then
CHROME="/usr/bin/google-chrome"
fi
"$CHROME" --no-first-run --no-default-browser-check $PROFILE_OPT "${CONSOLE_URI}" 2>&1 | head -3 & ;;
cygwin* )
echo "The -o option is not supported on CygWin";;
*) [[ $- =~ i ]] && echo >&2 "ERROR: Unknown ostype: $OSTYPE, supported types are darwin, linux and cygwin" ;;
esac
else
echo "$CONSOLE_URI"
fi
_popp TEMP_AWS_PARAMETERS
else
return 1
fi
return 0
}
_check_exists_rolefiles () {
local PROFILE="${AWS_PROFILE:-$(aws configure get default.session_tool_default_profile)}"
if [ ! -e ~/.aws/${PROFILE}_session-tool_roles.cfg ]; then
if [ ! -e ~/.aws/${PROFILE}_roles.cfg ]; then
_echoerr "ERROR: Neither ~/.aws/${PROFILE}_session-tool_roles.cfg nor ~/.aws/${PROFILE}_roles.cfg found, please run get_session -d or create ~/.aws/${PROFILE}_roles.cfg"
return 1
fi
fi
return 0
}
_check_exists_profile () {
local PROFILE="${AWS_PROFILE:-$(aws configure get default.session_tool_default_profile)}"
if test -z "$PROFILE" ; then
return 1
fi
return 0
}
_list_roles () {
local PROFILE="${AWS_PROFILE:-$(aws configure get default.session_tool_default_profile)}"
if _check_exists_profile ; then
if _check_exists_rolefiles ; then
find ~/.aws/ -iname ${PROFILE}_roles.cfg -or -iname ${PROFILE}_session-tool_roles.cfg 2>/dev/null | xargs cat | egrep -hv -e "^#" -e "^$" | sort -u | awk '{print $1}'
else
return 1
fi
else
if [ ! -z "$(find ~/.aws/ -iname \*_roles.cfg)" ] ; then
echo "# INFO: No AWS_PROFILE specified (can be set by get_session, or a default profile"
echo " can be defined with aws configure set default.session_tool_default_profile)"
echo "# but some profiles were located, so showing all roles defined:"
(find ~/.aws/ -iname \*_session-tool_roles.cfg ; find ~/.aws/ -iname \*_roles.cfg -not -iname \*_session-tool_roles.cfg) | xargs cat | egrep -hv -e "^#" -e "^$" | sort -u | awk '{print $1}'
else
_echoerr "ERROR: Unable to determine profile. Either specify AWS_PROFILE, do a get_session, or set a default profile with aws configure set default.session_tool_default_profile"
return 1
fi
fi
return 0
}
_sts_assume_role () {
if ! _session_ok STORED ; then
((DBG)) && echo $STORED_AWS_PARAMETER_EXPIRATION_LOCAL
return 1
fi
_pushp TEMP_AWS_PARAMETERS
_popp STORED_AWS_PARAMETERS
if [ -z "$1" ]; then
if [ -z "$AWS_ROLE_ALIAS" ]; then
_echoerr "ERROR: Missing mandatory role alias name."
_popp TEMP_AWS_PARAMETERS
return 1
fi
else
STORED_AWS_PARAMETER_AWS_ROLE_ALIAS="$1"
fi
export AWS_ROLE_ALIAS=${1:-$AWS_ROLE_ALIAS}
read tmp ROLE_ARN SESSION_NAME EXTERNAL_ID <<< $(cat ~/.aws/${AWS_PROFILE}_roles.cfg ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg 2>/dev/null | egrep -m 1 "^${AWS_ROLE_ALIAS} ")
if [ -z "$ROLE_ARN" ]; then
_echoerr "ERROR: Missing role_arn in ~/.aws/${AWS_PROFILE}_roles.cfg ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg"
_popp TEMP_AWS_PARAMETERS
return 1
fi
if [ -z "$SESSION_NAME" ]; then
_echoerr "ERROR: Missing session_name in ~/.aws/${AWS_PROFILE}_roles.cfg ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg"
_popp TEMP_AWS_PARAMETERS
return 1
fi
if [ -z "$EXTERNAL_ID" ]; then
local JSON=$(aws --output json sts assume-role --role-arn "$ROLE_ARN" --role-session-name "$AWS_USERNAME")
else
local JSON=$(aws --output json sts assume-role --role-arn "$ROLE_ARN" --role-session-name "$AWS_USERNAME" --external-id "$EXTERNAL_ID")
fi
if [ -z "$JSON" ]; then
_echoerr "ERROR: Unable to obtain session"
return 1
fi
local JSON_NORM=$(echo $JSON | $_PYTHON -mjson.tool)
AWS_SECRET_ACCESS_KEY=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "SecretAccessKey") print $4}')
AWS_SESSION_TOKEN=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "SessionToken") print $4}')
AWS_ACCESS_KEY_ID=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "AccessKeyId") print $4}')
AWS_EXPIRATION=$(echo "$JSON_NORM" | awk -F\" '{if ($2 == "Expiration") print $4}')
if [ -z "$AWS_SESSION_TOKEN" ]; then
_echoerr "ERROR: Unable to obtain session"
_popp TEMP_AWS_PARAMETERS
return 1
fi
local TS=$(_string_to_sec $AWS_EXPIRATION)
export AWS_EXPIRATION_S=$(echo $TS | awk -F, '{print $1}')
export AWS_EXPIRATION_LOCAL=$(echo $TS | awk -F, '{print $2}')
return 0
}
aws-assume-role () {
get_session -f -p $1 $3
assume_role $2
get_console_url
}
#
# Help descriptions
#
_get_session_usage() {
echo "Usage: get_session [-h] [-s] [-r] [-l] [-c] [-d|-u] [-v] [-i <file> -b <bucket>|-e] [-p <profile>] [<MFA token>]"
echo ""
echo " <MFA token> Your one time token. If not provided, and you provided"
echo " the -s option, the current credentials are stored."
echo " -p <profile> The aws credentials profile to use as an auth base."
echo " The provided profile name will be cached, and be the"
echo " new default for subsequent calls to get_session."
echo " Current cached profile: $PROFILE"
echo " To avoid having to enter a profile every time, you can"
echo " use 'aws configure set default.session_tool_default_profile <PROFILE>'"
echo " -s Save the resulting session to persistent storage"
echo " for retrieval by other shells. You will be prompted"
echo " twice for a passphrase to protect the stored credentials."
echo " Note that storing with an empty passphrase does not work."
echo " -r Restore previously saved state. You will be promptet for"
echo " the passphrase you stated when storing the session."
echo " -l List currently stored sessions including a best guess on"
echo " when the session expires based on file modification time."
echo " -c Resets session, removing all environment variables."
echo " -d Download a list of organization-wide roles to a profile-"
echo " specific file ~/.aws/[profile]_session-tool_roles.cfg"
echo " These entries can be overwritten in ~/.aws/[profile]_roles.cfg"
echo " Fetching is done before getting the session token, using only"
echo " the permissions granted by the profile."
echo " Upstream location and name of the roles list are configurable."
echo " Cannot be combined with other options."
echo " -u Uploads ~/.aws/[profile]_session-tool_roles.cfg to the"
echo " configured location. Requires more priviledges than download,"
echo " so is usually done after assume-role. Cannot be combined with"
echo " other options."
echo " -v Verifies that the current session (not profile) is valid"
echo " and not expired."
echo " -i <file> Import csv file containing api key into your aws profile."
echo " This will create or replace your api key in the awsops profile."
echo " Also used to import from the output generated by the below export."
echo " -e Export. Output a command line suitable for import on another host."
echo " -b <bucket> Set bucket name during import for roles file."
echo " -h Print this usage."
echo ""
echo "This command will on a successful authentication return session credentials"
echo "for the Basefarm main account. The credentials are returned in the form of"
echo "environment variables suitable for the aws and terraform cli. The returned"
echo "session has a duration of 12 hours."
echo ""
echo "At least one of -s, -r or MFA token needs to be provided."
echo ""
echo "Session state is stored in ~/.aws/${PROFILE}.aes, encrypted with a passphrase."
echo ""
echo "See also: get_console_url, assume_role, rotate_credentials."
echo "Version: $SESSION_TOOL_VERSION"
return 0
}
_assume_role_usage () {
local ROLE_ALIAS_DEFAULT=${STORED_AWS_PARAMETER_AWS_ROLE_ALIAS:-'<no cached value>'}
echo "Usage: assume_role [-h] [-l] <role alias>"
echo ""
echo " -h Print this usage."
echo " -l List available role aliases."
echo " role alias The alias of the role to assume."
echo " The alias name will be cached, so subsequent calls to"
echo " assume_role or get_console_url will use the cached value."
echo " Current cached default: $ROLE_ALIAS_DEFAULT"
echo ""
echo "This command will use session credentials stored in the shell"
echo "from previous calls to get_session The session credentials are"
echo "then used to assume the given role."
echo ""
echo "The session credentials for the assumed role will replace the"
echo "current session in the shell environment. The only way to retrieve"
echo "the current session after an assume_role is to have stored your"
echo "session using get_session with the -s option and then to"
echo "import them again using get_session -r command."
echo ""
echo "The assumed role credentials will only be valid for one hour,"
echo "this is a limitation in the underlaying AWS assume_role function."
echo ""
echo "The selected role alias will be cached in the AWS_ROLE_ALIAS environment"
echo "variable, so you do not have to provide it on subsequent calls to assume_role."
echo ""
echo "Roles are configured in locally in ~/.aws/${AWS_PROFILE}_roles.cfg, and"
echo "organization-wide in ~/.aws/${AWS_PROFILE}_session-tool_roles.cfg. The format of that file"
echo "is as follows. Comment lines begin with #. No other type of comments"
echo "are allowed. One line per role and each line is space separated."
echo "The role alias is a name you choose as a shortname for the role."
echo "external_id is optional."
echo ""
echo "Alias role_arn session_name external_id"
echo ""
echo "Example:"
echo "# Roles for assume_role"
echo "# Alias role_arn session_name external_id"
echo "bf-awsopslab-admin arn:aws:iam::1234567890:role/admin bf-awsopslab-admin BF-AWSOpsLab"
echo "foo-test arn:aws:iam::0987654321:role/admin bf-awsopslab-admin"
echo ""
echo "See also: get_session, get_console_url."
return 0
}
_get_console_url_usage () {
local ROLE_ALIAS_DEFAULT=${STORED_AWS_PARAMETER_AWS_ROLE_ALIAS:-'<no cached value>'}
echo "Usage: get_console_url [-h] [-l] [-o|-d] [-u <url>] <role alias>"
echo ""
echo " -h Print this usage."
echo " -l List available role aliases."
echo " -o Open URL in browser using a role specific profile."
echo " -d Open URL in browser using the Default profile."
echo " -u <url> Open the specific URL and not the default AWS dashboard."
echo " role alias The alias of the role that will temporarily be assumed."
echo " The alias name will be cached, so subsequent calls to"
echo " assume_role or get_console_url will use the cached value."
echo " Current cached default: $ROLE_ALIAS_DEFAULT"