The latest release (5.10.13) introduces a new feature into Nexpose, scan exporting and importing. We're looking to address a need in air-gap environments, where customers can have multiple consoles to address network partitioning. This approach is not without its warts. For example, if you have deleted assets from a site, this process will bring them back to life.

This post is going to walk through a pair of Ruby scripts using the nexpose-client gem. The first script will export the site configuration and scans from one site, then after transferred to another console the second script will create a new site and import the data in. One restriction on scan importing is that you can only import scans newer than the most recent scan, so in order to pull in multiple scans, order must be preserved.

While a scan is importing, the Nexpose web interface will indicate an "Importing" status. You should note that the scan importing call is asynchronous. This means that your script will not block waiting for the scan to run, but will return as soon as the scan has been initialized. Because of this, you may wish to wait for each scan to complete before proceeding to the next.

Another side note on importing scans: the nexpose-client gem currently uses the rest-client gem to import the scan. We didn't want to force a new dependency for gem users, so scripts will need to explicitly require the rest-client gem in order to use the feature. Version 0.8.5 of the nexpose-client gem no longer has this dependency.

The Export Script

This script will take as input the site ID to export. It will also assume that it can write all the files to the local directory.

#!/usr/bin/evn ruby  
require 'nexpose'  
  
include Nexpose  
  
nsc = Connection.new('air-gapped-console', 'nxadmin', 'superSecretPassword')  
nsc.login  
at_exit { nsc.logout }  
  
# Allow the user to pass in the site ID to the script.  
site_id = ARGV[0].to_i  
  
# Write the site configuration to a file.  
site = Site.load(nsc, site_id)  
File.write('site.xml', site.to_xml)  
  
# Grab scans and sort by scan end time  
scans = nsc.site_scan_history(site_id).sort_by { |s| s.end_time }.map { |s| s.scan_id }  
  
# Scan IDs are not guaranteed to be in order, so use a proxy number to order them.  
i = 0  
scans.each do |scan_id|  
  nsc.export_scan(scan_id, "scan-#{i}.zip")  
  i += 1  
end  

The Import Script

This script assumes that you are running in the directory that you filled with data in the previous script (after transporting the lot of it to a new machine).

#!/usr/bin/evn ruby  
require 'nexpose'  
  
include Nexpose  
  
nsc = Connection.new('reporting-console', 'nxadmin', 'someOtherSecretPassword')  
nsc.login  
at_exit { nsc.logout }  
  
site_xml = File.read('site.xml')  
xml = REXML::Document.new(site_xml)  
site = Site.parse(xml)  
site.id = -1  
# Set to use the local scan engine.  
site.engine = nsc.engines.find { |e| e.name == 'Local scan engine' }.id  
site_id = site.save(nsc)  
  
# Import scans by numerical ordering  
scans = Dir.glob('scan-*.zip').map { |s| s.gsub(/scan-/, '').gsub(/\.zip/, '').to_i }.sort  
scans.each do |scan|  
  zip = "scan-#{scan}.zip"  
  puts "Importing #{zip}"  
  nsc.import_scan(site.id, zip)  
  # Poll until scan is complete before attempting to import the next scan.  
  last_scan = nsc.site_scan_history(site.id).max_by { |s| s.start_time }.scan_id  
  while (nsc.scan_status(last_scan) == 'running')  
    sleep 10  
  end  
  puts "Integration of #{zip} complete"  
end