必須面對這樣一個現實:并不是所有的API生來都是平等的。比如,你可能很想知道你的Amazon EC2(彈性計算云)實例是何時被終止的(使用ec2:TerminateInstance命令可以終止實 例),但是對一個對象何時被放入Amazon S3桶卻不那么感興趣(使用s3:PutObject可以將對象放入S3桶中)。在這個例子中,你可以刪除一個對象,但是你卻不能回復已被終止的實例 。所以這就提出了一個問題。“有沒有一種方法可以在某些API被調用時,讓我通過我的AWS賬戶被通知到呢?”答案是肯定的。但是盡管如此,如果一些API被意外地調用來支持AWS賬戶中的一些服務時,你如何被通知到呢?
這個博客帖子將會向你展示,在你的賬戶中,當你所監測的特定API被調用時,如何使用AWS CloudTrail,Amazon SNS和AWS Lambda來接收郵件通知。特別地,這個帖子包含了詳細的方案執行配置說明。這一方案包括:
CloudTrail –作為日志文件的來源以供分析。
Lambda – 執行解析和過濾邏輯。
SNS –發生通知。
下面這幅圖說明了這些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交互。
這些詳細的配置說明的前提條件如下:
一個AWS賬戶
AWS概念(諸如域和訪問密鑰)的基本知識
安裝在你的電腦上并配置有管理員權限的AWS命令行界面(命令不能以EC2的角色運行,這些命令需要實際的用戶證書)。你可以安裝《AWS CLI安裝指南》和《AWS CLI 配置文檔》,使用你的訪問密鑰,秘密金鑰,缺省輸出格式,和缺省域來安裝和配置AWS命令行界面。確保使用AWS CLI 1.7.22或以上版本。
在部署Lambda函數前,在電腦上測試該函數。測試該函數,你也必須安裝NodeJS和npm工具,并熟悉*nix族殼命令。
本次演練將使用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=
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 < { "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 < { "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" } } 如下是配置文件的組成部分: 參數source定義你將監測的服務API。你可以修改該參數來滿足你的要求。 參數regexp定義將會觸發通知消息的API調用列表。該參數也可以被修改來滿足你的要求。 sns部分規定你的SNS配置。將它的值修改為本帖前部分所述的域和第二步所創建的主題ARN。 編輯配置文件以達到你的要求,然后使用下面的命令將文件拷貝到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 RegExp(FILTER_CONFIG.regexp))) { var message = "Event : " + records[i].eventName + " " + "Source : " + records[i].eventSource + " " + "Params : " + JSON.stringify(records[i].requestParameters, null, '') + " " + "Region : " + records[i].awsRegion + " " 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 } }); Test the function locally 本地測試該函數 在將函數上傳給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函數 一旦你堅信函數已經可以按照預期運行,你可以準備在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服務 要做的最后一件事就是配置S3,在文件在桶中被創建時,能觸發Lambda函數。這是一個包含兩個步驟的過程。你首先需要添加權限到Lambda函數,授權你的S3桶可以觸發Lambda函數的執行。 AWS_ACCOUNT_ID=$(echo $ROLE_ARN | sed 's/^arn:aws:iam::(.*):.*$//') 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 < { "CloudFunction": "$FUNCTION_ARN", "Id": "cloudtrail-lambda-notifications", "Event": "s3:ObjectCreated:*" } } EOF 確保將 最后,開啟從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日志文件,而且電子郵件的發送速度取決于你的郵件設施。 如果十到十五分鐘后,你仍然沒有收到郵件,請檢查以下項目: 在你的桶中,是否有CloudTrail日志文件?如果沒有,檢查你的CloudTrail服務配置。 你的Lambda函數是否被調用了?如果沒有,檢查你的S3通知服務設置,包括Lambda函數調用權限。 在CloudWatch日志中檢查Lambda日志。在Lambda控制臺,可以獲得該鏈接。 在日志中: 是否已過濾事件列表為空?在配置文件中檢查你的正則表達式過濾器。 在給SNS主題發送消息時,是否有錯誤發生?檢查Lambda函數執行角色策略。 仍然收不到郵件通知嗎?在你訂閱該主題是,是否驗證過你的郵件地址?你已經檢查過你的垃圾郵件箱了嗎?