class Nexpose::Connection

Object that represents a connection to a Nexpose Security Console.

Examples

# Create a new Nexpose::Connection on the default port
nsc = Connection.new('10.1.40.10', 'nxadmin', 'password')

# Create a new Nexpose::Connection from a URI or "URI" String
nsc = Connection.from_uri('https://10.1.40.10:3780', 'nxadmin', 'password')

# Create a new Nexpose::Connection with a specific port
nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 443)

# Create a new Nexpose::Connection with a silo identifier
nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, 'default')

# Create a new Nexpose::Connection with a two-factor authentication (2FA) token
nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, nil, '123456')

# Create a new Nexpose::Connection with an excplicitly trusted web certificate
trusted_cert = ::File.read('cert.pem')
nsc = Connection.new('10.1.40.10', 'nxadmin', 'password', 3780, nil, nil, trusted_cert)

# Login to NSC and Establish a Session ID
nsc.login

# Check Session ID
if nsc.session_id
    puts 'Login Successful'
else
    puts 'Login Failure'
end

# Logout
logout_success = nsc.logout

Object that represents a connection to a Nexpose Security Console.

Attributes

host[R]

The hostname or IP Address of the NSC

open_timeout[RW]

The optional HTTP open_timeout value, in seconds For more information visit the link below: ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html#open_timeout-attribute-method

password[R]

The password used to login to the NSC

port[R]

The port of the NSC (default is 3780)

request_xml[R]

The last XML request sent by this object, useful for debugging.

response_xml[R]

The last XML response received by this object, useful for debugging.

session_id[R]

Session ID of this connection

timeout[RW]

The main HTTP read_timeout value, in seconds For more information visit the link below: ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html#read_timeout-attribute-method

token[R]

The token used to login to the NSC

trust_store[R]

The trust store to validate connections against if any

url[R]

The URL for communication

username[R]

The username used to login to the NSC

Public Class Methods

from_uri(uri, user, pass, silo_id = nil, token = nil, trust_cert = nil) click to toggle source

A constructor to load a Connection object from a URI

# File lib/nexpose/connection.rb, line 70
def self.from_uri(uri, user, pass, silo_id = nil, token = nil, trust_cert = nil)
  uri = URI.parse(uri)
  new(uri.host, user, pass, uri.port, silo_id, token, trust_cert)
end
new(ip, user, pass, port = 3780, silo_id = nil, token = nil, trust_cert = nil) click to toggle source

A constructor for Connection

@param [String] ip The IP address or hostname/FQDN of the Nexpose console. @param [String] user The username for Nexpose sessions. @param [String] pass The password for Nexpose sessions. @param [Fixnum] port The port number of the Nexpose console. @param [String] silo_id The silo identifier for Nexpose sessions. @param [String] token The two-factor authentication (2FA) token for Nexpose sessions. @param [String] trust_cert The PEM-formatted web certificate of the Nexpose console. Used for SSL validation.

# File lib/nexpose/connection.rb, line 84
def initialize(ip, user, pass, port = 3780, silo_id = nil, token = nil, trust_cert = nil)
  @host         = ip
  @username     = user
  @password     = pass
  @port         = port
  @silo_id      = silo_id
  @token        = token
  @trust_store  = create_trust_store(trust_cert) unless trust_cert.nil?
  @session_id   = nil
  @url          = "https://#{@host}:#{@port}/api/API_VERSION/xml"
  @timeout      = 120
  @open_timeout = 120
end

Public Instance Methods

_append_asset!(xml, asset) click to toggle source

Utility method for appending a HostName or IPRange object into an XML object, in preparation for ad hoc scanning.

@param [REXML::Document] xml Prepared API call to execute. @param [HostName|IPRange] asset Asset to append to XML.

# File lib/nexpose/scan.rb, line 235
def _append_asset!(xml, asset)
  if asset.is_a? Nexpose::IPRange
    xml.add_element('range', 'from' => asset.from, 'to' => asset.to)
  else # Assume HostName
    host = REXML::Element.new('host')
    host.text = asset
    xml.add_element(host)
  end
end
_maintenance_restart() click to toggle source
# File lib/nexpose/maint.rb, line 59
def _maintenance_restart
  parameters = { 'cancelAllTasks' => false,
                 'cmd' => 'restartServer',
                 'targetTask' => 'maintModeHandler' }
  xml = AJAX.form_post(self, '/admin/global/maintenance/maintCmd.txml', parameters)
  !!(xml =~ /succeded="true"/)
end
_scan_ad_hoc(xml) click to toggle source

Utility method for executing prepared XML and extracting Scan launch information.

@param [REXML::Document] xml Prepared API call to execute. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 251
def _scan_ad_hoc(xml)
  r = execute(xml, '1.1', timeout: 60)
  Scan.parse(r.res)
end
_scan_ad_hoc_with_schedules(xml) click to toggle source

Utility method for executing prepared XML for adhoc with schedules

@param [REXML::Document] xml Prepared API call to execute.

# File lib/nexpose/scan.rb, line 260
def _scan_ad_hoc_with_schedules(xml)
  r = execute(xml, '1.1', timeout: 60)
  r.success
end
activity() click to toggle source

Retrieve a list of current scan activities across all Scan Engines managed by Nexpose. This method returns lighter weight objects than scan_activity.

@return [Array] Array of ScanData objects associated with

each active scan on the engines.
# File lib/nexpose/scan.rb, line 320
def activity
  r = execute(make_xml('ScanActivityRequest'))
  res = []
  if r.success
    r.res.elements.each('//ScanSummary') do |scan|
      res << ScanData.parse(scan)
    end
  end
  res
end
all_vulns() click to toggle source

Retrieve all vulnerability definitions currently in a Nexpose console.

Note, this can easily take 30 seconds to complete and will load over 55,000 vulnerability definitions.

@return [Array] Collection of vulnerability definitions.

# File lib/nexpose/vuln_def.rb, line 10
def all_vulns
  uri  = '/api/2.0/vulnerability_definitions'
  resp = AJAX.get(self, uri, AJAX::CONTENT_TYPE::JSON, per_page: 2_147_483_647)
  json = JSON.parse(resp, symbolize_names: true)
  json[:resources].map { |e| VulnerabilityDefinition.new.object_from_hash(self, e) }
end
asset_group_tags(asset_group_id) click to toggle source

Lists all the tags on an asset_group

@param [Fixnum] asset_group_id id of the group on which tags are listed @return [Array] list of tags on asset group

# File lib/nexpose/tag.rb, line 80
def asset_group_tags(asset_group_id)
  tag_summary = []
  asset_group_tag = JSON.parse(AJAX.get(self, "/api/2.0/asset_groups/#{asset_group_id}/tags", AJAX::CONTENT_TYPE::JSON, { per_page: 2_147_483_647 }))
  asset_group_tag['resources'].each do |json|
    tag_summary << TagSummary.parse(json)
  end
  tag_summary
end
Also aliased as: group_tags, list_asset_group_tags
asset_groups()
Alias for: list_asset_groups
asset_scan_history(asset_id) click to toggle source

Retrieve the scan history for an asset. Note: This is not optimized for querying many assets.

@param [Fixnum] asset_id Unique identifer of an asset. @return [Array] A list of scans for the asset.

# File lib/nexpose/device.rb, line 142
def asset_scan_history(asset_id)
  uri = "/data/assets/#{asset_id}/scans"
  AJAX.preserving_preference(self, 'asset-scan-history') do
    data = DataTable._get_json_table(self, uri, {}, 500, nil, true)
    data.each { |a| a['assetID'] = asset_id.to_s }
    data.map(&AssetScan.method(:parse_json))
  end
end
asset_tags(asset_id) click to toggle source

Lists all the tags on an asset

@param [Fixnum] asset_id of the asset to list the applied tags for @return [Array] list of tags on asset

# File lib/nexpose/tag.rb, line 32
def asset_tags(asset_id)
  tag_summary = []
  asset_tag   = JSON.parse(AJAX.get(self, "/api/2.0/assets/#{asset_id}/tags", AJAX::CONTENT_TYPE::JSON, { per_page: 2_147_483_647 }))
  asset_tag['resources'].select { |r| r['asset_ids'].find { |i| i == asset_id } }.each do |json|
    tag_summary << TagSummary.parse(json)
  end
  tag_summary
end
Also aliased as: list_asset_tags
asset_vulns(dev_id)
Alias for: list_device_vulns
assets(site_id = nil)
Alias for: list_site_devices
backup(platform_independent = false, description = nil) click to toggle source

Create a backup of this security console's data. A restart will be initiated in order to put the product into maintenance mode while the backup is made. It will then restart automatically.

@param [Boolean] platform_independent Whether to make a platform

independent backup.

@param [String] description A note about this backup which will be

visible through the web interface.

@return [Boolean] Whether a backup is successfully initiated.

# File lib/nexpose/maint.rb, line 24
def backup(platform_independent = false, description = nil)
  parameters = { 'backup_desc' => description,
                 'cmd' => 'backup',
                 'platform_independent' => platform_independent,
                 'targetTask' => 'backupRestore' }
  xml = AJAX.form_post(self, '/admin/global/maintenance/maintCmd.txml', parameters)
  if !!(xml =~ /succeded="true"/)
    _maintenance_restart
  end
end
completed_assets(scan_id) click to toggle source

