亚马逊AWS官方博客

基于 Permission Boundary 的多账号管理权限限定方法

需求背景

在AWS云环境中,多账号管理最好的方法是采用AWS Control Tower和Organization。 而对于中国区的用户,在没有合适的原生服务可用时,也可以采取一些自开发的方法来部分满足需求。本文针对一个具体的案例,给出了解决方案,作者也希望借此能够为大家提供一种思路,以解决更多多账号管理中遇到的问题。

企业在采用多账号管理进行管理时,常常需要设立一个跨账号的管理员角色,我们暂且将这个管理员称为总管理员。这个角色对于每个单独的账号都具有Admin权限。同时,每个账号还会设立单独的管理员权限。总管理员的日常工作是在各个独立的账号中设立一些标准的服务,例如安全组、有特殊用途的EC2主机、S3 桶等,但是各个账号的管理员不能对总管理员建立的服务做更改。这就需要建立一种特殊的保护机制,而总管理员建立和删除哪些服务是动态进行的,所以这种保护机制最好能够在资源建立后自动生效。

 

如上图所示,总管理员在各个分账号中建立了资源(红色),各个分账号的管理员只能管理各自建立的资源(与管理员图标背景相同颜色的资源),而不能管理红色的资源。

 

方案概述

总管理员是一个在各个分账号中都存在的role,这个role有管理员权限,当总管理员在各个分账号中执行assume role的命令时,就可以管理这个账号了。关于如何设置跨账号的管理权限,请参考AWS博客文章How to Enable Cross-Account Access to the AWS Management Console

缺省情况下,总管理员的角色与分账号管理员拥有同样的权限,无法做到分账号管理员无权更改总管理员创建的资源。要达到这个效果,我们需要使用IAM Permission Boundary的功能。如果想详细了解AM Permission Boundary的功能,可以参考文档Permissions Boundaries for IAM Entities

假设张三做为总管理员,在account-a中创建了一个S3 bucket,名字是security-boundary-test,而做为account-a的管理员李四无权修改这个bucket,我们只需在account-a中生成一个policy, 内容如下:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        },
        {
            "Sid": "Restrict",
            "Effect": "Deny",
            "NotAction": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws-cn:s3:::security-boundary-test"
            ]
        }
    ]
}

并将这个policy做为Permissions Boundary加到用户李四对应的policy上就可以了。

下面我们仔细解读一下上面这个policy的含义,这个policy有两个部分,

第一部分:

{
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "*",
            "Resource": "*"
        },

说明对任何资源允许任何操作。

第二部分:

{
            "Sid": "Restrict",
            "Effect": "Deny",
            "NotAction": [
                "s3:Get*",
                "s3:List*"
            ],
            "Resource": [
                "arn:aws-cn:s3:::security-boundary-test"
            ]
        }

说明对Resource中定义的S3 bucket只允许只读操作

"s3:Get*",
"s3:List*"

两部分结合起来,就是对第二部分Resource中定义的特定资源,只能执行NotAction中指定的操作,而对其他资源的权限不做限制。

那么回到我们在需求背景中定义的问题,如果我们能够动态生成一个类似于上面的policy文件,使得总管理员创建的资源自动加到第二部分Resource中,而这些Resource对应的操作权限都定义为只读,添加在第二部分NotAction中,然后将这个policy作为Permissions Boundary加到各个分账号管理员对于的policy中,问题就可以解决了。

要生成这样一个policy,动态变化的部分有两个,一个是NotAction对应的list,另一个是Resource对应的list。NotAction应该都是些只读的操作,而且这些操作要与Resource中定义的资源相呼应。对所有资源的只读操作的总集合,我们可以通过查看系统中的AWS托管Policy ReadOnlyAccess得到。而Resource中定义的都是资源的ARN,ARN是由“:”分割开的多个部分,第三部分就是资源的名称。只要我们能得到资源的ARN,就可以通过查看ReadOnlyAccess Policy,找到对应于这个资源的只读操作集。现在要解决的核心问题就是如何获得资源的ARN

我们知道,监测和记录AWS上所有API操作的服务是CloudTrail,那么我们能不能通过查找CloudTrail的日志文件从而获得资源的ARN呢?答案是有些时候可以,有些时候不行。为什么会这样呢,下面我们研究几个CloudTrail日志来看看。

1.创建一个新的KMS Key:

我们可以发现在reponseElements里面有arn的信息。

2.创建EC2


我们可以发现在reponseElements里面没有arn的信息,只有InstanceID。

3.创建S3 bucket

 

这回我们在reponseElements没有发现任何有用信息,而在requestPatameters里面有bucketName, 但是还是没有arn。

4.创建Lambda Function

 

在reponseElements里面可以找到functionArn,但是这次的名字不叫arn。

5.创建一个SQS队列

 

这次我们只能得到一个在requestParameters里面的queueName, 还是没有arn。

6.创建DynamoDB table

 

 

这次运气不错,能够得到一个tableArn。

