Skip to content

Commit

Permalink
Print mostly to stderr so users can source the export line
Browse files Browse the repository at this point in the history
This makes it possible to do things like:

    source <(aws-google-auth --print-creds)

... to avoid the copy-and-paste routine that would otherwise be
necessary to set the various shell variables.
  • Loading branch information
benley committed Oct 28, 2020
1 parent d473d67 commit 50aeb0a
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 38 deletions.
9 changes: 3 additions & 6 deletions aws_google_auth/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
#!/usr/bin/env python
from __future__ import print_function

import argparse
import base64
import os
import sys
import logging

import keyring
from six import print_ as print
from tzlocal import get_localzone

from aws_google_auth import _version
Expand Down Expand Up @@ -77,7 +74,7 @@ def cli(cli_args):
config = resolve_config(args)
process_auth(args, config)
except google.ExpectedGoogleException as ex:
print(ex)
logging.error(ex)
sys.exit(1)
except KeyboardInterrupt:
pass
Expand Down Expand Up @@ -274,8 +271,8 @@ def process_auth(args, config):
else:
config.role_arn, config.provider = util.Util.pick_a_role(roles)
if not config.quiet:
print("Assuming " + config.role_arn)
print("Credentials Expiration: " + format(amazon_client.expiration.astimezone(get_localzone())))
util.Util.message("Assuming " + config.role_arn)
util.Util.message("Credentials Expiration: " + format(amazon_client.expiration.astimezone(get_localzone())))

if config.print_creds:
amazon_client.print_export_line()
Expand Down
2 changes: 2 additions & 0 deletions aws_google_auth/amazon.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python
from __future__ import print_function

import base64
import boto3
Expand Down Expand Up @@ -72,6 +73,7 @@ def print_export_line(self):
self.session_token,
self.expiration.strftime('%Y-%m-%dT%H:%M:%S%z'))

# Print to stdout (not stderr) so the output can be sourced by a shell.
print(formatted)

@property
Expand Down
40 changes: 18 additions & 22 deletions aws_google_auth/google.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import print_function

import base64
import io
import json
Expand All @@ -15,10 +13,10 @@
from distutils.spawn import find_executable
from bs4 import BeautifulSoup
from requests import HTTPError
from six import print_ as print
from six.moves import urllib_parse, input
from six.moves import urllib_parse

from aws_google_auth import _version
from aws_google_auth.util import Util

# The U2F USB Library is optional, if it's there, include it.
try:
Expand Down Expand Up @@ -377,7 +375,7 @@ def handle_captcha(self, sess, payload):
if find_executable('xv') is None and find_executable('display') is None:
open_image = False

print("Please visit the following URL to view your CAPTCHA: {}".format(captcha_url))
Util.message("Please visit the following URL to view your CAPTCHA: {}".format(captcha_url))

if open_image:
try:
Expand All @@ -387,10 +385,7 @@ def handle_captcha(self, sess, payload):
except Exception:
pass

try:
captcha_input = raw_input("Captcha (case insensitive): ") or None
except NameError:
captcha_input = input("Captcha (case insensitive): ") or None
captcha_input = Util.get_input("Captcha (case insensitive): ")

