NexposeRunner 0.0.19b → 1.0.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/README.md +0 -0
- data/bin/scan +0 -0
- data/lib/NexposeRunner/version.rb +1 -2
- data/lib/nexpose-runner/command_line_arg_parser.rb +0 -0
- data/lib/nexpose-runner/constants.rb +1 -0
- data/lib/nexpose-runner/scan.rb +26 -10
- data/lib/nexpose-runner/scan_run_description.rb +0 -0
- data/spec/data/test_exclist.txt +2 -0
- data/spec/scan_spec.rb +57 -43
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1753280c807b7cbc9e9c3d2ce7926f544e455608
|
4
|
+
data.tar.gz: a4c4be6de7157c5a746469d0c9f878369e468328
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58d0eb323fd62798dd5fdfb5fd5e614f3524e6323f7d07e2a95104889b857cc1f0a15a18d184dd0db703eea92b776518b6f245afe53d0739e1152b5b64a95cbb
|
7
|
+
data.tar.gz: 2bd74be1ef45c64a473110a1c3b623675745d55a2e3577e97e540953c891e03475b88d34c06d3c6986ec1824c00e9ad19560874dd9b710390bd13feb280b57f0
|
data/README.md
CHANGED
File without changes
|
data/bin/scan
CHANGED
File without changes
|
File without changes
|
@@ -14,6 +14,7 @@ module CONSTANTS
|
|
14
14
|
VULNERABILITY_DETAIL_REPORT_NAME = 'nexpose-vulnerability-detail-report.csv'
|
15
15
|
SOFTWARE_REPORT_NAME = 'nexpose-software-report.csv'
|
16
16
|
POLICY_REPORT_NAME = 'nexpose-policy-report.csv'
|
17
|
+
MAX_RETRY_COUNT = 5
|
17
18
|
|
18
19
|
AUDIT_REPORT_FILE_NAME = 'nexpose-audit-report.html'
|
19
20
|
AUDIT_REPORT_NAME = 'audit-report'
|
data/lib/nexpose-runner/scan.rb
CHANGED
@@ -8,7 +8,7 @@ require 'nexpose-runner/scan_run_description'
|
|
8
8
|
|
9
9
|
module NexposeRunner
|
10
10
|
module Scan
|
11
|
-
|
11
|
+
|
12
12
|
def self.allow_vulnerabilities?(vulnerabilities, run_details)
|
13
13
|
vuln_array = []
|
14
14
|
exceptions_array = get_exceptions(run_details)
|
@@ -33,8 +33,13 @@ module NexposeRunner
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def self.get_exceptions(run_details)
|
36
|
-
|
37
|
-
|
36
|
+
path = "#{run_details.exceptions_list_url}"
|
37
|
+
uri = URI(path)
|
38
|
+
if path.include? "http:"
|
39
|
+
ex = Net::HTTP.get(uri).split("\n")
|
40
|
+
elsif (File.file?(path))
|
41
|
+
ex = File.read(path).split("\n")
|
42
|
+
end
|
38
43
|
ex
|
39
44
|
end
|
40
45
|
|
@@ -58,7 +63,7 @@ module NexposeRunner
|
|
58
63
|
puts "Scan complete for #{run_details.site_name}, Generating Vulnerability Report"
|
59
64
|
vulnerabilities = generate_report(CONSTANTS::VULNERABILITY_REPORT_QUERY, site.id, nsc)
|
60
65
|
generate_csv(vulnerabilities, CONSTANTS::VULNERABILITY_REPORT_NAME)
|
61
|
-
|
66
|
+
|
62
67
|
puts "Scan complete for #{run_details.site_name}, Generating Vulnerability Detail Report"
|
63
68
|
vuln_details = generate_report(CONSTANTS:: VULNERABILITY_DETAIL_REPORT_QUERY, site.id, nsc)
|
64
69
|
generate_csv(vuln_details, CONSTANTS::VULNERABILITY_DETAIL_REPORT_NAME)
|
@@ -73,7 +78,7 @@ module NexposeRunner
|
|
73
78
|
|
74
79
|
puts "Scan complete for #{run_details.site_name}, Generating Audit Report"
|
75
80
|
generate_template_report(nsc, site.id, CONSTANTS::AUDIT_REPORT_FILE_NAME, CONSTANTS::AUDIT_REPORT_NAME, CONSTANTS::AUDIT_REPORT_FORMAT)
|
76
|
-
|
81
|
+
|
77
82
|
puts "Scan complete for #{run_details.site_name}, Generating Xml Report"
|
78
83
|
generate_template_report(nsc, site.id, CONSTANTS::XML_REPORT_FILE_NAME, CONSTANTS::XML_REPORT_NAME, CONSTANTS::XML_REPORT_FORMAT)
|
79
84
|
|
@@ -83,7 +88,7 @@ module NexposeRunner
|
|
83
88
|
def self.verify_run(vulnerabilities, run_details)
|
84
89
|
|
85
90
|
if run_details.exceptions_list_url.to_s.empty? and vulnerabilities.count > 0
|
86
|
-
raise StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE
|
91
|
+
raise StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE
|
87
92
|
|
88
93
|
elsif vulnerabilities.count == 0
|
89
94
|
puts "No vulnerabilities found!"
|
@@ -98,13 +103,24 @@ module NexposeRunner
|
|
98
103
|
|
99
104
|
puts "Starting scan for #{run_details.site_name} using the #{run_details.scan_template_id} scan template"
|
100
105
|
scan = site.scan nsc
|
101
|
-
|
106
|
+
retry_count = 0
|
102
107
|
begin
|
103
108
|
sleep(3)
|
104
|
-
|
105
|
-
|
109
|
+
begin
|
110
|
+
stats = nsc.scan_statistics(scan.id)
|
111
|
+
rescue
|
112
|
+
if retry_count == CONSTANTS::MAX_RETRY_COUNT
|
113
|
+
raise
|
114
|
+
end
|
115
|
+
puts "Status Check failed, incrementing retry count to #{retry_count}"
|
116
|
+
retry_count = retry_count + 1
|
117
|
+
next
|
118
|
+
end
|
119
|
+
status = stats.status
|
106
120
|
puts "Current #{run_details.site_name} scan status: #{status.to_s} -- PENDING: #{stats.tasks.pending.to_s} ACTIVE: #{stats.tasks.active.to_s} COMPLETED #{stats.tasks.completed.to_s}"
|
121
|
+
retry_count = 0
|
107
122
|
end while status == Nexpose::Scan::Status::RUNNING
|
123
|
+
|
108
124
|
end
|
109
125
|
|
110
126
|
def self.create_site(run_details, nsc)
|
@@ -118,7 +134,7 @@ module NexposeRunner
|
|
118
134
|
end
|
119
135
|
site.save nsc
|
120
136
|
puts "Created site #{run_details.site_name} successfully with the following host(s) #{run_details.ip_addresses.join(', ')}"
|
121
|
-
|
137
|
+
|
122
138
|
site
|
123
139
|
end
|
124
140
|
|
File without changes
|
data/spec/scan_spec.rb
CHANGED
@@ -31,8 +31,8 @@ describe 'nexpose-runner' do
|
|
31
31
|
@mock_vuln_report = 'ip_address,title,date_published,severity,summary,fix
|
32
32
|
10.5.0.15,Database Open Access,2010-01-01,Severe,Restrict database access,<p><p>Configure the database server to only allow access to trusted systems. For example, the PCI DSS standard requires you to place the database in an internal network zone, segregated from the DMZ </p></p>
|
33
33
|
10.5.0.15.180,MySQL Obsolete Version,2007-07-25,Critical,Upgrade to the latest version of Oracle MySQL,<p>Download and apply the upgrade from: <a href=http://dev.mysql.com/downloads/mysql>http://dev.mysql.com/downloads/mysql</a></p>'.chomp
|
34
|
-
@mock_exceptions = "Database Open Access\nMySQL Obsolete Version"
|
35
|
-
|
34
|
+
@mock_exceptions = "Database Open Access\nMySQL Obsolete Version"
|
35
|
+
|
36
36
|
@mock_vuln_detail_report = 'stuff'.chomp
|
37
37
|
|
38
38
|
@mock_software_report = 'name,ip_address,host_name,description,description,vendor,name,version
|
@@ -89,40 +89,40 @@ describe 'nexpose-runner' do
|
|
89
89
|
it 'should throw an error if no connection url is passed' do
|
90
90
|
options = @options.clone
|
91
91
|
options['connection_url'] = nil
|
92
|
-
expect {
|
93
|
-
NexposeRunner::Scan.start(options)
|
92
|
+
expect {
|
93
|
+
NexposeRunner::Scan.start(options)
|
94
94
|
}.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me the URL/IP address to your Nexpose Server')
|
95
95
|
end
|
96
96
|
|
97
97
|
it 'should throw an error if no username is passed' do
|
98
98
|
options = @options.clone
|
99
99
|
options['username'] = nil
|
100
|
-
expect {
|
101
|
-
NexposeRunner::Scan.start(options)
|
100
|
+
expect {
|
101
|
+
NexposeRunner::Scan.start(options)
|
102
102
|
}.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a username to login to Nexpose with')
|
103
103
|
end
|
104
104
|
|
105
105
|
it 'should throw an error if no password is passed' do
|
106
106
|
options = @options.clone
|
107
107
|
options['password'] = nil
|
108
|
-
expect {
|
109
|
-
NexposeRunner::Scan.start(options)
|
108
|
+
expect {
|
109
|
+
NexposeRunner::Scan.start(options)
|
110
110
|
}.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a password to login to Nexpose with')
|
111
111
|
end
|
112
112
|
|
113
113
|
it 'should throw an error if no site name is passed' do
|
114
114
|
options = @options.clone
|
115
115
|
options['site_name'] = nil
|
116
|
-
expect {
|
117
|
-
NexposeRunner::Scan.start(options)
|
116
|
+
expect {
|
117
|
+
NexposeRunner::Scan.start(options)
|
118
118
|
}.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a Nexpose Site Name')
|
119
119
|
end
|
120
120
|
|
121
121
|
it 'should throw an error if no ip address is passed' do
|
122
122
|
options = @options.clone
|
123
123
|
options['ip_addresses'] = '';
|
124
|
-
expect {
|
125
|
-
NexposeRunner::Scan.start(options)
|
124
|
+
expect {
|
125
|
+
NexposeRunner::Scan.start(options)
|
126
126
|
}.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me an IP Address to scan')
|
127
127
|
end
|
128
128
|
|
@@ -136,9 +136,9 @@ describe 'nexpose-runner' do
|
|
136
136
|
|
137
137
|
it 'should use 3780 as default if port is empty string' do
|
138
138
|
expect(Nexpose::Connection).to receive(:new)
|
139
|
-
.with(@options['connection_url'],
|
140
|
-
@options['username'],
|
141
|
-
@options['password'],
|
139
|
+
.with(@options['connection_url'],
|
140
|
+
@options['username'],
|
141
|
+
@options['password'],
|
142
142
|
'3780')
|
143
143
|
.and_return(@mock_nexpose_client)
|
144
144
|
|
@@ -181,37 +181,37 @@ describe 'nexpose-runner' do
|
|
181
181
|
describe 'wait for the Nexpose Scan to complete' do
|
182
182
|
it 'should call to check the status of the scan' do
|
183
183
|
expect(@mock_nexpose_client).to receive(:scan_statistics).with(@mock_scan_id)
|
184
|
-
|
184
|
+
|
185
185
|
NexposeRunner::Scan.start(@options)
|
186
186
|
end
|
187
|
-
|
187
|
+
|
188
188
|
it 'should call to check the status until it is not running' do
|
189
189
|
expect(@mock_scan_summary).to receive(:status)
|
190
190
|
.and_return(Nexpose::Scan::Status::RUNNING)
|
191
191
|
.exactly(3).times
|
192
192
|
.ordered
|
193
|
-
|
193
|
+
|
194
194
|
expect(@mock_scan_summary).to receive(:status)
|
195
195
|
.and_return(Nexpose::Scan::Status::FINISHED)
|
196
196
|
.once
|
197
197
|
.ordered
|
198
|
-
|
198
|
+
|
199
199
|
NexposeRunner::Scan.start(@options)
|
200
200
|
end
|
201
|
-
|
201
|
+
|
202
202
|
it 'should sleep for 3 seconds if the status is still running' do
|
203
203
|
expect(@mock_scan_summary).to receive(:status)
|
204
204
|
.and_return(Nexpose::Scan::Status::RUNNING)
|
205
205
|
.exactly(3).times
|
206
206
|
.ordered
|
207
|
-
|
207
|
+
|
208
208
|
expect(@mock_scan_summary).to receive(:status)
|
209
209
|
.and_return(Nexpose::Scan::Status::FINISHED)
|
210
210
|
.once
|
211
211
|
.ordered
|
212
212
|
|
213
213
|
expect(NexposeRunner::Scan).to receive(:sleep).with(3).exactly(4).times
|
214
|
-
|
214
|
+
|
215
215
|
NexposeRunner::Scan.start(@options)
|
216
216
|
end
|
217
217
|
end
|
@@ -230,16 +230,16 @@ describe 'nexpose-runner' do
|
|
230
230
|
expect(Nexpose::AdhocReportConfig).to receive(:new)
|
231
231
|
.with(CONSTANTS::AUDIT_REPORT_NAME, CONSTANTS::AUDIT_REPORT_FORMAT, @mock_site_id)
|
232
232
|
.and_return(@mock_report)
|
233
|
-
|
233
|
+
|
234
234
|
expect(Nexpose::AdhocReportConfig).to receive(:new)
|
235
235
|
.with(CONSTANTS::XML_REPORT_NAME, CONSTANTS::XML_REPORT_FORMAT, @mock_site_id)
|
236
236
|
.and_return(@mock_report)
|
237
237
|
|
238
238
|
expect_template_report_to_be_called_with(CONSTANTS::AUDIT_REPORT_FILE_NAME)
|
239
239
|
expect_template_report_to_be_called_with(CONSTANTS::XML_REPORT_FILE_NAME)
|
240
|
-
|
241
|
-
expect {
|
242
|
-
NexposeRunner::Scan.start(@options)
|
240
|
+
|
241
|
+
expect {
|
242
|
+
NexposeRunner::Scan.start(@options)
|
243
243
|
}.to raise_error(StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE)
|
244
244
|
end
|
245
245
|
end
|
@@ -247,19 +247,30 @@ describe 'nexpose-runner' do
|
|
247
247
|
it 'should throw exception if vulnerability exists' do
|
248
248
|
expect_report_to_be_called_with(CONSTANTS::VULNERABILITY_REPORT_NAME, CONSTANTS::VULNERABILITY_REPORT_QUERY, @mock_vuln_report)
|
249
249
|
|
250
|
-
expect {
|
251
|
-
NexposeRunner::Scan.start(@options)
|
250
|
+
expect {
|
251
|
+
NexposeRunner::Scan.start(@options)
|
252
252
|
}.to raise_error(StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE)
|
253
253
|
end
|
254
254
|
|
255
255
|
it 'should not throw exception if exceptions exist for all vulnerabilities' do
|
256
256
|
expect_report_to_be_called_with(CONSTANTS::VULNERABILITY_REPORT_NAME, CONSTANTS::VULNERABILITY_REPORT_QUERY, @mock_vuln_report)
|
257
|
-
|
257
|
+
|
258
258
|
options = @options.clone
|
259
259
|
options['exceptions_list_url'] = 'http://google.com'
|
260
|
-
|
260
|
+
|
261
261
|
expect_exceptions_to_be_called_with(options['exceptions_list_url'])
|
262
|
-
|
262
|
+
|
263
|
+
NexposeRunner::Scan.start(options)
|
264
|
+
end
|
265
|
+
|
266
|
+
it 'should not throw exception if exceptions exist from a file for all vulnerabilities' do
|
267
|
+
expect_report_to_be_called_with(CONSTANTS::VULNERABILITY_REPORT_NAME, CONSTANTS::VULNERABILITY_REPORT_QUERY, @mock_vuln_report)
|
268
|
+
|
269
|
+
options = @options.clone
|
270
|
+
options['exceptions_list_url'] = 'spec/data/test_exclist.txt'
|
271
|
+
|
272
|
+
expect_exceptions_to_be_called_with_file(options['exceptions_list_url'])
|
273
|
+
|
263
274
|
NexposeRunner::Scan.start(options)
|
264
275
|
end
|
265
276
|
end
|
@@ -275,6 +286,10 @@ def expect_exceptions_to_be_called_with(exceptions_list_url)
|
|
275
286
|
.with(uri).and_return(@mock_exceptions)
|
276
287
|
end
|
277
288
|
|
289
|
+
def expect_exceptions_to_be_called_with_file(exceptions_list_url)
|
290
|
+
expect(File.read(exceptions_list_url)).to eq "Database Open Access\nMySQL Obsolete Version\n"
|
291
|
+
end
|
292
|
+
|
278
293
|
def expect_report_to_be_called_with(report_name, report_query, report_response)
|
279
294
|
expect(@mock_report).to receive(:add_filter)
|
280
295
|
.with('version', '1.3.0')
|
@@ -311,29 +326,29 @@ def get_mock_nexpose_client
|
|
311
326
|
|
312
327
|
allow(Nexpose::Connection).to receive(:new)
|
313
328
|
.and_return(mock_nexpose_client)
|
314
|
-
|
329
|
+
|
315
330
|
allow(mock_nexpose_client).to receive(:make_xml)
|
316
331
|
.with(any_args)
|
317
332
|
.and_return(xml)
|
318
|
-
|
333
|
+
|
319
334
|
allow(mock_nexpose_client).to receive(:make_xml)
|
320
335
|
.with(any_args)
|
321
336
|
.and_return(xml)
|
322
|
-
|
337
|
+
|
323
338
|
allow(mock_nexpose_client).to receive(:filter)
|
324
339
|
.with(any_args)
|
325
|
-
.and_return({})
|
326
|
-
|
340
|
+
.and_return({})
|
341
|
+
|
327
342
|
allow(mock_nexpose_client).to receive(:execute)
|
328
343
|
.with(any_args)
|
329
344
|
.and_return(mock_api_request)
|
330
|
-
|
345
|
+
|
331
346
|
allow(mock_api_request).to receive(:success)
|
332
347
|
.and_return(false) #this is just to shut up the underlying api.
|
333
|
-
|
348
|
+
|
334
349
|
allow(mock_api_request).to receive(:attributes)
|
335
|
-
.and_return(xml)
|
336
|
-
|
350
|
+
.and_return(xml)
|
351
|
+
|
337
352
|
mock_nexpose_client
|
338
353
|
end
|
339
354
|
|
@@ -345,9 +360,9 @@ def get_mock_scan_summary
|
|
345
360
|
allow(mock_scan_summary).to receive(:tasks).and_return(tasks)
|
346
361
|
|
347
362
|
allow(mock_scan_summary).to receive(:status).and_return(
|
348
|
-
Nexpose::Scan::Status::RUNNING,
|
349
363
|
Nexpose::Scan::Status::RUNNING,
|
350
|
-
|
364
|
+
Nexpose::Scan::Status::RUNNING,
|
365
|
+
Nexpose::Scan::Status::RUNNING,
|
351
366
|
Nexpose::Scan::Status::FINISHED)
|
352
367
|
mock_scan_summary
|
353
368
|
end
|
@@ -411,4 +426,3 @@ def get_mock_scan
|
|
411
426
|
allow(mock_scan).to receive(:id).and_return(@mock_scan_id)
|
412
427
|
mock_scan
|
413
428
|
end
|
414
|
-
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: NexposeRunner
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Gibson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-12-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nexpose
|
@@ -91,6 +91,7 @@ files:
|
|
91
91
|
- lib/nexpose-runner/constants.rb
|
92
92
|
- lib/nexpose-runner/scan.rb
|
93
93
|
- lib/nexpose-runner/scan_run_description.rb
|
94
|
+
- spec/data/test_exclist.txt
|
94
95
|
- spec/scan_config_spec.rb
|
95
96
|
- spec/scan_spec.rb
|
96
97
|
- spec/spec_helper.rb
|
@@ -109,12 +110,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
109
110
|
version: '0'
|
110
111
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
112
|
requirements:
|
112
|
-
- - "
|
113
|
+
- - ">="
|
113
114
|
- !ruby/object:Gem::Version
|
114
|
-
version:
|
115
|
+
version: '0'
|
115
116
|
requirements: []
|
116
117
|
rubyforge_project:
|
117
|
-
rubygems_version: 2.5.2
|
118
|
+
rubygems_version: 2.4.5.2
|
118
119
|
signing_key:
|
119
120
|
specification_version: 4
|
120
121
|
summary: This is a gem that provides the ability to create a new site, add an IP to
|
@@ -122,6 +123,7 @@ summary: This is a gem that provides the ability to create a new site, add an IP
|
|
122
123
|
and finally produce a reports for vulnerabilities, installed software, and policy
|
123
124
|
compliance.
|
124
125
|
test_files:
|
126
|
+
- spec/data/test_exclist.txt
|
125
127
|
- spec/scan_config_spec.rb
|
126
128
|
- spec/scan_spec.rb
|
127
129
|
- spec/spec_helper.rb
|