Retrieve a list of assets which completed in a given scan. If called during a scan, this method returns currently completed assets. A “completed” asset can be in one of three states: completed successfully, failed due to an error, or stopped by a user.

@param [Fixnum] scan_id Unique identifier of a scan. @return [Array] List of completed assets.

# File lib/nexpose/device.rb, line 106
def completed_assets(scan_id)
  uri = "/data/asset/scan/#{scan_id}/complete-assets"
  AJAX.preserving_preference(self, 'scan-complete-assets') do
    data = DataTable._get_json_table(self, uri, {}, 500, nil, false)
    data.map(&CompletedAsset.method(:parse_json))
  end
end
completed_scans(site_id) click to toggle source

Retrieve a history of the completed scans for a given site.

@param [FixNum] site_id Site ID to find scans for. @return [CompletedScan] details of the completed scans for the site.

# File lib/nexpose/site.rb, line 70
def completed_scans(site_id)
  table = { 'table-id' => 'site-completed-scans' }
  data  = DataTable._get_json_table(self, "/data/scan/site/#{site_id}", table)
  data.map(&CompletedScan.method(:parse_json))
end
console_command(cmd_string) click to toggle source

Execute an arbitrary console command that is supplied as text via the supplied parameter. Console commands are documented in the administrator's guide. If you use a command that is not listed in the administrator's guide, the application will return the XMLResponse.

# File lib/nexpose/manage.rb, line 12
def console_command(cmd_string)
  xml = make_xml('ConsoleCommandRequest', {})
  cmd = REXML::Element.new('Command')
  cmd.text = cmd_string
  xml << cmd

  r = execute(xml)
  if r.success
    r.res.elements.each('//Output') do |out|
      return out.text.to_s
    end
  else
    false
  end
end
db_maintenance(clean_up = false, compress = false, reindex = false) click to toggle source

Initiate database maintenance tasks to improve database performance and consistency. A restart will be initiated in order to put the product into maintenance mode while the tasks are run. It will then restart automatically.

@param [Boolean] clean_up Removes any unnecessary data from the database. @param [Boolean] compress Compresses the database tables and reclaims

unused, allocated space.

@param [Boolean] reindex Drops and recreates the database indexes for

improved performance.

@return [Boolean] Whether a maintenance tasks are successfully initiated.

# File lib/nexpose/maint.rb, line 47
def db_maintenance(clean_up = false, compress = false, reindex = false)
  return unless compress || clean_up || reindex
  parameters = { 'cmd' => 'startMaintenance', 'targetTask' => 'dbMaintenance' }
  parameters['cleanup']  = 1 if clean_up
  parameters['compress'] = 1 if compress
  parameters['reindex']  = 1 if reindex
  xml = AJAX.form_post(self, '/admin/global/maintenance/maintCmd.txml', parameters)
  if !!(xml =~ /succeded="true"/)
    _maintenance_restart
  end
end
delete_asset(device_id)
Alias for: delete_device
delete_asset_group(id) click to toggle source

Delete an asset group and all associated data.

@param [Fixnum] id Asset group ID to delete.

@return [Boolean] Whether group deletion succeeded.

# File lib/nexpose/group.rb, line 12
def delete_asset_group(id)
  r = execute(make_xml('AssetGroupDeleteRequest', { 'group-id' => id }))
  r.success
end
Also aliased as: delete_group
delete_device(device_id) click to toggle source
# File lib/nexpose/device.rb, line 129
def delete_device(device_id)
  r = execute(make_xml('DeviceDeleteRequest', { 'device-id' => device_id }))
  r.success
end
Also aliased as: delete_asset
delete_discovery_connection(id) click to toggle source

Delete an existing connection to a target used for dynamic discovery of assets.

@param [Fixnum] id ID of an existing discovery connection.

# File lib/nexpose/discovery.rb, line 23
def delete_discovery_connection(id)
  xml      = make_xml('DiscoveryConnectionDeleteRequest', { 'id' => id })
  response = execute(xml, '1.2')
  response.success
end
delete_engine(engine_id, scope = 'silo') click to toggle source

Removes a scan engine from the list of available engines.

@param [Fixnum] engine_id Unique ID of an existing engine to remove. @param [String] scope Whether the engine is global or silo scoped. @return [Boolean] true if engine successfully deleted.

# File lib/nexpose/engine.rb, line 12
def delete_engine(engine_id, scope = 'silo')
  xml = make_xml('EngineDeleteRequest', { 'engine-id' => engine_id, 'scope' => scope })
  response = execute(xml, '1.2')
  response.success
end
delete_group(id)
Alias for: delete_asset_group
delete_report(report_id) click to toggle source

Delete a previously generated report.

@param [Fixnum] report_id ID of individual report to delete.

# File lib/nexpose/report.rb, line 65
def delete_report(report_id)
  xml = make_xml('ReportDeleteRequest', { 'report-id' => report_id })
  execute(xml).success
end
delete_report_config(report_config_id) click to toggle source

Delete a previously generated report definition. Also deletes any reports generated from that configuration.

@param [Fixnum] report_config_id ID of the report configuration to remove.

# File lib/nexpose/report.rb, line 75
def delete_report_config(report_config_id)
  xml = make_xml('ReportDeleteRequest', { 'reportcfg-id' => report_config_id })
  execute(xml).success
end
delete_report_template(template_id) click to toggle source

Deletes an existing, custom report template. Cannot delete built-in templates.

@param [String] template_id Unique identifier of the report template to remove.

# File lib/nexpose/report_template.rb, line 29
def delete_report_template(template_id)
  AJAX.delete(self, "/data/report/templates/#{URI.escape(template_id)}")
end
delete_role(role, scope = Scope::SILO)
Alias for: role_delete
delete_scan(scan_id) click to toggle source

Delete a scan and all its data from a console. Warning, this method is destructive and not guaranteed to leave a site in a valid state. DBCC may need to be run to correct missing or empty assets.

@param [Fixnum] scan_id Scan ID to remove data for.

# File lib/nexpose/scan.rb, line 480
def delete_scan(scan_id)
  AJAX.delete(self, "/data/scan/#{scan_id}")
end
delete_scan_template(id) click to toggle source

Delete a scan template from the console. Cannot be used to delete a built-in template.

@param [String] id Unique identifier of an existing scan template.

# File lib/nexpose/scan_template.rb, line 21
def delete_scan_template(id)
  AJAX.delete(self, "/data/scan/templates/#{URI.encode(id)}")
end
delete_shared_cred(id)
delete_shared_credential(id) click to toggle source
# File lib/nexpose/shared_credential.rb, line 17
def delete_shared_credential(id)
  AJAX.post(self, "/data/credential/shared/delete?credid=#{id}")
end
Also aliased as: delete_shared_cred
delete_silo(silo_id) click to toggle source

Delete the specified silo

@return Whether or not the delete request succeeded.

# File lib/nexpose/silo.rb, line 27
def delete_silo(silo_id)
  r = execute(make_xml('SiloDeleteRequest', { 'silo-id' => silo_id }), '1.2')
  r.success
end
delete_silo_profile(silo_profile_id) click to toggle source

Delete the specified silo profile

@return Whether or not the delete request succeeded.

# File lib/nexpose/silo_profile.rb, line 27
def delete_silo_profile(silo_profile_id)
  r = execute(make_xml('SiloProfileDeleteRequest', { 'silo-profile-id' => silo_profile_id }), '1.2')
  r.success
end
delete_silo_user(user_id) click to toggle source

Delete the specified silo user

@return Whether or not the delete request succeeded.

# File lib/nexpose/multi_tenant_user.rb, line 26
def delete_silo_user(user_id)
  r = execute(make_xml('MultiTenantUserDeleteRequest', { 'user-id' => user_id }), '1.2')
  r.success
end
delete_site(site_id) click to toggle source

Delete the specified site and all associated scan data.

@return Whether or not the delete request succeeded.

# File lib/nexpose/site.rb, line 31
def delete_site(site_id)
  r = execute(make_xml('SiteDeleteRequest', { 'site-id' => site_id }))
  r.success
end
delete_tag(tag_id) click to toggle source

Deletes a tag by ID

@param [Fixnum] tag_id ID of tag to delete

# File lib/nexpose/tag.rb, line 23
def delete_tag(tag_id)
  AJAX.delete(self, "/api/2.0/tags/#{tag_id}")
end
delete_ticket(ticket) click to toggle source

Deletes a Nexpose ticket.

@param [Fixnum] ticket Unique ID of the ticket to delete. @return [Boolean] Whether or not the ticket deletion succeeded.

# File lib/nexpose/ticket.rb, line 26
def delete_ticket(ticket)
  # TODO: Take Ticket object, too, and pull out IDs.
  delete_tickets([ticket])
end
delete_tickets(tickets) click to toggle source

Deletes a Nexpose ticket.

@param [Array] tickets Array of unique IDs of tickets to delete. @return [Boolean] Whether or not the ticket deletions succeeded.

# File lib/nexpose/ticket.rb, line 36
def delete_tickets(tickets)
  # TODO: Take Ticket objects, too, and pull out IDs.
  xml = make_xml('TicketDeleteRequest')
  tickets.each do |id|
    xml.add_element('Ticket', { 'id' => id })
  end

  (execute xml, '1.2').success
end
delete_user(user_id) click to toggle source

Delete a user from the Nexpose console.

@param [Fixnum] user_id Unique ID for the user to delete. @return [Boolean] Whether or not the user deletion succeeded.

# File lib/nexpose/user.rb, line 36
def delete_user(user_id)
  response = execute(make_xml('UserDeleteRequest', { 'id' => user_id }))
  response.success
end
delete_vuln_exception(id) click to toggle source

Delete an existing vulnerability exception.

@param [Fixnum] id The ID of a vuln exception. @return [Boolean] Whether or not deletion was successful.

# File lib/nexpose/vuln_exception.rb, line 110
def delete_vuln_exception(id)
  xml = make_xml('VulnerabilityExceptionDeleteRequest',
                 { 'exception-id' => id })
  execute(xml, '1.2').success
end
device_vulns(dev_id)
Alias for: list_device_vulns
devices(site_id = nil)
Alias for: list_site_devices
discovery_connections()
download(url, file_name = nil) click to toggle source

Download a specific URL, typically a report. Include an optional file_name parameter to write the output to a file.

Note: XML and HTML reports have charts not downloaded by this method.

Would need to do something more sophisticated to grab
all the associated image files.
# File lib/nexpose/connection.rb, line 135
def download(url, file_name = nil)
  return nil if (url.nil? || url.empty?)
  uri          = URI.parse(url)
  http         = Net::HTTP.new(@host, @port)
  http.use_ssl = true
  if @trust_store.nil?
    http.verify_mode = OpenSSL::SSL::VERIFY_NONE # XXX: security issue
  else
    http.cert_store = @trust_store
  end
  headers = { 'Cookie' => "nexposeCCSessionID=#{@session_id}" }

  if file_name
    http.request_get(uri.to_s, headers) do |resp|
      ::File.open(file_name, 'wb') do |file|
        resp.read_body { |chunk| file.write(chunk) }
      end
    end
  else
    resp = http.get(uri.to_s, headers)
    resp.body
  end
end
engine_activity(engine_id) click to toggle source

Provide a list of current scan activities for a specific Scan Engine.

@return [Array] Array of ScanSummary objects associated with

each active scan on the engine.
# File lib/nexpose/engine.rb, line 55
def engine_activity(engine_id)
  xml = make_xml('EngineActivityRequest', { 'engine-id' => engine_id })
  r   = execute(xml)
  arr = []
  if r.success
    r.res.elements.each('//ScanSummary') do |scan_event|
      arr << ScanSummary.parse(scan_event)
    end
  end
  arr
end
engine_pools()
Alias for: list_engine_pools
engine_versions() click to toggle source

Obtain the version information for each scan engine. Includes Product, Content, and Java versions.

# File lib/nexpose/manage.rb, line 49
def engine_versions
  info     = console_command('version engines')
  versions = []
  engines  = info.sub('VERSION INFORMATION\n', '').split(/\n\n/)
  engines.each do |eng|
    engdata = {}
    eng.split(/\n/).each do |kv|
      key, value = kv.split(/:\s*/)
      key = key.sub('Local Engine  ', '').sub('Remote Engine ', '')
      engdata[key] = value
    end
    versions << engdata
  end
  versions
end
engines()
Alias for: list_engines
execute(xml, version = '1.1', options = {}) click to toggle source

Execute an API request

# File lib/nexpose/connection.rb, line 119
def execute(xml, version = '1.1', options = {})
  options.store(:timeout, @timeout) unless options.key?(:timeout)
  options.store(:open_timeout, @open_timeout)
  @request_xml = xml.to_s
  @api_version = version
  response = APIRequest.execute(@url, @request_xml, @api_version, options, @trust_store)
  @response_xml = response.raw_response_data
  response
end
export_scan(scan_id, zip_file = nil) click to toggle source

Export the data associated with a single scan, and optionally store it in a zip-compressed file under the provided name.

@param [Fixnum] scan_id Scan ID to remove data for. @param [String] zip_file Filename to export scan data to. @return [Fixnum] On success, returned the number of bytes written to

zip_file, if provided. Otherwise, returns raw ZIP binary data.
# File lib/nexpose/scan.rb, line 413
def export_scan(scan_id, zip_file = nil)
  http              = AJAX.https(self)
  headers           = { 'Cookie' => "nexposeCCSessionID=#{@session_id}", 'Accept-Encoding' => 'identity' }
  resp              = http.get("/data/scan/#{scan_id}/export", headers)

  case resp
  when Net::HTTPSuccess
    if zip_file
      ::File.open(zip_file, 'wb') { |file| file.write(resp.body) }
    else
      resp.body
    end
  when Net::HTTPForbidden
    raise Nexpose::PermissionError.new(resp)
  else
    raise Nexpose::APIError.new(resp, "#{resp.class}: Unrecognized response.")
  end
end
filter(field, operator, value = '') click to toggle source

Perform an asset filter search that will locate assets matching the provided conditions.

For example, the following call will return assets with Java installed:

nsc.filter(Search::Field::SOFTWARE, Search::Operator::CONTAINS, 'java')

@param [String] field Constant from Search::Field @param [String] operator Constant from Search::Operator @param [String] value Search term or constant from Search::Value @return [Array] List of matching assets.

# File lib/nexpose/filter.rb, line 15
def filter(field, operator, value = '')
  criterion = Criterion.new(field, operator, value)
  criteria  = Criteria.new(criterion)
  search(criteria)
end
find_asset_by_address(address, site_id = nil)
find_device_by_address(address, site_id = nil) click to toggle source

Find a Device by its address.

This is a convenience method for finding a single device from a SiteDeviceListing. If no site_id is provided, the first matching device will be returned when a device occurs across multiple sites.

@param [String] address Address of the device to find. Usually the IP address. @param [FixNum] site_id Site ID to restrict search to. @return [Device] The first matching Device with the provided address,

if found.
# File lib/nexpose/device.rb, line 17
def find_device_by_address(address, site_id = nil)
  r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id }))
  if r.success
    device = REXML::XPath.first(r.res, "SiteDeviceListingResponse/SiteDevices/device[@address='#{address}']")
    if device
      return Device.new(device.attributes['id'].to_i,
                        device.attributes['address'],
                        device.parent.attributes['site-id'],
                        device.attributes['riskfactor'].to_f,
                        device.attributes['riskscore'].to_f)
    end
  end
  nil
end
Also aliased as: find_asset_by_address
find_vuln_check(search_term, partial_words = true, all_words = true) click to toggle source

Search for Vulnerability Checks.

@param [String] search_term Search terms to search for. @param [Boolean] partial_words Allow partial word matches. @param [Boolean] all_words All words must be present. @return [Array] List of matching Vulnerability Checks.

# File lib/nexpose/vuln.rb, line 76
def find_vuln_check(search_term, partial_words = true, all_words = true)
  uri = "/data/vulnerability/vulnerabilities/dyntable.xml?tableID=VulnCheckSynopsis&phrase=#{URI.encode(search_term)}&allWords=#{all_words}"
  data = DataTable._get_dyn_table(self, uri)
  data.map do |vuln|
    XML::VulnCheck.new(vuln)
  end
end
find_vulns_by_cve(cve) click to toggle source

Search for any vulnerability definitions which refer to a given CVE.

@param [String] cve A valid CVE. @return [Array] A list of vuln definitions which check the CVE.

# File lib/nexpose/vuln_def.rb, line 22
def find_vulns_by_cve(cve)
  uri  = '/api/2.0/vulnerability_definitions'
  resp = AJAX.get(self, uri, AJAX::CONTENT_TYPE::JSON, cve: cve)
  json = JSON.parse(resp, symbolize_names: true)
  json[:resources].map { |e| VulnerabilityDefinition.new.object_from_hash(self, e) }
end
find_vulns_by_date(from, to = nil) click to toggle source

Find vulnerabilities by date available in Nexpose. This is not the date the original vulnerability was published, but the date the check was made available in Nexpose.

@param [String] from Vulnerability publish date in format YYYY-MM-DD. @param [String] to Vulnerability publish date in format YYYY-MM-DD. @return [Array] List of vulnerabilities published in

Nexpose between the provided dates.
# File lib/nexpose/vuln.rb, line 93
def find_vulns_by_date(from, to = nil)
  uri = "/data/vulnerability/synopsis/dyntable.xml?addedMin=#{from}"
  uri += "&addedMax=#{to}" if to
  DataTable._get_dyn_table(self, uri).map { |v| VulnSynopsis.new(v) }
end
find_vulns_by_ref(source, id) click to toggle source

Search for any vulnerability definitions which refer to a given reference ID.

Examples:

find_vulns_by_ref('oval', 'OVAL10476')
find_vulns_by_ref('bid', 35067)
find_vulns_by_ref('secunia', 35188)

@param [String] source External vulnerability reference source. @param [String] id Unique vulnerability reference ID. @return [Array] A list of vuln definitions which

check the vulnerability.
# File lib/nexpose/vuln_def.rb, line 42
def find_vulns_by_ref(source, id)
  uri  = '/api/2.0/vulnerability_definitions'
  resp = AJAX.get(self,
                  uri,
                  AJAX::CONTENT_TYPE::JSON,
                  source: source, id: id)
  json = JSON.parse(resp, symbolize_names: true)
  json[:resources].map { |e| VulnerabilityDefinition.new.object_from_hash(self, e) }
end
find_vulns_by_title(title, all_words = true) click to toggle source

Search for any vulnerability definitions which refer to a given title.