# Update the payload
payload['identifier-captcha-input'] = captcha_input
Expand Down Expand Up @@ -475,7 +470,7 @@ def handle_sk(self, sess):
if attempts_remaining <= 0:
break
else:
input(
Util.get_input(
"Insert your U2F device and press enter to try again..."
)
attempts_remaining -= 1
Expand Down Expand Up @@ -531,7 +526,7 @@ def handle_sms(self, sess):
response_page = BeautifulSoup(sess.text, 'html.parser')
challenge_url = sess.url.split("?")[0]

sms_token = input("Enter SMS token: G-") or None
sms_token = Util.get_input("Enter SMS token: G-") or None

payload = {
'challengeId':
Expand Down Expand Up @@ -597,7 +592,7 @@ def handle_prompt(self, sess):

self.check_prompt_code(response_page)

print("Open the Google App, and tap 'Yes' on the prompt to sign in ...")
Util.message("Open the Google App, and tap 'Yes' on the prompt to sign in ...")

self.session.headers['Referer'] = sess.url

Expand Down Expand Up @@ -673,7 +668,7 @@ def check_prompt_code(response):
"""
num_code = response.find("div", {"jsname": "EKvSSd"})
if num_code:
print("numerical code for prompt: {}".format(num_code.string))
Util.message("numerical code for prompt: {}".format(num_code.string))

def handle_totp(self, sess):
response_page = BeautifulSoup(sess.text, 'html.parser')
Expand All @@ -682,7 +677,7 @@ def handle_totp(self, sess):
challenge_url = sess.url.split("?")[0]
challenge_id = challenge_url.split("totp/")[1]

mfa_token = input("MFA token: ") or None
mfa_token = Util.get_input("MFA token: ") or None

if not mfa_token:
raise ValueError(
Expand All @@ -709,12 +704,12 @@ def handle_totp(self, sess):
def handle_iap(self, sess):
response_page = BeautifulSoup(sess.text, 'html.parser')
challenge_url = sess.url.split("?")[0]
phone_number = input('Enter your phone number:') or None
phone_number = Util.get_input('Enter your phone number:') or None

while True:
try:
choice = int(
input(
Util.get_input(
'Type 1 to receive a code by SMS or 2 for a voice call:'
))
if choice not in [1, 2]:
Expand Down Expand Up @@ -778,7 +773,7 @@ def handle_iap(self, sess):
response_page = BeautifulSoup(sess.text, 'html.parser')
challenge_url = sess.url.split("?")[0]

token = input("Enter " + send_method + " token: G-") or None
token = Util.get_input("Enter " + send_method + " token: G-") or None

payload = {
'challengeId':
Expand Down Expand Up @@ -858,20 +853,21 @@ def handle_selectchallenge(self, sess):
if k in auth_methods and k not in unavailable_challenge_ids
}

print('Choose MFA method from available:')
print('\n'.join(
Util.message('Choose MFA method from available:')
Util.message('\n'.join(
'{}: {}'.format(*i) for i in list(auth_methods.items())))

selected_challenge = input("Enter MFA choice number ({}): ".format(
challenge_ids[-1:][0])) or None
selected_challenge = Util.get_input(
"Enter MFA choice number ({}): ".format(
challenge_ids[-1:][0])) or None

if selected_challenge is not None and int(selected_challenge) in challenge_ids:
challenge_id = int(selected_challenge)
else:
# use the highest index as that will default to prompt, then sms, then totp, etc.
challenge_id = challenge_ids[-1:][0]

print("MFA Type Chosen: {}".format(auth_methods[challenge_id]))
Util.message("MFA Type Chosen: {}".format(auth_methods[challenge_id]))

# We need the specific form of the challenge chosen
challenge_form = response_page.find(
Expand Down
7 changes: 5 additions & 2 deletions aws_google_auth/u2f.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from u2flib_host import u2f, exc, appid
from u2flib_host.constants import APDU_USE_NOT_SATISFIED

from aws_google_auth.util import Util

"""
The facet/appID used by Google auth does not seem to be valid
Need to apply some patches to u2flib_host to not validate the
Expand Down Expand Up @@ -69,8 +71,9 @@ def u2f_auth(challenges, facet):
if e.code == APDU_USE_NOT_SATISFIED:
remove = False
if not prompted:
print('Touch the flashing U2F device to '
'authenticate...')
Util.message(
'Touch the flashing U2F device to '
'authenticate...')
prompted = True
else:
pass
Expand Down
21 changes: 13 additions & 8 deletions aws_google_auth/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ class Util:

@staticmethod
def get_input(prompt):
return input(prompt)
Util.message(prompt, end="")
return input()

@staticmethod
def pick_a_role(roles, aliases=None, account=None):
Expand Down Expand Up @@ -43,26 +44,26 @@ def pick_a_role(roles, aliases=None, account=None):
enriched_roles_tab.append([i + 1, role_property[0], role_property[1]])

while True:
print(tabulate(enriched_roles_tab, headers=['No', 'AWS account', 'Role'], ))
Util.message(tabulate(enriched_roles_tab, headers=['No', 'AWS account', 'Role'], ))
prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(enriched_roles))
choice = Util.get_input(prompt)

try:
return list(ordered_roles.items())[int(choice) - 1]
except (IndexError, ValueError):
print("Invalid choice, try again.")
Util.message("Invalid choice, try again.")
else:
while True:
for i, role in enumerate(filtered_roles):
print("[{:>3d}] {}".format(i + 1, role))
Util.message("[{:>3d}] {}".format(i + 1, role))

prompt = 'Type the number (1 - {:d}) of the role to assume: '.format(len(filtered_roles))
choice = Util.get_input(prompt)

try:
return list(filtered_roles.items())[int(choice) - 1]
except (IndexError, ValueError):
print("Invalid choice, try again.")
Util.message("Invalid choice, try again.")

@staticmethod
def touch(file_name, mode=0o600):
Expand Down Expand Up @@ -95,8 +96,12 @@ def get_password(prompt):
if sys.stdin.isatty():
password = getpass.getpass(prompt)
else:
print(prompt, end="")
sys.stdout.flush()
Util.message(prompt, end="")
password = sys.stdin.readline()
print("")
Util.message("")
return password

@staticmethod
def message(*msg, **print_kwargs):
"""Print a user-interaction message to stderr."""
print(*msg, file=sys.stderr, **print_kwargs)

0 comments on commit 50aeb0a

Please sign in to comment.