使用STS服务和STS Tag标签、通过AssumeRole方式为应用生成临时授权

一、背景

在某些环境内,需要为某个系统生成一个临时访问授权,例如某应用允许用户发起对S3存储桶的特定目录的写入操作,而且有超时时间,过期后失效。此时,可使用STS服务临时生成AK/SK密钥,然后将临时AK/SK作为应用程序上传时候调用的角色即可。

本文编写参考了这篇博客,并针对AWS中国区的IAM策略添加了aws-cn标签。

本文编写中,可能混用中英文,其对应关系是:

  • IAM Role -> IAM角色;
  • IAM Policy -> IAM 策略;
  • IAM Trust relationship -> IAM信任关系。

STS整个原理和流程如下图。

以上架构用简单的直观的方式可描述为:

  • A希望对S3临时读写,想获得一个临时有效的AKSK做上传。
  • 于是要先封装一个IAM Role 叫做B,这个角色不是一个真实用户,而是个虚拟的角色,这个角色上挂了一个IAM Policy允许对特定S3桶的特定目录做读写。同时,这个角色的信任关系也配置上策略允许某特定用户Assume(可翻译为呈现,或者借用)使用这个角色B。
  • 现在继续创建一个IAM用户C,并给C挂载上一个IAM Policy,允许C这个用户去调用sts服务和Assume B。此外,再为这个用户C创建一个长期使用的AKSK密钥作为程序调用接口。
  • 最后,封装一个通过STS获取临时AKSK的应用程序D。应用D代码内嵌入IAM用户C的AKSK
  • 现在整个体系可以转起来了。A程序先调用D,试图获取临时AKSK。D程序调用IAM用户C的长期有效的AKSK,访问AWS STS服务,并声明试图Assume成为IAM角色B。STS收到完成的请求后,生成一个可以Assume成为IAM角色B的临时AKSK,然后将这个临时AKSK返回给A。最后,A使用这个临时AKSK可以访问S3的特定目录。

访问STS时候,如果不使用SDK调用STS,或者希望白名单制定STS的Endpoint,那么中国区可使用如下地址:

  • 北京区:sts.cn-north-1.amazonaws.com.cn
  • 宁夏区:sts.cn-northwest-1.amazonaws.com.cn

下面做配置。请注意本文描述的几个资源创建有先后顺序关系,尽量按顺序可完成IAM设置,否则还需要跳回到前边的步骤去补充IAM的ARN等参数。有关脚本已经改为在中国区可执行,如果是在AWS Global区域之行,请替换下文中所有策略中aws-cn为aws即可。

二、创建IAM用户和长期有效的AKSK用于程序调用STS

上文讲解架构中,描述了客户需要自己封装一个服务来调用STS服务,然后返回临时AKSK给最终用户。那么这个服务调用STS是需要长期有效的AKSK。因此我们新建一个IAM User,并创建一个长期有效的AKSK。

1、创建IAM策略

进入IAM策略界面,创建策略,名为“sts-iam-user-policy”。其内容如下。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "*"
        }
    ]
}

这段IAM策略将允许调用STS服务。

2、创建IAM用户和AKSK

新建一个IAM用户,名为“ServerUser”。然后将上边创建的IAM策略“sts-iam-user-policy”绑定到这个用户上。这样这个用户就可以调用Assume Role了。

接下来为这个用户创建AKSK。AKSK创建完毕后,请妥善保管,后续会用于服务器端调用STS服务。

三、创建S3存储桶

正常创建一个存储桶sts-iam-demo-bucket。然后在存储桶内创建目录(也称为PREFIX前缀),例如本文后续会使用 S3://sts-iam-demo-bucket/PREFIX/123456/ 这样的目录。为进一步验证STS服务的Tag标签功能,还可以在创建一个 S3://sts-iam-demo-bucket/PREFIX/456789/ 目录,后续用来测试特定访问权限是仅限自己的目录,对别的目录无权限。

四、创建被Assume的IAM角色和S3访问策略

1、创建IAM策略允许对S3访问

进入IAM策略,新建一个策略,保存名为“sts-s3-access-policy”,其内容如下:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ListYourObjects",
            "Effect": "Allow",
            "Action": "s3:ListBucket",
            "Resource": [
                "arn:aws-cn:s3:::sts-iam-demo-bucket"
            ],
            "Condition": {
                "StringLike": {
                    "s3:prefix": [
                        "PREFIX/${aws:PrincipalTag/userid}/"
                    ]
                }
            }
        },
        {
            "Sid": "ReadWriteDeleteYourObjects",
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:PutObject",
                "s3:DeleteObject"
            ],
            "Resource": [
                "arn:aws-cn:s3:::sts-iam-demo-bucket/PREFIX/${aws:PrincipalTag/userid}/",
                "arn:aws-cn:s3:::sts-iam-demo-bucket/PREFIX/${aws:PrincipalTag/userid}/*"
            ]
        }
    ]
}

