def connectES(esEndPoint):
    """
    Description: Creates a connection to the Elasticsearch Domain.
    Parameters: esEndPoint - string - The domain endpoint, found in the AWS Elasticsearch Console but not including the https:// prefix.
    """

    consoleLog("Connecting to the ES Endpoint {0}".format(esEndPoint), "DEBUG",
               esLogLevelGv)
    try:
        esClient = Elasticsearch(timeout=120,
                                 hosts=[{
                                     "host": esEndPoint,
                                     "port": 443
                                 }],
                                 http_auth=esAuthTypeGv,
                                 use_ssl=True,
                                 verify_certs=True,
                                 connection_class=RequestsHttpConnection,
                                 retry_on_timeout=True)
        return esClient
    except Exception as E:
        consoleLog(
            "Unable to connect to Elasticsearch domain : {0}".format(
                esEndPoint) + " Exception : " + E, "ERROR", esLogLevelGv)
        exit(3)
Example #2
0
def writeDemoData(esClient, DemoDataIndex, eventName="CreateUser"):

    global DEBUG
    
    jsonDoc = {
        "eventVersion": "1.05",
        "eventTime": "2020-08-02T03:48:24Z",
        "eventSource": "iam.amazonaws.com",
        "eventName": "CreateUser",
        "awsRegion": "us-east-1",
    
        "userIdentity": {
          "type": "AssumedRole",
          "principalId": "AROA4RKH6JM6LD63WC7LA:awsvolks-Isengard",
          "arn": "arn:aws:sts::861828696892:assumed-role/God/awsvolks-Isengard",
          "accountId": "861828696892",
          "accessKeyId": "TEST EVENT",
          "sessionContext": {
            "sessionIssuer": {
              "type": "Role",
              "principalId": "TEST EVENT",
              "arn": "TEST EVENT",
              "accountId": "861828696892",
              "userName": "******"
            }
            }
        },
        "sourceIPAddress": "0.0.0.0",
        "userAgent": "TEST EVENT",
        "requestParameters": "{\"userName\": \"billybob-cliandconsole\", \"tags\": [{\"key\": \"test\", \"value\": \"true\"}]}",
        "responseElements": "TEST EVENT",
        "requestID": "f0c539ce-7f0e-489e-a89f-32612de519ff",
        "eventID": "TEST EVENT",
        "eventType": "AwsApiCall",
        "recipientAccountId": "861828696892",
        "Enriched": {
          "knownGood": "false",
          "knownBad": "false"
        },
        "geoip": {
          "city_name": "",
          "country_name": "",
          "latitude": "",
          "longitude": "",
          "country_code3": "",
          "continent_code": "",
          "postal_code": "",
          "region_code": "",
          "region_name": "",
          "timezone": ""
        }
      }
    
    retval = esClient.index(index=DemoDataIndex, body=jsonDoc)     
    consoleLog("Added demo document to Elasticsearch index {0}. Doc Id:{1}".format(DemoDataIndex, retval["_id"]),"DEBUG",esLogLevelGv)
    return retval["_id"]
Example #3
0
def lambda_handler(event, context):
    

    # Connect to Elasticsearch
    esClient = connectES(esEndPointEv)
    consoleLog("lambda_handler : Elasticsearch connection.","DEBUG",esLogLevelGv)  

    
    #
    # Write Demo Data
    #
    
    DemoDataIndex = "cloudtrail-2020.08.02"
    
    DocId = writeDemoData(esClient, DemoDataIndex)
    
    deleteDemoData(esClient, DemoDataIndex, DocId)
     

    
    return {
            'statusCode': 200,
            'body': json.dumps("ok")
        }
