0

I am trying to shorten my work of getting a aws access token and writing it to ~/.aws/credentials file by writing a bash alias like this on my mac :

alias trial='function mfa(){ aws sts get-session-token --serial-number $2 --token-code $1|python3 -c "import sys,subprocess;obj=eval(''.join(sys.stdin.readlines()).replace('\n',''));AccessKeyId=obj['Credentials']['AccessKeyId'];SecretAccessKey=obj['Credentials']['SecretAccessKey'];SessionToken=obj['Credentials']['SessionToken'];subprocess.check_output('aws configure set aws_access_key_id '+AccessKeyId+' --profile mfa', shell=True);subprocess.check_output('aws configure set aws_secret_access_key '+SecretAccessKey+' --profile mfa', shell=True);subprocess.check_output('aws configure set aws_session_token '+SessionToken+' --profile mfa', shell=True);"};mfa'

But for some reason this is not working. Specifically bash compiler is not happy with the part after python3 -c. Can someone help ?

11
  • 1
    I suggest to use a function. See: help function. Commented Jul 19, 2018 at 19:02
  • I am already using function as you can see from my alias string. Commented Jul 19, 2018 at 19:30
  • 2
    why do you need an alias to a function? you can just name the function whatever you want to name the alias. As is, it is very difficult to read. Commented Jul 19, 2018 at 19:33
  • The problem is that you're using single quotes inside the function, but single quotes are also the delimiter around the alias. That's producing invalid syntax. Commented Jul 19, 2018 at 19:59
  • 2
    Don't add your answer to the question; post it as an actual answer. Commented Jul 19, 2018 at 21:33

3 Answers 3

3

I wouldn't use Python for this at all; bash with jq suffices.

mfa() {
  [[ $1 && $2 ]] || {
    echo "Usage: mfa token-code serial-number" >&2
    return 1
  }
  token_json=$(aws sts get-session-token --serial-number "$2" --token-code "$1") || return
  IFS=$'\t' read -r accessKeyId secretAccessKey sessionToken _ < <(
    jq -r '
      .Credentials | [.AccessKeyId, .SecretAccessKey, .SessionToken] | @tsv
    ' <<<"$token_json"
  ) && [[ $accessKeyId && $secretAccessKey && $sessionToken ]] || return
  aws configure set aws_access_key_id "$accessKeyId" --profile mfa || return
  aws configure set aws_secret_access_key "$secretAccessKey" --profile mfa || return
  aws configure set aws_session_token "$sessionToken" --profile mfa
}

If you really want to embed the Python source in your .bashrc, you can do that too:

mfa_py=$(cat <<'END-OF-PYTHON'
# adopting fixes made by Barmar to the Python code here
import sys, subprocess, json

obj=json.loads(sys.stdin.read())
AccessKeyId=obj['Credentials']['AccessKeyId']
SecretAccessKey=obj['Credentials']['SecretAccessKey']
SessionToken=obj['Credentials']['SessionToken']
subprocess.check_output(['aws', 'configure', 'set', 'aws_access_key_id', AccessKeyId, '--profile', 'mfa'])
subprocess.check_output(['aws', 'configure', 'set', 'aws_secret_access_key', SecretAccessKey, '--profile', 'mfa'])
subprocess.check_output(['aws', 'configure', 'set', 'aws_session_token', SessionToken, '--profile', 'mfa'])
END-OF-PYTHON
)

mfa() {
  aws sts get-session-token --serial-number "$2" --token-code "$1" | python -c "$mfa_py"
}

In your original alias definition, quotes that were clearly intended to be literal were being parsed as syntactic by the shell, so they weren't still available to be read by the Python interpreter.

Here, we're using a quoted heredoc to ensure that everything between the <<'END-OF-PYTHON' and the END-OF-PYTHON -- quotes included -- is treated as literal.

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. eventually I ended up putting everything in python file and callig that python file using alias. Edited my question to include the answer.
2

Don't use an alias, just name the function mfa. And put the Python code in a file.

mfa.py:

import sys, subprocess, json

obj=json.loads(sys.stdin.read())
AccessKeyId=obj['Credentials']['AccessKeyId']
SecretAccessKey=obj['Credentials']['SecretAccessKey']
SessionToken=obj['Credentials']['SessionToken']
subprocess.check_output(['aws', 'configure', 'set', 'aws_access_key_id', AccessKeyId, '--profile', 'mfa'])
subprocess.check_output(['aws', 'configure', 'set', 'aws_secret_access_key', SecretAccessKey, '--profile', 'mfa'])
subprocess.check_output(['aws', 'configure', 'set', 'aws_session_token', SessionToken, '--profile', 'mfa'])

Note that I give a list to subprocess.check_output rather than constructing a string, so that shell=True isn't needed.

Then define the function:

mfa() {
    aws sts get-session-token --serial-number "$2" --token-code "$1" | python /path/to/mfa.py
}

I assume the session token is JSON, not Python syntax, so I use json.loads() rather than eval(). And to read all of standard input, use sys.stdin.read() rather than joining readlines(); that creates a list unnecessarily, just to join it back into one long string.

3 Comments

Thanks. eventually I ended up putting everything in python file and callig that python file using alias. Edited my question to include the answer.
@tejas, ...why would you use an alias even then? I probably only see a case where an alias is more fit-to-purpose than a function once a year, if that; and there's nothing about what you're doing here that makes it one of those cases.
Yes, that was stupid of me to be stubborn about using alias. Realized that functions work even better.
0

Thanks everyone for the replies. Eventually I ended up putting everything in a python file like this :

import sys, subprocess, json
output = subprocess.Popen('aws sts get-session-token --serial-number sys.argv[2] --token-code '+sys.argv[1], shell=True,stdout=subprocess.PIPE)
output = json.loads(output.communicate()[0].decode('utf-8').strip())
AccessKeyId=output['Credentials']['AccessKeyId']
SecretAccessKey=output['Credentials']['SecretAccessKey']
SessionToken=output['Credentials']['SessionToken']
subprocess.check_output('aws configure set aws_access_key_id '+AccessKeyId+' --profile mfa', shell=True)
subprocess.check_output('aws configure set aws_secret_access_key '+SecretAccessKey+' --profile mfa', shell=True)
subprocess.check_output('aws configure set aws_session_token '+SessionToken+' --profile mfa', shell=True)

And then on the bash_profile I have an alias :

alias a="python3 ~/bin/python3_mfa.py $1"

And this is working. but if I put some more commands in the alias it stops working. trying to figure that out. for example :

alias a="python3 ~/bin/python3_mfa.py $1;eb ssh env --profile mfa;"

doesn't work.

6 Comments

$1 here isn't referring to the alias's argument, because aliases don't have arguments at all (which is part of why the reflexive answer to questions about aliases in the freenode #bash channel is "if you have to ask, use a function instead"). Try running set -- 'this is $1' 'this is $2' and then running your alias a; you'll see it run python3 ~/bin/python3_mfa.py this is $1;eb ssh env --profile mfa
The equivalent function -- except where $1 actually works -- would be a() { python3 ~/bin/python3_mfa.py "$1"; eb ssh env --profile mfa; }
...an alias is just textual prefix substitution -- they don't have a call stack location. No call stack -> no arguments, no local variables, no return value, etc.
As another aside -- shell=True is dangerous. If someone intercepted your call to AWS and gave you an access key of $(rm -rf ~), you'd have a very, very bad day. There's a reason the code Barmar and I suggested doesn't use it.
The problem you're having with the longer alias is the reason why functions are better than aliases. Functions can process arguments, aliases can't.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.