简体   繁体   English

如何在通过 SSO 凭据连接时使用 AWS Python SDK

[英]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.我正在尝试创建一个 python 脚本来连接到我的 AWS 账户并与之交互。 I was reading up on it here https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html我在这里阅读它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).我看到它从 ~/.aws/credentials(在 Linux 机器上)读取您的凭据。 I however and not connecting with an IAM user but SSO user.然而,我并没有连接 IAM 用户而是 SSO 用户。 Thus, the profile connection data I use is located at ~/.aws/sso/cache directory.因此,我使用的配置文件连接数据位于 ~/.aws/sso/cache 目录。

Inside that directory, I see two json files.在该目录中,我看到两个 json 文件。 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.我在文档中没有看到任何关于如何告诉它使用我的 SSO 用户的信息。

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.即使我可以从命令提示符运行相同的命令。

So here's the long and hairy answer tested on boto3==1.21.39 :所以这是在boto3==1.21.39上测试的长而多毛的答案:

It's an eight-step process where:这是一个八步过程,其中:

  1. register the client using sso-oidc.register_client使用sso-oidc.register_client注册客户端
  2. start the device authorization flow using sso-oidc.start_device_authorization使用sso-oidc.start_device_authorization启动设备授权流程
  3. redirect the user to the sso login page using webbrowser.open使用webbrowser.open将用户重定向到 sso 登录页面
  4. poll sso-oidc.create_token until the user completes the signin轮询sso-oidc.create_token直到用户完成登录
  5. list and present the account roles to the user using sso.list_account_roles使用sso.list_account_roles列出并向用户呈现帐户角色
  6. get role credentials using sso.get_role_credentials使用sso.get_role_credentials获取角色凭证
  7. create a new boto3 session with the session credentials from (6)使用 (6) 中的 session 凭据创建一个新的 boto3 session
  8. eat a cookie吃一块饼干

Step 8 is really key and should not be overlooked as part of any successful authorization flow.第 8 步非常关键,在任何成功的授权流程中都不应被忽视。

In the sample below the account_id should be the account id of the account you are trying to get credentials for.在下面的示例中, account_id应该是您尝试获取凭据的帐户的帐户 ID。 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). start_url应该是 aws 为您生成的 url 以启动 sso 流程(在 AWS SSO 管理控制台中的“设置”下)。

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'],
)

This was fixed in boto3 1.14 .这已在boto3 1.14中修复。

So given you have a profile like this in your ~/.aws/config :因此,假设您在~/.aws/config中有这样的配置文件:

[profile sso_profile]
sso_start_url = <sso-url>
sso_region = <sso-region>
sso_account_id = <account-id>
sso_role_name = <role>
region = <eu-west-1>
output = text

And then login with aws sso login --profile sso_profile然后使用aws sso login --profile sso_profile

You will be able to create a session:您将能够创建 session:

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

Your current.aws/sso/cache folder structure looks like this:您的 current.aws/sso/cache 文件夹结构如下所示:

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

The 2 json files contain 3 different parameters that are useful. 2 个 json 文件包含 3 个有用的不同参数。

botocore-client-XXXXXXXX.json -> clientId and clientSecret
cXXXXXXXXXXXXXXXXXXX.json -> accessToken

Using the access token in cXXXXXXXXXXXXXXXXXXX.json you can call get-role-credentials .使用 cXXXXXXXXXXXXXXXXXXXXX.json 中的访问令牌,您可以调用get-role-credentials The output from this command can be used to create a new session.此命令中的 output 可用于创建新的 session。

Your Python file should look something like this:您的 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')

A well-formed boto3 -based script should transparently authenticate based on profile name.格式良好的基于boto3的脚本应该基于配置文件名称透明地进行身份验证。 It is not recommended to handle the cached files or keys or tokens yourself, since the official code methods might change in the future.不建议自己处理缓存的文件或密钥或令牌,因为官方代码方法将来可能会更改。 To see the state of your profile(s), run aws configure list --examples:要查看您的配置文件的 state,请运行aws configure list --examples:

$ 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']

What works for me is the following:对我有用的是以下内容:

import boto 3


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

using boto3==1.20.18 .使用boto3==1.20.18

This would work if you had previously configured SSO for aws ie.如果您之前为 aws 配置了 SSO,这将起作用,即。 aws configure sso . aws configure sso

Interestingly enough, I don't have to go through this if I use ipython , I just aws sso login beforehand and then call boto3.Session() .有趣的是,如果我使用ipython ,我不必通过此 go ,我只需预先aws sso login ,然后调用boto3.Session() I am trying to figure out whether there is something wrong with my approach - I fully agree with what was said above with respect to transparency and although it is a working solution, I am not in love with it.我试图弄清楚我的方法是否有问题——我完全同意上面关于透明度的说法,虽然这是一个可行的解决方案,但我并不喜欢它。


EDIT: there was something wrong and here is how I fixed it:编辑:出了点问题,这是我修复它的方法:

  1. run aws configure sso (as above);运行aws configure sso (如上);
  2. install aws-vault - it basically replaces aws sso login --profile <profile-name> ;安装aws-vault - 它基本上取代了aws sso login --profile <profile-name>
  3. run aws-vault exec <profile-name> to create a sub-shell with AWS credentials exported to environment variables.运行aws-vault exec <profile-name>以创建一个子 shell,其中 AWS 凭证导出到环境变量。

Doing so, it is possible to run any boto3 command both interactively (eg. iPython) and from a script, as in my case.这样做,可以交互地(例如 iPython)和从脚本运行任何boto3命令,就像我的例子一样。 Therefore, the snippet above simply becomes:因此,上面的代码片段就变成了:

import boto 3


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

Here for further details on AWS vault.在此处了解有关 AWS 保管库的更多详细信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM