Learn How To Create a Custom Threat Map in Splunk: Part 2

In Part 1 of this screencast series, Ian went over the basics of setting up our Splunk app using the Splunk Web Framework. He also went through the steps of setting up a basic Google Map in Splunk. In this second part of the series, he is going to cover how customize our map.



In Part 1 of this screencast series, I went over the basics of setting up our Splunk app using the Splunk Web Framework. I also went through the steps of setting up a basic Google Map in Splunk. In this second part of the series, I am going to cover how customize our map.

Also, you may watch the associated screencasts if you prefer.

Part 1: Customize the Map

We will need to override the GoogleMapsView that Splunk provides us, because we want to customize the way this map looks.

Below, where we are loading our required dependencies, add the following: var CustomGoogleMapView = GoogleMapView.extend({ });

Here, we are extending the GoogleMapView to create our CustomGoogleMapView in order to override the way the default GoogleMapView works, specifically inside of it’s render() method.

THE NITTY GRITTY

You don’t need to read this section in order to continue to following along, but this provides a little more in-depth detail into how this all works. If you go to $SPLUNK_HOME/<appname>/share/splunk/search_mrsparkle/exposed/js/build/simplexml.min/mvc/ this is where Splunk keeps all of its views, so there’s one for a table, a chart and others including Google Maps, specifically within the googlemapview.js file. If you look at this file you will see that GoogleMapView is extending from BaseSplunkView, which all Splunk views extend from. The beauty of these views is that they allow for us to handle the data Splunk provides us from our Search Managers with ease. If you scroll down to the bottom of googlemapview.js you will see the render method – this is what we will be overriding. Of course, we aren’t limited to just overriding our render method, we could override other methods in here as well, but there’s no need to for our purposes here.

Let’s go ahead and modify our CustomGoogleMapsView to look like this:

var CustomGoogleMapView = GoogleMapView.extend({
    render: function() {
        if (!this.manager) {
            return;
        }

        if (!this.resultsModel || !this.resultsModel.hasData()) {
            if (this.resultsModel && !this.resultsModel.hasData() && this._isJobDone) {
                this.message("no-results");
            }
        return this;
        }

    if (!this.map) {
        this.createMap();
    }

    var that = this;
    this.clearMarkers();
    this.resultsModel.collection().each(function(row) {
        var lat = parseFloat(row.get("lat"));
        var lng = parseFloat(row.get("lng"));
        var latlng = new google.maps.LatLng(lat, lng);
        var marker = new google.maps.Marker({
            position: latlng,
            map: that.map
        });
        that.markers.push(marker);
    });
    return this;
    }
});

What we’ve added here is actually what the default render() method looks like in googlemapview.js, from which we are extending. The part we’ll be concentrating on is the  method. Here, Splunk is looping through each row of results from our search and outputting the data to the map. For example, its pulling the latitude and longitude values with >span class="theme:twilight font:consolas font-size:16 line-height:25 lang:default decode:true crayon-inline " rel="font-size: 14px; line-height: 1.6em; background-color: initial;" data-verified="redactor" data-redactor-tag="span" data-redactor-style="font-size: 14px; line-height: 1.6em; background-color: initial;">var lat = parseFloat(row.get("lat")); and var lng = parseFloat(row.get("lng")); The parseFloat method is used because the latitude and longitude come back as float values. Normally, we can just dovar foo = row.get("bar");

Now, before going any further, go back to where you defined your googleMapView and change: 

var googleMapView = new GoogleMapView({
    ... code removed for brevity ...
});

to:

var googleMapView = new CustomGoogleMapView({
    ... code removed for brevity ...
});

Since we will now be using our CustomGoogleMapView.

Moving forward, we are going to go ahead and modify the render() method inside our CustomGoogleMapView. First, let’s pull some additional values from our search results, so below the var latlng variable definition add the following:

var count = row.get("count");
var src_ip = row.get("Source IP");
var category = row.get("Category");
var city = row.get("City");

