nexpose 0.2.8 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|