通过上面的几个例子我们可以发现,CloudTrail并不能完全满足需求,而且就是能找到arn,其给出的反馈信息也是很不标准,有的叫arn,有的叫xxxArn;有的在reponseElements包含有用信息(可以唯一标识一个资源的符号,ID或者Name),有的在requestParameters里面包含有用信息。

那么还有什么工具可以帮我们呢?对了,可以试试AWS Config,因为Config里面会记录所有资源的信息。我们看看几个Config的日志文件的片段,了解一下里面有些什么信息:

1.创建IAM policy

 

 

我们可以找到ARN。

2.创建VPC

 

我们还是能够找到ARN。

从这两个例子我们可以发现,AWS Config的日志文件格式非常标准,里面都有ARN信息,而且ARN的最后一段(以“/”为分割符)要么是ID信息,要么是Name信息。而这些ID或者Name都是可以从CloudTrail的日志找到。

至此,我们基本可以得出结论,结合CloudTrail和AWS Config,我们就能找到所需要的ARN。也许你会问,既然Config的日志文件有ARN信息,为什么不直接用这个文件,为什么还要用CloudTrail? 我们的目的是要找到特定对象(在本文的例子中是总管理员张三)创建的资源,Config的日志文件中并不包含创建者的信息,所以不能只依靠AWS Config。我们的思路是,先利用CloudTrail,找出张三创建的资源,然后在看看能不能找到arn(也许是xxxArn,记得,CloudTrail的日志文件格式并不标准), 如果能找到,任务就完成了。如果不能,那就从CloudTrail里面得到资源的唯一标识,利用这个标识再去Config的日志文件中找ARN。

在理清了基本思路后,我们得出了下面这张架构图:

 

 

图中橙色的线代表触发,绿色的线代表调用。

下面仔细解释一下这张图:

1.CloudTail的日志文件保存在CloudWatch Log中,通过订阅log group filter触发一个Lambda Function(change_boundary_doc)

以上面说的总管理员张三为例,我们先要在AWS account中创建一个有管理员权限的role,名字为zhangsan,那么log group filter就可以这样定义:

{$.userIdentity.sessionContext.sessionIssuer.userName=“zhangsan”&& ($.eventName = *Create* || $.eventName = *Delete* || $.eventName = Run* || $.eventName = Terminate* || $.eventName = *Remove*)}

当有人通过zhangsan这个角色创建或者删除资源时,就会触发Lambda Function。

2.AWS Config的日志通过CloudWatch Event获取,定义格式如下:

定义完成后,再为这个event加一个target, Lambda Function– save_arn_to_db

3.Lambda Function – save_arn_to_db

这个Lambda完成两个功能,当收到一条config 事件后,判断是添加操作还是删除操作。添加操作是指调用含有run,create的API, 删除操作是指调用含有delete,remove或者terminate的API。如果是添加操作,就在DynamoDB的add_resourceArn中添加一条记录;如果是删除操作,就在delete_resourceArn中添加一条记录。

4.关于CloudTrail和AWS Config的事件是否同步的问题

由于我们使用CloudWatch Event来接受AWS Config的日志变更,所以理论上会比较快,一般会在1分钟以内。而用AWS CloudWatch Log接收来自CloudTrail的日志会比较慢,一般5分钟会有发布一次。所以理论上当Lambda- change_boundary_doc发现角色zhangsan创建或者删除一个资源的时候,另一个Lambda- save_arn_to_db已经在DynamoDB中完成了更新。当然,也不能排除意外,就是当收到CloudTrail的日志后,还没收到Config的日志,这时就无法在DynamoDB中得到ARN。为了对这种不一致的情况做个缓冲,我们加了一个SQS队列。如果change_boundary_doc得到一个resource ID但是在DynamoDB中查不到相关记录时,暂时将ID放在SQS队里里面。

5.Lambda Function – handle_task_queue

这个Lambda的功能是定期(缺省5分钟)处理SQS里面的数据,如果发现resource ID并且能够在DynamoDB里面找到ARN,就去修改policy的定义。

6.最后在谈谈 Lambda Function – change_boundary_doc

这个Lambda完成如下几件事情:

  • 在收到的CloudTrail日志中查找resource ID或ARN,如果能找到ARN,就直接修改policy的定义。否则去DynamoDB里面找ARN,找到后,删除DynamoDB里面相应的记录,并修改policy的定义
  • 如果当前无法在DynamoDB里面找到ARN,就放入SQS里面

 

局限

采用这种方案可以在一定程度上解决问题,但是还存在一些局限:

1.一些操作不支持资源级权限,无法用限制资源ARN的方法限制操作。

2.由于CloudTrail的格式不标准,目前的程序还不能保证各种资源的resource ID都能取出,目前已经测试过的有:EC2,ENI,EBS,Security Group,SQS,Lambda,IAM policy,IAM role,KMS,VPC,subnet。

 

源代码

本文中涉及到的示例代码可以从https://github.com/shaneliuyx/permission_boundary_sample.git 下载

 

本篇作者

刘育新

AWS ProServe 团队高级顾问,长期从事企业客户入云解决方案的制定和项目的实施工作。