From 68be5ddf67234b6f86e7e0e84eaea6864a16c8a6 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Fri, 1 Oct 2021 14:03:00 +1000 Subject: [PATCH 1/9] Added all feature and SSO profile check --- awssso | 65 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/awssso b/awssso index baffe4e..9f8a53c 100755 --- a/awssso +++ b/awssso @@ -22,7 +22,7 @@ DOCKER_CMD = ['docker', 'run', '--rm', '-t', '-v', f'{Path.home()}/.aws:/root/.a AWS_CONFIG_PATH = f'{Path.home()}/.aws/config' AWS_CREDENTIAL_PATH = f'{Path.home()}/.aws/credentials' AWS_SSO_CACHE_PATH = f'{Path.home()}/.aws/sso/cache' -AWS_DEFAULT_REGION = 'eu-west-1' +AWS_DEFAULT_REGION = 'us-east-1' class Colour: @@ -39,7 +39,7 @@ class Colour: def main(): parser = argparse.ArgumentParser(description='Retrieves AWS credentials from SSO for use with CLI/Boto3 apps.') - parser.add_argument('profile', + parser.add_argument('--profile', '-p', action='store', nargs='?', help='AWS config profile to retrieve credentials for.') @@ -60,6 +60,10 @@ def main(): action='store_true', help='Use the docker version of the AWS CLI') + parser.add_argument('--all', '-a', + action='store_true', + help='Populates credentials for all SSO profiles') + args = parser.parse_args() # validate aws v2 @@ -79,31 +83,47 @@ def main(): global VERBOSE_MODE VERBOSE_MODE = args.verbose - profile = _add_prefix(args.profile if args.profile else _select_profile()) + if args.all: + profiles, sso_profile = _get_all_profiles() + + if args.login: + _spawn_cli_for_auth(sso_profile, args.docker) + + for profile in profiles: + _set_profile_credentials(profile, False) + + else: + profile = _add_prefix(args.profile if args.profile else _select_profile()) - if args.login: - _spawn_cli_for_auth(profile, args.docker) + if args.login: + _spawn_cli_for_auth(profile, args.docker) - _set_profile_credentials(profile, args.use_default if profile != 'default' else False) + _set_profile_credentials(profile, args.use_default if profile != 'default' else False) def _set_profile_credentials(profile_name, use_default=False): profile_opts = _get_aws_profile(profile_name) - cache_login = _get_sso_cached_login(profile_opts) - credentials = _get_sso_role_credentials(profile_opts, cache_login) - if not use_default: - _store_aws_credentials(profile_name, profile_opts, credentials) - else: - _store_aws_credentials('default', profile_opts, credentials) - _copy_to_default_profile(profile_name) + if profile_opts != "NotSSO": + cache_login = _get_sso_cached_login(profile_opts) + credentials = _get_sso_role_credentials(profile_opts, cache_login) + + if not use_default: + _store_aws_credentials(profile_name, profile_opts, credentials) + else: + _store_aws_credentials('default', profile_opts, credentials) + _copy_to_default_profile(profile_name) def _get_aws_profile(profile_name): _print_msg(f'\nReading profile: [{profile_name}]') config = _read_config(AWS_CONFIG_PATH) profile_opts = config.items(profile_name) - profile = dict(profile_opts) + if "sso_start_url" in dict(profile_opts): + profile = dict(profile_opts) + else: + _print_msg(f'\n [{profile_name}] is not an SSO profile') + profile = "NotSSO" return profile @@ -213,6 +233,21 @@ def _spawn_cli_for_auth(profile, docker=False): stdout=sys.stdout, check=True) +def _get_all_profiles(): + config = _read_config(AWS_CONFIG_PATH) + + profiles = [] + + for section in config.sections(): + profiles.append(str(section)) + profiles.sort() + + for profile_name in profiles: + profile_opts = config.items(profile_name) + if "sso_start_url" in dict(profile_opts): + sso_profile = profile_name + + return(profiles, sso_profile) def _print_colour(colour, message, always=False): if always or VERBOSE_MODE: @@ -263,4 +298,4 @@ def _load_json(path): if __name__ == "__main__": - main() + main() \ No newline at end of file From cddecd1b6fcbace2c6b34aeff726de5c23358278 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Fri, 1 Oct 2021 14:47:57 +1000 Subject: [PATCH 2/9] made changes to README.md --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index c3bd3fd..5796b09 100644 --- a/README.md +++ b/README.md @@ -88,6 +88,7 @@ If you want to avoid having to set a profile, use the `-d` option detailed below - `--login` Invokes the AWS CLI to perform a SSO login and refresh SSO credentials. - `--docker` Use the Docker version of the AWS CLI - `-d, --use-default` Copies the chosen profile and credentials to the default profile. This removes the need to pass a profile name or export the `AWS_PROFILE` environment variable. +- `-a, --all` gets temporary credentials for all SSO profiles ## Example @@ -118,3 +119,38 @@ Here is a simple example that I use in my own day-to-day routine. Adding to credential files under [default] Copying profile [profile dev-env] to [default] ``` + +``` + $ awssso --login -v -all + + Attempting to automatically open the SSO authorization page in your default browser. + If the browser does not open or you wish to use a different device to authorize this request, + open the following URL: + + https://device.sso.eu-west-1.amazonaws.com/ + + Then enter the code: + + ABCD-WXYZ + Successully logged into Start URL: https://yoursso.awsapps.com/start + + Reading profile: [profile dev-env] + + Checking for SSO credentials... + Found credentials. Valid until 2020-05-01 22:32:11 UTC + + Fetching short-term CLI/Boto3 session token... + Got session token. Valid until 2020-05-01 18:32:11 UTC + + Adding to credential files under [dev-env] + + Reading profile: [profile prod-env] + + Checking for SSO credentials... + Found credentials. Valid until 2020-05-01 22:32:11 UTC + + Fetching short-term CLI/Boto3 session token... + Got session token. Valid until 2020-05-01 18:32:11 UTC + + Adding to credential files under [prod-env] +``` \ No newline at end of file From fba0ec91dde53993be1f00c80fc4d68035b00623 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Mon, 6 Dec 2021 10:28:35 +1100 Subject: [PATCH 3/9] pushing change to remove profile from name in credentials file --- awssso | 1 + 1 file changed, 1 insertion(+) diff --git a/awssso b/awssso index 9f8a53c..c938286 100755 --- a/awssso +++ b/awssso @@ -109,6 +109,7 @@ def _set_profile_credentials(profile_name, use_default=False): credentials = _get_sso_role_credentials(profile_opts, cache_login) if not use_default: + profile_name = re.sub(r"^profile ", "", profile_name) _store_aws_credentials(profile_name, profile_opts, credentials) else: _store_aws_credentials('default', profile_opts, credentials) From f381b9ba4af1bf7e9af8d406e36bf208da9f6111 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Fri, 11 Mar 2022 14:53:34 +1100 Subject: [PATCH 4/9] added check for default and all flags together --- awssso | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/awssso b/awssso index c938286..5eacb6d 100755 --- a/awssso +++ b/awssso @@ -66,6 +66,9 @@ def main(): args = parser.parse_args() + if args.all and args.use_default: + print("--use-default will ignored when --all is selected") + # validate aws v2 try: cmd = DOCKER_CMD if args.docker else ['aws'] @@ -90,7 +93,7 @@ def main(): _spawn_cli_for_auth(sso_profile, args.docker) for profile in profiles: - _set_profile_credentials(profile, False) + _set_profile_credentials(profile, False, args.all) else: profile = _add_prefix(args.profile if args.profile else _select_profile()) @@ -98,10 +101,10 @@ def main(): if args.login: _spawn_cli_for_auth(profile, args.docker) - _set_profile_credentials(profile, args.use_default if profile != 'default' else False) + _set_profile_credentials(profile, args.use_default if profile != 'default' else False, args.all) -def _set_profile_credentials(profile_name, use_default=False): +def _set_profile_credentials(profile_name, use_default=False, set_all=False): profile_opts = _get_aws_profile(profile_name) if profile_opts != "NotSSO": @@ -111,11 +114,10 @@ def _set_profile_credentials(profile_name, use_default=False): if not use_default: profile_name = re.sub(r"^profile ", "", profile_name) _store_aws_credentials(profile_name, profile_opts, credentials) - else: + elif use_default and not set_all: _store_aws_credentials('default', profile_opts, credentials) _copy_to_default_profile(profile_name) - def _get_aws_profile(profile_name): _print_msg(f'\nReading profile: [{profile_name}]') config = _read_config(AWS_CONFIG_PATH) From 2e32caaca0cee9c19a808647c23cc3b52c0f0580 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Thu, 17 Mar 2022 16:32:05 +1100 Subject: [PATCH 5/9] added compatibility for multiple aws orgs --- awssso | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/awssso b/awssso index 5eacb6d..1427124 100755 --- a/awssso +++ b/awssso @@ -3,6 +3,7 @@ import argparse import hashlib import json +from multiprocessing.connection import wait import os import re import subprocess @@ -87,11 +88,19 @@ def main(): VERBOSE_MODE = args.verbose if args.all: - profiles, sso_profile = _get_all_profiles() - - if args.login: - _spawn_cli_for_auth(sso_profile, args.docker) + profiles, sso_profiles_name = _get_all_profiles() + if args.login: + if len(sso_profiles_name) > 1: + _print_warn(f"You have {len(sso_profiles_name)} different SSO start URLs in your config file.") + _print_warn(f"AWS CLI will prompt you to login {len(sso_profiles_name)} times to get credentials for all SSO organizations") + for sso_profile in sso_profiles_name: + _spawn_cli_for_auth(sso_profile, args.docker) + + if len(sso_profiles_name) > 1 and not args.login: + _print_warn(f"You have {len(sso_profiles_name)} different SSO start URLs in your config file.") + _print_warn(f"All your credentials might not be up to date so if you receive a credentials error, use --login --all to login to all organizations") + for profile in profiles: _set_profile_credentials(profile, False, args.all) @@ -240,6 +249,8 @@ def _get_all_profiles(): config = _read_config(AWS_CONFIG_PATH) profiles = [] + sso_profiles = [] + sso_profiles_name = [] for section in config.sections(): profiles.append(str(section)) @@ -248,9 +259,11 @@ def _get_all_profiles(): for profile_name in profiles: profile_opts = config.items(profile_name) if "sso_start_url" in dict(profile_opts): - sso_profile = profile_name + if str(dict(profile_opts)['sso_start_url']) not in sso_profiles: + sso_profiles.append(str(dict(profile_opts)['sso_start_url'])) + sso_profiles_name.append(profile_name) - return(profiles, sso_profile) + return(profiles, sso_profiles_name) def _print_colour(colour, message, always=False): if always or VERBOSE_MODE: From d883d4b4556cd73a621f30fed64a75bbbfe56099 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Thu, 17 Mar 2022 16:38:54 +1100 Subject: [PATCH 6/9] added better error handling --- awssso | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/awssso b/awssso index 1427124..147a1d3 100755 --- a/awssso +++ b/awssso @@ -3,7 +3,6 @@ import argparse import hashlib import json -from multiprocessing.connection import wait import os import re import subprocess @@ -117,7 +116,7 @@ def _set_profile_credentials(profile_name, use_default=False, set_all=False): profile_opts = _get_aws_profile(profile_name) if profile_opts != "NotSSO": - cache_login = _get_sso_cached_login(profile_opts) + cache_login = _get_sso_cached_login(profile_opts, profile_name) credentials = _get_sso_role_credentials(profile_opts, cache_login) if not use_default: @@ -139,7 +138,7 @@ def _get_aws_profile(profile_name): return profile -def _get_sso_cached_login(profile): +def _get_sso_cached_login(profile, profile_name): _print_msg('\nChecking for SSO credentials...') cache = hashlib.sha1(profile["sso_start_url"].encode("utf-8")).hexdigest() @@ -147,7 +146,7 @@ def _get_sso_cached_login(profile): if not Path(sso_cache_file).is_file(): _print_error( - 'Current cached SSO login is invalid/missing. Login with the AWS CLI tool or use --login') + f'Current cached SSO login is invalid/missing for [profile {profile_name}]. Login with the AWS CLI tool or use --login') else: data = _load_json(sso_cache_file) @@ -156,14 +155,14 @@ def _get_sso_cached_login(profile): if data.get('region') != profile['sso_region']: _print_error( - 'SSO authentication region in cache does not match region defined in profile') + f'SSO authentication region in cache does not match region defined in [profile {profile_name}]') if now > expires_at: _print_error( - 'SSO credentials have expired. Please re-validate with the AWS CLI tool or --login option.') + f'SSO credentials have expired for [profile {profile_name}]. Please re-validate with the AWS CLI tool or --login option.') if (now + timedelta(minutes=15)) >= expires_at: - _print_warn('Your current SSO credentials will expire in less than 15 minutes!') + _print_warn(f'Your current SSO credentials will expire in less than 15 minutes! for [profile {profile_name}]') _print_success(f'Found credentials. Valid until {expires_at.astimezone(tzlocal())}') return data From 2ac8df06ea59805d7bd4a2c87049860f7bba908d Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Thu, 17 Mar 2022 16:46:36 +1100 Subject: [PATCH 7/9] added error handling --- awssso | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/awssso b/awssso index 147a1d3..52e1959 100755 --- a/awssso +++ b/awssso @@ -146,7 +146,7 @@ def _get_sso_cached_login(profile, profile_name): if not Path(sso_cache_file).is_file(): _print_error( - f'Current cached SSO login is invalid/missing for [profile {profile_name}]. Login with the AWS CLI tool or use --login') + f'Current cached SSO login is invalid/missing for [{profile_name}]. Login with the AWS CLI tool or use --login') else: data = _load_json(sso_cache_file) @@ -155,14 +155,14 @@ def _get_sso_cached_login(profile, profile_name): if data.get('region') != profile['sso_region']: _print_error( - f'SSO authentication region in cache does not match region defined in [profile {profile_name}]') + f'SSO authentication region in cache does not match region defined in [{profile_name}]') if now > expires_at: _print_error( - f'SSO credentials have expired for [profile {profile_name}]. Please re-validate with the AWS CLI tool or --login option.') + f'SSO credentials have expired for [{profile_name}]. Please re-validate with the AWS CLI tool or --login option.') if (now + timedelta(minutes=15)) >= expires_at: - _print_warn(f'Your current SSO credentials will expire in less than 15 minutes! for [profile {profile_name}]') + _print_warn(f'Your current SSO credentials will expire in less than 15 minutes! for [{profile_name}]') _print_success(f'Found credentials. Valid until {expires_at.astimezone(tzlocal())}') return data From 74a9f936ad73a6a425834036122043ab52bda279 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Thu, 17 Mar 2022 16:48:27 +1100 Subject: [PATCH 8/9] fixed indentation --- awssso | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/awssso b/awssso index 52e1959..8897e5a 100755 --- a/awssso +++ b/awssso @@ -99,7 +99,7 @@ def main(): if len(sso_profiles_name) > 1 and not args.login: _print_warn(f"You have {len(sso_profiles_name)} different SSO start URLs in your config file.") _print_warn(f"All your credentials might not be up to date so if you receive a credentials error, use --login --all to login to all organizations") - + for profile in profiles: _set_profile_credentials(profile, False, args.all) From f73f696f18d4599374fd888deefaf5b81fd8ade8 Mon Sep 17 00:00:00 2001 From: Syed Hasnain Date: Tue, 29 Mar 2022 14:52:39 +1100 Subject: [PATCH 9/9] fixed small issues in README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5796b09..4a1eb4e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ sso_role_name = DevOps aws sso login --profile dev-env ``` -4. Copy the `awssso` script to somewhere you can run it. Usually somewhere on your `%PATH%` or make a symlink to it from somewhere like `/usr/local/bin`. Make sure to make it executable, i.e. `chmod ug+x awssso`. +3. Copy the `awssso` script to somewhere you can run it. Usually somewhere on your `%PATH%` or make a symlink to it from somewhere like `/usr/local/bin`. Make sure to make it executable, i.e. `chmod ug+x awssso`. That's it. You should be good to go. @@ -57,7 +57,7 @@ That's it. You should be good to go. You can run `awssso` passing it the name of the profile you want credentials for. ```bash - $ awssso dev-env + $ awssso --profile dev-env ``` If you don't pass a profile name it will allow you to select from a list: