亚马逊AWS官方博客
在中国区实现 SCP 功能的替代方案
2019 年 3 月,AWS 发布了服务控制策略(Service Control Policies,SCP)功能。这是一种组织策略,可用于管理 AWS Organizations 中的权限。
截止到 2021 年 4 月,AWS 位于中国大陆地区的两个区域(BJS、ZHY)暂未支持 SCP 功能。下面将讨论一种替代方案,以便在 BJS 和 ZHY 实现类似 SCP 的功能。本方案具有如下特点:
- 自动化。方案部署完成后,自动对被管理账号中创建的 IAM 实体进行策略关联。
- 无服务器化。本方案无需部署 EC2,无需考虑操作系统层的运维工作。
- 低成本。无服务器化方案通常机遇调用次数进行计费,本方案仅在用户创建 IAM 实体时产生调用,使用成本接近于 0。
方案架构说明
AWS IAM 服务的权限边界功能
对于 AWS Organizations 来说,SCP 限制成员账户中的 IAM 实体(用户和角色)的权限。在 AWS IAM 服务中,权限边界 功能具备相似的功能。
权限边界是一个高级功能,它使用托管策略设置可以为 IAM 实体授予的最大权限。实体的权限边界取决于实体上关联的基于 IAM 身份的策略和权限边界同时允许的操作。
基于 IAM 身份的策略是附加到用户、用户组或角色的内联或托管策略。基于身份的策略向实体授予权限,而权限边界限制这些权限。有效的权限是两种策略类型的交集。其中任一项策略中的显式拒绝将覆盖允许。其关系说明如下图:
图1:权限边界效果示意
如果可以对某个 AWS 账号内所有被创建出来的 IAM 实体自动设置权限边界,也相当于对这个账号设置了 SCP 功能。
方案架构设计
当用户在 AWS 内创建 IAM 实体(IAM User 或 IAM Role)时,会产生一个事件。本方案利用这个事件来触发一个 Lambda 函数,利用这个 Lambda 函数为创建出来的 IAM 实体关联一个权限边界,以此对新创建的 IAM 实体进行权限控制。整个过程如下图所示:
图2: 方案架构
权限边界策略
上述架构将创建/使用以下资源:
序号 | 资源类型 | 资源名称 | 说明 |
1 | EventBridge Bus | default | 接受 API 调用事件 |
2 | EventBridge Rule | scp-rule | 过滤出创建 IAM 实体的事件 |
3 | Lambda Function | scp-Permission | 实现自动关联权限边界的功能 |
4 | IAM Role | scp-lambda | Lambda 函数使用的角色 |
5 | IAM Policy | scpPolicy | 被关联的权限边界策略 |
6 | S3 Bucket | 自定义 | 创建 CloudTrail Trail 时需要指定 |
7 | CloudTrail Trail | 自定义 | 捕获 API 调用事件 |
除 EventBridge Bus 和 IAM Policy 外,所涉及到的其它服务资源在创建时均设置如下标签:
Key | Value |
Owner | SCP-Supervisor |
为保证本方案的正常执行,创建出来的上述资源不能被删除或修改。然而在实际应用过程中,一个 IAM 实体很有可能被授予操作上述服务的权限。这就要求自动关联给 IAM 实体的权限边界策略中必须包含以下三类声明:
- 禁止针对附加了 Owner: SCP-Supervisro 标签的资源进行任何操作;
- 禁止针对 arn:aws-cn:iam::<ACCOUNT_ID>:policy/scpPolicy 策略进行任何修改操作,包括修改、删除、解除关联;
- 显性允许其他任何操作。
除上述三类声明外,用户再根据实际管理需要增加其它权限声明,以形成完整的权限边界策略。
方案部署说明
-
- 创建 CloudTrail Trail。如之前在 AWS 账号中已经创建 Trail,可跳过此步骤;如账号中不存在 Trail,可参考 官方文档 中的步骤。注意在创建时,为 Trail 添加 Owner:SCP-Supervisor 标签。
- 创建 IAM Role。信任实体为 Lambda 服务,并关联 IAMFullAccess 策略,添加 Owner:SCP-Supervisor 标签,可根据需要自定义 Role 的名称,如下图:
图3:为 Lambda 函数创建 IAM Role
- 创建作为权限边界的 IAM 策略,策略名 scpPolicy,包含策略内容如下:(以下策略内容仅包含保护本方案所需资源的策略声明,实际应用中还需根据需要添加其它权限声明):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAllOtherAccess",
"Effect": "Allow",
"Action": "*",
"Resource": "*"
},
{
"Sid": "TaggedResourcesProtect",
"Effect": "Deny",
"Action": "*",
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:ResourceTag/Owner": "SCP-Supervisor"
}
}
},
{
"Sid": "OtherResourcesProtect",
"Effect": "Deny",
"Action": "*",
"Resource": [
"arn:aws-cn:iam::<ACCOUNT_ID>:policy/scpPolicy",
"arn:aws-cn:cloudtrail:cn-north-1:<ACCOUNT_ID>:trail/scp-trail-<ACCOUNT_ID>",
"arn:aws-cn:s3:::scp-trail-do-not-delete-<ACCOUNT_ID>"
]
},
{
"Sid": "DenyBoundaryRemove",
"Effect": "Deny",
"Action": [
"iam:DeleteUserPermissionsBoundary",
"iam:DeleteRolePermissionsBoundary"
],
"Resource": [
"arn:aws-cn:iam::<ACCOUNT_ID>:role/*",
"arn:aws-cn:iam::<ACCOUNT_ID>:user/*"
],
"Condition": {
"ArnEquals": {
"iam:PermissionsBoundary": "arn:aws-cn:iam::<ACCOUNT_ID>:policy/scpPolicy"
}
}
}
]
}
- 创建 Lambda 函数,选择6,使用之前创建的 scp-lambda 角色,可根据需要自定义函数名称,如下图:
图4:创建 Lambda 函数
为创建好的函数添加标签:
图5:函数标签
为创建好的函数设置环境变量:
图6:函数环境变量
进入到【代码】框,粘贴如下代码:
import json
import os
import boto3
scpBoundary = os.environ['SCP_BOUNDARY_POLICY']
def lambda_handler(event, context):
iam_client = boto3.client('iam')
accountID = event['account']
boundaryArn = "arn:aws-cn:iam::" + accountID + ":policy/" + scpBoundary
if event['detail']['eventName'] == "CreateUser":
User_Name = event['detail']['responseElements']['user']['userName']
identityArn = event['detail']['responseElements']['user']['arn']
iam_client.put_user_permissions_boundary(
UserName=User_Name,
PermissionsBoundary=boundaryArn
)
elif event['detail']['eventName'] == "CreateRole":
identityArn = event['detail']['responseElements']['role']['arn']
Role_Name = identityArn.split('/')[-1]
iam_client.put_role_permissions_boundary(
RoleName=Role_Name,
PermissionsBoundary=boundaryArn
)
else:
print("Others")
rspAction="Permissions boundary policy has been attached"
output = {
"Identity ARN": identityArn,
"Respond Action": rspAction}
return json.dumps(output)
选择【Deploy】,保存代码。
- 创建 EventBridge Rule。
可根据需要自定义 Rule 的名称。在【定义模式】环节,选择“自定义模式”,粘贴如下代码并保存:
{
"source": ["aws.iam"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["iam.amazonaws.com"],
"eventName": ["CreateUser", "CreateRole"],
"errorCode": [{
"exists": false
}]
}
}
界面显示如下:
图7:定义事件模式
保持使用 default 事件总线即可,选择 Lambda 函数作为目标,并从下来菜单中选择刚刚创建的 lambda. 函数:
图8:设定规则的目标
为规则添加标签:
图9:添加规则的标签
至此,整个方案部署完毕。可自行创建一个 IAM User 或 Role,创建完成后查看这个刚创建的 IAM 实体的权限,能够看到已被自动关联了权限边界策略:scpPolicy。Lambda 执行需要一个很短的时间。若 IAM 实体创建完成后立即查看其权限,可能出现未设置权限边界的情况。刷新一下页面即可看到结果,如下图:
图10:方案效果
延伸说明
本文介绍了一个方案原型,以便在 AWS 中国大陆地区的两个区域内实现类似 SCP 的功能效果。但本文所涉及到的所有管理资源均部署在被管账号内,实际应用中还需要考虑以下两个问题:
- 需要 SCP 功能的企业往往拥有多个账号,上述方案如何实现对多账号的统一管理?并确保可以针对不同账号设置权限边界?
- 当被管理账号越来越多时,如何追溯不同账号使用了那种权限边界策略?
为了解决上述两个关键问题,需要增加以下服务:
- Amazon API Gateway
- Amazon S3
- Amazon EventBridge
- Amazon DynamoDB
- Amazon SNS
整个方案的架构升级为如下结构:
图11:多账号管理架构
如希望进一步了解该方案,可从 这里 获得方案源代码及架构详细说明。