AWS Assume IAM role 的使用

AWS 要授权给他人访问指定资源有哪几种方式呢?
  1. 创建一个 AWS 帐号让别人用,那是 AWS 干的事
  2. 在自己帐号下创建一个用户,把 Access Key ID 和 Secret Access Key 告诉别人。可为该用户限定权限,但任何获得那两个 Key 的人都能使用该用户。
  3. 创建一个 IAM Role, 并指定谁(帐号或 Role) 能以该 Role 的身份来访问。被 Assume 的 Role 可限定权限和会话有效期。

用 Assume Role 的方式具有更高的安全可控性,还不用维护 Access Key ID 和 Secret Access Key。比如在构建和部署时通常是有一个特定的 Account, 然后 Assume 到别的 IAM Role 去操作资源。

本文将详细介绍在帐号 A 创建一个 IAM Role(标注为 R) 并分配一些权限,然后允许另一个帐号 B 以 IAM Role - R 的身份来访问帐号 A 下的资源。IAM Role 将用 awscli 来创建,Assume Role 的过程用 awscli 和 boto3 Python 代码两种方式来演示。

帐号 A 下创建 IAM Role

首选要以帐号 A 登陆 AWS,不管哪种方式只要让 aws s3 cli 能正常工作就行。AWS 获得访问资格(Credentials) 的顺序可参照 Boto3 的文档 Configuring Credentials

比如说在 ~/.aws/credentials 中配置了 profile A 和 B,分别对应帐号 A 和 B 的 profile 名称,在帐号 A 下创建 IAM Role 前用 export AWS_DEFAULT_PROFILE=A 指明当前为帐号 A

准备 role-trust-policy.json
 1{
 2  "Version": "2012-10-17",
 3  "Statement": [
 4    {
 5      "Effect": "Allow",
 6      "Principal": {
 7        "AWS": "arn:aws:iam::123456789022:root"
 8      },
 9      "Action": "sts:AssumeRole",
10      "Condition": {}
11    }
12  ]
13}

这里的 123456789022 是帐号 B 的 Account ID, 如果允许更多的帐号可以往上面的 Principal 中添加。创建 test-assumed-role
$ aws iam create-role --role-name test-assumed-role --assume-role-policy-document file://role-trust-policy.json
再给新建的 test-assumed-role 加上 S3 的只读权限
$ aws iam attach-role-policy --role-name test-assumed-role --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

帐号 B Assume 帐号 A 的 role(awscli)

在帐号 A 下创建好 test-assumed-role 后,现在为 awscli 切换到帐号 B 下,比如 export AWS_DEFAULT_PROFILE=B。接着要用 aws sts assume-role 命令
$ aws sts assume-role --role-arn arn:aws:iam::123456789011:role/test-assumed-role --role-session-name awscli-session
其中的 123456789011 是,sts  是指 Security Token Service,执行后输出如下
 1{
 2    "Credentials": {
 3        "AccessKeyId": "PNKDIESJGWAURFEWDLLT",
 4        "SecretAccessKey": "YcYdWHbtFZEDj/GetAyqRWdgvExxVpDJgC",
 5        "SessionToken": "IQoJb3JpZ2luX2VjEDoaCXVzLWVhc3QdDD1......QyAd6mnRruoJfLo1DUA==",
 6        "Expiration": "2021-11-10T02:36:46+00:00"
 7    },
 8    "AssumedRoleUser": {
 9        "AssumedRoleId": "EXYARBWXQYCCAUONCJHG:awscli-session",
10        "Arn": "arn:aws:sts::123456789011:assumed-role/test-assumed-role/awscli-session"
11    }
12}

这时候得到一组新的 AccessKeyId, SecretAccessKey 和 SessionToken,可以在 ~/.aws/credentials 中配置一个新的 profile C, 然后 export AWS_DEFAULT_PROFILE=C 来使用。 或都用 export 分别导出三个环境变量 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, 和 AWS_SESSION_TOKEN, 分别对应前面的三个值。
$ export AWS_ACCESS_KEY_ID=<Credentials.AccessKeyId>
$ export AWS_SECRET_ACCESS_KEY=<Credentials.SecretAccessKey>
$ export AWS_SESSION_TOKEN=<Credentials.SessionToken>
替换上面的 <Credentials.*> 为 aws sts assume-role ... 返回的实际的相应字符串