Note: This method will return a maximum of 500 results. If the search yields a high number of results, consider add more specific words to the title.

@param [String] title A (partial) title to search for. @param [Boolean] all_words Whether to include all words from the search

phrase in the search.

@return [Array] A list of vuln definitions with titles matching

the provided value.
# File lib/nexpose/vuln_def.rb, line 64
def find_vulns_by_title(title, all_words = true)
  uri    = '/api/2.0/vulnerability_definitions'
  params = { title: title, all_words: all_words }
  resp   = AJAX.get(self, uri, AJAX::CONTENT_TYPE::JSON, params)
  json   = JSON.parse(resp, symbolize_names: true)
  json[:resources].map { |e| VulnerabilityDefinition.new.object_from_hash(self, e) }
end
generate_report(report_id, wait = false) click to toggle source

Generate a new report using the specified report definition.

# File lib/nexpose/report.rb, line 25
def generate_report(report_id, wait = false)
  xml = make_xml('ReportGenerateRequest', { 'report-id' => report_id })
  response = execute(xml)
  if response.success
    response.res.elements.each('//ReportSummary') do |summary|
      summary = ReportSummary.parse(summary)
      # If not waiting or the report is finished, return now.
      return summary unless wait && summary.status == 'Started'
    end
  end
  so_far = 0
  while wait
    summary = last_report(report_id)
    return summary unless summary.status == 'Started'
    sleep 5
    so_far += 5
    if (so_far % 60).zero?
      puts "Still waiting. Current status: #{summary.status}"
    end
  end
  nil
end
get_user_id(user_name) click to toggle source

Retrieve the User ID based upon the user's login name.

@param [String] user_name User name to search for.

# File lib/nexpose/user.rb, line 27
def get_user_id(user_name)
  users.find { |user| user.name.eql? user_name }
end
group_assets(group_id) click to toggle source

Get a list of all assets currently associated with a group.

@param [Fixnum] group_id Unique identifier of an asset group. @return [Array] List of group assets.

# File lib/nexpose/device.rb, line 72
def group_assets(group_id)
  payload = { 'sort' => 'assetName',
              'table-id' => 'group-assets',
              'groupID' => group_id }
  results = DataTable._get_json_table(self, '/data/asset/group', payload)
  results.map { |a| FilteredAsset.new(a) }
end
group_tags(asset_group_id)
Alias for: asset_group_tags
groups()
Alias for: list_asset_groups
import_assets(site_id, assets) click to toggle source

Import external assets into a Nexpose console.

This method will synchronously import a collection of assets into the console. Each call to this method will be treated as a single event.

This method should only be used against “static” sites, i.e., those not tied to a dynamic population service like vSphere, AWS, etc.

If a paused scan exists on the site at the time of import, the newly imported assets will not be included in the scan when it resumes.

@param [Fixnum] site_id Existing site to import assets into. @param [Array] assets External assets to import. @return [Array] collection of import results.

# File lib/nexpose/external.rb, line 18
def import_assets(site_id, assets)
  json = JSON.generate(Array(assets).map(&:to_h))
  import_assets_from_json(site_id, json)
end
import_assets_from_json(site_id, json) click to toggle source

Import external assets into a Nexpose console.

@param [Fixnum] site_id Existing site to import assets into. @param [String] json JSON representation of assets to import. @return [Array] collection of import results.

# File lib/nexpose/external.rb, line 29
def import_assets_from_json(site_id, json)
  uri  = "/api/2.1/sites/#{site_id}/assets"
  # Wait up to 5 minutes for a response.
  resp = AJAX.post(self, uri, json, AJAX::CONTENT_TYPE::JSON, 300)
  arr  = JSON.parse(resp, symbolize_names: true)
  arr.map { |e| External::ImportResult.new.object_from_hash(self, e) }
end
import_scan(site_id, zip_file) click to toggle source

Import scan data into a site.

This method is designed to work with export_scan to migrate scan data from one console to another. This method will import the data as if run from a local scan engine.

Scan importing is restricted to only importing scans in chronological order. It assumes that it is the latest scan for a given site, and will abort if attempting to import an older scan.

@param [Fixnum] site_id Site ID of the site to import the scan into. @param [String] zip_file Path to a previously exported scan archive. @return [Fixnum] The scan ID on success.

# File lib/nexpose/scan.rb, line 446
def import_scan(site_id, zip_file)
  data = Rexlite::MIME::Message.new
  data.add_part(site_id.to_s, nil, nil, 'form-data; name="siteid"')
  data.add_part(session_id, nil, nil, 'form-data; name="nexposeCCSessionID"')
  ::File.open(zip_file, 'rb') do |scan|
    data.add_part(scan.read, 'application/zip', 'binary',
                  "form-data; name=\"scan\"; filename=\"#{zip_file}\"")
  end

  post = Net::HTTP::Post.new('/data/scan/import')
  post.body = data.to_s
  post.set_content_type('multipart/form-data', boundary: data.bound)

  # Avoiding AJAX#request, because the data can cause binary dump on error.
  http = AJAX.https(self)
  AJAX.headers(self, post)
  response = http.request(post)
  case response
  when Net::HTTPOK
    response.body.empty? ? response.body : response.body.to_i
  when Net::HTTPUnauthorized
    raise Nexpose::PermissionError.new(response)
  else
    raise Nexpose::APIError.new(post, response.body)
  end
end
incomplete_assets(scan_id) click to toggle source

Retrieve a list of assets which are incomplete in a given scan. If called during a scan, this method returns currently incomplete assets which may be in progress.

@param [Fixnum] scan_id Unique identifier of a scan. @return [Array] List of incomplete assets.

# File lib/nexpose/device.rb, line 121
def incomplete_assets(scan_id)
  uri = "/data/asset/scan/#{scan_id}/incomplete-assets"
  AJAX.preserving_preference(self, 'scan-incomplete-assets') do
    data = DataTable._get_json_table(self, uri, {}, 500, nil, false)
    data.map(&IncompleteAsset.method(:parse_json))
  end
end
last_report(report_config_id) click to toggle source

Get details of the last report generated with the specified report id.

# File lib/nexpose/report.rb, line 56
def last_report(report_config_id)
  history = report_history(report_config_id)
  history.sort { |a, b| b.generated_on <=> a.generated_on }.first
end
last_scan(site_id) click to toggle source

Retrieve the scan summary statistics for the latest completed scan on a site.

Method will not return data on an active scan.

@param [FixNum] site_id Site ID to find latest scan for. @return [ScanSummary] details of the last completed scan for a site.

# File lib/nexpose/site.rb, line 61
def last_scan(site_id)
  site_scan_history(site_id).select(&:end_time).max_by(&:end_time)
end
list_asset_group_tags(asset_group_id)
Alias for: asset_group_tags
list_asset_groups() click to toggle source

Retrieve an array of all asset groups the user is authorized to view or manage.

@return [Array] Array of AssetGroupSummary objects.

# File lib/nexpose/group.rb, line 24
def list_asset_groups
  r = execute(make_xml('AssetGroupListingRequest'))
  groups = []
  if r.success
    r.res.elements.each('AssetGroupListingResponse/AssetGroupSummary') do |group|
      groups << AssetGroupSummary.new(group.attributes['id'].to_i,
                                      group.attributes['name'],
                                      group.attributes['description'],
                                      group.attributes['riskscore'].to_f,
                                      group.attributes['dynamic'].to_i == 1)
    end
  end
  groups
end
Also aliased as: groups, asset_groups
list_asset_tags(asset_id)
Alias for: asset_tags
list_asset_vulns(dev_id)
Alias for: list_device_vulns
list_assets(site_id = nil)
Alias for: list_site_devices
list_backups() click to toggle source

Retrieve a list of all backups currently stored on the Console.

@return [Array] List of backups.

# File lib/nexpose/maint.rb, line 9
def list_backups
  data = DataTable._get_dyn_table(self, '/data/admin/backups?tableID=BackupSynopsis')
  data.map { |b| Backup.parse(b) }
end
list_device_vulns(dev_id) click to toggle source

List the vulnerability findings for a given device ID.

@param [Fixnum] dev_id Unique identifier of a device (asset). @return [Array] List of vulnerability findings.

# File lib/nexpose/device.rb, line 85
def list_device_vulns(dev_id)
  parameters = { 'devid' => dev_id,
                 'table-id' => 'vulnerability-listing' }
  json = DataTable._get_json_table(self,
                                   '/data/vulnerability/asset-vulnerabilities',
                                   parameters)
  json.map { |vuln| VulnFinding.new(vuln) }
end
list_devices(site_id = nil)
Alias for: list_site_devices
list_discovery_connections() click to toggle source

Retrieve information about all available connections for dynamic discovery of assets, including whether or not connections are active.

# File lib/nexpose/discovery.rb, line 8
def list_discovery_connections
  xml         = make_xml('DiscoveryConnectionListingRequest')
  response    = execute(xml, '1.2')
  connections = []
  response.res.elements.each('DiscoveryConnectionListingResponse/DiscoveryConnectionSummary') do |conn|
    connections << DiscoveryConnection.parse(conn)
  end
  connections
end
Also aliased as: discovery_connections
list_engine_pools() click to toggle source

Retrieve a list of all Scan Engine Pools managed by the Security Console.

@return [Array] Array of EnginePoolSummary objects

