如何在使用SSO凭证连接时使用AWS Python SDK

27

我正在尝试创建一个 Python 脚本来连接并与我的 AWS 帐户进行交互。在这里https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html 阅读有关此内容的信息。

我发现它从 ~/.aws/credentials(在 Linux 机器上)中读取您的凭据。然而,我不是使用 IAM 用户而是 SSO 用户进行连接。因此,我使用的配置文件连接数据位于 ~/.aws/sso/cache 目录中。

在该目录中,我看到两个 JSON 文件。一个具有以下键:

  • startUrl
  • region
  • accessToken
  • expiresAt

第二个具有以下键:

  • clientId
  • clientSecret
  • expiresAt

我在文档中没有找到如何告诉它使用我的 SSO 用户的任何位置。

因此,当我尝试运行我的脚本时,我会收到错误,例如

botocore.exceptions.ClientError: An error occurred (AuthFailure) when calling the DescribeSecurityGroups operation: AWS was not able to validate the provided access credentials
即使我可以从命令提示符中成功运行相同的命令,但也存在问题。

你能澄清一下你的设置吗?sso文件里有什么?如果你想这样做,你可以使用AWS_SHARED_CREDENTIALS_FILE环境变量指定凭证文件的新位置。 - Marcin
@Marcin 如果我没有说明清楚,我很抱歉。sso 不是一个文件,而是一个目录。我正在更新工单。 - user1015214
这些 clientIdclientSecretaccessToken 是临时的 AWS 凭证吗?如果是,那么我认为您需要手动在 boto3 中加载它,并使用这些凭证创建新的 session - Marcin
@Marcin 是的,它们是。它们会定期过期并需要刷新。您能否提供有关如何使用会话来实现此目的的更多详细信息? - user1015214
AWS CLI提供了get-role-credentials命令来处理SSO。也许需要使用它来刷新凭证,但我不确定它的具体工作方式。 - Marcin
5个回答

35
这个问题已在boto3 1.14中得到解决。
因此,假设您在~/.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)>

然后使用$ aws sso login --profile sso_profile登录

您将能够创建一个会话:

import boto3
boto3.setup_default_session(profile_name='sso_profile')
client = boto3.client('<whatever service you want>')

19

根据最新的boto3版本于2023年10月23日更新(感谢评论者@Adam Smith的指导,他的无形之手引导我们更新了在较新版本的boto3中提取角色凭证的方法):

下面是经过测试的冗长而复杂的答案,适用于boto3==1.28.69

这是一个八步骤的过程,其中:

  1. 使用sso-oidc.register_client注册客户端
  2. 使用sso-oidc.start_device_authorization启动设备授权流程
  3. 使用webbrowser.open将用户重定向到SSO登录页面
  4. 轮询sso-oidc.create_token直到用户完成登录
  5. 使用sso.list_account_roles列出并向用户展示账户角色
  6. 使用sso.get_role_credentials获取角色凭证
  7. 使用(6)中的会话凭证创建一个新的boto3会话
  8. 吃一个饼干
第8步非常关键,作为任何成功的授权流程的一部分,不应被忽视。
在下面的示例中,account_id 应该是您要获取凭证的账户的账户ID。而 start_url 应该是AWS为您生成的用于启动SSO流程的URL(在AWS SSO管理控制台的设置下)。
from time import time, sleep
import webbrowser
from boto3.session import Session

# if your sso is setup in a different region, you will
# want to include region_name=sso_region in the 
# session constructor below
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]

# earlier versions of the sso api returned the 
# role credentials directly, but now they appear
# to be in a subkey called `roleCredentials`
role_creds = sso.get_role_credentials(
    roleName=role['roleName'],
    accountId=account_id,
    accessToken=access_token,
)['roleCredentials']
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'],
)