以上这段IAM Policy将允许对存储桶sts-iam-demo-bucket的名为PREFIX前缀(或叫目录)内的名为userid标签的子目录进行读取、写入、删除操作。${aws:PrincipalTag/userid}标签是一个变量,届时会通过STS的tag标签功能传递过来一个变量,例如传输123456这样的id,那么就意味着可以对“s3://sts-iam-demo-bucket/PREFIX/123456/”这个路径读写。

如果希望简化管理,可以直接把这段变量删除了,直接给本目录权限也是可以的。

2、创建IAM角色

进入IAM角色界面,创建一个角色,名为“stsAssumedRole”。这个角色稍后就是被Assume的角色。

创建角色的第一步,提示使用场景,这个时候选择“AWS服务”,在页面下方选择“EC2”,然后点击下一步即可。

为了让这个角色能够读取S3存储桶,将上文创建的IAM策略“sts-s3-access-policy”挂载到这个角色上。由此,具有了本角色的用户,将能读写S3存储桶。

3、设置IAM信任关系

新创建的角色默认是不允许被Assume过来的。因此还要将前文步骤创建的服务器端用户“ServerUser”和本步骤创建的被Assume的Role即“stsAssumedRole”二者之间配置信用关系。

进入IAM用户界面,查看用户“ServerUser”的ARN代码,可获得类似如下代码:

arn:aws-cn:iam::420029960748:user/ServerUser

将这个ARN复制下来。请注意中国区的ARN是aws-cn,海外区是aws,这是中国区和海外区的差异。

进入IAM角色界面,找到刚创建的“stsAssumedRole”。点击这角色查看详情,在页面中间部分点击第二个标签页“信任关系(Trust Relationships)”,然后点击右侧的编辑按钮。将原有的信任关系的配置完全删除,然后粘贴如下部分:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com.cn"
            },
            "Action": "sts:AssumeRole"
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws-cn:iam::420029960748:user/ServerUser"
            },
            "Action": [
                "sts:AssumeRole",
                "sts:TagSession"
            ]
        }
    ]
}

保存完成。由此配置,就意味着允许ServerUser(包括其名下长期有效的AKSK)可以Assume成为角色“stsAssumedRole”,并按照本角色下的权限访问S3。

支持配置工作完成。接下来模拟下环境运行。

五、调用STS并Assume角色以获取临时AKSK

在模拟环境运行,这里有两个方法,1是用AWSCLI,适合测试。2是用SDK,例如Python代码通过boto3 SDK调用STS。

1、用AWSCLI访问STS获取临时AKSK

在作为服务器端角色的EC2上,配置AWSCLI和前文生成的长期使用的AKSK。然后运行如下脚本,获取新的临时AKSK:

aws sts assume-role --role-arn arn:aws-cn:iam::420029960748:role/stsAssumedRole --role-session-name demo01 --tags Key=userid,Value=123456 --duration-seconds 3600

然后即可获取返回结果。

{
    "Credentials": {
        "AccessKeyId": "xxxxxxxxxxxxxxxxxxx",
        "SecretAccessKey": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "SessionToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "Expiration": "2022-05-08T04:27:02+00:00"
    },
    "AssumedRoleUser": {
        "AssumedRoleId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx:demo01",
        "Arn": "arn:aws-cn:sts::420029960748:assumed-role/stsAssumedRole/demo01"
    },
    "PackedPolicySize": 3
}
(END)

这里即可获得临时AKSK输出,还有临时SessionToken。

2、用Python代码通过SDK访问STS获取临时AKSK

在作为服务器端角色的EC2上,构建如下一段Python代码。这段代码需要服务器用户长期有效的AKSK来调用STS。

import boto3

client = boto3.client('sts')
response = client.assume_role(
    RoleArn='arn:aws-cn:iam::420029960748:role/stsAssumedRole',
    RoleSessionName='demo02',
    DurationSeconds=900,
    Tags=[
        {
            'Key': 'userid',
            'Value': '123456'
        }
    ]
)

print(response)

调用完毕,获取到了如下的临时AKSK。