associated with each engine associated with this security console.
# File lib/nexpose/pool.rb, line 11
def list_engine_pools
  response = execute(make_xml('EnginePoolListingRequest'), '1.2')
  arr = []
  if response.success
    response.res.elements.each('EnginePoolListingResponse/EnginePoolSummary') do |pool|
      arr << EnginePoolSummary.new(pool.attributes['id'],
                                   pool.attributes['name'],
                                   pool.attributes['scope'])
    end
  end
  arr
end
Also aliased as: engine_pools
list_engines() click to toggle source

Retrieve a list of all Scan Engines managed by the Security Console.

@return [Array] Array of EngineSummary objects associated

with each engine associated with this security console.
# File lib/nexpose/engine.rb, line 72
def list_engines
  response = execute(make_xml('EngineListingRequest'))
  arr      = []
  if response.success
    response.res.elements.each('//EngineSummary') do |engine|
      arr << EngineSummary.new(engine.attributes['id'].to_i,
                               engine.attributes['name'],
                               engine.attributes['address'],
                               engine.attributes['port'].to_i,
                               engine.attributes['status'],
                               engine.attributes['scope'])
    end
  end
  arr
end
Also aliased as: engines
list_report_templates() click to toggle source

Provide a list of all report templates the user can access on the Security Console.

@return [Array] List of current report templates.

# File lib/nexpose/report_template.rb, line 11
def list_report_templates
  r = execute(make_xml('ReportTemplateListingRequest', {}))
  templates = []
  if r.success
    r.res.elements.each('//ReportTemplateSummary') do |template|
      templates << ReportTemplateSummary.parse(template)
    end
  end
  templates
end
Also aliased as: report_templates
list_reports() click to toggle source

Provide a listing of all report definitions the user can access on the Security Console.

@return [Array] List of current report configuration.

# File lib/nexpose/report.rb, line 11
def list_reports
  r = execute(make_xml('ReportListingRequest'))
  reports = []
  if r.success
    r.res.elements.each('//ReportConfigSummary') do |report|
      reports << ReportConfigSummary.parse(report)
    end
  end
  reports
end
Also aliased as: reports
list_scan_templates() click to toggle source

List the scan templates currently configured on the console.

@return [Array] list of scan template summary objects.

# File lib/nexpose/scan_template.rb, line 9
def list_scan_templates
  templates = JSON.parse(AJAX.get(self, '/api/2.0/scan_templates'))
  templates['resources'].map { |t| ScanTemplateSummary.new(t) }
end
Also aliased as: scan_templates
list_shared_credentials() click to toggle source
# File lib/nexpose/shared_credential.rb, line 5
def list_shared_credentials
  creds = DataTable._get_json_table(self,
                                    '/data/credential/shared/listing',
                                    { 'sort' => -1,
                                      'table-id' => 'credential-listing' })
  creds.map { |c| SharedCredentialSummary.from_json(c) }
end
list_shared_creds()
list_silo_profiles() click to toggle source

Retrieve a list of all silos the user is authorized to view or manage.

@return [Array] Array of SiloSummary objects.

# File lib/nexpose/silo_profile.rb, line 10
def list_silo_profiles
  r = execute(make_xml('SiloProfileListingRequest'), '1.2')
  arr = []
  if r.success
    r.res.elements.each('SiloProfileListingResponse/SiloProfileSummaries/SiloProfileSummary') do |profile|
      arr << SiloProfileSummary.parse(profile)
    end
  end
  arr
end
Also aliased as: silo_profiles
list_silo_users() click to toggle source

Retrieve a list of all users the user is authorized to view or manage.

@return [Array] Array of MultiTenantUserSummary objects.

# File lib/nexpose/multi_tenant_user.rb, line 10
def list_silo_users
  r = execute(make_xml('MultiTenantUserListingRequest'), '1.2')
  arr = []
  if r.success
    r.res.elements.each('MultiTenantUserListingResponse/MultiTenantUserSummaries/MultiTenantUserSummary') do |user|
      arr << MultiTenantUserSummary.parse(user)
    end
  end
  arr
end
Also aliased as: silo_users
list_silos() click to toggle source

Retrieve a list of all silos the user is authorized to view or manage.

@return [Array] Array of SiloSummary objects.

# File lib/nexpose/silo.rb, line 10
def list_silos
  r = execute(make_xml('SiloListingRequest'), '1.2')
  arr = []
  if r.success
    r.res.elements.each('SiloListingResponse/SiloSummaries/SiloSummary') do |silo|
      arr << SiloSummary.parse(silo)
    end
  end
  arr
end
Also aliased as: silos
list_site_devices(site_id = nil) click to toggle source

Retrieve a list of all of the assets in a site.

If no site-id is specified, then return all of the assets for the Nexpose console, grouped by site-id.

@param [FixNum] site_id Site ID to request device listing for. Optional. @return [Array] Array of devices associated with the site, or

all devices on the console if no site is provided.
# File lib/nexpose/device.rb, line 43
def list_site_devices(site_id = nil)
  r = execute(make_xml('SiteDeviceListingRequest', { 'site-id' => site_id }))

  devices = []
  if r.success
    r.res.elements.each('SiteDeviceListingResponse/SiteDevices') do |site|
      site_id = site.attributes['site-id'].to_i
      site.elements.each('device') do |device|
        devices << Device.new(device.attributes['id'].to_i,
                              device.attributes['address'],
                              site_id,
                              device.attributes['riskfactor'].to_f,
                              device.attributes['riskscore'].to_f)
      end
    end
  end
  devices
end
Also aliased as: devices, list_devices, assets, list_assets
list_site_tags(site_id)
Alias for: site_tags
list_sites() click to toggle source

Retrieve a list of all sites the user is authorized to view or manage.

@return [Array] Array of SiteSummary objects.

# File lib/nexpose/site.rb, line 10
def list_sites
  r = execute(make_xml('SiteListingRequest'))
  arr = []
  if r.success
    r.res.elements.each('SiteListingResponse/SiteSummary') do |site|
      arr << SiteSummary.new(site.attributes['id'].to_i,
                             site.attributes['name'],
                             site.attributes['description'],
                             site.attributes['riskfactor'].to_f,
                             site.attributes['riskscore'].to_f)
    end
  end
  arr
end
Also aliased as: sites
list_tags()
Alias for: tags
list_tickets() click to toggle source
# File lib/nexpose/ticket.rb, line 6
def list_tickets
  # TODO: Should take in filters as arguments.
  xml = make_xml('TicketListingRequest')
  r   = execute(xml, '1.2')
  tickets = []
  if r.success
    r.res.elements.each('TicketListingResponse/TicketSummary') do |summary|
      tickets << TicketSummary.parse(summary)
    end
  end
  tickets
end
Also aliased as: tickets
list_users() click to toggle source

Retrieve a list of all users configured on this console.

@return [Array] Array of users.

# File lib/nexpose/user.rb, line 10
def list_users
  r = execute(make_xml('UserListingRequest'))
  arr = []
  if r.success
    r.res.elements.each('UserListingResponse/UserSummary') do |summary|
      arr << UserSummary.parse(summary)
    end
  end
  arr
end
Also aliased as: users
list_vuln_categories() click to toggle source

Retrieve a list of the different vulnerability check categories.

@return [Array] Array of currently valid check categories.

# File lib/nexpose/vuln.rb, line 37
def list_vuln_categories
  data = DataTable._get_dyn_table(self, '/data/vulnerability/categories/dyntable.xml?tableID=VulnCategorySynopsis')
  data.map { |c| c['Category'] }
end
Also aliased as: vuln_categories
list_vuln_exceptions(status = nil) click to toggle source

Retrieve all active vulnerability exceptions.

@param [String] status Filter exceptions by the current status.

@see Nexpose::VulnException::Status

@return [Array] List of matching vulnerability exceptions.

# File lib/nexpose/vuln_exception.rb, line 12
def list_vuln_exceptions(status = nil)
  unless is_valid_vuln_exception_status?(status)
    raise "Unknown Status ~> '#{status}' :: For available options refer to Nexpose::VulnException::Status"
  end

  status = Nexpose::VulnException::Status.const_get(status_string_to_constant(status)) unless status.nil?

  results = []
  ajax_data = []

  url_size = 500
  url_page = 0

  req = Nexpose::AJAX.get(self, "/api/3/vulnerability_exceptions?size=#{url_size}&page=#{url_page}")
  data = JSON.parse(req, object_class: OpenStruct)
  ajax_data << data.resources

  if data.links.count > 1
    loop do
      url_page += 1
      req = Nexpose::AJAX.get(self, "/api/3/vulnerability_exceptions?size=#{url_size}&page=#{url_page}")
      data = JSON.parse(req, object_class: OpenStruct)
      ajax_data << data.resources
      links = data.links.select { |ll| ['self', 'last'].include?(ll.rel) }
      break if links[0].href == links[1].href
    end
  end

  ajax_data.compact!
  ajax_data.flatten!

  ajax_data.each do |vuln_excep|
    ve = VulnException.new(vuln_excep.scope.vulnerabilityID, vuln_excep.scope.type, vuln_excep.submit.reason, vuln_excep.state)
    ve.id                = vuln_excep.id
    ve.submitter         = vuln_excep.submit.name
    ve.submitter_comment = vuln_excep.submit.comment
    ve.submit_date       = Time.parse(vuln_excep.submit.date) unless vuln_excep.submit.date.nil?
    ve.asset_id          = vuln_excep.scope.assetID
    ve.site_id           = vuln_excep.scope.siteID
    ve.asset_group_id    = vuln_excep.scope.assetGroupID
    ve.port              = vuln_excep.scope.port
    ve.vuln_key          = vuln_excep.scope.key
    ve.expiration        = Time.parse(vuln_excep.expires) unless vuln_excep.expires.nil?
    unless vuln_excep.review.nil?
      ve.reviewer          = vuln_excep.review.name
      ve.reviewer_comment  = vuln_excep.review.comment
      ve.review_date       = Time.parse(vuln_excep.review.date) unless vuln_excep.review.date.nil?
    end
    results << ve
  end
  results.keep_if { |v| v.status == status } unless status.nil?
  results