def lambda_handler(event, context):

    #
    # To do: Run all rule from this type, run only single rule from this type
    #
    #

    # Initialize iterator used in bulk load.
    esBulkMessagesGv = []
    esBulkComplianceMessagesGv = []

    # Mark start of function execution.
    timerDict = {}
    timerDict["lambda_handler : Started Processing"] = int(time.time())
    consoleLog(
        "lambda_handler : Started Processing @ {0} {1}:{2}:{3}".format(
            datetime.now().strftime("%Y-%m-%d"),
            datetime.now().hour,
            datetime.now().minute,
            datetime.now().second), "INFO", esLogLevelGv)

    # If any errors are found reading environment variables, don't continue.
    if configError == True:
        consoleLog(
            "lambda_handler : Not executing, configuration error detected.",
            "ERROR", esLogLevelGv)
        return

    # Connect to Elasticsearch
    esClient = connectES(esEndPointEv)
    consoleLog("lambda_handler : Elasticsearch connection.", "DEBUG",
               esLogLevelGv)

    # Connect to DynamoDB
    dynamodb_client = boto3.resource('dynamodb')
    dynamodb_table = dynamodb_client.Table(DynamoDBname)
    consoleLog(
        "Dynamo table MonitorLizard status = " + dynamodb_table.table_status,
        "DEBUG", esLogLevelGv)

    # Connect to SNS
    sns = boto3.client('sns')

    if TEST_SNS:
        response = sns.publish(
            TopicArn=SnsTopicArn,
            Message='Test message from Monitor Lizard',
        )
        consoleLog("Sent test SNS message.", "INFO", esLogLevelGv)
        return

    #
    # Execute rules of type "Login anomaly"
    #

    consoleLog("Executing rule type " + RuleType, "INFO", esLogLevelGv)

    response = dynamodb_table.scan(
        FilterExpression=Attr('RuleType').eq(RuleType))

    if len(TEST_RULE):
        print("Executing TEST_RULE only ")
        runRule(esClient, dynamodb_table, sns, TEST_RULE, RuleType)
        return

    for Rule in response["Items"]:
        print()

        if not Rule["RuleActive"]:
            print(
                "Skipping SIEM rule because rule has been deactivated (RuleActive=false)"
            )
            continue

        if Rule["LastRun"] + (Rule["RunScheduleInMinutes"] * 60) < int(
                time.time()):

            print("Executing rule: " + Rule["RuleId"])

            runRule(esClient, dynamodb_table, sns, Rule["RuleId"],
                    Rule["RuleType"])

            # Updateing SIEM rule last run time stamp
            response = dynamodb_table.update_item(
                Key={
                    'RuleId': Rule["RuleId"],
                    'RuleType': Rule["RuleType"]
                },
                UpdateExpression="set LastRun=:l",
                ExpressionAttributeValues={':l': int(time.time())},
                ReturnValues="UPDATED_NEW")
        else:
            print("--------------------------")

            if (TEST_IGNORE_LASTRUN):
                runRule(esClient, dynamodb_table, sns, Rule["RuleId"],
                        Rule["RuleType"])
            else:
                print("Skipping SIEM rule because of LastRun setting: " +
                      Rule["RuleId"])

    return {'statusCode': 200, 'body': json.dumps("ok")}
