Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 38 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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]
```
92 changes: 71 additions & 21 deletions awssso
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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.')
Expand All @@ -60,8 +60,15 @@ 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()

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']
Expand All @@ -79,43 +86,67 @@ 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_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 args.login:
_spawn_cli_for_auth(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)

_set_profile_credentials(profile, args.use_default if profile != 'default' else False)
else:
profile = _add_prefix(args.profile if args.profile else _select_profile())

if args.login:
_spawn_cli_for_auth(profile, args.docker)

_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)
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, profile_name)
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)
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)
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


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()
sso_cache_file = f'{AWS_SSO_CACHE_PATH}/{cache}.json'

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_name}]. Login with the AWS CLI tool or use --login')

else:
data = _load_json(sso_cache_file)
Expand All @@ -124,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_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_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_name}]')

_print_success(f'Found credentials. Valid until {expires_at.astimezone(tzlocal())}')
return data
Expand Down Expand Up @@ -213,6 +244,25 @@ def _spawn_cli_for_auth(profile, docker=False):
stdout=sys.stdout,
check=True)

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))
profiles.sort()

for profile_name in profiles:
profile_opts = config.items(profile_name)
if "sso_start_url" in dict(profile_opts):
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_profiles_name)

def _print_colour(colour, message, always=False):
if always or VERBOSE_MODE:
Expand Down Expand Up @@ -263,4 +313,4 @@ def _load_json(path):


if __name__ == "__main__":
main()
main()