Sumo LogicでAmazon Inspectorをウォッチしてみた
おはようございます、加藤です。今回もSumo Logicについてブログを書きます。
Amazon Inspectorの情報をSumo Logicへ送って可視化します。
構成
構成は以下のようになっています。
- InspectorからEC2をスキャン
- 以下のイベントをSNS Topicへ通知
- 実行完了
- 実行開始
- 実行ステータスが変更されました
- 報告された結果
- SNS TopicをイベントとしてLambda Functionが起動
- Sumo LogicのHTTP Endpointへ転送
下記のページを参考にして作成しました。
作ってみた
Sumo Logic Http Endpointの作成
Sumo LogicでLambdaからの情報を受け取る設定をします。
左メニューからManage Data→Collectionを開きます。
Add Collector→Hosted Collectorと進んでHosted Collectorを作成します。
Name | Description | Category | Time Zone |
---|---|---|---|
Amazon Web Service | 空 | 空 | (GMT+09:00) Asia/Tokyo |
Collectorの作成が完了すると、ソースのセットアップへ画面偏移します。
HTTP Logs & Metricsをクリックします。
Name,Source Categoryを設定します。
Name | Description | Source Host | Source Category |
---|---|---|---|
Amazon Inspector | 空 | 空 | aws/inspector |
SaveするとURLが表示されるので、コピーして保存しておきましょう。
SNSトピックの作成
SNSトピックを作成します。
トピック名 | 表示名 |
---|---|
inspector | inspector |
トピックのポリシーを編集します。
{ "Version": "2008-10-17", "Id": "inspector-sns-publish-policy", "Statement": [ { "Sid": "inspector-sns-publish-statement", "Effect": "Allow", "Principal": { "Service": "inspector.amazonaws.com" }, "Action": "SNS:Publish", "Resource": "arn:aws:sns:*" } ] }
Inspector
Inspectorの構築やEC2へのAgentインストールは説明を省略します。
下記のページなどを参考に設定してください。
InspectorのテンプレートでSNSトピックへ情報を飛ばすように設定します。
Lambda
IAMロール作成
Lambda用のIAMロールを作成します。
Inspectorの読み取り権限とCloudWatch Logsへの書き込み権限をアタッチします。
ロール名 | アタッチするポリシー | 信頼関係 |
---|---|---|
Lambda-Inspector | AWSLambdaBasicExecutionRole, AmazonInspectorReadOnlyAccess | lambda.amazonaws.com |
ファンクション作成
Lambda Functionを作成します。
名前 | ランタイム | ロール |
---|---|---|
inspector-sumologic | Python2.7 | Lambda-inspector |
作成したSNSトピック"inspector"をトリガとして設定します。
メモリを128MB、タイムアウトを5分に設定します。
ハンドラを"lambda_function.sumo_inspector_handler"に書き換えて、ファンクションを記述します。
"YOUR_SUMOLOGIC_HTTP_ENDPOINT"はSumo Logicで取得したURLに置換してください。
import json import httplib import base64,zlib import urlparse import boto3 import datetime import logging ################################################################## # Configuration # ################################################################## # Enter Sumo Http source endpoint here. sumoEndpoint = "YOUR_SUMOLOGIC_HTTP_ENDPOINT" # include auxiliary data (e.g for assessment template, run, or target) in the collected event or not contextLookup = True ################################################################## # Main Code # ################################################################## up = urlparse.urlparse(sumoEndpoint) options = { 'hostname': up.hostname, 'path': up.path, 'method': 'POST' }; # Internal variables used for this Lambda function resourceMap = {'finding':{},'target':{},'run':{},'template':{}, 'rulesPackage':{}} # prepare logger logger = logging.getLogger() logger.setLevel(logging.INFO) # main function to send data to a Sumo HTTP source def sendSumo(msg, toCompress = False): conn = httplib.HTTPSConnection(options['hostname']) if (toCompress): headers = {"Content-Encoding": "gzip"} finalData = compress(msg) else: headers = {"Content-type": "text/html","Accept": "text/plain"} finalData =msg headers.update({"X-Sumo-Client": "inspector-aws-lambda"}) conn.request(options['method'], options['path'], finalData,headers) response = conn.getresponse() conn.close() return (response.status,response.reason) # Simple function to compress data def compress(data, compresslevel=9): compress = zlib.compressobj(compresslevel, zlib.DEFLATED, 16 + zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) compressedData = compress.compress(data) compressedData += compress.flush() return compressedData # This function looks up an Inspector object based on its arn and type. Returned object will be used to provide extra context for the final message to Sumo def lookup(objectId,objectType = 'run'): client = boto3.client('inspector') finalObj = None objectMap = resourceMap.get(objectType) if (objectMap is None): resourceMap[objectType]= objectMap = {} try: if (objectType=='run'): run = objectMap.get(objectId) if (run is None): runs = client.describe_assessment_runs(assessmentRunArns=[objectId]) if (runs is not None): run = runs['assessmentRuns'][0] # For run item, we only collect important properties objectMap[objectId] = finalObj = {'name':run['name'],'createdAt':'%s' % run['createdAt'], 'state':run['state'],'durationInSeconds':run['durationInSeconds'],'startedAt':'%s' % run['startedAt'],'assessmentTemplateArn':run['assessmentTemplateArn']} else: finalObj = run elif (objectType=='template'): template = objectMap.get(objectId) if (template is None): templates = client.describe_assessment_templates(assessmentTemplateArns=[objectId]) if (templates is not None): finalObj = objectMap[objectId] = templates['assessmentTemplates'][0] else: finalObj = template elif (objectType=='rulesPackage'): rulesPackage = objectMap.get(objectId) if (rulesPackage is None): rulesPackages = client.describe_rules_packages(rulesPackageArns=[objectId]) if (rulesPackages is not None): finalObj = objectMap[objectId] = rulesPackages['rulesPackages'][0] else: finalObj = rulesPackage elif (objectType=='target'): target = objectMap.get(objectId) if (target is None): targets = client.describe_assessment_targets(assessmentTargetArns=[objectId]) if (targets is not None): finalObj = objectMap[objectId] = targets['assessmentTargets'][0] else: finalObj = target elif (objectType == 'finding'): finding = objectMap.get(objectId) if (finding is None): findings = client.describe_findings(findingArns=[objectId]) if (findings is not None): finalObj = objectMap[objectId] = findings['findings'][0] else: finalObj = finding except Exception as e: logger.error(e) raise return finalObj # simple utility function to deserialize datetime objects def json_deserializer(obj): if isinstance(obj, datetime.datetime): return obj.strftime('%Y-%m-%dT%H:%M:%SZ') elif isinstance(obj, date): return obj.strftime('%Y-%m-%d') # Let the base class default method raise the TypeError return json.JSONEncoder.default(self, obj) def sumo_inspector_handler(event, context): if ('Records' in event): for record in event['Records']: # get actual SNS message snsObj = record['Sns'] dataObj = {'Timestamp':snsObj['Timestamp'],'Message':snsObj['Message'],'MessageId':snsObj['MessageId']} msgObj = json.loads(snsObj['Message']) if (contextLookup): # do reverse lookup of each of the following items in Message: target, run, template. if ('template' in msgObj): lookupItem = lookup(msgObj['template'],'template') if (lookupItem is not None): logger.info("Got a template item back") msgObj['templateLookup']= lookupItem else: print("Could not lookup template: %s" % msgObj['template']) if ('run' in msgObj): lookupItem = lookup(msgObj['run'],'run') if (lookupItem is not None): msgObj['runLookup']= lookupItem else: logger.info("Could not lookup run: %s" % msgObj['run']) if ('target' in msgObj): lookupItem = lookup(msgObj['target'],'target') if (lookupItem is not None): msgObj['targetLookup']= lookupItem else: logger.info("Could not lookup target: %s" % msgObj['target']) if ('finding' in msgObj): # now query findings finding = lookup(msgObj['finding'],'finding') if (finding is not None): # now query rulesPackage inside the finding rulesPackage = lookup(finding['serviceAttributes']['rulesPackageArn'],'rulesPackage') if (rulesPackage is not None): finding['rulesPackageLookup'] = rulesPackage else: logger.info("Cannot lookup rulesPackageArn: %s"% finding['serviceAttributes']['rulesPackageArn']) msgObj['findingDetails'] = finding # construct final data object dataObj = {'Timestamp':snsObj['Timestamp'],'Message':msgObj,'MessageId':snsObj['MessageId']} # now send this object to Sumo side rs = sendSumo(json.dumps(dataObj,default=json_deserializer),toCompress=True) if (rs[0]!=200): logger.info('Error sending data to sumo with code: %d and message: %s '% (rs[0],rs[1])) logger.info(json.dumps(dataObj,default=json_deserializer)) else: logger.info("Sent data to Sumo successfully") else: logger.info('Unrecoganized data')
テストデータを作成し、正常に動作するか確認します。
{ "eventVersion": "1.03", "userIdentity": { "type": "AssumedRole", "principalId": "AIDACKCEVSQ6C2EXAMPLE", "arn": "arn:aws:iam::444455556666:user/Alice", "accountId": "444455556666", "accessKeyId": "AKIAI44QH8DHBEXAMPLE", "sessionContext": { "attributes": { "mfaAuthenticated": "false", "creationDate": "2016-04-14T17:05:54Z" }, "sessionIssuer": { "type": "Role", "principalId": "AIDACKCEVSQ6C2EXAMPLE", "arn": "arn:aws:iam::444455556666:user/Alice", "accountId": "444455556666", "userName": "Alice" } } }, "eventTime": "2016-04-14T17:12:34Z", "eventSource": "inspector.amazonaws.com", "eventName": "CreateResourceGroup", "awsRegion": "us-west-2", "sourceIPAddress": "205.251.233.179", "userAgent": "console.amazonaws.com", "requestParameters": { "resourceGroupTags": [ { "key": "Name", "value": "ExampleEC2Instance" } ] }, "responseElements": { "resourceGroupArn": "arn:aws:inspector:us-west-2:444455556666:resourcegroup/0-oclRMp8B" }, "requestID": "148256d2-0264-11e6-a9b5-b98a7d3b840f", "eventID": "e5ea533e-eede-46cc-94f6-0d08e6306ff0", "eventType": "AwsApiCall", "apiVersion": "v20160216", "recipientAccountId": "444455556666" }
テストを実行します。
正常に終了しました!
確認してみる
Inspectorのテンプレートを実行して、実際にSumo Logicへ情報を転送してみます。
Sumo Logicで確認してみましょう。New Log Search を選択します。
検索条件を以下の用に設定し、検索します。
- _sourceCategory=aws/inspector
無事にInspectorを実行したログを取得できました!!
検索条件の下の、"Save As"というテキストをクリックして、この検索設定を保存しておきます。
Amazon Inspectorという名前でフォルダを作成して、その配下に保存しました。
最後にダッシュボードを作成します。Sumo LogicにはInspector用のダッシュボードが用意されているのでそのまま使用します。
左メニューからApp Catalogを開き、Amazon Inspectorをクリックします。
ライブラリに追加します。
Overviewのダッシュボードを確認してみました。情報を表示できています!
Sumo Logicのインフラ活用法セミナーやります!
2018年10月4日(木)にクラスメソッドの共催でSumo Logicジャパン社設立記念セミナーが行われます。Sumo Logicの特徴やAWSなどインフラへの活用方法をレクチャーする内容です。Sumo Logicを使った監視に興味がある方は、こちらのイベントもチェックしてみてください。