def runRule(esClient, dynamodb_table, sns, RuleId, RuleType):

    # Read SIEM rule from DynamoDB
    DBresponse = dynamodb_table.query(
        KeyConditionExpression=Key('RuleId').eq(RuleId)
        & Key('RuleType').eq(RuleType), )

    try:
        Rule_ES_Index = DBresponse["Items"][0]["ES_Index"]
        Rule_Query = json.loads(
            DBresponse["Items"][0]["Query"])  # must include an aggregation
        Rule_LastAggResult = DBresponse["Items"][0]["LastAggResult"]
        Rule_AlertPeriodMinutes = DBresponse["Items"][0]["AlertPeriodMinutes"]
        Rule_AlertText = DBresponse["Items"][0]["AlertText"]
        Rule_Description = DBresponse["Items"][0]["Description"]
        Rule_AlertMinimumEventCount = DBresponse["Items"][0][
            "AlertMinimumEventCount"]
        Rule_Active = DBresponse["Items"][0]["RuleActive"]
    except Exception as E:
        consoleLog("Error reading some of the database values.", "ERROR",
                   esLogLevelGv)
        return

    #
    # Remove eventTime range filter and replace with new time filter
    #
    query_start_range = int(time.time()) - (Rule_AlertPeriodMinutes * 60) - (
        TEST_AlertPeriod * 60)  # unix time
    query_start_date = datetime.utcfromtimestamp(query_start_range).strftime(
        '%Y-%m-%dT%H:%M:%SZ')  # Zulu time

    n = -1
    for filter in Rule_Query["query"]["bool"]["filter"]:
        n = n + 1
        if "range" in filter:
            if "eventTime" in filter["range"]:
                Rule_Query["query"]["bool"]["filter"].pop(n)

    # set new dateTime range filter
    Rule_Query["query"]["bool"]["filter"].append(
        {'range': {
            'eventTime': {
                'gte': query_start_date
            }
        }})

    consoleLog(
        "Modified date range filter for query: " +
        str(Rule_Query["query"]["bool"]["filter"]), "DEBUG", esLogLevelGv)

    #
    # Read Elasticsearch aggregation query
    #   with newly set time range filter
    #

    result = esClient.search(index=Rule_ES_Index, body=Rule_Query)
    hits = result["hits"]["total"]["value"]
    #bucket = result["aggregations"]["my_count"]["buckets"]                                       ## replace my_count with a dynamically detected custom agg name

    #
    # Iterate through ElasticSearch aggregation result
    #

    # GroupValues_current is an array with all aggregated values (group by) and the unix time from this query
    CurrentAggResult = {}
    QueryTime = int(time.time())
    AlertOccurrences = {}
    """
    Example result:
    
    "aggregations" : {
    "agg1" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "arn:aws:sts::861828696892:assumed-role/God/awsvolks-Isengard",
          "doc_count" : 17,
          "agg2" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "United States",
                "doc_count" : 16
              },
              {
                "key" : "Australia",
                "doc_count" : 1
              }
            ]
          }
        }
      ]
    }
    """

    x = 0
    # Get aggregation values and add current time
    for customAggName, value in result["aggregations"].items():
        # level 1 (first custom aggregation name)
        if isinstance(value, (dict, list)):
            buckets_layer1 = result["aggregations"][customAggName]["buckets"]

            for bucket_layer1 in buckets_layer1:
                x = x + 1
                Layer1_GroupValue = bucket_layer1["key"]
                Layer1_Count = bucket_layer1["doc_count"]

                print("Group Layer 1 value = ", Layer1_GroupValue, ' (count=',
                      Layer1_Count, ")")

                # Get aggregation values of second layer
                for customAggName2, value2 in bucket_layer1.items():

                    if isinstance(value2, (dict, list)):

                        AllLayer2GroupValues = ""
                        for bucket_layer2 in bucket_layer1[customAggName2][
                                "buckets"]:
                            AllLayer2GroupValues = AllLayer2GroupValues + bucket_layer2[
                                "key"] + ", "

                        NumberOfLayer2Groups = len(
                            bucket_layer1[customAggName2]["buckets"])

                        if NumberOfLayer2Groups >= Rule_AlertMinimumEventCount:
                            consoleLog("Rule fired.", "DEBUG", esLogLevelGv)
                            AlertOccurrences[x] = {
                                'GroupValue': Layer1_GroupValue,
                                'OccurrenceCount': NumberOfLayer2Groups,
                                'Occurrences': AllLayer2GroupValues
                            }

    #
    # Raise alerts
    #
    for x in AlertOccurrences:

        Message = Rule_AlertText
        Message = Message + "\nDescription: " + Rule_Description
        Message = Message + "\nRule Id: " + RuleId + "\nRule Type: " + RuleType + "\nElasticsearch Index: " + Rule_ES_Index + "\nAlert window: " + str(
            Rule_AlertPeriodMinutes) + " minutes\n"
        Message = Message + "\nGroup value: " + AlertOccurrences[x][
            'GroupValue']
        Message = Message + "\nNumber of Occurrences: " + str(
            AlertOccurrences[x]['OccurrenceCount'])
        Message = Message + "\nOccurrences (comma separated): " + AlertOccurrences[
            x]['Occurrences']

        consoleLog("ALERT MESSAGE:" + Message, "DEBUG", esLogLevelGv)

        if SEND_ALERT:
            consoleLog(
                "Send alert for new occurrence: " +
                AlertOccurrences[x]['GroupValue'], "INFO", esLogLevelGv)
            response = sns.publish(
                TopicArn=SnsTopicArn,
                Message=Message,
            )

            # Add alert to Elasticsearch index
            AlertDateTime = datetime.utcfromtimestamp(int(
                time.time())).strftime('%Y-%m-%dT%H:%M:%SZ')

            jsonDoc = {
                "AlertDateTime": AlertDateTime,
                "Rule_Id": RuleId,
                "Rule_Type": RuleType,
                "Alert_Value": AlertOccurrences[x]['GroupValue']
            }
            retval = esClient.index(index="monitorlizardalerts", body=jsonDoc)
            consoleLog(
                "Add document to Elasticsearch index MonitorLizardAlerts : {0}"
                .format(retval), "DEBUG", esLogLevelGv)
        else:
            consoleLog("Alerting deactivated.", "INFO", esLogLevelGv)

    return
    "DynamoDB"]  # Data storage bucket for persistent storage