end
Also aliased as: vuln_exceptions
list_vuln_types()
Alias for: vuln_types
list_vulns(full = false) click to toggle source

Retrieve summary details of all vulnerabilities.

@param [Boolean] full Whether or not to gather the full summary.

Without the flag, only id, title, and severity are returned.
It can take twice a long to retrieve full summary information.

@return [Array] Collection of all known vulnerabilities.

# File lib/nexpose/vuln.rb, line 12
def list_vulns(full = false)
  xml = make_xml('VulnerabilityListingRequest')
  # TODO: Add a flag to do stream parsing of the XML to improve performance.
  response = execute(xml, '1.2')
  vulns = []
  if response.success
    response.res.elements.each('VulnerabilityListingResponse/VulnerabilitySummary') do |vuln|
      if full
        vulns << XML::VulnerabilitySummary.parse(vuln)
      else
        vulns << XML::Vulnerability.new(vuln.attributes['id'],
                                        vuln.attributes['title'],
                                        vuln.attributes['severity'].to_i)
      end
    end
  end
  vulns
end
Also aliased as: vulns
login() click to toggle source

Establish a new connection and Session ID

# File lib/nexpose/connection.rb, line 99
def login
  login_hash = { 'sync-id' => 0, 'password' => @password, 'user-id' => @username, 'token' => @token }
  login_hash['silo-id'] = @silo_id if @silo_id
  r = execute(make_xml('LoginRequest', login_hash))
  if r.success
    @session_id = r.sid
    true
  end
rescue APIError
  raise AuthenticationFailed.new(r)
end
logout() click to toggle source

Logout of the current connection

# File lib/nexpose/connection.rb, line 112
def logout
  r = execute(make_xml('LogoutRequest', { 'sync-id' => 0 }))
  return true if r.success
  raise APIError.new(r, 'Logout failed')
end
past_scans(limit = nil) click to toggle source

Get a history of past scans for this console, sorted by most recent first.

Please note that for consoles with a deep history of scanning, this method could return an excessive amount of data (and take quite a bit of time to retrieve). Consider limiting the amount of data with the optional argument.

@param [Fixnum] limit The maximum number of records to return from this call. @return [Array] List of completed scans, ordered by most

recently completed first.
# File lib/nexpose/scan.rb, line 372
def past_scans(limit = nil)
  uri    = '/data/scan/global/scan-history'
  rows   = AJAX.row_pref_of(limit)
  params = { 'sort' => 'endTime', 'dir' => 'DESC', 'startIndex' => 0 }
  AJAX.preserving_preference(self, 'global-completed-scans') do
    data = DataTable._get_json_table(self, uri, params, rows, limit)
    data.map(&CompletedScan.method(:parse_json))
  end
end
pause_scan(scan_id) click to toggle source

Pauses a scan.

@param [Fixnum] scan_id The scan ID.

# File lib/nexpose/scan.rb, line 308
def pause_scan(scan_id)
  r = execute(make_xml('ScanPauseRequest', 'scan-id' => scan_id))
  r.success ? r.attributes['success'] == '1' : false
end
paused_scans(site_id = nil, limit = nil) click to toggle source

Get paused scans. Provide a site ID to get paused scans for a site. With no site ID, all paused scans are returned.

@param [Fixnum] site_id Site ID to retrieve paused scans for. @param [Fixnum] limit The maximum number of records to return from this call. @return [Array] List of paused scans.

# File lib/nexpose/scan.rb, line 389
def paused_scans(site_id = nil, limit = nil)
  if site_id
    uri    = "/data/scan/site/#{site_id}?status=active"
    rows   = AJAX.row_pref_of(limit)
    params = { 'sort' => 'endTime', 'dir' => 'DESC', 'startIndex' => 0 }
    AJAX.preserving_preference(self, 'site-active-scans') do
      data = DataTable._get_json_table(self, uri, params, rows, limit).select { |scan| scan['paused'] }
      data.map(&ActiveScan.method(:parse_json))
    end
  else
    uri  = '/data/site/scans/dyntable.xml?printDocType=0&tableID=siteScansTable&activeOnly=true'
    data = DataTable._get_dyn_table(self, uri).select { |scan| (scan['Status'].include? 'Paused') }
    data.map(&ActiveScan.method(:parse_dyntable))
  end
end
recall_vuln_exception(id) click to toggle source

Recall a vulnerability exception. Recall is used by a submitter to undo an exception request that has not been approved yet.

You can only recall a vulnerability exception that has 'Under Review' status.

@param [Fixnum] id Unique identifier of the exception to resubmit. @return [Boolean] Whether or not the recall was accepted by the console.

# File lib/nexpose/vuln_exception.rb, line 99
def recall_vuln_exception(id)
  xml = make_xml('VulnerabilityExceptionRecallRequest',
                 { 'exception-id' => id })
  execute(xml, '1.2').success
end
remove_assets_from_site(asset_ids, site_id) click to toggle source

Remove (or delete) one or more assets from a site. With asset linking enabled, this will remove the association of an asset from the given site. If this is the only site of which an asset is a member, the asset will be deleted. If asset linking is disabled, the assets will be deleted.

@param [Array] asset_ids The asset IDs to be removed from the site. @param [Fixnum] site_id The site ID to remove the assets from.

# File lib/nexpose/device.rb, line 159
def remove_assets_from_site(asset_ids, site_id)
  AJAX.post(self, "/data/assets/bulk-delete?siteid=#{site_id}", asset_ids, Nexpose::AJAX::CONTENT_TYPE::JSON)
end
remove_tag_from_asset(asset_id, tag_id) click to toggle source

Removes a tag from an asset

@param [Fixnum] asset_id on which to remove tag @param [Fixnum] tag_id to remove from asset

# File lib/nexpose/tag.rb, line 47
def remove_tag_from_asset(asset_id, tag_id)
  AJAX.delete(self, "/api/2.0/assets/#{asset_id}/tags/#{tag_id}")
end
remove_tag_from_asset_group(asset_group_id, tag_id) click to toggle source

Removes a tag from an asset_group

@param [Fixnum] asset_group_id id of group on which to remove tag @param [Fixnum] tag_id of the tag to remove from asset group

# File lib/nexpose/tag.rb, line 96
def remove_tag_from_asset_group(asset_group_id, tag_id)
  AJAX.delete(self, "/api/2.0/asset_groups/#{asset_group_id}/tags/#{tag_id}")
end
Also aliased as: remove_tag_from_group
remove_tag_from_group(asset_group_id, tag_id)
remove_tag_from_site(site_id, tag_id) click to toggle source

Removes a tag from a site

@param [Fixnum] site_id id of the site on which to remove the tag @param [Fixnum] tag_id id of the tag to remove

# File lib/nexpose/tag.rb, line 71
def remove_tag_from_site(site_id, tag_id)
  AJAX.delete(self, "/api/2.0/sites/#{site_id}/tags/#{tag_id}")
end
report_history(report_config_id) click to toggle source

Provide a history of all reports generated with the specified report definition.

# File lib/nexpose/report.rb, line 50
def report_history(report_config_id)
  xml = make_xml('ReportHistoryRequest', { 'reportcfg-id' => report_config_id })
  ReportSummary.parse_all(execute(xml))
end
report_templates()
reports()
Alias for: list_reports
restart() click to toggle source

Restart the application.

There is no response to a RestartRequest. When the application shuts down as part of the restart process, it terminates any active connections. Therefore, the application cannot issue a response when it restarts.

# File lib/nexpose/manage.rb, line 79
def restart
  execute(make_xml('RestartRequest', {})).success
end
resubmit_vuln_exception(id, comment, reason = nil) click to toggle source

Resubmit a vulnerability exception request with a new comment and reason after an exception has been rejected.

You can only resubmit a request that has a “Rejected” status; if an exception is “Approved” or “Under Review” you will receive an error message stating that the exception request cannot be resubmitted.

@param [Fixnum] id Unique identifier of the exception to resubmit. @param [String] comment Comment to justify the exception resubmission. @param [String] reason The reason for the exception status, if changing.

@see Nexpose::VulnException::Reason

@return [Boolean] Whether or not the resubmission was valid.

# File lib/nexpose/vuln_exception.rb, line 80
def resubmit_vuln_exception(id, comment, reason = nil)
  options           = { 'exception-id' => id }
  options['reason'] = reason if reason
  xml               = make_xml('VulnerabilityExceptionResubmitRequest', options)
  comment_xml       = make_xml('comment', {}, comment, false)
  xml.add_element(comment_xml)
  r = execute(xml, '1.2')
  r.success
end
resume_scan(scan_id) click to toggle source

Resumes a scan.

