nexpose 0.2.8 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/nexpose.rb +18 -9
- data/lib/nexpose/ajax.rb +127 -0
- data/lib/nexpose/alert.rb +29 -36
- data/lib/nexpose/common.rb +13 -12
- data/lib/nexpose/connection.rb +18 -13
- data/lib/nexpose/creds.rb +16 -55
- data/lib/nexpose/dag.rb +73 -0
- data/lib/nexpose/data_table.rb +134 -0
- data/lib/nexpose/device.rb +111 -0
- data/lib/nexpose/engine.rb +194 -0
- data/lib/nexpose/filter.rb +341 -0
- data/lib/nexpose/group.rb +33 -37
- data/lib/nexpose/manage.rb +4 -0
- data/lib/nexpose/pool.rb +142 -0
- data/lib/nexpose/report.rb +72 -278
- data/lib/nexpose/report_template.rb +249 -0
- data/lib/nexpose/scan.rb +196 -54
- data/lib/nexpose/scan_template.rb +103 -0
- data/lib/nexpose/site.rb +91 -237
- data/lib/nexpose/ticket.rb +173 -119
- data/lib/nexpose/user.rb +11 -3
- data/lib/nexpose/vuln.rb +81 -338
- data/lib/nexpose/vuln_exception.rb +368 -0
- metadata +12 -4
- data/lib/nexpose/misc.rb +0 -35
- data/lib/nexpose/scan_engine.rb +0 -325
data/lib/nexpose/group.rb
CHANGED
@@ -8,35 +8,33 @@ module Nexpose
|
|
8
8
|
#
|
9
9
|
# @return [Boolean] Whether group deletion succeeded.
|
10
10
|
#
|
11
|
-
def
|
11
|
+
def delete_asset_group(id)
|
12
12
|
r = execute(make_xml('AssetGroupDeleteRequest', {'group-id' => id}))
|
13
13
|
r.success
|
14
14
|
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
# Retrieve a list of all asset groups the user is authorized to view or
|
16
|
+
# Retrieve an array of all asset groups the user is authorized to view or
|
19
17
|
# manage.
|
20
18
|
#
|
21
19
|
# @return [Array[AssetGroupSummary]] Array of AssetGroupSummary objects.
|
22
20
|
#
|
23
|
-
def
|
21
|
+
def list_asset_groups
|
24
22
|
r = execute(make_xml('AssetGroupListingRequest'))
|
25
23
|
|
26
|
-
|
24
|
+
groups = []
|
27
25
|
if r.success
|
28
26
|
r.res.elements.each('AssetGroupListingResponse/AssetGroupSummary') do |group|
|
29
|
-
|
30
|
-
group.attributes['name']
|
31
|
-
group.attributes['description']
|
27
|
+
groups << AssetGroupSummary.new(group.attributes['id'].to_i,
|
28
|
+
group.attributes['name'],
|
29
|
+
group.attributes['description'],
|
32
30
|
group.attributes['riskscore'].to_f)
|
33
31
|
end
|
34
32
|
end
|
35
|
-
|
33
|
+
groups
|
36
34
|
end
|
37
35
|
|
38
|
-
alias_method :
|
39
|
-
alias_method :
|
36
|
+
alias_method :groups, :list_asset_groups
|
37
|
+
alias_method :asset_groups, :list_asset_groups
|
40
38
|
end
|
41
39
|
|
42
40
|
# Summary value object for asset group information.
|
@@ -53,7 +51,7 @@ module Nexpose
|
|
53
51
|
# @param [Connection] connection Connection to security console.
|
54
52
|
#
|
55
53
|
def delete(connection)
|
56
|
-
connection.
|
54
|
+
connection.delete_asset_group(@id)
|
57
55
|
end
|
58
56
|
end
|
59
57
|
|
@@ -71,13 +69,11 @@ module Nexpose
|
|
71
69
|
end
|
72
70
|
|
73
71
|
def save(connection)
|
74
|
-
xml =
|
72
|
+
xml = "<AssetGroupSaveRequest session-id='#{connection.session_id}'>"
|
75
73
|
xml << to_xml
|
76
74
|
xml << '</AssetGroupSaveRequest>'
|
77
|
-
|
78
|
-
if
|
79
|
-
@id = response.attributes['group-id'].to_i if @id < 0
|
80
|
-
end
|
75
|
+
res = connection.execute(xml)
|
76
|
+
@id = res.attributes['group-id'].to_i if res.success and @id < 1
|
81
77
|
end
|
82
78
|
|
83
79
|
# Get an XML representation of the group that is valid for a save request.
|
@@ -87,12 +83,12 @@ module Nexpose
|
|
87
83
|
# @return [String] XML representation of the asset group.
|
88
84
|
#
|
89
85
|
def to_xml
|
90
|
-
xml = %
|
91
|
-
xml << %
|
86
|
+
xml = %(<AssetGroup id="#{@id}" name="#{@name}")
|
87
|
+
xml << %( description="#{@description}") if @description
|
92
88
|
xml << '>'
|
93
89
|
xml << '<Devices>'
|
94
90
|
@devices.each do |device|
|
95
|
-
xml << %
|
91
|
+
xml << %(<device id="#{device.id}"/>)
|
96
92
|
end
|
97
93
|
xml << '</Devices>'
|
98
94
|
xml << '</AssetGroup>'
|
@@ -100,45 +96,45 @@ module Nexpose
|
|
100
96
|
|
101
97
|
# Launch ad hoc scans against each group of assets per site.
|
102
98
|
#
|
103
|
-
# @param [Connection] connection Connection to console where asset group
|
104
|
-
#
|
105
|
-
#
|
99
|
+
# @param [Connection] connection Connection to console where asset group
|
100
|
+
# is configured.
|
101
|
+
# @return [Hash] Hash of site ID to Scan launch information for each scan.
|
106
102
|
#
|
107
103
|
def rescan_assets(connection)
|
108
|
-
sites_ids = @devices.
|
109
|
-
scans =
|
104
|
+
sites_ids = @devices.map { |d| d.site_id }.uniq
|
105
|
+
scans = {}
|
110
106
|
sites_ids.each do |id|
|
111
|
-
|
112
|
-
scans
|
107
|
+
to_scan = @devices.select { |d| d.site_id == id }
|
108
|
+
scans[id] = connection.scan_devices(to_scan)
|
113
109
|
end
|
114
110
|
scans
|
115
111
|
end
|
116
112
|
|
117
113
|
# Load an existing configuration from a Nexpose instance.
|
118
114
|
#
|
119
|
-
# @param [Connection] connection Connection to console where asset group
|
115
|
+
# @param [Connection] connection Connection to console where asset group
|
116
|
+
# is configured.
|
120
117
|
# @param [Fixnum] id Asset group ID of an existing group.
|
121
|
-
# @return [AssetGroup] Asset group configuration loaded from a Nexpose
|
118
|
+
# @return [AssetGroup] Asset group configuration loaded from a Nexpose
|
119
|
+
# console.
|
122
120
|
#
|
123
121
|
def self.load(connection, id)
|
124
|
-
|
125
|
-
|
122
|
+
xml = %(<AssetGroupConfigRequest session-id="#{connection.session_id}" group-id="#{id}"/>)
|
123
|
+
r = APIRequest.execute(connection.url, xml)
|
126
124
|
parse(r.res)
|
127
125
|
end
|
128
126
|
|
129
|
-
alias_method :get, :load
|
130
|
-
|
131
127
|
def self.parse(xml)
|
132
128
|
return nil unless xml
|
133
129
|
|
134
130
|
group = REXML::XPath.first(xml, 'AssetGroupConfigResponse/AssetGroup')
|
135
|
-
asset_group = new(group.attributes['name']
|
136
|
-
group.attributes['description']
|
131
|
+
asset_group = new(group.attributes['name'],
|
132
|
+
group.attributes['description'],
|
137
133
|
group.attributes['id'].to_i,
|
138
134
|
group.attributes['riskscore'].to_f)
|
139
135
|
group.elements.each('Devices/device') do |dev|
|
140
136
|
asset_group.devices << Device.new(dev.attributes['id'].to_i,
|
141
|
-
dev.attributes['address']
|
137
|
+
dev.attributes['address'],
|
142
138
|
dev.attributes['site-id'].to_i,
|
143
139
|
dev.attributes['riskfactor'].to_f,
|
144
140
|
dev.attributes['riskscore'].to_f)
|
data/lib/nexpose/manage.rb
CHANGED
@@ -7,6 +7,7 @@ module Nexpose
|
|
7
7
|
# supplied parameter. Console commands are documented in the
|
8
8
|
# administrator's guide. If you use a command that is not listed in the
|
9
9
|
# administrator's guide, the application will return the XMLResponse.
|
10
|
+
#
|
10
11
|
def console_command(cmd_string)
|
11
12
|
xml = make_xml('ConsoleCommandRequest', {})
|
12
13
|
cmd = REXML::Element.new('Command')
|
@@ -26,6 +27,7 @@ module Nexpose
|
|
26
27
|
# Obtain system data, such as total RAM, free RAM, total disk space,
|
27
28
|
# free disk space, CPU speed, number of CPU cores, and other vital
|
28
29
|
# information.
|
30
|
+
#
|
29
31
|
def system_information
|
30
32
|
r = execute(make_xml('SystemInformationRequest', {}))
|
31
33
|
|
@@ -42,6 +44,7 @@ module Nexpose
|
|
42
44
|
|
43
45
|
# Induce the application to retrieve required updates and restart
|
44
46
|
# if necessary.
|
47
|
+
#
|
45
48
|
def start_update
|
46
49
|
execute(make_xml('StartUpdateRequest', {})).success
|
47
50
|
end
|
@@ -52,6 +55,7 @@ module Nexpose
|
|
52
55
|
# shuts down as part of the restart process, it terminates any active
|
53
56
|
# connections. Therefore, the application cannot issue a response when it
|
54
57
|
# restarts.
|
58
|
+
#
|
55
59
|
def restart
|
56
60
|
execute(make_xml('RestartRequest', {})).success
|
57
61
|
end
|
data/lib/nexpose/pool.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
module Nexpose
|
2
|
+
module NexposeAPI
|
3
|
+
include XMLUtils
|
4
|
+
|
5
|
+
# Retrieve a list of all Scan Engine Pools managed by the Security Console.
|
6
|
+
#
|
7
|
+
# @return [Array[EnginePoolSummary]] Array of EnginePoolSummary objects
|
8
|
+
# associated with each engine associated with this security console.
|
9
|
+
#
|
10
|
+
def list_engine_pools
|
11
|
+
response = execute(make_xml('EnginePoolListingRequest'), '1.2')
|
12
|
+
arr = []
|
13
|
+
if response.success
|
14
|
+
response.res.elements.each('EnginePoolListingResponse/EnginePoolSummary') do |pool|
|
15
|
+
arr << EnginePoolSummary.new(pool.attributes['id'],
|
16
|
+
pool.attributes['name'],
|
17
|
+
pool.attributes['scope'])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
arr
|
21
|
+
end
|
22
|
+
|
23
|
+
alias_method :engine_pools, :list_engine_pools
|
24
|
+
end
|
25
|
+
|
26
|
+
# A summary of an engine pool.
|
27
|
+
#
|
28
|
+
class EnginePoolSummary
|
29
|
+
|
30
|
+
# Unique identifier of the engine pool.
|
31
|
+
attr_reader :id
|
32
|
+
# Name of the engine pool.
|
33
|
+
attr_reader :name
|
34
|
+
# Whether the engine pool has global or silo scope.
|
35
|
+
attr_reader :scope
|
36
|
+
|
37
|
+
def initialize(id, name, scope = 'silo')
|
38
|
+
@id = id
|
39
|
+
@name = name
|
40
|
+
@scope = scope
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Engine pool configuration object.
|
45
|
+
#
|
46
|
+
class EnginePool
|
47
|
+
|
48
|
+
# Unique identifier of the engine pool.
|
49
|
+
attr_accessor :id
|
50
|
+
# Name of the engine pool.
|
51
|
+
attr_accessor :name
|
52
|
+
# Whether the engine pool has global or silo scope.
|
53
|
+
attr_accessor :scope
|
54
|
+
# Array containing (EngineSummary*) for each engine assigned to the pool.
|
55
|
+
attr_accessor :engines
|
56
|
+
|
57
|
+
def initialize(name, scope = 'silo', id = -1)
|
58
|
+
@name, @scope, @id = name, scope, id.to_i
|
59
|
+
@engines = []
|
60
|
+
end
|
61
|
+
|
62
|
+
# Add an engine to the pool by name (not ID).
|
63
|
+
#
|
64
|
+
# EngineSummary objects should just be appended to the pool directly,
|
65
|
+
# e.g., pool.engines << nsc.engines.find { |e| e.name == 'Cleveland' }
|
66
|
+
#
|
67
|
+
# @param [String] engine_name Name used to identify a paired scan engine.
|
68
|
+
#
|
69
|
+
def add(engine_name)
|
70
|
+
@engines << EngineSummary.new(-1, engine_name, nil, 40814, nil)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns detailed information about a single engine pool.
|
74
|
+
#
|
75
|
+
# @param [Connection] connection Connection to console where site exists.
|
76
|
+
# @param [String] name The name of the engine pool.
|
77
|
+
# @param [String] silo The silo of the engine pool.
|
78
|
+
# @return [EnginePool] Engine pool configuration object.
|
79
|
+
#
|
80
|
+
def self.load(connection, name, scope = 'silo')
|
81
|
+
xml = %(<EnginePoolDetailsRequest session-id="#{connection.session_id}">)
|
82
|
+
xml << %(<EnginePool name="#{name}" scope="#{scope}"/>)
|
83
|
+
xml << '</EnginePoolDetailsRequest>'
|
84
|
+
|
85
|
+
r = connection.execute(xml, '1.2')
|
86
|
+
if r.success
|
87
|
+
r.res.elements.each('EnginePoolDetailsResponse/EnginePool') do |pool|
|
88
|
+
config = EnginePool.new(pool.attributes['name'],
|
89
|
+
pool.attributes['scope'],
|
90
|
+
pool.attributes['id'].to_i)
|
91
|
+
r.res.elements.each('EnginePoolDetailsResponse/EnginePool/EngineSummary') do |summary|
|
92
|
+
config.engines << EngineSummary.new(summary.attributes['id'].to_i,
|
93
|
+
summary.attributes['name'],
|
94
|
+
summary.attributes['address'],
|
95
|
+
summary.attributes['port'].to_i,
|
96
|
+
summary.attributes['status'],
|
97
|
+
summary.attributes['scope'])
|
98
|
+
end
|
99
|
+
return config
|
100
|
+
end
|
101
|
+
end
|
102
|
+
nil
|
103
|
+
end
|
104
|
+
|
105
|
+
# Save an engine pool to a security console.
|
106
|
+
#
|
107
|
+
# @param [Connection] connection Connection to console where site exists.
|
108
|
+
#
|
109
|
+
def save(connection)
|
110
|
+
request = @id > 0 ? 'EnginePoolUpdateRequest' : 'EnginePoolCreateRequest'
|
111
|
+
xml = %(<#{request} session-id="#{connection.session_id}">)
|
112
|
+
xml << '<EnginePool'
|
113
|
+
xml << %( id="#{@id}") if @id > 0
|
114
|
+
xml << %( name="#{@name}" scope="#{@scope}">)
|
115
|
+
@engines.each do |engine|
|
116
|
+
xml << %(<Engine name="#{engine.name}" />)
|
117
|
+
end
|
118
|
+
xml << '</EnginePool>'
|
119
|
+
xml << %(</#{request}>)
|
120
|
+
|
121
|
+
r = connection.execute(xml, '1.2')
|
122
|
+
if r.success
|
123
|
+
r.res.elements.each(request.gsub('Request', 'Response')) do |v|
|
124
|
+
return @id = v.attributes['id'].to_i
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Deletes an engine pool
|
130
|
+
#
|
131
|
+
# @param [Connection] connection Connection to console where site exists.
|
132
|
+
#
|
133
|
+
def delete(connection)
|
134
|
+
xml = %(<EnginePoolDeleteRequest session-id="#{connection.session_id}">)
|
135
|
+
xml << %(<EnginePool name="#{@name}" scope="#{@scope}" />)
|
136
|
+
xml << '</EnginePoolDeleteRequest>'
|
137
|
+
|
138
|
+
r = connection.execute(xml, '1.2')
|
139
|
+
r.success
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/nexpose/report.rb
CHANGED
@@ -6,6 +6,24 @@ module Nexpose
|
|
6
6
|
module NexposeAPI
|
7
7
|
include XMLUtils
|
8
8
|
|
9
|
+
# Provide a listing of all report definitions the user can access on the
|
10
|
+
# Security Console.
|
11
|
+
#
|
12
|
+
# @return [Array[ReportConfigSummary]] List of current report configuration.
|
13
|
+
#
|
14
|
+
def list_reports
|
15
|
+
r = execute(make_xml('ReportListingRequest'))
|
16
|
+
reports = []
|
17
|
+
if r.success
|
18
|
+
r.res.elements.each('//ReportConfigSummary') do |report|
|
19
|
+
reports << ReportConfigSummary.parse(report)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
reports
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :reports, :list_reports
|
26
|
+
|
9
27
|
# Generate a new report using the specified report definition.
|
10
28
|
def generate_report(report_id, wait = false)
|
11
29
|
xml = make_xml('ReportGenerateRequest', { 'report-id' => report_id })
|
@@ -43,65 +61,31 @@ module Nexpose
|
|
43
61
|
history.sort { |a, b| b.generated_on <=> a.generated_on }.first
|
44
62
|
end
|
45
63
|
|
46
|
-
# Delete a previously generated report definition.
|
47
|
-
# Also deletes any reports generated from that configuration.
|
48
|
-
def delete_report_config(report_config_id)
|
49
|
-
xml = make_xml('ReportDeleteRequest', { 'reportcfg-id' => report_config_id })
|
50
|
-
execute(xml).success
|
51
|
-
end
|
52
|
-
|
53
64
|
# Delete a previously generated report.
|
65
|
+
#
|
66
|
+
# @param [Fixnum] report_id ID of individual report to delete.
|
67
|
+
#
|
54
68
|
def delete_report(report_id)
|
55
69
|
xml = make_xml('ReportDeleteRequest', { 'report-id' => report_id })
|
56
70
|
execute(xml).success
|
57
71
|
end
|
58
72
|
|
59
|
-
#
|
60
|
-
#
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
end
|
68
|
-
end
|
69
|
-
templates
|
70
|
-
end
|
71
|
-
|
72
|
-
alias_method :report_templates, :report_template_listing
|
73
|
-
|
74
|
-
# Retrieve the configuration for a report template.
|
75
|
-
def get_report_template(template_id)
|
76
|
-
xml = make_xml('ReportTemplateConfigRequest', { 'template-id' => template_id })
|
77
|
-
ReportTemplate.parse(execute(xml))
|
78
|
-
end
|
79
|
-
|
80
|
-
# Provide a listing of all report definitions the user can access on the
|
81
|
-
# Security Console.
|
82
|
-
def report_listing
|
83
|
-
r = execute(make_xml('ReportListingRequest'))
|
84
|
-
reports = []
|
85
|
-
if r.success
|
86
|
-
r.res.elements.each('//ReportConfigSummary') do |report|
|
87
|
-
reports << ReportConfigSummary.parse(report)
|
88
|
-
end
|
89
|
-
end
|
90
|
-
reports
|
91
|
-
end
|
92
|
-
|
93
|
-
alias_method :reports, :report_listing
|
94
|
-
|
95
|
-
# Retrieve the configuration for a report definition.
|
96
|
-
def get_report_config(report_config_id)
|
97
|
-
xml = make_xml('ReportConfigRequest', { 'reportcfg-id' => report_config_id })
|
98
|
-
ReportConfig.parse(execute(xml))
|
73
|
+
# Delete a previously generated report definition.
|
74
|
+
# Also deletes any reports generated from that configuration.
|
75
|
+
#
|
76
|
+
# @param [Fixnum] report_config_id ID of the report configuration to remove.
|
77
|
+
#
|
78
|
+
def delete_report_config(report_config_id)
|
79
|
+
xml = make_xml('ReportDeleteRequest', { 'reportcfg-id' => report_config_id })
|
80
|
+
execute(xml).success
|
99
81
|
end
|
100
82
|
end
|
101
83
|
|
102
84
|
# Data object for report configuration information.
|
103
85
|
# Not meant for use in creating new configurations.
|
86
|
+
#
|
104
87
|
class ReportConfigSummary
|
88
|
+
|
105
89
|
# The report definition (config) ID.
|
106
90
|
attr_reader :config_id
|
107
91
|
# The ID of the report template.
|
@@ -137,8 +121,10 @@ module Nexpose
|
|
137
121
|
end
|
138
122
|
|
139
123
|
# Summary of a single report.
|
124
|
+
#
|
140
125
|
class ReportSummary
|
141
|
-
|
126
|
+
|
127
|
+
# The ID of the generated report.
|
142
128
|
attr_reader :id
|
143
129
|
# The report definition (configuration) ID.
|
144
130
|
attr_reader :config_id
|
@@ -193,6 +179,7 @@ module Nexpose
|
|
193
179
|
attr_accessor :format
|
194
180
|
attr_accessor :owner
|
195
181
|
attr_accessor :time_zone
|
182
|
+
attr_accessor :language
|
196
183
|
|
197
184
|
# Array of filters associated with this report.
|
198
185
|
attr_accessor :filters
|
@@ -220,16 +207,17 @@ module Nexpose
|
|
220
207
|
end
|
221
208
|
|
222
209
|
def to_xml
|
223
|
-
xml = %
|
224
|
-
xml << %
|
225
|
-
xml << %
|
210
|
+
xml = %(<AdhocReportConfig format='#{@format}' template-id='#{@template_id}')
|
211
|
+
xml << %( owner='#{@owner}') if @owner
|
212
|
+
xml << %( timezone='#{@time_zone}') if @time_zone
|
213
|
+
xml << %( language='#{@language}') if @language
|
226
214
|
xml << '>'
|
227
215
|
|
228
216
|
xml << '<Filters>'
|
229
217
|
@filters.each { |filter| xml << filter.to_xml }
|
230
218
|
xml << '</Filters>'
|
231
219
|
|
232
|
-
xml << %
|
220
|
+
xml << %(<Baseline compareTo='#{@baseline}' />) if @baseline
|
233
221
|
|
234
222
|
xml << '</AdhocReportConfig>'
|
235
223
|
end
|
@@ -244,7 +232,7 @@ module Nexpose
|
|
244
232
|
# @return Report in text format except for PDF, which returns binary data.
|
245
233
|
#
|
246
234
|
def generate(connection)
|
247
|
-
xml = %
|
235
|
+
xml = %(<ReportAdhocGenerateRequest session-id='#{connection.session_id}'>)
|
248
236
|
xml << to_xml
|
249
237
|
xml << '</ReportAdhocGenerateRequest>'
|
250
238
|
response = connection.execute(xml)
|
@@ -309,7 +297,8 @@ module Nexpose
|
|
309
297
|
|
310
298
|
# Retrieve the configuration for an existing report definition.
|
311
299
|
def self.load(connection, report_config_id)
|
312
|
-
connection.
|
300
|
+
xml = %(<ReportConfigRequest session-id='#{connection.session_id}' reportcfg-id='#{report_config_id}'/>)
|
301
|
+
ReportConfig.parse(connection.execute(xml))
|
313
302
|
end
|
314
303
|
|
315
304
|
alias_method :get, :load
|
@@ -319,7 +308,7 @@ module Nexpose
|
|
319
308
|
#
|
320
309
|
# Returns the new configuration.
|
321
310
|
def self.build(connection, site_id, site_name, type, format, generate_now = false)
|
322
|
-
name = %
|
311
|
+
name = %(#{site_name} #{type} report in #{format})
|
323
312
|
config = ReportConfig.new(name, type, format)
|
324
313
|
config.frequency = Frequency.new(true, false)
|
325
314
|
config.filters << Filter.new('site', site_id)
|
@@ -329,7 +318,7 @@ module Nexpose
|
|
329
318
|
|
330
319
|
# Save the configuration of this report definition.
|
331
320
|
def save(connection, generate_now = false)
|
332
|
-
xml = %
|
321
|
+
xml = %(<ReportSaveRequest session-id='#{connection.session_id}' generate-now='#{generate_now ? 1 : 0}'>)
|
333
322
|
xml << to_xml
|
334
323
|
xml << '</ReportSaveRequest>'
|
335
324
|
response = connection.execute(xml)
|
@@ -351,18 +340,22 @@ module Nexpose
|
|
351
340
|
end
|
352
341
|
|
353
342
|
def to_xml
|
354
|
-
xml = %
|
355
|
-
xml << %
|
343
|
+
xml = %(<ReportConfig format='#{@format}' id='#{@id}' name='#{@name}' template-id='#{@template_id}')
|
344
|
+
xml << %( owner='#{@owner}') if @owner
|
345
|
+
xml << %( timezone='#{@time_zone}') if @time_zone
|
346
|
+
xml << %( language='#{@language}') if @language
|
347
|
+
xml << '>'
|
348
|
+
xml << %(<description>#{@description}</description>) if @description
|
356
349
|
|
357
350
|
xml << '<Filters>'
|
358
351
|
@filters.each { |filter| xml << filter.to_xml }
|
359
352
|
xml << '</Filters>'
|
360
353
|
|
361
354
|
xml << '<Users>'
|
362
|
-
@users.each { |user| xml << %
|
355
|
+
@users.each { |user| xml << %(<user id='#{user}' />) }
|
363
356
|
xml << '</Users>'
|
364
357
|
|
365
|
-
xml << %
|
358
|
+
xml << %(<Baseline compareTo='#{@baseline}' />) if @baseline
|
366
359
|
xml << @frequency.to_xml if @frequency
|
367
360
|
xml << @delivery.to_xml if @delivery
|
368
361
|
xml << @db_export.to_xml if @db_export
|
@@ -412,7 +405,9 @@ module Nexpose
|
|
412
405
|
# or raw_xml. If the vuln-status filter is not included in the configuration,
|
413
406
|
# all the vulnerability test results (including invulnerable instances) are
|
414
407
|
# exported by default in csv and raw_xml reports.
|
408
|
+
#
|
415
409
|
class Filter
|
410
|
+
|
416
411
|
# The ID of the specific site, group, device, or scan.
|
417
412
|
# For scan, this can also be "last" for the most recently run scan.
|
418
413
|
# For vuln-status, the ID can have one of the following values:
|
@@ -430,7 +425,7 @@ module Nexpose
|
|
430
425
|
end
|
431
426
|
|
432
427
|
def to_xml
|
433
|
-
%
|
428
|
+
%(<filter id='#{@id}' type='#{@type}' />)
|
434
429
|
end
|
435
430
|
|
436
431
|
def self.parse(xml)
|
@@ -443,7 +438,9 @@ module Nexpose
|
|
443
438
|
end
|
444
439
|
|
445
440
|
# Data object associated with when a report is generated.
|
441
|
+
#
|
446
442
|
class Frequency
|
443
|
+
|
447
444
|
# Will the report be generated after a scan completes (true),
|
448
445
|
# or is it ad hoc/scheduled (false).
|
449
446
|
attr_accessor :after_scan
|
@@ -459,7 +456,7 @@ module Nexpose
|
|
459
456
|
end
|
460
457
|
|
461
458
|
def to_xml
|
462
|
-
xml = %
|
459
|
+
xml = %(<Generate after-scan='#{@after_scan ? 1 : 0}' schedule='#{@scheduled ? 1 : 0}'>)
|
463
460
|
xml << @schedule.to_xml if @schedule
|
464
461
|
xml << '</Generate>'
|
465
462
|
end
|
@@ -483,7 +480,9 @@ module Nexpose
|
|
483
480
|
end
|
484
481
|
|
485
482
|
# Data object for configuration of where a report is stored or delivered.
|
483
|
+
#
|
486
484
|
class Delivery
|
485
|
+
|
487
486
|
# Whether to store report on server.
|
488
487
|
attr_accessor :store_on_server
|
489
488
|
# Directory location to store report in (for non-default storage).
|
@@ -499,8 +498,8 @@ module Nexpose
|
|
499
498
|
|
500
499
|
def to_xml
|
501
500
|
xml = '<Delivery>'
|
502
|
-
xml << %
|
503
|
-
xml << %
|
501
|
+
xml << %(<Storage storeOnServer='#{@store_on_server ? 1 : 0}'>)
|
502
|
+
xml << %(<location>#{@location}</location>) if @location
|
504
503
|
xml << '</Storage>'
|
505
504
|
xml << @email.to_xml if @email
|
506
505
|
xml << '</Delivery>'
|
@@ -526,7 +525,9 @@ module Nexpose
|
|
526
525
|
end
|
527
526
|
|
528
527
|
# Configuration structure for database exporting of reports.
|
528
|
+
#
|
529
529
|
class DBExport
|
530
|
+
|
530
531
|
# The DB type to export to.
|
531
532
|
attr_accessor :type
|
532
533
|
# Credentials needed to export to the specified database.
|
@@ -540,10 +541,10 @@ module Nexpose
|
|
540
541
|
end
|
541
542
|
|
542
543
|
def to_xml
|
543
|
-
xml = %
|
544
|
+
xml = %(<DBExport type='#{@type}'>)
|
544
545
|
xml << @credentials.to_xml if @credentials
|
545
546
|
@parameters.each_pair do |name, value|
|
546
|
-
xml << %
|
547
|
+
xml << %(<param name='#{name}'>#{value}</param>)
|
547
548
|
end
|
548
549
|
xml << '</DBExport>'
|
549
550
|
end
|
@@ -566,7 +567,9 @@ module Nexpose
|
|
566
567
|
# The user_id, password and realm attributes should ONLY be used
|
567
568
|
# if a security blob cannot be generated and the data is being
|
568
569
|
# transmitted/stored using external encryption (e.g., HTTPS).
|
570
|
+
#
|
569
571
|
class ExportCredential
|
572
|
+
|
570
573
|
# Security blob for exporting to a database.
|
571
574
|
attr_accessor :credential
|
572
575
|
attr_accessor :user_id
|
@@ -580,9 +583,9 @@ module Nexpose
|
|
580
583
|
|
581
584
|
def to_xml
|
582
585
|
xml = '<credentials'
|
583
|
-
xml << %
|
584
|
-
xml << %
|
585
|
-
xml << %
|
586
|
+
xml << %( userid='#{@user_id}') if @user_id
|
587
|
+
xml << %( password='#{@password}') if @password
|
588
|
+
xml << %( realm='#{@realm}') if @realm
|
586
589
|
xml << '>'
|
587
590
|
xml << @credential if @credential
|
588
591
|
xml << '</credentials>'
|
@@ -600,213 +603,4 @@ module Nexpose
|
|
600
603
|
nil
|
601
604
|
end
|
602
605
|
end
|
603
|
-
|
604
|
-
# Data object for report template summary information.
|
605
|
-
# Not meant for use in creating new templates.
|
606
|
-
class ReportTemplateSummary
|
607
|
-
# The ID of the report template.
|
608
|
-
attr_reader :id
|
609
|
-
# The name of the report template.
|
610
|
-
attr_reader :name
|
611
|
-
# One of: data|document. With a data template, you can export
|
612
|
-
# comma-separated value (CSV) files with vulnerability-based data.
|
613
|
-
# With a document template, you can create PDF, RTF, HTML, or XML reports
|
614
|
-
# with asset-based information.
|
615
|
-
attr_reader :type
|
616
|
-
# The visibility (scope) of the report template. One of: global|silo
|
617
|
-
attr_reader :scope
|
618
|
-
# Whether the report template is built-in, and therefore cannot be modified.
|
619
|
-
attr_reader :built_in
|
620
|
-
# Description of the report template.
|
621
|
-
attr_reader :description
|
622
|
-
|
623
|
-
def initialize(id, name, type, scope, built_in, description)
|
624
|
-
@id = id
|
625
|
-
@name = name
|
626
|
-
@type = type
|
627
|
-
@scope = scope
|
628
|
-
@built_in = built_in
|
629
|
-
@description = description
|
630
|
-
end
|
631
|
-
|
632
|
-
def self.parse(xml)
|
633
|
-
description = nil
|
634
|
-
xml.elements.each('description') { |desc| description = desc.text }
|
635
|
-
ReportTemplateSummary.new(xml.attributes['id'],
|
636
|
-
xml.attributes['name'],
|
637
|
-
xml.attributes['type'],
|
638
|
-
xml.attributes['scope'],
|
639
|
-
xml.attributes['builtin'] == '1',
|
640
|
-
description)
|
641
|
-
end
|
642
|
-
end
|
643
|
-
|
644
|
-
# Definition object for a report template.
|
645
|
-
class ReportTemplate
|
646
|
-
# The ID of the report template.
|
647
|
-
attr_accessor :id
|
648
|
-
# The name of the report template.
|
649
|
-
attr_accessor :name
|
650
|
-
# With a data template, you can export comma-separated value (CSV) files
|
651
|
-
# with vulnerability-based data. With a document template, you can create
|
652
|
-
# PDF, RTF, HTML, or XML reports with asset-based information. When you
|
653
|
-
# retrieve a report template, the type will always be visible even though
|
654
|
-
# type is implied. When ReportTemplate is sent as a request, and the type
|
655
|
-
# attribute is not provided, the type attribute defaults to document,
|
656
|
-
# allowing for backward compatibility with existing API clients.
|
657
|
-
attr_accessor :type
|
658
|
-
# The visibility (scope) of the report template.
|
659
|
-
# One of: global|silo
|
660
|
-
attr_accessor :scope
|
661
|
-
# The report template is built-in, and cannot be modified.
|
662
|
-
attr_accessor :built_in
|
663
|
-
# Description of this report template.
|
664
|
-
attr_accessor :description
|
665
|
-
|
666
|
-
# Array of report sections.
|
667
|
-
attr_accessor :sections
|
668
|
-
# Map of report properties.
|
669
|
-
attr_accessor :properties
|
670
|
-
# Array of report attributes, in the order they will be present in a report.
|
671
|
-
attr_accessor :attributes
|
672
|
-
# Display asset names with IPs.
|
673
|
-
attr_accessor :show_device_names
|
674
|
-
|
675
|
-
def initialize(name, type = 'document', id = -1, scope = 'silo', built_in = false)
|
676
|
-
@name = name
|
677
|
-
@type = type
|
678
|
-
@id = id
|
679
|
-
@scope = scope
|
680
|
-
@built_in = built_in
|
681
|
-
|
682
|
-
@sections = []
|
683
|
-
@properties = {}
|
684
|
-
@attributes = []
|
685
|
-
@show_device_names = false
|
686
|
-
end
|
687
|
-
|
688
|
-
# Save the configuration for a report template.
|
689
|
-
def save(connection)
|
690
|
-
xml = %Q{<ReportTemplateSaveRequest session-id='#{connection.session_id}' scope='#{@scope}'>}
|
691
|
-
xml << to_xml
|
692
|
-
xml << '</ReportTemplateSaveRequest>'
|
693
|
-
response = connection.execute(xml)
|
694
|
-
if response.success
|
695
|
-
@id = response.attributes['template-id']
|
696
|
-
end
|
697
|
-
end
|
698
|
-
|
699
|
-
def delete(connection)
|
700
|
-
xml = %Q{<ReportTemplateDeleteRequest session-id='#{connection.session_id}' template-id='#{@id}'>}
|
701
|
-
xml << '</ReportTemplateDeleteRequest>'
|
702
|
-
response = connection.execute(xml)
|
703
|
-
if response.success
|
704
|
-
@id = response.attributes['template-id']
|
705
|
-
end
|
706
|
-
end
|
707
|
-
|
708
|
-
# Retrieve the configuration for a report template.
|
709
|
-
def self.load(connection, template_id)
|
710
|
-
connection.get_report_template(template_id)
|
711
|
-
end
|
712
|
-
|
713
|
-
alias_method :get, :load
|
714
|
-
|
715
|
-
include Sanitize
|
716
|
-
|
717
|
-
def to_xml
|
718
|
-
xml = %Q{<ReportTemplate id='#{@id}' name='#{@name}' type='#{@type}'}
|
719
|
-
xml << %Q{ scope='#{@scope}'} if @scope
|
720
|
-
xml << %Q{ builtin='#{@built_in}'} if @built_in
|
721
|
-
xml << '>'
|
722
|
-
xml << %Q{<description>#{@description}</description>} if @description
|
723
|
-
|
724
|
-
unless @attributes.empty?
|
725
|
-
xml << '<ReportAttributes>'
|
726
|
-
@attributes.each do |attr|
|
727
|
-
xml << %Q(<ReportAttribute name='#{attr}'/>)
|
728
|
-
end
|
729
|
-
xml << '</ReportAttributes>'
|
730
|
-
end
|
731
|
-
|
732
|
-
unless @sections.empty?
|
733
|
-
xml << '<ReportSections>'
|
734
|
-
properties.each_pair do |name, value|
|
735
|
-
xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
|
736
|
-
end
|
737
|
-
@sections.each { |section| xml << section.to_xml }
|
738
|
-
xml << '</ReportSections>'
|
739
|
-
end
|
740
|
-
|
741
|
-
xml << %Q{<Settings><showDeviceNames enabled='#{@show_device_names ? 1 : 0}' /></Settings>}
|
742
|
-
xml << '</ReportTemplate>'
|
743
|
-
end
|
744
|
-
|
745
|
-
def self.parse(xml)
|
746
|
-
xml.res.elements.each('//ReportTemplate') do |tmp|
|
747
|
-
template = ReportTemplate.new(tmp.attributes['name'],
|
748
|
-
tmp.attributes['type'],
|
749
|
-
tmp.attributes['id'],
|
750
|
-
tmp.attributes['scope'] || 'silo',
|
751
|
-
tmp.attributes['builtin'])
|
752
|
-
tmp.elements.each('//description') do |desc|
|
753
|
-
template.description = desc.text
|
754
|
-
end
|
755
|
-
|
756
|
-
tmp.elements.each('//ReportAttributes/ReportAttribute') do |attr|
|
757
|
-
template.attributes << attr.attributes['name']
|
758
|
-
end
|
759
|
-
|
760
|
-
tmp.elements.each('//ReportSections/property') do |property|
|
761
|
-
template.properties[property.attributes['name']] = property.text
|
762
|
-
end
|
763
|
-
|
764
|
-
tmp.elements.each('//ReportSection') do |section|
|
765
|
-
template.sections << Section.parse(section)
|
766
|
-
end
|
767
|
-
|
768
|
-
tmp.elements.each('//showDeviceNames') do |show|
|
769
|
-
template.show_device_names = show.attributes['enabled'] == '1'
|
770
|
-
end
|
771
|
-
|
772
|
-
return template
|
773
|
-
end
|
774
|
-
nil
|
775
|
-
end
|
776
|
-
end
|
777
|
-
|
778
|
-
# Section specific content to include in a report template.
|
779
|
-
class Section
|
780
|
-
# Name of the report section.
|
781
|
-
attr_accessor :name
|
782
|
-
# Map of properties specific to the report section.
|
783
|
-
attr_accessor :properties
|
784
|
-
|
785
|
-
def initialize(name)
|
786
|
-
@name = name
|
787
|
-
@properties = {}
|
788
|
-
end
|
789
|
-
|
790
|
-
include Sanitize
|
791
|
-
|
792
|
-
def to_xml
|
793
|
-
xml = %Q{<ReportSection name='#{@name}'>}
|
794
|
-
properties.each_pair do |name, value|
|
795
|
-
xml << %Q{<property name='#{name}'>#{replace_entities(value)}</property>}
|
796
|
-
end
|
797
|
-
xml << '</ReportSection>'
|
798
|
-
end
|
799
|
-
|
800
|
-
def self.parse(xml)
|
801
|
-
name = xml.attributes['name']
|
802
|
-
xml.elements.each("//ReportSection[@name='#{name}']") do |elem|
|
803
|
-
section = Section.new(name)
|
804
|
-
elem.elements.each("//ReportSection[@name='#{name}']/property") do |property|
|
805
|
-
section.properties[property.attributes['name']] = property.text
|
806
|
-
end
|
807
|
-
return section
|
808
|
-
end
|
809
|
-
nil
|
810
|
-
end
|
811
|
-
end
|
812
606
|
end
|