SnsTopicArn = os.environ["SNS_TOPIC_ARN"]

# Init global Variables
configError = False
esAuthTypeGv = ""
esLogLevelGv = ""
esClient = ""
dynamodb_client = ""
dynamodb_table = ""

#Is a valid log level set.
if esLogLevelEv not in ("DEBUG", "INFO", "ERROR"):
    configError = True
    consoleLog(
        "Environment Variable : ES_LOG_LEVEL must be set to one of \"DEBUG\", \"INFO\" or \"ERROR\".",
        "ERROR", "ERROR")
else:
    esLogLevelGv = esLogLevelEv
    consoleLog(
        "Lambda function log level set to {} defined in ES_LOG_LEVEL environment variable."
        .format(esLogLevelEv), "DEBUG", esLogLevelGv)

#Check region has been set and is valid.
if esRegionEv not in [
        region['RegionName']
        for region in boto3.client('ec2').describe_regions()['Regions']
]:
    configError = True
    consoleLog("Environment Variable : ES_REGION is invalid.", "ERROR",
               esLogLevelGv)
def runRule(esClient, dynamodb_table, sns, RuleId, RuleType):

    global DEBUG

    # Read SIEM rule from DynamoDB
    DBresponse = dynamodb_table.query(
        KeyConditionExpression=Key('RuleId').eq(RuleId)
        & Key('RuleType').eq(RuleType), )

    try:
        Rule_ES_Index = DBresponse["Items"][0]["ES_Index"]
        Rule_Query = json.loads(
            DBresponse["Items"][0]["Query"])  # must include an aggregation
        Rule_LastAggResult = DBresponse["Items"][0]["LastAggResult"]
        Rule_AlertPeriodMinutes = DBresponse["Items"][0]["AlertPeriodMinutes"]
        Rule_AlertText = DBresponse["Items"][0]["AlertText"]
        Rule_Description = DBresponse["Items"][0]["Description"]
        Rule_Condition = json.loads(DBresponse["Items"][0]["Rule_Condition"])
        Rule_Active = DBresponse["Items"][0]["RuleActive"]
    except Exception as E:
        print(E)
        consoleLog("Error reading some of the database values. JSON error?.",
                   "ERROR", esLogLevelGv)
        return

    #
    # Remove eventTime range filter and replace with new time filter
    #
    query_start_range = int(time.time()) - (Rule_AlertPeriodMinutes * 60) - (
        TEST_AlertPeriod * 60)  # unix time
    query_start_date = datetime.utcfromtimestamp(query_start_range).strftime(
        '%Y-%m-%dT%H:%M:%SZ')  # Zulu time

    n = -1
    for filter in Rule_Query["query"]["bool"]["filter"]:
        n = n + 1
        if "range" in filter:
            if "eventTime" in filter["range"]:
                Rule_Query["query"]["bool"]["filter"].pop(n)

    # set new dateTime range filter
    Rule_Query["query"]["bool"]["filter"].append(
        {'range': {
            'eventTime': {
                'gte': query_start_date
            }
        }})

    consoleLog(
        "Modified date range filter for query: " +
        str(Rule_Query["query"]["bool"]["filter"]), "DEBUG", esLogLevelGv)

    #
    # Run Elasticsearch query
    #

    result = esClient.search(index=Rule_ES_Index, body=Rule_Query)
    hits = result["hits"]["total"]["value"]

    AlertOccurrences = {}
    QueryTime = int(time.time())

    print("Number of hits: ", hits)

    if hits > 0:
        for hit in result["hits"]["hits"]:

            if DEBUG:
                print("-------rule query hit---------")
                print("checking rule condition for document id " + hit["_id"])
                #print(hit)

            # Test
            '''
            Rule_Condition = {
                "matches": [{
                    "search_field" : "requestParameters",
                    "search_regex" : '.*{"key": "test", "value": "true"}.*',
                    "search_logic" : True
                },{
                    "search_field" : "recipientAccountId",
                    "search_regex" : '861828696892',
                    "search_logic" : True
                }]
            }
            
            this needs to be stored in DynamoDB in the following format:
            
            {
                "matches": [{
                    "search_field" : "requestParameters",
                    "search_regex" : ".*{.*\\"key\\".*:.* \\"test\\", \\"value\\".*:.* \\"true\\".*",
                    "search_logic" : "True"
                },{
                    "search_field" : "recipientAccountId",
                    "search_regex" : "861828696892",
                    "search_logic" : "True"
                }]
            }
            '''

            # Check all conditions for this hit
            # Only alert, if all conditions are met
            RuleFired = True

            for match in Rule_Condition["matches"]:

                search_field = match["search_field"]  # Elasticsearch field
                search_regex = r"{}".format(match["search_regex"])  # regex
                search_logic = match[
                    "search_logic"]  # True or False (Rule fires if regex is a match or not match)

                # Convert to boolean value
                if search_logic.lower() == "true":
                    search_logic = True
                else:
                    search_logic = False

                print("Check if ", search_field, " matches ", search_regex,
                      " is ", search_logic)

                # check if search_field exists
                if search_field in hit["_source"]:

                    if hit["_source"][search_field]:

                        # check for search_regex
                        line = str(hit["_source"][search_field])
                        matchObj = re.search(search_regex, line, re.M | re.I)

                        if matchObj and search_logic:
                            print(
                                ' > condition met. Alert only raised if all conditions match.'
                            )
                        else:
                            if not matchObj and not search_logic:
                                print(
                                    ' > condition met.  Alert only raised if all conditions match.'
                                )
                            else:
                                print(
                                    ' > condition NOT met. No alert for this rule raised.'
                                )
                                RuleFired = False
                else:
                    print("Field ", search_field,
                          " not found in Elasticsearch document ")

            # Fire rule if all conditions are met
            if RuleFired:
                docId = hit["_id"]
                AlertOccurrences[docId] = hit["_source"]
                print("Rule fired ")

    #
    # Raise alerts
    #
    for AggField in AlertOccurrences:

        Message = Rule_AlertText
        Message = Message + "\nDescription: " + Rule_Description
        Message = Message + "\nQuery Result: " + str(
            AlertOccurrences[AggField])
        Message = Message + "\nRule Id: " + RuleId + "\nRule Type: " + RuleType + "\nElasticsearch Index: " + Rule_ES_Index + "\nAlert window: " + str(
            Rule_AlertPeriodMinutes) + " minutes\n"
        Message = Message + "\nAlert Query: " + str(Rule_Query)

        consoleLog("ALERT MESSAGE:" + Message, "DEBUG", esLogLevelGv)

        if SEND_ALERT:
            consoleLog("Send alert for document _id: " + AggField, "INFO",
                       esLogLevelGv)
            response = sns.publish(
                TopicArn=SnsTopicArn,
                Message=Message,
            )

            # Add alert to Elasticsearch index
            AlertDateTime = datetime.utcfromtimestamp(int(
                time.time())).strftime('%Y-%m-%dT%H:%M:%SZ')

            jsonDoc = {
                "AlertDateTime": AlertDateTime,
                "Rule_Id": RuleId,
                "Rule_Type": RuleType,
                "Alert_Value": AggField
            }
            retval = esClient.index(index="monitorlizardalerts", body=jsonDoc)
            consoleLog(
                "Add document to Elasticsearch index MonitorLizardAlerts : {0}"
                .format(retval), "DEBUG", esLogLevelGv)

        else:
            consoleLog("Alerting deactivated.", "INFO", esLogLevelGv)
    return
