必须面对这样一个现实:并不是所有的API生来都是平等的。比如,你可能很想知道你的Amazon EC2(弹性计算云)实例是何时被终止的(使用ec2:TerminateInstance命令可以终止实 例),但是对一个对象何时被放入Amazon S3桶却不那么感兴趣(使用s3:PutObject可以将对象放入S3桶中)。在这个例子中,你可以删除一个对象,但是你却不能回复已被终止的实例 。所以这就提出了一个问题。“有没有一种方法可以在某些API被调用时,让我通过我的AWS账户被通知到呢?”答案是肯定的。但是尽管如此,如果一些API被意外地调用来支持AWS账户中的一些服务时,你如何被通知到呢?
这个博客帖子将会向你展示,在你的账户中,当你所监测的特定API被调用时,如何使用AWS CloudTrail,Amazon SNS和AWS Lambda来接收邮件通知。特别地,这个帖子包含了详细的方案执行配置说明。这一方案包括:
下面这幅图说明了这些AWS服务时如何协同运行的。
下面则讲述了上图所示流程的故障:
1.CloudTrail为你的账户中的每一次API调用产生一个日志条目。它在JSON文本文件中将这些日志条目聚集,压缩文本文件,并将它们拷贝到Amazon S3桶中(通过配置,Amazon S3桶被设置为接收CloudTrail日志文件的)。
2.作为一个Lambda函数,日志解析逻辑被部署。当CloudTrail拷贝一个新的文件到你的S3桶中时,Lambda被触发。
3.Lambda函数使用一个包含特定API关键字列表的配置文件。一旦特定的API关键字被发现,这些关键字就会触发一个通知消息。配置文件存储在一个S3桶中。
4.无论何时,当CloudTrail日志中发现相关关键字时,Lambda函数会发送一个通知消息到SNS主题.
5.SNS通过邮件,Amazon SQS(简单队列服务),短消息,HTTP调用 ,或移动推送等方式将通知分派给每一位主题订阅者。
本帖的剩余部分演练执行前述图表中所示流程所需的具体步骤。我将通过在Amazon Linux实例的AWS命令行界面中完成这一讲述中的AWS交互。
这些详细的配置说明的前提条件如下:
本次演练将使用us-west-2(US West (Oregon))作为缺省域。本次演练的完整源代码可在GitHub获得。
演练
第一步:创建一个S3桶并配置CloudTrail服务
首先,你要为CloudTrail服务创建一个S3桶来存储你的账户的API调用所产生的CloudTrail日志文件并开启CloudTrail服务。如果你已经为你的账户开启了CloudTrail服务,跳过这一步,直接到第二步。
创建的S3桶必须有唯一的名称并需要一个IAM(Identity and Access Management身份及访问管理)桶策略来确保CloudTrail有充足的权限在桶中创建文件。Amazon S3桶策略提供缺省的CloudTrail S3策略。
下面的cloudtrail create-subscription命令将会自动创建桶,为访问CloudTrail服务关联桶策略,并在那个域中为你的账户开启和配置CloudTrail服务。
确保你修改了$BUCKETNAME参数的值因为S3桶的名称是全局唯一的。
REGION=us-west-2 BUCKETNAME=cloudtrail-logs-abcd1234 TRAILNAME=security-blog-post aws cloudtrail create-subscription --region $REGION --s3-new-bucket $BUCKETNAME --name $TRAILNAME
从现在开始,控制台,AWS CLI,或软件开发工具包(Software Development Kit)调用API的操作都将会被记录并以文本文件的形式发送给你。文本文件的内容与下面的内容相似:
现在你将创建一个SNS主题,该主题将会向订阅者发送该主题接受的每一条消息的副本。订阅者可以使用不同的协议:HTTPS, Mail, SQS, SMS (仅能向符合美国电话号码格式的号码发送),也可以向移动设备推送通知消息。
{ "Records": [ { "eventVersion": "2.0", "eventSource": "aws:s3", "awsRegion": "us-west-2", "eventTime": "2014-11-27T21:08:30.487Z", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "AWS:ARxxxxxxxxxxSW:i-4fxxxxa5" }, "requestParameters": { "sourceIPAddress": "54.211.178.99" }, "responseElements": { "x-amz-request-id": "F104F805121C9B79", "x-amz-id-2": "Lf8hbNPkrhLAT4sHT7iBYFnIdCJTmxcr1ClX93awYfF530O9AijCgja19rk3MyMF" }, "s3": { "s3SchemaVersion": "1.0", "configurationId": "quickCreateConfig", "bucket": { "name": "aws-cloudtrail-xxx", "ownerIdentity": { "principalId": "AHxxxxxxxxQT" }, "arn": "arn:aws:s3:::aws-cloudtrail-xxx" }, "object": { nbsp; "key": "AWSLogs/577xxxxxxxx68/CloudTrail/us-west-2/2014/11/27/577xxxxxxxx68_CloudTrail_us-west-2_20141127T2110Z_JWcmX2IqHMUoBRIM.json.gz", "size": 2331, "eTag": "63d801bb561037f59f2cb4d1c03c2392" } } } ] }
第二步:创建一个SNS主题并订阅一个电子邮件地址
为了演练的目的,你将为一个电子邮件地址订阅该SNS主题。Lambda函数将会为CloudTrail日志文件所发现的每一次意外的API调用产生一条SNS通知消息。创建SNS主题并未你的电子邮件地址订阅该主题
EMAIL=<YOUR EMAIL ADDRESS> TOPIC_ARN=$(aws sns create-topic --name cloudtrail_notifications --output text --region $REGION) aws sns subscribe --protocol email --topic-arn $TOPIC_ARN --notification-endpoint $EMAIL --region $REGION echo $TOPIC_ARN
确保写入主题ARN作为前述命令的输出。而后你将使用该主题配置Lambda函数。ARN主题应与下面所述相似。
arn:aws:sns:us-west-2:577xxxxxxx68:cloudtrail_notifications
你将收到一封确认邮件(万一你没有受到,检查你的垃圾文件)。这封邮件包含一个确认订阅的链接。你必须点击该链接来完成订阅流程。
第三步:为你的Lambda函数创建IAM角色
Lambda函数需要一个IAM角色才能正常运行。
IAM执行角色,一种IAM角色,它赋予你自定义代码权限,使你能够访问该角色所需的AWS资源。当Lambda代表你执行Lambda函数时承担这一角色。
一个角色有两部分:一个信任策略(规定哪些服务或用户可以被授权承担这一角色)和一个或多个规定所授予权限的策略。当你的函数被执行时,Lambda承担一个角色。你的函数仅具有该角色的策略中所规定的权限。
下面的命令创建信任策略,授权Lambda来承担执行角色。
cat > role_trust_policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Sid": "", "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] } EOF
然后,创建执行角色。
ROLE_ARN=$(aws iam create-role --role-name lambda-security-blog --assume-role-policy-document file://./role_trust_policy.json --query 'Role.Arn' --output text) echo $ROLE_ARN
既然你已经创建了执行角色,添加Lambda函数所需的权限。下面的命令假设前面的命令的所设置的环境变量仍然存在。
cat > role_exec_policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:*" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "s3:GetObject" ], "Resource": [ "arn:aws:s3:::$BUCKETNAME/*" ] }, { "Effect": "Allow", "Action": ["sns:Publish"], "Resource": [ "$TOPIC_ARN" ] } ] } EOF
前述命令中的第一个语句授权Lambda函数发布自己的日志到Amazon CloudWatch日志。第二个语句授权Lambda函数 从 S3桶中的CloudTrail日志 [p1] 读取数据。最后的语句授权Lambda函数发送关于前面创建的主题的消息。
下面的命令会将这三个语句添加到执行角色。
aws iam put-role-policy --role-name lambda-security-blog --policy-name lambda-execution-role --policy-document file://./role_exec_policy.json
第四步:创建一个Lambda函数
你已经开启了CloudTrail服务,它所关联的S3桶,和一个SNS主题,你就可以创建CloudTrail日志解析函数并将该函数部署到Lambda上。
创建一个桶来存数配置文件
Lambda函数使用正则表达式来匹配意外的API调用。它将使用存储在S3上的外部配置文件来存储你需要函数去发现的正则表达式。使用外部配置文件可以使你更好地微调表达式,而不必再每次变化时都重新部署Lambda函数。
为了简化部署,配置文件会和CloudTrail日志文件存储在相同的S3桶中。配置文件如下:
Lambda函数使用正则表达式来匹配意外的API调用。它将使用存储在S3上的外部配置文件来存储你需要函数去发现的正则表达式。使用外部配置文件可以使你更好地微调表达式,而不必再每次变化时都重新部署Lambda函数。
为了简化部署,配置文件会和CloudTrail日志文件存储在相同的S3桶中。配置文件如下:
{ "source" : "iam|signin|sts", "regexp" : "Password|AccessKey|PutRolePolicy|User|Token|Login", "sns" : { "region" : "us-west-2", "topicARN" : "arn:aws:sns:us-west-2:577xxxxxxx68:PushNotifications" } }
如下是配置文件的组成部分:
编辑配置文件以达到你的要求,然后使用下面的命令将文件拷贝到S3桶中:
aws s3 cp filter_config.json s3://$BUCKETNAME/filter_config.json --region $REGION
创建Lambda函数
你可以使用任何一款JavaScript文本编辑器来创建Lambda函数。在写这篇博客时,Lambda函数使用的是Node.js架构。Lambda函数的核心是JavaScript函数,该函数被定义为Node.js处理器。在写代码时,记得Node.js架构是非同步的。每一次函数调用会立即产生调用日志。你可以使用回调函数或未来函数来同步你的代码执行结果。
在部署Lambda函数时,你必须定义Lambda函数执行的触发事件。当前,Lambda函数可以应对API调用,DynamoDB表格变动,S3事件,或SNS通知来触发函数。
该演练使用Q JavaScript库。Q库提取非同步操作作为未来对象(也被称为未来函数)。Lambda函数按照排列的顺序来执行以下操作:
1. 下载第四步开始创建的Lambda函数配置文件。
2. 下载CloudTrail日志文件,其可获得性正是系统刚刚通知你的。Lambda将一个记录对象传递给函数。该记录对象包含刚刚在S3上创建的文件的名称。
3. 解压CloudTrail日志文件到一个临时存储位置。
4. 过滤文件,仅寻找来自所选的AWS服务的日志条目。在该案例中,Lambda函数仅寻找IAM,登录,和STS所产生的记录。
5. 搜索给定的正则表达式。
1. 找到匹配记录时,发送通知到SNS服务。
我将会将Lambda函数分解为不同的JavaScript函数,每一个子函数执行前述操作中的某一项。Lambda函数的核心包含在下面的内容中。它执行所述的操作。
完整的源代表可从 GitHub 获得。为了表述清晰,下面呈现的片段不包括错误处理,日志,或Q库相关的代表。
exports.handler = function(event, context) { var bucket = event.Records[0].s3.bucket.name; var key = event.Records[0].s3.object.key; // Download from S3, Gunzip, Filter and send notifications download(bucket, key) .then(extract) .then(filter) .then(notify) .catch(function (error) { // Handle any error from all above steps }) .done(function() { context.done(); }); };
Q库确保每一个函数会按顺序被调用。
下载配置文件
使用AWS JavaScriptSDK,从S3下载配置文件。
var s3 = new aws.S3({apiVersion: '2006-03-01'}); var params = { Bucket:FILTER_CONFIG_BUCKET, Key:FILTER_CONFIG_FILE }; var body = s3.getObject(params) .createReadStream() .on('data', function(data) { FILTER_CONFIG = JSON.parse(data); });
上述的代码使你可以下载配置文件并解析文本,创建一个JavaScript对象。你需要配置全局变量FILTER_CONFIG_BUCKET和FILTER_CONFIG_FILE的值为你的桶的名称和配置文件的名称。
从S3下载CloudTrail日志文件
使用 Node.js 为 I/O( 输入 / 输出 ) 操作提供的 fs 模块和针对 S3 API的AWS Node.js 架构 从 S3 下载 CloudTrail文件,并将其存储在本地目的(/tmp)。
观察日志的输出结果。在Lambda部署代码前修正所有的错误。
// extract file name from key name var fileName = key.match(/.*//(.*).json.gz$/)[1] var file = fs.createWriteStream('/tmp/' + fileName + '.json.gz'); // pipe from S3 to local file var s3 = new aws.S3({apiVersion: '2006-03-01'}); var params = { Bucket:bucket, Key:key }; var stream = s3.getObject(params).createReadStream(); stream.pipe(file);
上述命令将会在Lambda容器的/tmp目录下存储一个文件副本。
解压CloudTrail日志文件到一个临时存储位置
使用Node.js GZIP库,从压缩文件中提取基于文本的日志文件。
var gzFileName = file.path.match(/.*//(.*).json.gz$/)[1]; var jsFileName = '/tmp/' + gzFileName + '.json'; var zlib = require('zlib'); var unzip = zlib.createGunzip(); var inp = fs.createReadStream(file.path); var out = fs.createWriteStream(jsFileName); inp.pipe(unzip).pipe(out);
上面的这个操作将会产生一个输入流,以从本地文件系统中读取文件,然后将这个输入流输送到一个输出流附属于本文件系统的另一个文件。
筛选所选的AWS服务
既然本地有一个JSON文本文件可用,解析它,寻找那些AWS服务和API以便系统为其产生一个SNS通知。
var cloudTrailLog = require(file); var records = cloudTrailLog.Records.filter(function(x) { return x.eventSource.match(new RegExp(FILTER_CONFIG.source)); });
搜索正则表达式
上一步在内存中加载了大量的记录对象以供解析。为每一个匹配的记录建一个消息,并将消息传送给sendNotification()函数。
for (var i = 0; i < records.length; i++) { if (records[i].eventName.match(new RegExp(FILTER_CONFIG.regexp))) { var message = "Event : " + records[i].eventName + "/n" + "Source : " + records[i].eventSource + "/n" + "Params : " + JSON.stringify(records[i].requestParameters, null, '') + "/n" + "Region : " + records[i].awsRegion + "/n" var task = sendNotification(message, FILTER_CONFIG.sns.topicARN, ''); } }
发送SNS通知
最后,使用AWS JavaScript软件开发工具包发送SNS通知。
var sns = new aws.SNS({ apiVersion: '2010-03-31', region: FILTER_CONFIG.sns.region }); var params = {} params = { Message: msg, TopicArn: topicARN }; sns.publish(params, function(err,data) { if (err) { // an error occurred } else { // successful response } });
在将函数上传给Lambda并在AWS测试该函数前,我们推荐在本地测试该函数。在开发周期的早期,通过快速地捕捉编程错误,可以极大地缩短你的开发,部署,测试,调试周期。Lambda函数部署前进行本地测试应该被看做一项最好的实践,因为这允许你在写代码的同时测试它,并根据AWS账户进行定制。
为了对Lambda函数进行本地测试,我写了一小串代码。这串代表将会调用Lambda函数,正如该函数将会被从Lambda服务调用一样。该代码判断将会为你的函数提供两个对象:
1. 一个输入记录,与该记录被链接到S3事件后,Lambda提供的记录相似。Lambda将会创建一个文件(input.json)用来存储该记录。
2. 一个上下文记录,与Lambda提供给你的函数的记录相似。
本次测试所用的输入记录与下面的类似:
{ "Records": [ { "eventVersion": "2.0", "eventSource": "aws:s3", "awsRegion": "us-west-2", "eventTime": "2014-11-27T21:08:30.487Z", "eventName": "ObjectCreated:Put", "userIdentity": { "principalId": "AWS:AROXXXXXXXXXXSW:i-4ffXXXXa5" }, "requestParameters": { "sourceIPAddress": "54.211.178.99" }, "responseElements": { "x-amz-request-id": "F104F805121C9B79", "x-amz-id-2": "Lf8hbNPkrhLAT4sHT7iBYFnIdCJTmxcr1ClX93awYfF530O9AijCgja19rk3MyMF" }, "s3": { "s3SchemaVersion": "1.0", "configurationId": "quickCreateConfig", "bucket": { "name": "aws-cloudtrail-xxx", "ownerIdentity": { "principalId": "AH4XXXXXXXQT" }, "arn": "arn:aws:s3:::aws-cloudtrail-xxx" }, "object": { "key": "AWSLogs/577XXXXXXX68/CloudTrail/us-west-2/2014/11/27/577XXXXXXXX68_CloudTrail_us-west-2_20141127T2110Z_JWcmX2IqHMUoBRIM.json.gz", "size": 2331, "eTag": "63d801bb561037f59f2cb4d1c03c2392" } } } ] }
确保桶的名称和密钥指向你的桶中的一个真实的CloudTrail日志。大小和电子标签的值不会被函数使用,你不必修改它们的值。
你可以从一个真实的Lambda函数或获取该输入记录对象。调用函数存储在main.js中,与下面的代码类似:
var lambda = require('./cloudtrail.js') //your lambda function var event = require('./input.json') //your input record var context = {} context.done = function(arg1, arg2) { console.log('context.done') } lambda.handler(event, context)
本次测试代码前,先在本地安装测试需要的程序。
npm install aws-sdk q
你必须为AWS软件开发工具包配置一个访问密钥,密码密钥,和缺省域。如果你正向上面所述的一样,在使用AWS命令行界面,那你的本地配置就是正确的。否则,运行aws configure命令来创建一个缺省配置文件。
然后,在你的电脑上,输入命令行来调用节点。
node main.js
观察日志的输出结果。在Lambda部署代码前修正所有的错误。
一旦你坚信函数已经可以按照预期运行,你可以准备在Lambda部署该函数了。首先,创建一个包含该函数和依赖程序(Q数据库)的.zip文件(cloudtrail.js)。
zip -r cloudtrail.zip cloudtrail.js node_modules -x node_modules/aws-sdk//*
在该文件中,你不需要包含AWS JavaScript软件开发工具包,因为Lambda的执行环境默认提供该工具包。
现在,上传并在Lambda上创建该函数(参数$ROLE_ARN的值必须设置为本帖前半部分第三步中,aws iam create-role命令返回的值)。
FUNCTION_ARN=$(aws lambda create-function --zip-file fileb://./cloudtrail.zip --function-name cloudtrail --runtime nodejs --role "$ROLE_ARN" --handler cloudtrail.handler --region $REGION --output text --query "FunctionArn")
记住参数Function_ARN的输出值,因为你在下一步将会用到该值。
要做的最后一件事就是配置S3,在文件在桶中被创建时,能触发Lambda函数。这是一个包含两个步骤的过程。你首先需要添加权限到Lambda函数,授权你的S3桶可以触发Lambda函数的执行。
AWS_ACCOUNT_ID=$(echo $ROLE_ARN | sed 's/^arn:aws:iam::/(.*/):.*$//1/') aws lambda add-permission --function-name cloudtrail --statement-id Id-cloudtrail --action "lambda:InvokeFunction" --principal s3.amazonaws.com --source-arn arn:aws:s3:::$BUCKETNAME --source-account $AWS_ACCOUNT_ID --region $REGION
确保将$FUNCTION_ARN的值设置成第四步中Function_ARN的输出值,正如本帖的前半部分,上传代码时返回的Function_ARN的值。
然后,配置S3通知系统。你即可以在S2 web控制台进行配置,也可以在AWS CLI中使用命令行进行配置。但是你需要一个配置文件。
cat > s3_notifications_config.json <<EOF { "CloudFunctionConfiguration": { "CloudFunction": "$FUNCTION_ARN", "Id": "cloudtrail-lambda-notifications", "Event": "s3:ObjectCreated:*" } } EOF
确保将<FUNCTION_ARN>的值设置成“部署该函数”部分,上传函数时Function_ARN的输出值,正如本帖的前半部分所述。
最后,开启从S3到Lambda的事件通知功能。
aws s3api put-bucket-notification --notification-configuration file://./s3_notifications_config.json --bucket $BUCKETNAME --region $REGION
如果你已经按照说明,进行了如上步骤的操作,恭喜你!你可以对你的部署进行端到端的测试了。
好消息时,你只需进行几步的操作来测试部署。你只需产生几次意外的API调用(这些调用是你在filter_config.json配置文件中已经配置过的)。如果你在使用前面建议的配置文件,登录到AWS Management Console(AWS管理控制台),创建几个角色,然后为这几个角色添加和删除策略。几分钟后,CloudTrail服务将会产生日志文件,而S3服务则会触发Lambda函数,并给你的SNS主题发送消息。
第六步:排除故障
如果你没有即刻收到电子邮件通知,请耐心等一等。CloudTrail服务需要几分钟来发送CloudTrail日志文件,而且电子邮件的发送速度取决于你的邮件设施。
如果十到十五分钟后,你仍然没有收到邮件,请检查以下项目:
在日志中:
活动推荐: 5月26日 CSDN在线培训——AWS 云计算环境中的机器学习
( 翻译/吕东梅 责编/王鑫贺 )
订阅“AWS中文技术社区”微信公众号,实时掌握AWS技术及产品消息!
AWS中文技术社区为广大开发者提供了一个Amazon Web Service技术交流平台 ,推送AWS最新资讯、技术视频、技术文档、精彩技术博文等相关精彩内容,更有AWS社区专家与您直接沟通交流!快加入AWS中文技术社区,更快更好的了解AWS云计算技术。