告别固定密钥!在单一账户下用 Cognito 实现 AWS CLI 的 MFA 单点登录
大家好,很多朋友,特别是通过合作伙伴或服务商使用 AWS 的同学,可能会发现自己的 IAM Identity Center 功能受限,无法像在组织管理账户里那样轻松配置 CLI 的 SSO (aws configure sso
)。那么,我们就要放弃治疗,退回使用古老的、不安全的静态 IAM 用户密钥吗?
绝对不行!今天,我就教大家如何利用 AWS Cognito,在自己的单一账户内,从零开始打造一个支持 MFA 的 CLI 登录门户,彻底告别固定凭据的烦恼。
为什么是 Cognito?
在前面提到的场景下,我们需要的本质是一个“身份验证服务”,它能:
- 验证我就是我(最好带上 MFA)。
- 验证通过后,给我一个“临时通行证”(临时的 AWS STS 凭证)。
- 这个过程要能被 CLI 工具自动化。
Cognito 完美地满足了这三点。它就像一个为我们自己搭建的、轻量级的“迷你 IAM Identity Center”。
而另一个选项 AWS Managed Microsoft AD,虽然技术上也能通过 SAML 联合登录实现,但它是一个重量级且昂贵的解决方案,通常用于已经深度绑定微软生态的大型企业。对于个人或小团队在单一账户下的需求来说,完全是“杀鸡用牛刀”,成本和复杂度都太高了。
所以,我们的主角就是 Cognito。
方案核心:Cognito 用户池 + 身份池 = 临时凭证生成器
这个方案的核心是 Cognito 的两大组件协同工作:
- Cognito 用户池 (User Pool): 我们的“用户数据库”。我们在这里创建用户,并为用户强制启用 MFA。它只负责认证(Authentication)——确认我们是谁。
- Cognito 身份池 (Identity Pool): 我们的“权限交换中心”。它信任来自用户池的已认证用户,并被授权可以为他们代领一个拥有特定权限的 IAM 角色(临时凭证)。它负责授权(Authorization)——决定我们能做什么。
整个流程就像这样:
CLI -> Cognito 用户池 (输入密码+MFA码) -> 获取身份令牌 -> Cognito 身份池 (用身份令牌交换) -> 获取临时 AWS 凭证 (Access Key, Secret Key, Session Token)
实战步骤:四步打造 CLI 登录系统
第 1 步:创建 Cognito 用户池 (用户中心)
- 进入 AWS Cognito 服务控制台,点击“创建用户池”。
- 在配置登录体验时,选择“Cognito user pool”作为提供商类型。
- 关键: 在“多重要素认证 (MFA)”部分,选择 “强制 (Required)”,并选择“验证器应用 (Authenticator apps)”作为 MFA 类型。这样就确保了所有登录行为都必须经过 MFA。
- 其他设置可以保持默认,完成用户池的创建。
- 在创建好的用户池中,进入“用户”标签页,手动创建一个用户(例如,dave)。首次登录时系统会要求我们设置密码和绑定 MFA 设备(如 Google Authenticator)。
第 2 步:创建 Cognito 身份池 (权限交换机)
- 再次进入 Cognito 控制台,切换到“联合身份池 (Federated Identities)”,点击“创建新的身份池”。
- 给身份池命名,然后展开“身份验证提供商”部分。
- 点击 “Cognito” 标签,输入我们上一步创建的用户池 ID 和应用客户端 ID。
- 点击“创建池”。AWS 会提示我们为此身份池创建一个新的 IAM 角色。允许它创建!这个角色非常重要,它就是我们登录后最终“扮演”的角色。
第 3 步:配置 IAM 角色权限
- 进入 IAM 服务控制台,找到上一步为我们创建的那个 IAM 角色(通常名字里包含我们的身份池名称)。
- 这个角色默认权限很小。我们需要为它附加我们希望在 CLI 中拥有的权限策略。例如,如果我们希望拥有管理员权限,就附加
AdministratorAccess
策略。如果我们只想拥有 S3 的读写权限,就附加相应的策略。这是决定我们登录后能力范围的关键一步。
第 4 步:编写 CLI 登录脚本 (魔法发生的地方)
标准的 AWS CLI 没有内置 aws configure cognito
命令,所以我们需要一个小脚本来自动化这个登录流程。我们可以用任何我们喜欢的语言(Python, Bash, PowerShell)来写。
下面是一个使用 Python 和 Boto3 的简单示例 login.py
:
import boto3
import getpass
import configparser
import os# --- 配置Cognito信息 ---
USER_POOL_ID = 'us-east-1_xxxxxxxxx' # 替换为我们的用户池ID
CLIENT_ID = 'xxxxxxxxxxxxxxxxxxxxxx' # 替换为我们的用户池应用客户端ID
IDENTITY_POOL_ID = 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' # 替换为我们的身份池ID
REGION = 'us-east-1' # 替换为我们的区域
ROLE_ARN = 'arn:aws:iam::123456789012:role/Cognito_YourIdentityPoolAuth_Role' # 替换为身份池为我们创建的角色ARN
# -----------------------------def get_cognito_tokens(username, password):client = boto3.client('cognito-idp', region_name=REGION)try:# 第一步:发起认证resp = client.initiate_auth(ClientId=CLIENT_ID,AuthFlow='USER_PASSWORD_AUTH',AuthParameters={'USERNAME': username,'PASSWORD': password,})# 第二步:处理MFA挑战if 'ChallengeName' in resp and resp['ChallengeName'] == 'SOFTWARE_TOKEN_MFA':mfa_code = input("请输入我们的MFA验证码: ")resp = client.respond_to_auth_challenge(ClientId=CLIENT_ID,ChallengeName='SOFTWARE_TOKEN_MFA',Session=resp['Session'],ChallengeResponses={'USERNAME': username,'SOFTWARE_TOKEN_MFA_CODE': mfa_code})return resp['AuthenticationResult']['IdToken']except client.exceptions.NotAuthorizedException:print("错误:用户名或密码不正确。")return Noneexcept Exception as e:print(f"发生未知错误: {e}")return Nonedef get_aws_credentials(id_token):client = boto3.client('cognito-identity', region_name=REGION)# 用ID Token从身份池获取身份IDidentity_id_resp = client.get_id(IdentityPoolId=IDENTITY_POOL_ID,Logins={f'cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}': id_token})identity_id = identity_id_resp['IdentityId']# 用身份ID获取临时AWS凭证creds_resp = client.get_credentials_for_identity(IdentityId=identity_id,Logins={f'cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}': id_token})return creds_resp['Credentials']def update_aws_config(credentials):aws_config_path = os.path.expanduser('~/.aws/credentials')config = configparser.ConfigParser()if os.path.exists(aws_config_path):config.read(aws_config_path)profile_name = 'cognito-sso' # 我们可以自定义profile名称if not config.has_section(profile_name):config.add_section(profile_name)config.set(profile_name, 'aws_access_key_id', credentials['AccessKeyId'])config.set(profile_name, 'aws_secret_access_key', credentials['SecretAccessKey'])config.set(profile_name, 'aws_session_token', credentials['SessionToken'])with open(aws_config_path, 'w') as configfile:config.write(configfile)print(f"凭证已成功更新到profile '{profile_name}' 中。")print("现在我们可以使用 'aws s3 ls --profile cognito-sso' 来测试了。")if __name__ == "__main__":username = input("请输入用户名: ")password = getpass.getpass("请输入密码: ")id_token = get_cognito_tokens(username, password)if id_token:credentials = get_aws_credentials(id_token)update_aws_config(credentials)
如何使用这个脚本:
- 安装 boto3:
pip install boto3
- 将脚本中顶部的配置信息替换为我们自己的 Cognito 和 IAM 信息。
- 运行脚本:
python login.py
- 按照提示输入用户名、密码和 MFA 码。
- 脚本会自动获取临时凭证,并写入到
~/.aws/credentials
文件的一个新的 profile 中(例如[cognito-sso]
)。 - 之后,我们所有的 AWS CLI 命令都可以通过
--profile cognito-sso
来使用这些临时凭证,例如aws s3 ls --profile cognito-sso
。
结论:自己动手,丰衣足食
虽然我们无法使用组织级的 IAM Identity Center,但这并不意味着我们必须在安全上妥协。通过组合 Cognito 用户池和身份池,我们成功地为自己构建了一个轻量、安全、且支持 MFA 的 CLI 登录解决方案。
这个方案不仅解决了我们的燃眉之急,更让我们深入理解了 AWS 身份联邦的强大能力。当我们需要为我们的应用程序或团队成员提供安全的 AWS 资源访问时,这套思路同样适用。