この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
おはようございます、加藤です。今回も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 |
トピックのポリシーを編集します。
topic_policy.json
{
"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に置換してください。
lambda_function.py
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')
テストデータを作成し、正常に動作するか確認します。
sampledata.json
{
"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を使った監視に興味がある方は、こちらのイベントもチェックしてみてください。