Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use the AWS Python SDK while connecting via SSO credentials

I am attempting to create a python script to connect to and interact with my AWS account. I was reading up on it here https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html

and I see that it reads your credentials from ~/.aws/credentials (on a Linux machine). I however and not connecting with an IAM user but SSO user. Thus, the profile connection data I use is located at ~/.aws/sso/cache directory.

Inside that directory, I see two json files. One has the following keys:

  • startUrl
  • region
  • accessToken
  • expiresAt

the second has the following keys:

  • clientId
  • clientSecret
  • expiresAt

I don't see anywhere in the docs about how to tell it to use my SSO user.

Thus, when I try to run my script, I get error such as

botocore.exceptions.ClientError: An error occurred (AuthFailure) when calling the DescribeSecurityGroups operation: AWS was not able to validate the provided access credentials

even though I can run the same command fine from the command prompt.

like image 247
user1015214 Avatar asked Jun 10 '20 19:06

user1015214


People also ask

Does AWS SDK support Python?

Get started quickly using AWS with boto3, the AWS SDK for Python. Boto3 makes it easy to integrate your Python application, library, or script with AWS services including Amazon S3, Amazon EC2, Amazon DynamoDB, and more.


Video Answer


2 Answers

This was fixed in boto3 1.14.

So given you have a profile like this in your ~/.aws/config:

[profile sso_profile]
sso_start_url = <sso-url>
sso_region = <sso-region>
sso_account_id = <account-id>
sso_role_name = <role>
region = <default region>
output = <default output (json or text)>

And then login with $ aws sso login --profile sso_profile

You will be able to create a session:

import boto3
boto3.setup_default_session(profile_name='sso_profile')
client = boto3.client('<whatever service you want>')
like image 168
Karl Anka Avatar answered Nov 15 '22 12:11

Karl Anka


So here's the long and hairy answer tested on boto3==1.21.39:

It's an eight-step process where:

  1. register the client using sso-oidc.register_client
  2. start the device authorization flow using sso-oidc.start_device_authorization
  3. redirect the user to the sso login page using webbrowser.open
  4. poll sso-oidc.create_token until the user completes the signin
  5. list and present the account roles to the user using sso.list_account_roles
  6. get role credentials using sso.get_role_credentials
  7. create a new boto3 session with the session credentials from (6)
  8. eat a cookie

Step 8 is really key and should not be overlooked as part of any successful authorization flow.

In the sample below the account_id should be the account id of the account you are trying to get credentials for. And the start_url should be the url that aws generates for you to start the sso flow (in the AWS SSO management console, under Settings).

from time import time, sleep
import webbrowser
from boto3.session import Session
session = Session()
account_id = '1234567890'
start_url = 'https://d-0987654321.awsapps.com/start'
region = 'us-east-1' 
sso_oidc = session.client('sso-oidc')
client_creds = sso_oidc.register_client(
    clientName='myapp',
    clientType='public',
)
device_authorization = sso_oidc.start_device_authorization(
    clientId=client_creds['clientId'],
    clientSecret=client_creds['clientSecret'],
    startUrl=start_url,
)
url = device_authorization['verificationUriComplete']
device_code = device_authorization['deviceCode']
expires_in = device_authorization['expiresIn']
interval = device_authorization['interval']
webbrowser.open(url, autoraise=True)
for n in range(1, expires_in // interval + 1):
    sleep(interval)
    try:
        token = sso_oidc.create_token(
            grantType='urn:ietf:params:oauth:grant-type:device_code',
            deviceCode=device_code,
            clientId=client_creds['clientId'],
            clientSecret=client_creds['clientSecret'],
        )
        break
    except sso_oidc.exceptions.AuthorizationPendingException:
        pass
 
access_token = token['accessToken']
sso = session.client('sso')
account_roles = sso.list_account_roles(
    accessToken=access_token,
    accountId=account_id,
)
roles = account_roles['roleList']
# simplifying here for illustrative purposes
role = roles[0]
role_creds = sso.get_role_credentials(
    roleName=role['roleName'],
    accountId=account_id,
    accessToken=access_token,
)
session = Session(
    region_name=region,
    aws_access_key_id=role_creds['accessKeyId'],
    aws_secret_access_key=role_creds['secretAccessKey'],
    aws_session_token=role_creds['sessionToken'],
)
like image 44
2ps Avatar answered Nov 15 '22 12:11

2ps