There is a variety of information available about your Sites and Scans that can be used to make decisions. I began thinking about how to analyze the load across Scan Engines or Sites, but quickly realized there are too many ways to break down the data to be able to write a single script. To that end, I'm going to walk through some scripts which use the Nexpose gem (version 0.1.7) to access the API and collate performance. These can be built upon to drive decisions about how to configure your Security Console.

By Configuration

My first pass is to analyze the number of assets configured per site and see how they are spread across the engines (analyze_load_by_config.rb):

#!/usr/bin/env
ruby
require 'nexpose' include Nexpose
nsc = Connection.new('host', 'user', 'password')
nsc.login at_exit { nsc.logout }
count_by_config = {}
nsc.sites.each do |site| 
config = Site.load(nsc, site.id) 
next if config.scan_template =~ /discovery/ 
count_by_config[config.engine] = 0 
config.assets.each do |asset|
count = 1 
count = (asset.to.to_i - asset.from.to_i)
if defined? asset.to and asset.from 
count_by_config[config.engine] = count
end
end 
total = count_by_config.values.reduce(0)
{ |acc, count| acc = count }
engines = nsc.engines count_by_config.each do |id, count|
name = engines.find { |eng| eng.id == id }.name  
percent = '%.2f' % (count.to_f / total * 100)
puts "#{name} : #{count} (#{percent}%)"
end 

This produces the following results:

Local scan engine : 1 (2.22%)
remote-engine-two : 30 (66.67%)
remote-engine-one : 14 (31.11%)

So by pure configuration, "remote-engine-two" is configured to scan twice as many assets.

By Discovered Devices

The above approach doesn't work if you have sites configured to scan across a range that includes a number of inactive IPs. To address this, we need to gather data around actively found assets. (analyze_load_raw_device_count.rb):

#!/usr/bin/env
ruby
require 'nexpose' include Nexpose
nsc = Connection.new('host', 'user', 'password')
nsc.login at_exit { nsc.logout }
asset_count = {}
engine_load = {}
nsc.sites.each do |site|
asset_count[site.id] = nsc.site_device_listing(site.id).count
last_scan = nsc.last_scan(site.id)
engine_load[last_scan.engine_id] ||= 0
engine_load[last_scan.engine_id] = asset_count[site.id]
end
total_assets = asset_count.values.reduce(0)
{ |acc, count| acc = count }
engines = nsc.engines engine_load.each do |id, count|
name = engines.find { |eng| eng.id == id }.name
percent = '%.2f' % (count.to_f / total_assets * 100)
puts "#{name} : #{count} (#{percent}%)"
end

This script produced the following output:

Local scan engine : 394 (90.78%)
remote-engine-two : 33 (7.60%)
remote-engine-one : 7 (1.61%)

Wow! Makes it look like I'm totally abusing my local scan engine. We saw earlier, though, that only one site is scanning from the local engine.

Ignoring Discovery Scans

I did a huge discovery scan using my Local scan engine, and the last results don't actually reflect the current device load. So I added a couple lines just inside the first each block (line 13, analyze_load_ignore_discovery.rb):

config = Site.load(nsc, site.id)
next if config.scan_template =~ /discover/

Now I get output like this:

Local scan engine : 18 (31.03%)
remote-engine-two : 33 (56.90%)
remote-engine-one : 7 (12.07%)

That's more like it. I still have devices scanning off my local engine, which isn't ideal, but I'm also under-utilizing "remote-engine-one". Time to re-balance my sites.

If you want to build off these scripts without cutting and pasting from here, they've been committed to github: https://github.com/rapid7/nexpose-client/tree/master/scripts