@param [Fixnum] scan_id The scan ID.

# File lib/nexpose/scan.rb, line 299
def resume_scan(scan_id)
  r = execute(make_xml('ScanResumeRequest', 'scan-id' => scan_id), '1.1', timeout: 60)
  r.success ? r.attributes['success'] == '1' : false
end
reverse_engine_connection(engine_id) click to toggle source

Reverses the direction of a connection to an engine If the connection is currently initiated from the console this method will have the engine initiate the connection. If the connection is currently initiated by the engine this method with initiate the connection from the console instead. Requires a restart of the console for the connection to be properly established.

@param [Fixnum] engine_id Unique ID of the engine. @return [Boolean] true if the connection is successfully reversed.

# File lib/nexpose/engine.rb, line 28
def reverse_engine_connection(engine_id)
  uri      = "/api/2.1/engine/#{engine_id}/reverseConnection"
  response = AJAX.put(self, uri)
  response.eql?('true')
end
role_delete(role, scope = Scope::SILO) click to toggle source
# File lib/nexpose/role.rb, line 68
def role_delete(role, scope = Scope::SILO)
  xml = make_xml('RoleDeleteRequest')
  xml.add_element('Role', { 'name' => role, 'scope' => scope })
  response = execute(xml, '1.2')
  response.success
end
Also aliased as: delete_role
role_listing() click to toggle source

Returns a summary list of all roles.

# File lib/nexpose/role.rb, line 54
def role_listing
  xml   = make_xml('RoleListingRequest')
  r     = execute(xml, '1.2')
  roles = []
  if r.success
    r.res.elements.each('RoleListingResponse/RoleSummary') do |summary|
      roles << RoleSummary.parse(summary)
    end
  end
  roles
end
Also aliased as: roles
roles()
Alias for: role_listing
scan_activity() click to toggle source

Retrieve a list of current scan activities across all Scan Engines managed by Nexpose.

@return [Array] Array of ScanSummary objects associated with

each active scan on the engines.
# File lib/nexpose/scan.rb, line 337
def scan_activity
  r = execute(make_xml('ScanActivityRequest'))
  res = []
  if r.success
    r.res.elements.each('//ScanSummary') do |scan|
      res << ScanSummary.parse(scan)
    end
  end
  res
end
scan_asset(site_id, asset) click to toggle source

Perform an ad hoc scan of a single asset of a site.

@param [Fixnum] site_id Site ID that the assets belong to. @param [HostName|IPRange] asset Asset to scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 82
def scan_asset(site_id, asset)
  scan_assets(site_id, [asset])
end
scan_asset_with_schedule(site_id, asset, schedule) click to toggle source

Perform an ad hoc scan of a single asset of a site at a specific time

@param [Fixnum] site_id Site ID that the assets belong to. @param [HostName|IPRange] asset Asset to scan. @param [Array] list of scheduled times at which to run @return [Status] whether the request was successful

# File lib/nexpose/scan.rb, line 93
def scan_asset_with_schedule(site_id, asset, schedule)
  scan_assets_with_schedule(site_id, [asset], schedule)
end
scan_assets(site_id, assets) click to toggle source

Perform an ad hoc scan of a subset of assets for a site. Only assets from a single site should be submitted per request. Method is designed to take objects filtered from Site#assets.

For example:

site = Site.load(nsc, 5)
nsc.scan_assets(5, site.assets.take(10))

@param [Fixnum] site_id Site ID that the assets belong to. @param [Array] assets List of assets to scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 109
def scan_assets(site_id, assets)
  xml   = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  hosts = REXML::Element.new('Hosts')
  assets.each { |asset| _append_asset!(hosts, asset) }
  xml.add_element(hosts)

  _scan_ad_hoc(xml)
end
scan_assets_with_schedule(site_id, assets, schedules) click to toggle source

Perform an ad hoc scan of a subset of assets for a site by adding a specific runtime. Only assets from a single site should be submitted per request. Method is designed to take objects filtered from Site#assets.

For example:

site = Site.load(nsc, 5)
nsc.scan_assets_with_schedule(5, site.assets.take(10), schedules)

@param [Fixnum] site_id Site ID that the assets belong to. @param [Array] assets List of assets to scan. @param [Array] list of scheduled times at which to run @return [Status] whether the request was successful

# File lib/nexpose/scan.rb, line 131
def scan_assets_with_schedule(site_id, assets, schedules)
  xml   = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  hosts = REXML::Element.new('Hosts')
  assets.each { |asset| _append_asset!(hosts, asset) }
  xml.add_element(hosts)
  scheds = REXML::Element.new('Schedules')
  schedules.each { |sched| scheds.add_element(sched.as_xml) }
  xml.add_element(scheds)

  _scan_ad_hoc_with_schedules(xml)
end
scan_assets_with_template_and_engine(site_id, assets, scan_template, scan_engine) click to toggle source

Initiate an ad-hoc scan on a subset of site assets with a specific scan template and scan engine, which may differ from the site's defined scan template and scan engine.

@param [Fixnum] site_id Site ID to scan. @param [Array] assets Hostnames and/or IP addresses to scan. @param [String] scan_template The scan template ID. @param [Fixnum] scan_engine The scan engine ID. @return [Fixnum] Scan ID.

# File lib/nexpose/scan.rb, line 219
def scan_assets_with_template_and_engine(site_id, assets, scan_template, scan_engine)
  uri = "/data/site/#{site_id}/scan"
  assets.size > 1 ? addresses = assets.join(',') : addresses = assets.first
  params = { 'addressList' => addresses,
             'template' => scan_template,
             'scanEngine' => scan_engine }
  scan_id = AJAX.form_post(self, uri, params)
  scan_id.to_i
end
scan_device(device) click to toggle source

Perform an ad hoc scan of a single device.

@param [Device] device Device to scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 10
def scan_device(device)
  scan_devices([device])
end
scan_device_with_schedule(device, schedule) click to toggle source

Perform an ad hoc scan of a single device at a specific time.

@param [Device] device Device to scan. @param [Array] list of scheduled times at which to run @return [Status] whether the request was successful

# File lib/nexpose/scan.rb, line 20
def scan_device_with_schedule(device, schedule)
  scan_devices_with_schedule([device], schedule)
end
scan_devices(devices) click to toggle source

Perform an ad hoc scan of a subset of devices for a site. Nexpose only allows devices from a single site to be submitted per request. Method is designed to take objects from a Device listing.

For example:

devices = nsc.devices(5)
nsc.scan_devices(devices.take(10))

@param [Array] devices List of devices to scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 36
def scan_devices(devices)
  site_id = devices.map(&:site_id).uniq.first
  xml = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  elem = REXML::Element.new('Devices')
  devices.each do |device|
    elem.add_element('device', 'id' => "#{device.id}")
  end
  xml.add_element(elem)

  _scan_ad_hoc(xml)
end
scan_devices_with_schedule(devices, schedules) click to toggle source

Perform an ad hoc scan of a subset of devices for a site. Nexpose only allows devices from a single site to be submitted per request. Method is designed to take objects from a Device listing.

For example:

devices = nsc.devices(5)
nsc.scan_devices(devices.take(10))

@param [Array] devices List of devices to scan. @param [Array] list of scheduled times at which to run @return [Status] whether the request was successful

# File lib/nexpose/scan.rb, line 61
def scan_devices_with_schedule(devices, schedules)
  site_id = devices.map(&:site_id).uniq.first
  xml     = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  elem    = REXML::Element.new('Devices')
  devices.each do |device|
    elem.add_element('device', 'id' => "#{device.id}")
  end
  xml.add_element(elem)
  scheds = REXML::Element.new('Schedules')
  schedules.each { |sched| scheds.add_element(sched.as_xml) }
  xml.add_element(scheds)

  _scan_ad_hoc_with_schedules(xml)
end
scan_ips(site_id, ip_addresses) click to toggle source

Perform an ad hoc scan of a subset of IP addresses for a site. Only IPs from a single site can be submitted per request, and IP addresses must already be included in the site configuration. Method is designed for scanning when the targets are coming from an external source that does not have access to internal identfiers.

For example:

to_scan = ['192.168.2.1', '192.168.2.107']
nsc.scan_ips(5, to_scan)

@param [Fixnum] site_id Site ID that the assets belong to. @param [Array] ip_addresses Array of IP addresses to scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 185
def scan_ips(site_id, ip_addresses)
  xml   = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  hosts = REXML::Element.new('Hosts')
  ip_addresses.each do |ip|
    xml.add_element('range', 'from' => ip)
  end
  xml.add_element(hosts)

  _scan_ad_hoc(xml)
end
scan_ips_with_schedule(site_id, ip_addresses, schedules) click to toggle source

Perform an ad hoc scan of a subset of IP addresses for a site at a specific time. Only IPs from a single site can be submitted per request, and IP addresses must already be included in the site configuration. Method is designed for scanning when the targets are coming from an external source that does not have access to internal identfiers.

For example:

to_scan = ['192.168.2.1', '192.168.2.107']
nsc.scan_ips(5, to_scan)

@param [Fixnum] site_id Site ID that the assets belong to. @param [Array] ip_addresses Array of IP addresses to scan. @return [Status] whether the request was successful

