From 4f0a0c9a57fec7dec2a1e8db5e9bd1823587de56 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Mon, 23 Jul 2018 19:22:11 +0200 Subject: [PATCH 1/8] extract Google password retrieval --- aws_google_auth/__init__.py | 4 ++-- aws_google_auth/util.py | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/aws_google_auth/__init__.py b/aws_google_auth/__init__.py index 06ede8b..989371d 100644 --- a/aws_google_auth/__init__.py +++ b/aws_google_auth/__init__.py @@ -175,9 +175,9 @@ def process_auth(args, config): if keyring_password: config.password = keyring_password else: - config.password = getpass.getpass("Google Password: ") + config.password = util.Util.get_password("Google Password: ") else: - config.password = getpass.getpass("Google Password: ") + config.password = util.Util.get_password("Google Password: ") # Validate Options config.raise_if_invalid() diff --git a/aws_google_auth/util.py b/aws_google_auth/util.py index dd19ba2..b9093f2 100644 --- a/aws_google_auth/util.py +++ b/aws_google_auth/util.py @@ -79,3 +79,7 @@ def unicode_to_string_if_needed(object): return object.encode('utf-8') else: return object + + @staticmethod + def get_password(prompt): + return getpass.getpass("Google Password: ") From fca81d78dbdfe5078bb8762d71b7d43d5f85a136 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Mon, 23 Jul 2018 20:34:34 +0200 Subject: [PATCH 2/8] fix tests breaking after extraction --- aws_google_auth/tests/test_init.py | 51 ++++++++---------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/aws_google_auth/tests/test_init.py b/aws_google_auth/tests/test_init.py index fdd7dcc..7e29ad4 100644 --- a/aws_google_auth/tests/test_init.py +++ b/aws_google_auth/tests/test_init.py @@ -75,11 +75,10 @@ def test_main_method_chaining(self, process_auth, resolve_config, exit_if_unsupp ], process_auth.mock_calls) - @patch('getpass.getpass', spec=True) @patch('aws_google_auth.util', spec=True) @patch('aws_google_auth.amazon', spec=True) @patch('aws_google_auth.google', spec=True) - def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_getpass): + def test_process_auth_standard(self, mock_google, mock_amazon, mock_util): mock_config = Mock() mock_config.profile = False @@ -93,8 +92,6 @@ def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_g mock_amazon_client = Mock() mock_google_client = Mock() - mock_getpass.return_value = "pass" - mock_amazon_client.roles = { 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps' @@ -103,6 +100,7 @@ def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_g mock_util_obj = MagicMock() mock_util_obj.pick_a_role = MagicMock(return_value=("da_role", "da_provider")) mock_util_obj.get_input = MagicMock(side_effect=["input", "input2", "input3"]) + mock_util_obj.get_password = MagicMock(return_value="pass") mock_util.Util = mock_util_obj @@ -129,6 +127,7 @@ def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_g self.assertEqual([call.Util.get_input('Google username: '), call.Util.get_input('Google IDP ID: '), call.Util.get_input('Google SP ID: '), + call.Util.get_password('Google Password: '), call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])], mock_util.mock_calls) @@ -136,9 +135,6 @@ def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_g self.assertEqual([call()], mock_amazon_client.print_export_line.mock_calls) - self.assertEqual([call('Google Password: ')], - mock_getpass.mock_calls) - self.assertEqual([call.do_login(), call.parse_saml()], mock_google_client.mock_calls) @@ -154,11 +150,10 @@ def test_process_auth_standard(self, mock_google, mock_amazon, mock_util, mock_g 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, []) ], mock_util_obj.pick_a_role.mock_calls) - @patch('getpass.getpass', spec=True) @patch('aws_google_auth.util', spec=True) @patch('aws_google_auth.amazon', spec=True) @patch('aws_google_auth.google', spec=True) - def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util, mock_getpass): + def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util): mock_config = Mock() mock_config.saml_cache = False @@ -174,8 +169,6 @@ def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util, mock_amazon_client = Mock() mock_google_client = Mock() - mock_getpass.return_value = "pass" - mock_amazon_client.roles = { 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps' @@ -184,6 +177,7 @@ def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util, mock_util_obj = MagicMock() mock_util_obj.pick_a_role = MagicMock(return_value=("da_role", "da_provider")) mock_util_obj.get_input = MagicMock(side_effect=["input", "input2", "input3"]) + mock_util_obj.get_password = MagicMock(return_value="pass") mock_util.Util = mock_util_obj @@ -208,12 +202,10 @@ def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util, # Assert calls occur self.assertEqual([call.Util.get_input('Google username: '), call.Util.get_input('Google IDP ID: '), - call.Util.get_input('Google SP ID: ')], + call.Util.get_input('Google SP ID: '), + call.Util.get_password('Google Password: ')], mock_util.mock_calls) - self.assertEqual([call('Google Password: ')], - mock_getpass.mock_calls) - self.assertEqual([call.do_login(), call.parse_saml()], mock_google_client.mock_calls) @@ -227,11 +219,10 @@ def test_process_auth_specified_role(self, mock_google, mock_amazon, mock_util, self.assertEqual([], mock_util_obj.pick_a_role.mock_calls) - @patch('getpass.getpass', spec=True) @patch('aws_google_auth.util', spec=True) @patch('aws_google_auth.amazon', spec=True) @patch('aws_google_auth.google', spec=True) - def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_util, mock_getpass): + def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_util): mock_config = Mock() mock_config.saml_cache = False @@ -245,8 +236,6 @@ def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_ut mock_amazon_client = Mock() mock_google_client = Mock() - mock_getpass.return_value = "pass" - mock_amazon_client.roles = { 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps' @@ -255,6 +244,7 @@ def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_ut mock_util_obj = MagicMock() mock_util_obj.pick_a_role = MagicMock(return_value=("da_role", "da_provider")) mock_util_obj.get_input = MagicMock(side_effect=["input", "input2", "input3"]) + mock_util_obj.get_password = MagicMock(return_value="pass") mock_util.Util = mock_util_obj @@ -284,9 +274,6 @@ def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_ut 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'})], mock_util.mock_calls) - self.assertEqual([call('Google Password: ')], - mock_getpass.mock_calls) - self.assertEqual([call.do_login(), call.parse_saml()], mock_google_client.mock_calls) @@ -301,11 +288,10 @@ def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_ut 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}) ], mock_util_obj.pick_a_role.mock_calls) - @patch('getpass.getpass', spec=True) @patch('aws_google_auth.util', spec=True) @patch('aws_google_auth.amazon', spec=True) @patch('aws_google_auth.google', spec=True) - def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util, mock_getpass): + def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util): mock_config = Mock() mock_config.saml_cache = False @@ -320,8 +306,6 @@ def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util, mo mock_amazon_client = Mock() mock_google_client = Mock() - mock_getpass.return_value = "pass" - mock_amazon_client.roles = { 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps' @@ -330,6 +314,7 @@ def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util, mo mock_util_obj = MagicMock() mock_util_obj.pick_a_role = MagicMock(return_value=("da_role", "da_provider")) mock_util_obj.get_input = MagicMock(side_effect=["input", "input2", "input3"]) + mock_util_obj.get_password = MagicMock(return_value="pass") mock_util.Util = mock_util_obj @@ -355,13 +340,11 @@ def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util, mo self.assertEqual([call.Util.get_input('Google username: '), call.Util.get_input('Google IDP ID: '), call.Util.get_input('Google SP ID: '), + call.Util.get_password('Google Password: '), call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])], mock_util.mock_calls) - self.assertEqual([call('Google Password: ')], - mock_getpass.mock_calls) - self.assertEqual([call.do_login(), call.parse_saml()], mock_google_client.mock_calls) @@ -378,11 +361,10 @@ def test_process_auth_with_profile(self, mock_google, mock_amazon, mock_util, mo 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, []) ], mock_util_obj.pick_a_role.mock_calls) - @patch('getpass.getpass', spec=True) @patch('aws_google_auth.util', spec=True) @patch('aws_google_auth.amazon', spec=True) @patch('aws_google_auth.google', spec=True) - def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util, mock_getpass): + def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util): mock_config = Mock() mock_config.saml_cache = True @@ -396,8 +378,6 @@ def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util, mock_amazon_client = Mock() mock_google_client = Mock() - mock_getpass.return_value = "pass" - mock_amazon_client.roles = { 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps' @@ -406,6 +386,7 @@ def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util, mock_util_obj = MagicMock() mock_util_obj.pick_a_role = MagicMock(return_value=("da_role", "da_provider")) mock_util_obj.get_input = MagicMock(side_effect=["input", "input2", "input3"]) + mock_util_obj.get_password = MagicMock(return_value="pass") mock_util.Util = mock_util_obj @@ -432,10 +413,6 @@ def test_process_auth_with_saml_cache(self, mock_google, mock_amazon, mock_util, 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'}, [])], mock_util.mock_calls) - # Cache means no password request - self.assertEqual([], - mock_getpass.mock_calls) - # Cache means no google calls self.assertEqual([], mock_google_client.mock_calls) From 944db6f61f8e2065018383e249b7672cedb56e26 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Mon, 23 Jul 2018 19:48:58 +0200 Subject: [PATCH 3/8] check for tty when asking password --- aws_google_auth/tests/test_util.py | 18 +++++++++++++++++- aws_google_auth/util.py | 11 +++++++++-- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/aws_google_auth/tests/test_util.py b/aws_google_auth/tests/test_util.py index eafee23..078800b 100644 --- a/aws_google_auth/tests/test_util.py +++ b/aws_google_auth/tests/test_util.py @@ -3,7 +3,7 @@ import sys import unittest from aws_google_auth import util - +from mock import call, patch, Mock, MagicMock class TestUtilMethods(unittest.TestCase): @@ -46,3 +46,19 @@ def test_unicode_to_string_if_needed(self): self.assertEqual(util.Util.unicode_to_string_if_needed(None), None) self.assertEqual(util.Util.unicode_to_string_if_needed(1234), 1234) self.assertEqual(util.Util.unicode_to_string_if_needed("nop"), "nop") + + @patch('getpass.getpass', spec=True) + @patch('sys.stdin', spec=True) + def test_get_password_when_tty(self, mock_stdin, mock_getpass): + mock_stdin.isatty = MagicMock(return_value=True) + + mock_getpass.return_value = "pass" + + self.assertEqual(util.Util.get_password("Test: "), "pass") + + @patch('sys.stdin', spec=True) + def test_get_password_when_not_tty(self, mock_stdin): + mock_stdin.isatty = MagicMock(return_value=False) + mock_stdin.readline = MagicMock(return_value="pass") + + self.assertEqual(util.Util.get_password("Test: "), "pass") diff --git a/aws_google_auth/util.py b/aws_google_auth/util.py index b9093f2..c215738 100644 --- a/aws_google_auth/util.py +++ b/aws_google_auth/util.py @@ -4,7 +4,8 @@ from collections import OrderedDict from tabulate import tabulate from six.moves import input - +import sys +import getpass class Util: @@ -82,4 +83,10 @@ def unicode_to_string_if_needed(object): @staticmethod def get_password(prompt): - return getpass.getpass("Google Password: ") + if sys.stdin.isatty(): + password = getpass.getpass(prompt) + else: + print(prompt, end="") + password = sys.stdin.readline() + print("") + return password From a64a8e97d2f7f9a612b91d934d61096b6b3caa5c Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Wed, 25 Jul 2018 20:23:02 +0200 Subject: [PATCH 4/8] fix flake8 --- aws_google_auth/__init__.py | 1 - aws_google_auth/tests/test_util.py | 3 ++- aws_google_auth/util.py | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/aws_google_auth/__init__.py b/aws_google_auth/__init__.py index 989371d..c332bb3 100644 --- a/aws_google_auth/__init__.py +++ b/aws_google_auth/__init__.py @@ -8,7 +8,6 @@ from . import amazon import argparse -import getpass import keyring import os import sys diff --git a/aws_google_auth/tests/test_util.py b/aws_google_auth/tests/test_util.py index 078800b..c92ff0f 100644 --- a/aws_google_auth/tests/test_util.py +++ b/aws_google_auth/tests/test_util.py @@ -3,7 +3,8 @@ import sys import unittest from aws_google_auth import util -from mock import call, patch, Mock, MagicMock +from mock import patch, MagicMock + class TestUtilMethods(unittest.TestCase): diff --git a/aws_google_auth/util.py b/aws_google_auth/util.py index c215738..abcb6e9 100644 --- a/aws_google_auth/util.py +++ b/aws_google_auth/util.py @@ -7,6 +7,7 @@ import sys import getpass + class Util: @staticmethod From 3ca5e01e34e3374c53474f0f8d242428a9473edb Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Mon, 30 Jul 2018 19:10:28 +0200 Subject: [PATCH 5/8] import print from future to use end keyword argument print("string", end="") is not supported by print in python 2.7.14 --- aws_google_auth/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aws_google_auth/util.py b/aws_google_auth/util.py index abcb6e9..38f8b1b 100644 --- a/aws_google_auth/util.py +++ b/aws_google_auth/util.py @@ -1,5 +1,6 @@ #!/usr/bin/env python +from __future__ import print_function import os from collections import OrderedDict from tabulate import tabulate From 0a24b9231277a459cce2a296696803399af627d3 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Tue, 31 Jul 2018 00:41:03 +0200 Subject: [PATCH 6/8] fix test_process_auth_dont_resolve_alias test --- aws_google_auth/tests/test_init.py | 1 + 1 file changed, 1 insertion(+) diff --git a/aws_google_auth/tests/test_init.py b/aws_google_auth/tests/test_init.py index 7e29ad4..9c4901f 100644 --- a/aws_google_auth/tests/test_init.py +++ b/aws_google_auth/tests/test_init.py @@ -270,6 +270,7 @@ def test_process_auth_dont_resolve_alias(self, mock_google, mock_amazon, mock_ut self.assertEqual([call.Util.get_input('Google username: '), call.Util.get_input('Google IDP ID: '), call.Util.get_input('Google SP ID: '), + call.Util.get_password('Google Password: '), call.Util.pick_a_role({'arn:aws:iam::123456789012:role/read-only': 'arn:aws:iam::123456789012:saml-provider/GoogleApps', 'arn:aws:iam::123456789012:role/admin': 'arn:aws:iam::123456789012:saml-provider/GoogleApps'})], mock_util.mock_calls) From fa1f5cca99940af8982f4c15d24b9a378dadfae2 Mon Sep 17 00:00:00 2001 From: Edoardo Tenani Date: Tue, 31 Jul 2018 01:14:18 +0200 Subject: [PATCH 7/8] update README --- README.rst | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.rst b/README.rst index b4f97a5..a09ac3b 100644 --- a/README.rst +++ b/README.rst @@ -152,6 +152,27 @@ be able to use this via Docker; the Docker container will not be able to access any devices connected to the host ports. You will likely see the following error during runtime: "RuntimeWarning: U2F Device Not Found". +Feeding password from stdin +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To enhance usability when using third party tools for managing passwords (aka password manager) you can feed data in +``aws-google-auth`` from ``stdin``. + +When receiving data from ``stdin`` ``aws-google-auth`` disables the interactive prompt and uses ``stdin`` data. + +All interactive prompt could be feeded from ``stdin``, but before `#82 `_ +was not possible to feed the ``Google Password:`` prompt. + +Example usage: +:: + $ password-manager show password | aws-google-auth + Google Password: MFA token: + Assuming arn:aws:iam::123456789012:role/admin + Credentials Expiration: ... + +**Note:** this feature is intended for password manager integration, not for passing passwords from command line. +Please use interactive prompt if you need to pass the password manually, as this provide enhanced security avoid +password leakage to shell history. Storage of profile credentials ------------------------------ From 38bac153058c0b1eb523e9d568f2d1fad88a3293 Mon Sep 17 00:00:00 2001 From: Edoardo T Date: Thu, 2 Aug 2018 17:38:08 +0200 Subject: [PATCH 8/8] Update README.rst --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index a09ac3b..f9b393f 100644 --- a/README.rst +++ b/README.rst @@ -160,8 +160,7 @@ To enhance usability when using third party tools for managing passwords (aka pa When receiving data from ``stdin`` ``aws-google-auth`` disables the interactive prompt and uses ``stdin`` data. -All interactive prompt could be feeded from ``stdin``, but before `#82 `_ -was not possible to feed the ``Google Password:`` prompt. +Before `#82 `_, all interactive prompts could be fed from ``stdin`` already apart from the ``Google Password:`` prompt. Example usage: ::