def runRule(esClient, dynamodb_table, sns, RuleId, RuleType):

    # Read SIEM rule from DynamoDB
    DBresponse = dynamodb_table.query(
        KeyConditionExpression=Key('RuleId').eq(RuleId)
        & Key('RuleType').eq(RuleType), )

    try:
        Rule_ES_Index = DBresponse["Items"][0]["ES_Index"]
        Rule_Query = json.loads(
            DBresponse["Items"][0]["Query"])  # must include an aggregation
        Rule_LastAggResult = DBresponse["Items"][0]["LastAggResult"]
        Rule_AlertPeriodMinutes = DBresponse["Items"][0]["AlertPeriodMinutes"]
        Rule_AlertText = DBresponse["Items"][0]["AlertText"]
        Rule_Description = DBresponse["Items"][0]["Description"]
        Rule_AlertMinimumEventCount = DBresponse["Items"][0][
            "AlertMinimumEventCount"]
        Rule_Active = DBresponse["Items"][0]["RuleActive"]
    except Exception as E:
        consoleLog("Error reading some of the database values.", "ERROR",
                   esLogLevelGv)
        return

    #
    # Remove eventTime range filter and replace with new time filter
    #
    query_start_range = int(time.time()) - (Rule_AlertPeriodMinutes * 60) - (
        TEST_AlertPeriod * 60)  # unix time
    query_start_date = datetime.utcfromtimestamp(query_start_range).strftime(
        '%Y-%m-%dT%H:%M:%SZ')  # Zulu time

    n = -1
    for filter in Rule_Query["query"]["bool"]["filter"]:
        n = n + 1
        if "range" in filter:
            if "eventTime" in filter["range"]:
                Rule_Query["query"]["bool"]["filter"].pop(n)

    # set new dateTime range filter
    Rule_Query["query"]["bool"]["filter"].append(
        {'range': {
            'eventTime': {
                'gte': query_start_date
            }
        }})

    consoleLog(
        "Modified date range filter for query: " +
        str(Rule_Query["query"]["bool"]["filter"]), "DEBUG", esLogLevelGv)

    #
    # Read Elasticsearch aggregation query
    #   with newly set time range filter
    #

    result = esClient.search(index=Rule_ES_Index, body=Rule_Query)
    hits = result["hits"]["total"]["value"]

    #
    # Iterate through ElasticSearch aggregation result
    #

    # GroupValues_current is an array with all aggregated values (group by) and the unix time from this query
    CurrentAggResult = {}
    QueryTime = int(time.time())

    # Get aggregation values and add current time
    for customAggName, value in result["aggregations"].items():
        if isinstance(value, (dict, list)):
            bucket = result["aggregations"][customAggName]["buckets"]

            for GroupValue in bucket:
                if GroupValue["doc_count"] >= Rule_AlertMinimumEventCount:
                    consoleLog(
                        GroupValue["key"] + " found at least " +
                        str(Rule_AlertMinimumEventCount) + " times.", "DEBUG",
                        esLogLevelGv)
                    CurrentAggResult[GroupValue[
                        "key"]] = QueryTime  # current occurence with current time
                else:
                    consoleLog(
                        GroupValue["key"] +
                        " found but did not reach minimum event count limit of "
                        + str(Rule_AlertMinimumEventCount) + " times.",
                        "DEBUG", esLogLevelGv)

    if DEBUG:
        print("Query result from current run (Group values with timestamp:")
        print(CurrentAggResult)
        print("Query result from previous run saved in DynamoDB:")
        print(Rule_LastAggResult)

    #
    # Compare current search result with stored results
    #

    AlertOccurrences = {}
    NewAggResult_tmp = Rule_LastAggResult
    NewAggResult = {}

    for currentAggField in CurrentAggResult:
        if currentAggField in Rule_LastAggResult.keys():
            occurrencerTimeDiff = CurrentAggResult[
                currentAggField] - Rule_LastAggResult[currentAggField]

            # Alert on this new occurrence (new within alert time frame)
            if (occurrencerTimeDiff / 60 > Rule_AlertPeriodMinutes):
                AlertOccurrences[currentAggField] = (occurrencerTimeDiff / 60 >
                                                     Rule_AlertPeriodMinutes)
                consoleLog(
                    currentAggField +
                    " found in previous occurrences. Alert because last occurrence was too long ago ",
                    "DEBUG", esLogLevelGv)

            NewAggResult_tmp[
                currentAggField] = QueryTime  # includes new and old occurrences (needs clean up later)

        else:
            # Alert on this new occurrence
            consoleLog(
                currentAggField +
                " found in current occurrences. Alert because its new",
                "DEBUG", esLogLevelGv)
            AlertOccurrences[currentAggField] = QueryTime
            NewAggResult_tmp[
                currentAggField] = QueryTime  # includes new and old occurrences (needs clean up later)

    #
    # Clean up NewAggResult (remove events older then Rule_AlertPeriodMinutes).
    #

    for AggField in NewAggResult_tmp:
        if (QueryTime - NewAggResult_tmp[AggField]) > Rule_AlertPeriodMinutes:
            consoleLog(
                AggField +
                " removed from stored list of occurrences (out of alert window)",
                "DEBUG", esLogLevelGv)
        else:
            # build new list
            NewAggResult[AggField] = NewAggResult_tmp[AggField]

    if DEBUG:
        print("New list of query results to be stored in DynamoDB")
        for AggField in NewAggResult:
            print(AggField + ":" + str(NewAggResult[AggField]))

    #
    # Update stored results (NewAggResult)
    #

    if not TEST_NOUPDATE:
        consoleLog("Updating database with list of occurrences", "INFO",
                   esLogLevelGv)
        response = dynamodb_table.update_item(
            Key={
                'RuleId': RuleId,
                'RuleType': RuleType
            },
            UpdateExpression="set LastAggResult=:l",
            ExpressionAttributeValues={':l': NewAggResult},
            ReturnValues="UPDATED_NEW")
    else:
        consoleLog(
            "Skip update of database for new list of occurrences (Test_NOUPDATE=True)",
            "INFO", esLogLevelGv)

    #
    # Raise alerts
    #
    for AggField in AlertOccurrences:

        Message = Rule_AlertText
        Message = Message + "\nDescription: " + Rule_Description
        Message = Message + "\n" + "Alert Value: " + AggField + "\nRule Id: " + RuleId + "\nRule Type: " + RuleType + "\nElasticsearch Index: " + Rule_ES_Index + "\nAlert window: " + str(
            Rule_AlertPeriodMinutes) + " minutes\n"
        Message = Message + "\nAlert Query: " + Rule_Query

        #consoleLog("ALERT MESSAGE:"+Message,"DEBUG",esLogLevelGv)

        if SEND_ALERT:
            consoleLog("Send alert for new occurrence: " + AggField, "INFO",
                       esLogLevelGv)
            response = sns.publish(
                TopicArn=SnsTopicArn,
                Message=Message,
            )

            # Add alert to Elasticsearch index
            AlertDateTime = datetime.utcfromtimestamp(int(
                time.time())).strftime('%Y-%m-%dT%H:%M:%SZ')

            jsonDoc = {
                "AlertDateTime": AlertDateTime,
                "Rule_Id": RuleId,
                "Rule_Type": RuleType,
                "Alert_Value": AggField
            }
            retval = esClient.index(index="monitorlizardalerts", body=jsonDoc)
            consoleLog(
                "Add document to Elasticsearch index MonitorLizardAlerts : {0}"
                .format(retval), "DEBUG", esLogLevelGv)
        else:
            consoleLog("Alerting deactivated.", "INFO", esLogLevelGv)

    return