洞察策略风险,用自然语言提问策略问题。 申请演示 →

Published:

CloudTrail 与 CloudWatch Events 协同工作的过度复杂方式

by FireMon

在我的云之旅中,最让人头疼的问题之一,就是理解 CloudTrail 与 CloudWatch Events 是如何协同工作的。不知为何,我花了好几年时间(以及大量测试)才真正弄明白两者之间的关联机制,尤其是它与多区域以及 AWS Organization 跟踪记录这些概念的配合方式。等我全部搞清楚之后,我本以为大家早就知道了,但最近的一些交流表明,这种困惑相当普遍。因此,我在这里尽力把它简化说明。

首先,我们要解决的问题是:基于 CloudTrail 事件构建 CloudWatch 规则,并用它们发送通知或触发 Lambda 函数

就这么简单——我希望针对某些简单的操作发送通知,比如开放新安全组规则的 API 调用(如果您想知道的话,就是 AuthorizeSecurityGroupIngress)。

要实现这一点,您需要具备以下条件:

  • 在发起该 API 调用的区域中启用 CloudTrail。
  • CloudTrail 将数据流式传输到 CloudWatch。
  • 一条 CloudWatch 规则,位于该 API 调用所在的区域,用于查找该特定 API 调用或所有 CloudTrail API 调用。

接下来是容易混淆的地方:

  • 当您创建多区域 CloudTrail 或 Organization 跟踪记录时,AWS 在后台实际上是在每一个区域(如果是 Org 跟踪记录,则是在每一个账户)中都设置了跟踪记录。它们都是彼此独立的跟踪记录,但每一个都被配置为将结果发送到一个共享的 S3 存储桶,而且您只能在其所属账户和区域中管理各自的跟踪记录。
  • 然而,针对 API 调用的 CloudWatch 事件只会在该 API 调用所在的区域中创建
  • 因此,如果您创建了多区域跟踪记录,数据会被集中收集,但事件只会在本地出现。位于主跟踪记录所在区域的 CloudWatch 规则,只会针对在该(主)区域中发起的 API 调用触发。因此,如果您为安全组变更设置了告警,即便 CloudTrail 已经启用,它也只会在主区域生效,而不会在其他区域生效。
  • CloudWatch 日志组/日志流会出现在主区域,而不是其他区域,但每个事件都是在触发该事件的区域中创建的。
  • 如果您想收集所有 API 调用事件,就需要使用一个未公开的事件定义(我已将其粘贴在下方)。
  • 如果您去读 Amazon 的文档……他们从未把这些内容讲清楚。至少我没能找到。事实上,我曾经在一次技术支持通话中弄明白了这一点,当我的事件开始不断涌入时,那位 AWS 代表还在不停地嘟囔:“我觉得它不是这样工作的。”他那晚过得很不好受。

不知为何,这一点对我来说非常反直觉。我原以为,如果把跟踪记录集中起来,就可以集中使用一条 CloudWatch 规则来根据 API 调用事件触发。遗憾的是,这完全不对;即便您集中了跟踪记录,仍然需要在您关注的每一个区域中创建规则。即使您使用 Event Bus 从多个账户收集事件,您仍然需要在每个账户的每个区域中创建 CloudWatch 规则,以便将事件发送到 Bus,然后还需要在 Event Bus 的每个区域中构建规则,以触发您想要的任何通知/操作。

如果您希望获得 CloudWatch 规则所支持的准实时告警能力或自动修复/操作,我建议按以下方式来处理:

  • 启用多区域跟踪记录。您只需执行一次,使用 Organization 跟踪记录即可满足需求。
  • 这会创建您所需的全部区域跟踪记录。它看起来像是一条集中的跟踪记录,但实际上是一组区域跟踪记录,将各自的数据发送到一个集中接收方。
  • 方案 1:在您希望获得准实时告警的每一个区域中创建 CloudWatch 规则。此时 CloudFormation 和 Terraform 会是您的好帮手。
  • 方案 2:集中收集所有事件。在每个区域中创建一条规则,将所有 CloudTrail 事件发送到 Lambda 函数或 SNS 主题,再由其转发到您的目标位置。我们自己就采用这种方法;我们通过自定义 API 端点发送,但您也可以流式传输到 Kinesis 或几乎任何目标。

为帮助您快速上手,这里提供两段代码示例。

用于让 CloudWatch 规则收集 CloudTrail 全部事件的隐藏筛选模式:

{

"detail-type": [

"AWS API Call via CloudTrail"

] }

以下是用于转发事件的 Lambda 示例代码,本例中转发到 Kinesis:

import json

import boto3

def lambda_handler(event, context):

kinesis = boto3.client('kinesis', region_name='us-west-2')

data = json.dumps(event)

print(data)

response = kinesis.put_record(

StreamName='cloudsec_prod_stream_alert_kinesis',

Data=data,

PartitionKey='test-client-id'

)

print(response)

return {

'statusCode': 200,

'body': json.dumps('Record added')

}

CloudTrail 与 CloudWatch 如何协同工作 | FireMon