Sumo LogicでAmazon Inspectorをウォッチしてみた

2018.04.04

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

おはようございます、加藤です。今回もSumo Logicについてブログを書きます。
Amazon Inspectorの情報をSumo Logicへ送って可視化します。

構成

構成は以下のようになっています。

  1. InspectorからEC2をスキャン
  2. 以下のイベントをSNS Topicへ通知
    • 実行完了
    • 実行開始
    • 実行ステータスが変更されました
    • 報告された結果
  3. SNS TopicをイベントとしてLambda Functionが起動
  4. Sumo LogicのHTTP Endpointへ転送

sumologic-inspector001

下記のページを参考にして作成しました。

作ってみた

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

sumologic-inspector010

sumologic-inspector011

sumologic-inspector012

Collectorの作成が完了すると、ソースのセットアップへ画面偏移します。
HTTP Logs & Metricsをクリックします。

sumologic-inspector013

Name,Source Categoryを設定します。

Name Description Source Host Source Category
Amazon Inspector aws/inspector

sumologic-inspector014

SaveするとURLが表示されるので、コピーして保存しておきましょう。

sumologic-inspector015

SNSトピックの作成

SNSトピックを作成します。

トピック名 表示名
inspector inspector

sumologic-inspector002

トピックのポリシーを編集します。

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:*"
    }
  ]
}

sumologic-inspector003

sumologic-inspector004

Inspector

Inspectorの構築やEC2へのAgentインストールは説明を省略します。
下記のページなどを参考に設定してください。

InspectorのテンプレートでSNSトピックへ情報を飛ばすように設定します。

sumologic-inspector0051

Lambda

IAMロール作成

Lambda用のIAMロールを作成します。
Inspectorの読み取り権限とCloudWatch Logsへの書き込み権限をアタッチします。

ロール名 アタッチするポリシー 信頼関係
Lambda-Inspector AWSLambdaBasicExecutionRole, AmazonInspectorReadOnlyAccess lambda.amazonaws.com

sumologic-inspector006

ファンクション作成

Lambda Functionを作成します。

名前 ランタイム ロール
inspector-sumologic Python2.7 Lambda-inspector

sumologic-inspector007

作成したSNSトピック"inspector"をトリガとして設定します。

sumologic-inspector008

sumologic-inspector0091

メモリを128MB、タイムアウトを5分に設定します。

sumologic-inspector018

ハンドラを"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')

sumologic-inspector016

テストデータを作成し、正常に動作するか確認します。

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"
}

sumologic-inspector017

テストを実行します。
正常に終了しました!

sumologic-inspector019

確認してみる

Inspectorのテンプレートを実行して、実際にSumo Logicへ情報を転送してみます。

sumo_inspector_handler021

Sumo Logicで確認してみましょう。New Log Search を選択します。

sumologic-inspector021

検索条件を以下の用に設定し、検索します。
- _sourceCategory=aws/inspector

sumologic-inspector024

無事にInspectorを実行したログを取得できました!!

sumo_inspector_handler0201

検索条件の下の、"Save As"というテキストをクリックして、この検索設定を保存しておきます。

Amazon Inspectorという名前でフォルダを作成して、その配下に保存しました。

sumologic-inspector025

最後にダッシュボードを作成します。Sumo LogicにはInspector用のダッシュボードが用意されているのでそのまま使用します。
左メニューからApp Catalogを開き、Amazon Inspectorをクリックします。

sumologic-inspector026

ライブラリに追加します。

sumologic-inspector027

sumologic-inspector028

Overviewのダッシュボードを確認してみました。情報を表示できています!

sumologic-inspector029

Sumo Logicのインフラ活用法セミナーやります!

2018年10月4日(木)にクラスメソッドの共催でSumo Logicジャパン社設立記念セミナーが行われます。Sumo Logicの特徴やAWSなどインフラへの活用方法をレクチャーする内容です。Sumo Logicを使った監視に興味がある方は、こちらのイベントもチェックしてみてください。

SumoLogic181004