Simply enough, we are pulling the "count", "Source IP", "Category" and "City" values from our search.

Next, we’re going to define a JavaScript object called iconSrc and add properties pertaining to specific categories we defined in our search. Each of these properties will link to the a specific skull image. When we are all done, s1.png will show if the count is categorized as ‘low’ so on and so forth:

var iconSrc = {};

iconSrc['low'] = '/dj/static/<appname>/skulls/s1.png';
iconSrc['medium'] = '/dj/static/<appname>/skulls/s2.png'; 
iconSrc['moderate'] = '/dj/static/<appname>/skulls/s3.png'; 
iconSrc['critical'] = '/dj/static/<appname>/skulls/s4.png'; 
iconSrc['severe'] = '/dj/static/<appname>/skulls/s5.png';

Note: Make sure to change <appname> to whatever the name of your app is.

Next, inside our marker variable, we are going to add the icon property. It will look like this

var marker = new google.maps.Marker({
    position: latlng,
    map: that.map,
    icon: iconSrc[category]
});

This uses the iconSrc object we defined above and inserts the category thats pulled out from our category variable: var category = row.get("Category");

As it loops through each row it will check the category and, for example, if the category pulled out of the specific row is ‘low’ the icon will then be iconSrc['low'] which maps to the image that we defined above in iconSrc['low'] = '/dj/static/<appname>/skulls/s1.png';

If you go back to your page at http://localhost:8000/dj/en-us/<appname>/home/ you should see something like this:

Threat Map 1
Threat Map

Next, we want to make it so we get popovers to provide us with additional information when we click on a specific skull.

Part 2: Add Popovers

Below our iconSrc obj add the following:

var contentString = '<div>' +
       '<ul>' +
       '<li><strong>IP:</strong> ' + src_ip + '</li>' +
       '<li><strong>City:</strong> ' + city + '</li>' +
       '<li><strong>Count:</strong> ' + count + '</li>' +
       '<li><strong>Level:</strong> ' + category + '</li>' +
       '</ul>' +
       '</div>';

We are using the variables we defined for src_ip, city, count and category to use as the additional data that we provide in the popover.

And then, below the maker variable, add:

var infowindow = new google.maps.InfoWindow({
    content: contentString,
    maxWidth: 200
});

Here, we are defining a new Google Maps InfoWindow and setting the content to our contentString variable.

Finally, we need to listen for a click event so that our popover appears when we click on a specific skull:

Before that.markers.push(marker); add:

google.maps.event.addListener(marker, 'click', function() {
    infowindow.open(that.map, marker);
});

Now, go back to your browser and click on a couple different skulls, you’ll end up with something that looks like this:

Map Overlay Issues
Map overlay

Not really ideal, since we would want one popover to close as another one opens. So, let’s go ahead and fix that next.

Part 3: Modify the Popovers

Inside of our click event listener, modify it so it looks like this:

google.maps.event.addListener(marker, 'click', function() {
    typeof infoWindowsOpenCurrently !== 'undefined' && infoWindowsOpenCurrently.close();
    infowindow.open(that.map, marker);
    infoWindowsOpenCurrently = infowindow;
});

What we are doing here is first checking whether or not infoWindowsOpenCurrently is undefined - if it’s not undefined then close it. The first time we click on an icon, infoWindowsOpenCurrently will be undefined. Then the overlay is opened for the specific marker we clicked on. Finally, we attach that infowindow to infoWindowsOpenCurrently, so it is no longer undefined.

On any subsequent clicks, infoWindowsOpenCurrently will no longer be undefined, so our first statementtypeof infoWindowsOpenCurrently !== 'undefined' will be true so the second part && infoWindowsOpenCurrently.close(); will be triggered to close infoWindowsOpenCurrently, which by this point is actual the previously opened infowindow.

Now, if you go back to the browser, you’ll see that it only opens one popover window at a time.

This is the end of Part 2, in Part 3 I go over how to add an area chart and pass the IP value from the map to the area chart.




Close off Canvas Menu