# File lib/nexpose/scan.rb, line 157
def scan_ips_with_schedule(site_id, ip_addresses, schedules)
  xml   = make_xml('SiteDevicesScanRequest', 'site-id' => site_id)
  hosts = REXML::Element.new('Hosts')
  ip_addresses.each do |ip|
    xml.add_element('range', 'from' => ip)
  end
  xml.add_element(hosts)
  scheds = REXML::Element.new('Schedules')
  schedules.each { |sched| scheds.add_element(sched.as_xml) }
  xml.add_element(scheds)

  _scan_ad_hoc_with_schedules(xml)
end
scan_site(site_id, blackout_override = false) click to toggle source

Initiate a site scan.

@param [Fixnum] site_id Site ID to scan. @param [Boolean] blackout_override Optional. Given suffencent permissions, force bypass blackout and start scan. @return [Scan] Scan launch information.

# File lib/nexpose/scan.rb, line 202
def scan_site(site_id, blackout_override = false)
  xml = make_xml('SiteScanRequest', 'site-id' => site_id)
  xml.add_attributes({ 'force' => true }) if blackout_override
  response = execute(xml)
  Scan.parse(response.res) if response.success
end
scan_statistics(scan_id) click to toggle source

Get scan statistics, including node and vulnerability breakdowns.

@param [Fixnum] scan_id Scan ID to retrieve statistics for. @return [ScanSummary] ScanSummary object providing statistics for the scan.

# File lib/nexpose/scan.rb, line 353
def scan_statistics(scan_id)
  r = execute(make_xml('ScanStatisticsRequest', 'scan-id' => scan_id))
  if r.success
    ScanSummary.parse(r.res.elements['//ScanSummary'])
  else
    false
  end
end
scan_status(scan_id) click to toggle source

Retrieve the status of a scan.

@param [Fixnum] scan_id The scan ID. @return [String] Current status of the scan. See Nexpose::Scan::Status.

# File lib/nexpose/scan.rb, line 290
def scan_status(scan_id)
  r = execute(make_xml('ScanStatusRequest', 'scan-id' => scan_id))
  r.success ? r.attributes['status'] : nil
end
scan_templates()
Alias for: list_scan_templates
selected_criticality_tag(asset_id) click to toggle source

Returns the criticality value which takes precedent for an asset

@param [Fixnum] asset_id id of asset on which criticality tag is selected @return [String] selected_criticality string of the relevant criticality; nil if not tagged

# File lib/nexpose/tag.rb, line 106
def selected_criticality_tag(asset_id)
  selected_criticality = AJAX.get(self, "/data/asset/#{asset_id}/selected-criticality-tag")
  selected_criticality.empty? ? nil : JSON.parse(selected_criticality)['name']
end
send_log(uri = 'https://support.rapid7.com') click to toggle source

Output diagnostic information into log files, zip the files, and encrypt the archive with a PGP public key that is provided as a parameter for the API call. Then upload the archive using HTTPS to a URL that is specified as an API parameter.

@param uri Upload server to send the support log package to.

# File lib/nexpose/manage.rb, line 90
def send_log(uri = 'https://support.rapid7.com')
  url = REXML::Element.new('URL')
  url.text = uri
  tpt = REXML::Element.new('Transport')
  tpt.add_attribute('protocol', 'https')
  tpt << url
  xml = make_xml('SendLogRequest')
  xml << tpt

  execute(xml).success
end
shared_credentials()
shared_creds()
silo_profiles()
Alias for: list_silo_profiles
silo_users()
Alias for: list_silo_users
silos()
Alias for: list_silos
site_scan_history(site_id) click to toggle source

Retrieve a list of all previous scans of the site.

@param [FixNum] site_id Site ID to request scan history for. @return [Array] Array of ScanSummary objects representing

each scan run to date on the site provided.
# File lib/nexpose/site.rb, line 42
def site_scan_history(site_id)
  r = execute(make_xml('SiteScanHistoryRequest', { 'site-id' => site_id }))
  scans = []
  if r.success
    r.res.elements.each('SiteScanHistoryResponse/ScanSummary') do |scan_event|
      scans << ScanSummary.parse(scan_event)
    end
  end
  scans
end
site_tags(site_id) click to toggle source

Lists all the tags on a site

@param [Fixnum] site_id id of the site to get the applied tags @return [Array] list of tags on site

# File lib/nexpose/tag.rb, line 56
def site_tags(site_id)
  tag_summary = []
  site_tag = JSON.parse(AJAX.get(self, "/api/2.0/sites/#{site_id}/tags", AJAX::CONTENT_TYPE::JSON, { per_page: 2_147_483_647 }))
  site_tag['resources'].each do |json|
    tag_summary << TagSummary.parse(json)
  end
  tag_summary
end
Also aliased as: list_site_tags
sites()
Alias for: list_sites
start_update() click to toggle source

Induce the application to retrieve required updates and restart if necessary.

# File lib/nexpose/manage.rb, line 68
def start_update
  execute(make_xml('StartUpdateRequest', {})).success
end
stop_scan(scan_id, wait_sec = 0) click to toggle source

Stop a running or paused scan.

@param [Fixnum] scan_id ID of the scan to stop. @param [Fixnum] wait_sec Number of seconds to wait for status to be

updated.
# File lib/nexpose/scan.rb, line 271
def stop_scan(scan_id, wait_sec = 0)
  r = execute(make_xml('ScanStopRequest', 'scan-id' => scan_id))
  if r.success
    so_far = 0
    while so_far < wait_sec
      status = scan_status(scan_id)
      return status if status == 'stopped'
      sleep 5
      so_far += 5
    end
  end
  r.success
end
system_information() click to toggle source

Obtain system data, such as total RAM, free RAM, total disk space, free disk space, CPU speed, number of CPU cores, and other vital information.

# File lib/nexpose/manage.rb, line 32
def system_information
  r = execute(make_xml('SystemInformationRequest', {}))

  if r.success
    res = {}
    r.res.elements.each('//Statistic') do |stat|
      res[stat.attributes['name'].to_s] = stat.text.to_s
    end
    res
  else
    false
  end
end
tags() click to toggle source

Lists all tags

@return [Array] List of current tags.

# File lib/nexpose/tag.rb, line 9
def tags
  tag_summary = []
  tags        = JSON.parse(AJAX.get(self, '/api/2.0/tags', AJAX::CONTENT_TYPE::JSON, { per_page: 2_147_483_647 }))
  tags['resources'].each do |json|
    tag_summary << TagSummary.parse(json)
  end
  tag_summary
end
Also aliased as: list_tags
tickets()
Alias for: list_tickets
update_engine(engine_id) click to toggle source

Kicks off an update on a single engine. A return result of true should be taken only to mean that the update was sent, not that it correctly applied.

Nexpose::APIError will be raised if the engine is already updating, or if the engine is offline or unresponsive.

@param [Fixnum] engine_id Unique ID of the engine. @return [Boolean] true if the update was sent

or if engine is already up to date.
# File lib/nexpose/engine.rb, line 45
def update_engine(engine_id)
  uri = "/data/engine/#{engine_id}/update"
  AJAX.post(self, uri)
end
users()
Alias for: list_users
vuln_categories()
vuln_details(vuln_id) click to toggle source

Retrieve details for a vulnerability.

@param [String] vuln_id Nexpose vulnerability ID, such as 'windows-duqu-cve-2011-3402'. @return [VulnerabilityDetail] Details of the requested vulnerability.

# File lib/nexpose/vuln.rb, line 59
def vuln_details(vuln_id)
  xml = make_xml('VulnerabilityDetailsRequest', { 'vuln-id' => vuln_id })
  response = execute(xml, '1.2')
  if response.success
    response.res.elements.each('VulnerabilityDetailsResponse/Vulnerability') do |vuln|
      return XML::VulnerabilityDetail.parse(vuln)
    end
  end
end
vuln_exceptions(status = nil)
vuln_types() click to toggle source

Retrieve a list of the different vulnerability check types.

@return [Array] Array of currently valid check types.

# File lib/nexpose/vuln.rb, line 48
def vuln_types
  data = DataTable._get_dyn_table(self, '/data/vulnerability/checktypes/dyntable.xml?tableID=VulnCheckCategorySynopsis')
  data.map { |c| c['Category'] }
end
Also aliased as: list_vuln_types
vulns(full = false)
Alias for: list_vulns

Private Instance Methods

create_trust_store(trust_cert) click to toggle source
# File lib/nexpose/connection.rb, line 159
def create_trust_store(trust_cert)
  store = OpenSSL::X509::Store.new
  store.trust
  store.add_cert(OpenSSL::X509::Certificate.new(trust_cert))
  store
end
is_valid_vuln_exception_status?(status) click to toggle source
# File lib/nexpose/vuln_exception.rb, line 118
def is_valid_vuln_exception_status?(status)
  return true if status.nil?
  valid_status = []
  Nexpose::VulnException::Status.constants.each { |con| valid_status << Nexpose::VulnException::Status.const_get(con) }
  valid_status << Nexpose::VulnException::Status.constants.map(&:to_s).map(&:downcase)
  valid_status.flatten.map(&:downcase).include?(status.downcase)
end
status_string_to_constant(status) click to toggle source
# File lib/nexpose/vuln_exception.rb, line 126
def status_string_to_constant(status)
  Nexpose::VulnException::Status.constants.find do |name|
    Nexpose::VulnException::Status.const_get(name).to_s.downcase == status.downcase || status.to_sym.downcase == name.downcase
  end
end