{'Credentials': {'AccessKeyId': 'xxxxxxxxxxxxxxxxxxx', 'SecretAccessKey': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'SessionToken': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'Expiration': datetime.datetime(2022, 5, 8, 3, 53, 21, tzinfo=tzutc())}, 'AssumedRoleUser': {'AssumedRoleId': 'AROAWDS54SIWNEF3ZYV4F:demo02', 'Arn': 'arn:aws-cn:sts::420029960748:assumed-role/stsAssumedRole/demo02'}, 'PackedPolicySize': 3, 'ResponseMetadata': {'RequestId': 'fe42a619-599e-428e-a581-737b7ca322f8', 'HTTPStatusCode': 200, 'HTTPHeaders': {'x-amzn-requestid': 'fe42a619-599e-428e-a581-737b7ca322f8', 'content-type': 'text/xml', 'content-length': '1487', 'date': 'Sun, 08 May 2022 03:38:21 GMT'}, 'RetryAttempts': 0}}

接下来验证这个临时AKSK具有正确权限。

六、验证临时AKSK访问S3

1、加载临时AKSK

另外启动一个EC2,安装AWSCLI,配置临时AKSK做验证测试。当然也可以用代码进行验证。以AWSCLI为例,执行如下命令。

export AWS_ACCESS_KEY_ID="xxxxxxxxxxxxxxxxxxx"
export AWS_SECRET_ACCESS_KEY="xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export AWS_SESSION_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

注意请包含两端的双引号。

2、验证权限

执行如下命令,验证临时AKSK有效。注意这里需要添加 --region,否则会因为没有默认region的参数而导致无法验证身份。

aws sts get-caller-identity --region cn-northwest-1

返回结果如下:

{    "Account": "420029960748",    "UserId": "AROAWDS54SIWNEF3ZYV4F:demo02",    "Arn": "arn:aws-cn:sts::420029960748:assumed-role/stsAssumedRole/demo02"}

这表示这个临时AKSK正确的获取到了要Assume的Role。那么接下来测试下,这个临时AKSK是不是和被Assume的Role一样具有正确的S3访问权限。

执行如下命令,使用AWSCLI的S3复制文件功能,将一个文件复制到有权限的存储桶内。注意这里和前边的步骤一样,都需要添加--region,否则会因为没有默认region的参数而导致无法验证身份。

aws s3 cp hehe.txt s3://sts-iam-demo-bucket/PREFIX/123456/hehe2.txt --region cn-northwest-1

可看到复制成功。返回结果如下。

[root@ip-172-31-32-30 ~]# aws s3 cp hehe.txt s3://sts-iam-demo-bucket/PREFIX/123456/hehe2.txt --region cn-northwest-1
upload: ./hehe.txt to s3://sts-iam-demo-bucket/PREFIX/123456/hehe2.txt
[root@ip-172-31-32-30 ~]#

接下来使用如下命令查看下这个有权限操作的目录的文件。注意这里和前边的步骤一样,都需要添加--region,否则会因为没有默认region的参数而导致无法验证身份。

aws s3 ls s3://sts-iam-demo-bucket/PREFIX/123456/ --region cn-northwest-1

返回结果如下 :

[root@ip-172-31-32-30 ~]# aws s3 ls s3://sts-iam-demo-bucket/PREFIX/123456/ --region cn-northwest-1
2022-04-28 10:23:33          0
2022-05-06 16:48:55          5 hehe.txt
2022-05-08 03:52:40          7 hehe2.txt
[root@ip-172-31-32-30 ~]#

由此证明了Assume的角色的S3权限工作正常。接下来尝试下list其他S3目录,可发现是无权限的,如下截图:

[root@ip-172-31-32-30 ~]# aws s3 ls s3://sts-iam-demo-bucket/PREFIX/456789/ --region cn-northwest-1
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied
[root@ip-172-31-32-30 ~]#

至此验证了STS服务的标签Tag功能,可在Assume角色时候传入特定参数,结合IAM Policy对S3存储桶的特定目录进行权限管控。

七、参考文档

通过 STS Session Tags 来对 AWS 资源进行更灵活的权限控制:

https://aws.amazon.com/cn/blogs/china/use-sts-session-tags-to-perform-more-flexible-permission-control-on-aws-resources/

STS官方文档:

https://docs.aws.amazon.com/zh_cn/IAM/latest/UserGuide/id_session-tags.html#id_session-tags_adding-assume-role

Python Boto3 SDK之STS调用

https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sts.html#STS.Client.assume_role