您建议最终用户将秘密凭据发送到https://d-0987654321.awsapps.com/start,这有点令人担忧。这个端点代表什么意思?否则(如果这起作用)-很棒的解决方案 :) - alex
1
抱歉,那本应是AWS为您在管理控制台中设置AWS SSO idp hive时生成的默认sso url的表示。我认为从0开始递减的模式会让它显而易见,但您说得对,我应该在上面澄清。 - 2ps
这个回答太好了,我希望我能点赞两次。非常感谢。 - Brian Wylie
将 sso_oidc 会话更改为以下内容,以便能够登录到未分配角色的配置文件(默认配置文件):config = botocore.config.Config(region_name=region, signature_version=botocore.UNSIGNED)sso_oidc = session.client('sso-oidc', config=config)可以删除上面的 UNSIGNED 参数以查看差异。 - Grant Carthew
1
我认为 role_creds 应该是 sso.get_role_credentials(...)['roleCredentials'] - undefined
我注意到从create_token返回的令牌没有刷新令牌字段,不像AWS CLI获取的令牌(我可以在.aws/sso/cache目录中看到它们),因此它们在8小时后过期,我需要手动重新授权(这对我的流程来说真的很糟糕)。 - undefined

8

您当前的 .aws/sso/cache 文件夹结构如下:

$ ls
botocore-client-XXXXXXXX.json       cXXXXXXXXXXXXXXXXXXX.json

这两个 JSON 文件包含了三个有用的不同参数。
botocore-client-XXXXXXXX.json -> clientId and clientSecret
cXXXXXXXXXXXXXXXXXXX.json -> accessToken

使用cXXXXXXXXXXXXXXXXXXX.json中的访问令牌,您可以调用get-role-credentials。此命令的输出可用于创建新会话。
您的Python文件应该类似于以下内容:
import json
import os
import boto3

dir = os.path.expanduser('~/.aws/sso/cache')

json_files = [pos_json for pos_json in os.listdir(dir) if pos_json.endswith('.json')]

for json_file in json_files :
    path = dir + '/' + json_file
    with open(path) as file :
        data = json.load(file)
        if 'accessToken' in data:
            accessToken = data['accessToken']

client = boto3.client('sso',region_name='us-east-1')
response = client.get_role_credentials(
    roleName='string',
    accountId='string',
    accessToken=accessToken
)

session = boto3.Session(aws_access_key_id=response['roleCredentials']['accessKeyId'], aws_secret_access_key=response['roleCredentials']['secretAccessKey'], aws_session_token=response['roleCredentials']['sessionToken'], region_name='us-east-1')

1
这可能在您当前的环境中有效...一段时间。但是更稳定的方法是指定所需的配置文件名称(从环境变量和/或命令行参数中)。这是首选方法,因为区域和令牌是从配置文件继承的。 - MarkHu

2

What works for me is the following:

import boto 3


session = boto3.Session(profile_name="sso_profile_name")
session.resource("whatever")

使用boto3==1.20.18

如果您之前配置了AWS SSO,那么这将起作用,例如aws configure sso

有趣的是,如果我使用ipython,我不必执行此操作,我只需事先aws sso login,然后调用boto3.Session()。 我正在努力弄清楚我的方法是否存在问题-我完全同意上面所说的透明度,尽管它是一种可行的解决方案,但我并不喜欢它。


编辑:出现问题,并且以下是如何解决:

  1. 运行aws configure sso(如上);
  2. 安装aws-vault-它基本上替换了aws sso login --profile <profile-name>
  3. 运行aws-vault exec <profile-name>以创建一个子shell,其中AWS凭据已导出到环境变量中。

这样做,就可以在交互式环境中(例如iPython)和从脚本中运行任何boto3命令,如我的情况。因此,上面的代码片段变得非常简单:

import boto 3


session = boto3.Session()
session.resource("whatever")

点击这里获取AWS保险库的更多详细信息。

2

一个良好的基于boto3的脚本应该根据配置文件名称透明地进行身份验证。不建议自己处理缓存文件、密钥或令牌,因为官方代码方法可能会在将来更改。要查看您的配置文件状态,请运行aws configure list--示例:

$ aws configure list --profile=sso

      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                      sso           manual    --profile

The SSO session associated with this profile has expired or is otherwise invalid.
To refresh this SSO session run aws sso login with the corresponding profile.

$ aws configure list --profile=old

      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                      old           manual    --profile
access_key     ****************3DSx shared-credentials-file    
secret_key     ****************sX64 shared-credentials-file    
    region                us-west-1              env    ['AWS_REGION', 'AWS_DEFAULT_REGION']

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接