2024-02-01


一种更便捷一步到位的切换 role 的方式
1export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s" \
2            $(aws sts assume-role \
3            --role-arn arn:aws:iam::123456789011:role/test-assumed-role \
4            --role-session-name awscli-session \
5            --query "Credentials.[AccessKeyId,SecretAccessKey,SessionToken]" \
6            --output text))


这时候执行 aws s3 ls 获得的就是帐号 A 中的 S3 Bucket 列表
$ aws s3 ls
2021-09-10 12:09:35 bucket1-of-A
2019-12-03 15:55:35 bucket2-of-A
想访问超出 test-assumed-role 之外的权限将被提示 Access Denied
$ aws s3 cp Jenkinsfile s3://wfe-files
upload failed: ./test.txt to s3://bucket-of-A/test.txte An error occurred (AccessDenied) when calling the PutObject operation: Access Denied
任何时候都通过 aws sts get-caller-identity 来查看当前所使用的角色
aws sts get-caller-identity
{
    "UserId": "EXYARBWXQYCCAUONCJHG:awscli-session",
    "Account": "123456789011",
    "Arn": "arn:aws:sts::123456789011:assumed-role/test-assumed-role/awscli-session"
}

帐号 B Assume 帐号 A 的 role(boto3)

awscli Assume 一个 IAM Role 的方式是重新获得一组 AccessKeyId, SecretAccessKey 和 SessionToken, 然后切换到被 Assumed 的 Role。用 Python 的 boto3 包实现是完全一样的。
 1import boto3
 2
 3aws_credentials_b = {
 4    'region_name': 'us-east-1',
 5    'aws_access_key_id':'PNKDIESJGWAURFEWDLLT',
 6    'aws_secret_access_key':'TdTMlDUSKecRadKeMlNIBEmIkRjmZOSvtnhgQDZc',
 7    'aws_session_token':'IQoJb3JpZ2luX2VjEDYabEbMG5J2lzlv......IEQisSAwzmnkv7LNf+'
 8}
 9
10
11sts=boto3.client('sts', **aws_credentials_b)
12
13stsresponse = sts.assume_role(
14    RoleArn="arn:aws:iam::123456789011:role/test-assumed-role", # under account A
15    RoleSessionName='assumed'
16)
17
18aws_credentials_assumed_role = {
19    'region_name':'us-east-1',
20    'aws_access_key_id':stsresponse["Credentials"]["AccessKeyId"],
21    'aws_secret_access_key':stsresponse["Credentials"]["SecretAccessKey"],
22    'aws_session_token':stsresponse["Credentials"]["SessionToken"]
23}
24
25
26boto3.setup_default_session(**aws_credentials_assumed_role)
27
28s3 = boto3.client('s3')
29buckets_of_a = [bucket['Name'] for bucket in s3.list_buckets()['Buckets']]

帐号 B 登陆,调用 boto3 的 sts.assume_role() 函数切换到帐号 A 下的 IAM Role test-assumed-role,之后的操作就限定到 test-assumed-role 的约束中了。

当然,使用 Python 的话可以进一步封装,比如默认以帐号 B 登陆,然后执行一个函数 switch_role(role_arn) 后,后续的 boto3 client 就全部变成了 assumed role 的角色了
 1import boto3
 2
 3def switch_role(assume_role_arn):
 4    sts=boto3.client('sts')
 5    sts_res = sts.assume_role(RoleArn=assume_role_arn, RoleSessionName='new_session')
 6
 7    new_credentials = {'aws' + re.sub('([A-Z]+)', r'_\1', key).lower(): value
 8                       for (key, value) in sts_res["Credentials"].items() if key != 'Expiration'}
 9
10    boto3.setup_default_session(**new_credentials)
11    
12switch_role('arn:aws:iam::123456789011:role/test-assumed-role')
13
14s3 = boto3.client('s3')
15buckets_of_a = [bucket['Name'] for bucket in s3.list_buckets()['Buckets']]

sts_res['Credentials'] 转换为 session 要求的格式是简化,但是要注意以后 assume_role() 响应格式的变化有可能影响到程序的正常执行。

链接:
  1. How do I assume an IAM role using the AWS CLI?
永久链接 https://yanbin.blog/how-to-assume-aws-iam-role/, 来自 隔叶黄莺 Yanbin's Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。