NexposeRunner 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/.travis.yml +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/NexposeRunner.gemspec +26 -0
- data/README.md +42 -0
- data/Rakefile +6 -0
- data/bin/scan +6 -0
- data/lib/NexposeRunner/version.rb +4 -0
- data/lib/NexposeRunner.rb +5 -0
- data/lib/nexpose-runner/constants.rb +64 -0
- data/lib/nexpose-runner/scan.rb +91 -0
- data/lib/nexpose-runner/scan_run_description.rb +36 -0
- data/spec/scan_spec.rb +279 -0
- data/spec/spec_helper.rb +9 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
NzBmMTJkNzgzNDFmNDgzZDY1ZmNmZDdmZGIyM2I4Mzk4ZWU2NjcxNQ==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
MTM0OTI1YWVlZWZiZGIzZTBlYjE2OWE5YzE2ZWYwODA3YWQ2M2NjYg==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
ZDE3NjZkNjljNTNmZTkzMTQ0ODA4MDcyZjZmN2E5M2FhNGQyMDFiODdhN2Zm
|
10
|
+
OWFhMjQ3ZTMxYmJjN2ZhMzc5ZDZjNDcxOTRiNTQ2Y2M2NTczMTg1ZDk5Y2Zm
|
11
|
+
NjI1NzA3NzRiZGVjYjQ5MGVlOWM5MzZmMzE3NDg4NmY1MzllMjU=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
NGM5NDY2MjA2ZTE1NzMxNDFkYmM0MjE2NDdhNzQwNjc1MjI5NjM3OWE0ZmRj
|
14
|
+
YWQxNDUxODZmN2NiZDcwNDVjMzczMjVmMTI1NjEzODA2YWVkMzkzMGNjNDk0
|
15
|
+
ZmRiMGE0Mjg1NDM5MDZhYjQ3ZjY2Zjc4MTAyY2QzZDk3NDc4MWI=
|
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
23
|
+
.idea/
|
data/.rspec
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
language: ruby
|
2
|
+
rvm:
|
3
|
+
- 2.0.0
|
4
|
+
- 1.9.3
|
5
|
+
- jruby-19mode
|
6
|
+
- rbx-2
|
7
|
+
deploy:
|
8
|
+
provider: rubygems
|
9
|
+
api_key:
|
10
|
+
secure: TPDSN4fEjEmyZdNz9kEtzTxqR45q3IZPjj5Zw/2TEoYjQEAor/reMvc0x5Zz2OWFLUTw5P7cF7SHepNR2KNFJ0JLs4XYUPRdxryTziDktQ8Q6WDuq892fhuo9cLRAevL8KyAUlvyZiYRqfw6JefvxGXZoGfwCMlXerNX7sh/qig=
|
11
|
+
gem: nexpose-runner
|
12
|
+
on:
|
13
|
+
tags: true
|
14
|
+
repo: amngibson/nexpose-runner
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Nathan Gibson
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'NexposeRunner/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'NexposeRunner'
|
8
|
+
spec.version = NexposeRunner::VERSION
|
9
|
+
spec.authors = ['Nathan Gibson']
|
10
|
+
spec.email = ['amngibson@gmail.com']
|
11
|
+
spec.summary = 'This is a gem that provides the ability to create a new site, add an IP to the site, and perform a scan against the site using a defined/passed scan template, and finally produce a reports for vulnerabilities, installed software, and policy compliance.'
|
12
|
+
spec.description = ''
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = %w(scan)
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_dependency 'nexpose', '0.8.3'
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
24
|
+
spec.add_development_dependency 'rake'
|
25
|
+
spec.add_development_dependency 'rspec', '3.0.0'
|
26
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# NexposeRunner::Scan
|
2
|
+
|
3
|
+
This is a ruby gem that basically wraps the nexpose-client gem. It was primarily created to automate scanning and reporting of dynamic hosts.
|
4
|
+
|
5
|
+
This gem will make a nexpose server connection, create a new site, initiate a scan against the assets in the site, and generate a vulnerability report, software report, and policy compliance report.
|
6
|
+
|
7
|
+
Basically this gem allows you to attach Nexpose to your Continuous Delivery/Continuous Integration pipeline. Though it can be used for other purposes.
|
8
|
+
|
9
|
+
At the end of the scan it will generate 3 csv reports and save them in the directory where the script was executed from. It will also raise an exception if a vulnerbaility is detected. This is used to break the Continuous Delivery/Continuous integration build.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
gem 'nexpose-runner'
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install nexpose-runner
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
This gem allows you to specify the Nexpose Server URL, Nexpose Username, Nexpose Password, Nexpose Server Port (optional, defaults to 3780), Site Name, Target IP Address, and a Scan Template.
|
28
|
+
|
29
|
+
|
30
|
+
$ scan "connection_url" "username" "password" "port" "site_name" "ip_address" "scan_template"
|
31
|
+
|
32
|
+
EXAMPLE:
|
33
|
+
|
34
|
+
$ scan "http://test.connection" "rapid7" "password" "3780" "my_cool_software_build-28" "10.5.0.15" "full-audit-widget-corp"
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
1. Fork it ( https://github.com/[my-github-username]/nexpose-scan/fork )
|
39
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
40
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
41
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
42
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/bin/scan
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
module CONSTANTS
|
2
|
+
REQUIRED_CONNECTION_URL_MESSAGE = 'OOPS! Looks like you forgot to give me the URL/IP address to your Nexpose Server'
|
3
|
+
REQUIRED_USERNAME_MESSAGE = 'OOPS! Looks like you forgot to give me a username to login to Nexpose with'
|
4
|
+
REQUIRED_PASSWORD_MESSAGE = 'OOPS! Looks like you forgot to give me a password to login to Nexpose with'
|
5
|
+
REQUIRED_SITE_NAME_MESSAGE = 'OOPS! Looks like you forgot to give me a Nexpose Site Name'
|
6
|
+
REQUIRED_IP_ADDRESS_MESSAGE = 'OOPS! Looks like you forgot to give me an IP Address to scan'
|
7
|
+
REQUIRED_SCAN_TEMPLATE_MESSAGE = 'OOPS! Looks like you forgot to give me a Scan Template to use'
|
8
|
+
VULNERABILITY_FOUND_MESSAGE = '---------All YOUR BASE ARE BELONG TO US---------------\nVulnerabilities were found, breaking build'
|
9
|
+
DEFAULT_PORT = '3780'
|
10
|
+
VULNERABILITY_REPORT_NAME = 'nexpose-vulnerability-report.csv'
|
11
|
+
SOFTWARE_REPORT_NAME = 'nexpose-software-report.csv'
|
12
|
+
POLICY_REPORT_NAME = 'nexpose-policy-report.csv'
|
13
|
+
|
14
|
+
VULNERABILITY_REPORT_QUERY = 'SELECT DISTINCT
|
15
|
+
ip_address,
|
16
|
+
title,
|
17
|
+
date_published,
|
18
|
+
severity,
|
19
|
+
summary,
|
20
|
+
fix
|
21
|
+
FROM fact_asset_scan_vulnerability_finding
|
22
|
+
JOIN dim_asset USING (asset_id)
|
23
|
+
JOIN dim_vulnerability USING (vulnerability_id)
|
24
|
+
JOIN dim_vulnerability_solution USING (vulnerability_id)
|
25
|
+
JOIN dim_solution_highest_supercedence USING (solution_id)
|
26
|
+
JOIN dim_solution ds ON superceding_solution_id = ds.solution_id'
|
27
|
+
|
28
|
+
SOFTWARE_REPORT_QUERY = 'SELECT
|
29
|
+
dsi.name,
|
30
|
+
da.ip_address,
|
31
|
+
da.host_name,
|
32
|
+
dos.description,
|
33
|
+
dht.description,
|
34
|
+
ds.vendor,
|
35
|
+
ds.name,
|
36
|
+
ds.version
|
37
|
+
FROM dim_asset da
|
38
|
+
JOIN dim_operating_system dos USING (operating_system_id)
|
39
|
+
JOIN dim_host_type dht USING (host_type_id)
|
40
|
+
JOIN dim_asset_software das USING (asset_id)
|
41
|
+
JOIN dim_software ds USING (software_id)
|
42
|
+
JOIN dim_site_asset dsa USING (asset_id)
|
43
|
+
JOIN dim_site dsi USING (site_id)
|
44
|
+
ORDER BY
|
45
|
+
da.ip_address,
|
46
|
+
ds.vendor,
|
47
|
+
ds.name'
|
48
|
+
|
49
|
+
POLICY_REPORT_QUERY = 'SELECT
|
50
|
+
fapr.compliance,
|
51
|
+
dpr.title,
|
52
|
+
dpr.description,
|
53
|
+
da.ip_address,
|
54
|
+
dp.title,
|
55
|
+
dp.benchmark_name,
|
56
|
+
dp.category,
|
57
|
+
dpr.scope,
|
58
|
+
fapr.proof
|
59
|
+
FROM fact_asset_policy_rule fapr
|
60
|
+
LEFT JOIN dim_policy dp on dp.policy_id = fapr.policy_id
|
61
|
+
LEFT JOIN dim_policy_rule dpr on dpr.policy_id = fapr.policy_id and fapr.rule_id = dpr.rule_id
|
62
|
+
LEFT JOIN dim_asset da on da.asset_id = fapr.asset_id
|
63
|
+
ORDER BY da.ip_address'
|
64
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require 'nexpose'
|
2
|
+
require 'csv'
|
3
|
+
require 'nexpose-runner/constants'
|
4
|
+
require 'nexpose-runner/scan_run_description'
|
5
|
+
|
6
|
+
|
7
|
+
module NexposeRunner
|
8
|
+
module Scan
|
9
|
+
def Scan.start(connection_url, username, password, port, site_name, ip_address, scan_template)
|
10
|
+
|
11
|
+
run_details = ScanRunDescription.new connection_url, username, password, port, site_name, ip_address, scan_template
|
12
|
+
run_details.verify
|
13
|
+
|
14
|
+
nsc = get_new_nexpose_connection(run_details)
|
15
|
+
|
16
|
+
site = create_site(run_details, nsc)
|
17
|
+
|
18
|
+
start_scan(nsc, site, run_details)
|
19
|
+
|
20
|
+
reports = generate_reports(nsc, site, run_details)
|
21
|
+
|
22
|
+
verify_run(reports[0])
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.generate_reports(nsc, site, run_details)
|
26
|
+
puts "Scan complete for #{run_details.site_name}, Generating Vulnerability Report"
|
27
|
+
vulnerbilities = generate_report(CONSTANTS::VULNERABILITY_REPORT_QUERY, site.id, nsc)
|
28
|
+
generate_csv(vulnerbilities, CONSTANTS::VULNERABILITY_REPORT_NAME)
|
29
|
+
|
30
|
+
puts "Scan complete for #{run_details.site_name}, Generating Software Report"
|
31
|
+
software = generate_report(CONSTANTS::SOFTWARE_REPORT_QUERY, site.id, nsc)
|
32
|
+
generate_csv(software, CONSTANTS::SOFTWARE_REPORT_NAME)
|
33
|
+
|
34
|
+
puts "Scan complete for #{run_details.site_name}, Generating Policy Report"
|
35
|
+
policies = generate_report(CONSTANTS::POLICY_REPORT_QUERY, site.id, nsc)
|
36
|
+
generate_csv(policies, CONSTANTS::POLICY_REPORT_NAME)
|
37
|
+
|
38
|
+
[vulnerbilities, software, policies]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.verify_run(vulnerabilities)
|
42
|
+
raise StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE if vulnerabilities.count > 0
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.start_scan(nsc, site, run_details)
|
46
|
+
|
47
|
+
puts "Starting scan for #{run_details.site_name} using the #{run_details.scan_template} scan template"
|
48
|
+
scan = site.scan nsc
|
49
|
+
|
50
|
+
begin
|
51
|
+
sleep(3)
|
52
|
+
status = nsc.scan_status(scan.id)
|
53
|
+
puts "Current #{run_details.site_name} scan status: #{status.to_s}"
|
54
|
+
end while status == Nexpose::Scan::Status::RUNNING
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.create_site(run_details, nsc)
|
58
|
+
puts "Creating a nexpose site named #{run_details.site_name}"
|
59
|
+
site = Nexpose::Site.new run_details.site_name, run_details.scan_template
|
60
|
+
site.add_ip run_details.ip_address
|
61
|
+
site.save nsc
|
62
|
+
puts "Created site #{run_details.site_name} successfully with the following host #{run_details.ip_address}"
|
63
|
+
site
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.get_new_nexpose_connection(run_details)
|
67
|
+
nsc = Nexpose::Connection.new run_details.connection_url, run_details.username, run_details.password, run_details.port
|
68
|
+
nsc.login
|
69
|
+
puts 'Successfully logged into the Nexpose Server'
|
70
|
+
nsc
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.generate_report(sql, site, nsc)
|
74
|
+
report = Nexpose::AdhocReportConfig.new(nil, 'sql')
|
75
|
+
report.add_filter('version', '1.3.0')
|
76
|
+
report.add_filter('query', sql)
|
77
|
+
report.add_filter('site', site)
|
78
|
+
report_output = report.generate(nsc)
|
79
|
+
CSV.parse(report_output.chomp, {:headers => :first_row})
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.generate_csv(csv_output, name)
|
83
|
+
CSV.open(name, 'w') do |csv_file|
|
84
|
+
csv_file << csv_output.headers
|
85
|
+
csv_output.each do |row|
|
86
|
+
csv_file << row
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class ScanRunDescription
|
2
|
+
attr_accessor :connection_url, :username, :password, :port, :site_name, :ip_address, :scan_template
|
3
|
+
@@port_value = ''
|
4
|
+
|
5
|
+
|
6
|
+
def initialize(connection_url, username, password, port, site_name, ip_address, scan_template)
|
7
|
+
self.connection_url = connection_url
|
8
|
+
self.username = username
|
9
|
+
self.password = password
|
10
|
+
@@port_value = port
|
11
|
+
self.site_name = site_name
|
12
|
+
self.ip_address = ip_address
|
13
|
+
self.scan_template = scan_template
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify
|
17
|
+
raise StandardError, CONSTANTS::REQUIRED_CONNECTION_URL_MESSAGE if connection_url.nil? || connection_url.empty?
|
18
|
+
raise StandardError, CONSTANTS::REQUIRED_USERNAME_MESSAGE if username.nil? || username.empty?
|
19
|
+
raise StandardError, CONSTANTS::REQUIRED_PASSWORD_MESSAGE if password.nil? || password.empty?
|
20
|
+
raise StandardError, CONSTANTS::REQUIRED_SITE_NAME_MESSAGE if site_name.nil? || site_name.empty?
|
21
|
+
raise StandardError, CONSTANTS::REQUIRED_IP_ADDRESS_MESSAGE if ip_address.nil? || ip_address.empty?
|
22
|
+
raise StandardError, CONSTANTS::REQUIRED_SCAN_TEMPLATE_MESSAGE if scan_template.nil? || scan_template.empty?
|
23
|
+
end
|
24
|
+
|
25
|
+
def port=(value)
|
26
|
+
@@port_value = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def port
|
30
|
+
get_value(@@port_value, CONSTANTS::DEFAULT_PORT)
|
31
|
+
end
|
32
|
+
|
33
|
+
def get_value(value_to_check, default)
|
34
|
+
(value_to_check.nil? || value_to_check.empty?) ? default : value_to_check
|
35
|
+
end
|
36
|
+
end
|
data/spec/scan_spec.rb
ADDED
@@ -0,0 +1,279 @@
|
|
1
|
+
require 'nexpose-runner/scan'
|
2
|
+
require 'nexpose-runner/constants'
|
3
|
+
|
4
|
+
describe 'nexpose-runner' do
|
5
|
+
before(:each) do
|
6
|
+
allow(NexposeRunner::Scan).to receive(:sleep)
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'scan' do
|
10
|
+
before(:each) do
|
11
|
+
@expected_connection = 'http://test.connection'
|
12
|
+
@expected_username = 'rapid7'
|
13
|
+
@expected_password = 'password'
|
14
|
+
@expected_port = '3781'
|
15
|
+
@expected_site_name = 'my_cool_software_build-28'
|
16
|
+
@expected_ip = '10.5.0.15'
|
17
|
+
@expected_scan_template = 'full-audit-widget-corp'
|
18
|
+
@mock_scan_id = '12'
|
19
|
+
@mock_site_id = '1'
|
20
|
+
|
21
|
+
@mock_no_vuln_report = 'ip_address,title,date_published,severity,summary,fix'
|
22
|
+
@mock_vuln_report = 'ip_address,title,date_published,severity,summary,fix
|
23
|
+
172.31.32.180,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>
|
24
|
+
172.31.32.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
|
25
|
+
|
26
|
+
@mock_software_report = 'name,ip_address,host_name,description,description,vendor,name,version
|
27
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,MAKEDEV,3.24-6.el6
|
28
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,acl,2.2.49-6.el6
|
29
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,acpid,1.0.10-2.1.el6
|
30
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,attr,2.4.44-7.el6
|
31
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,audit,2.2-4.el6_5
|
32
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,audit-libs,2.2-4.el6_5
|
33
|
+
my_cool_software_build-28,10.5.0.15,,CentOS Linux 6.5,Virtual Machine,Linux,authconfig,6.1.12-13.el6'.chomp
|
34
|
+
|
35
|
+
@mock_policy_report = 'compliance,title,description,ip_address,title,benchmark_name,category,scope,proof
|
36
|
+
false,Create Separate Partition for /tmp,The /tmp directory is a world-writable directory used for temporary storage by all users and some applications.,10.0.39.104,CIS CentOS 6 CentOS Level 1,centos_6_benchmark,Custom Policies,Custom,"Based on the following 2 results: * Based on the following 1 results: * * At least one specified RPM Package Information entry must match the given criteria. At least one evaluation must pass.<Table TableTitle=""""><tr RowTitle="""">centos-releasePASS * * At least one specified Text File Content entry must match the given criteria. At least one evaluation must pass.<Table TableTitle=""""><tr RowTitle=""Path:/etc/fstab Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$"">/etc/fstab<Table TableTitle=""""><tr RowTitle="""">Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$<td width=""40"">FAIL<Table TableTitle=""""><tr RowTitle="""">Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$<td width=""40"">FAIL<Table TableTitle=""""><tr RowTitle="""">Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$<td width=""40"">FAIL<Table TableTitle=""""><tr RowTitle="""">Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$<td width=""40"">FAIL<Table TableTitle=""""><tr RowTitle="""">Pattern:^[\s]*[\S][\s]([\S])[\s][\S][\s][\S][\s][\S][\s][\S]$<td width=""40"">FAIL"
|
37
|
+
false,Set nodev option for /tmp Partition,The nodev mount option specifies that the filesystem cannot contain special devices.,10.0.39.104,CIS CentOS 6 CentOS Level 1,centos_6_benchmark,Custom Policies,Custom,"Based on the following 2 results: * Based on the following 1 results: * * At least one specified RPM Package Information entry must match the given criteria. At least one evaluation must pass.<Table TableTitle=""""><tr RowTitle="""">centos-releasePASS * * At least one specified Text File Content entry must match the given criteria. At least one evaluation must pass.<Table TableTitle=""""><tr RowTitle="""">The specified Text File Content entry was not found based on given criteria."
|
38
|
+
'.chomp
|
39
|
+
|
40
|
+
|
41
|
+
@mock_scan = get_mock_scan
|
42
|
+
@mock_nexpose_client = get_mock_nexpose_client
|
43
|
+
@mock_nexpose_site = get_mock_nexpose_site
|
44
|
+
@mock_report = get_mock_report
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should create a session with the nexpose server' do
|
48
|
+
expect(Nexpose::Connection).to receive(:new)
|
49
|
+
.with(@expected_connection, @expected_username, @expected_password, @expected_port)
|
50
|
+
.and_return(@mock_nexpose_client)
|
51
|
+
|
52
|
+
expect(@mock_nexpose_client).to receive(:login)
|
53
|
+
.and_return(true)
|
54
|
+
|
55
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should throw an error if no connection url is passed' do
|
59
|
+
expect { NexposeRunner::Scan.start('', @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me the URL/IP address to your Nexpose Server')
|
60
|
+
expect { NexposeRunner::Scan.start(nil, @expected_port, @expected_username, @expected_password, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me the URL/IP address to your Nexpose Server')
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should throw an error if no username is passed' do
|
64
|
+
expect { NexposeRunner::Scan.start(@expected_connection, '', @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a username to login to Nexpose with')
|
65
|
+
expect { NexposeRunner::Scan.start(@expected_connection, nil, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a username to login to Nexpose with')
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should throw an error if no password is passed' do
|
69
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, '', @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a password to login to Nexpose with')
|
70
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, nil, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a password to login to Nexpose with')
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should throw an error if no site name is passed' do
|
74
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, '', @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a Nexpose Site Name')
|
75
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, nil, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a Nexpose Site Name')
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should throw an error if no ip address is passed' do
|
79
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, '', @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me an IP Address to scan')
|
80
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, nil, @expected_scan_template) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me an IP Address to scan')
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should throw an error if no scan template is passed' do
|
84
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, '') }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a Scan Template to use')
|
85
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, nil) }.to raise_error(StandardError, 'OOPS! Looks like you forgot to give me a Scan Template to use')
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'should use 3780 as default if port is empty string' do
|
89
|
+
expect(Nexpose::Connection).to receive(:new)
|
90
|
+
.with(@expected_connection, @expected_username, @expected_password, '3780')
|
91
|
+
.and_return(@mock_nexpose_client)
|
92
|
+
|
93
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, '', @expected_site_name, @expected_ip, @expected_scan_template)
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should create a new Nexpose site with the supplied site name and scan template' do
|
97
|
+
expect(Nexpose::Site).to receive(:new)
|
98
|
+
.with(@expected_site_name, @expected_scan_template)
|
99
|
+
.and_return(@mock_nexpose_site)
|
100
|
+
|
101
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should add the supplied ip address to the newly created site' do
|
105
|
+
expect(@mock_nexpose_site).to receive(:add_ip)
|
106
|
+
.with(@expected_ip)
|
107
|
+
|
108
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should save the new site configuration' do
|
112
|
+
expect(@mock_nexpose_site).to receive(:save)
|
113
|
+
.with(@mock_nexpose_client)
|
114
|
+
|
115
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'should initiate a scan' do
|
119
|
+
expect(@mock_nexpose_site).to receive(:scan)
|
120
|
+
.with(@mock_nexpose_client)
|
121
|
+
.and_return(@mock_scan)
|
122
|
+
|
123
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'wait for the Nexpose Scan to complete' do
|
127
|
+
it 'should call to check the status of the scan' do
|
128
|
+
expect(@mock_nexpose_client).to receive(:scan_status).with(@mock_scan_id)
|
129
|
+
|
130
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should call to check the status until it is not running' do
|
134
|
+
expect(@mock_nexpose_client).to receive(:scan_status)
|
135
|
+
.with(@mock_scan_id)
|
136
|
+
.and_return(Nexpose::Scan::Status::RUNNING)
|
137
|
+
.exactly(3).times
|
138
|
+
.ordered
|
139
|
+
|
140
|
+
expect(@mock_nexpose_client).to receive(:scan_status)
|
141
|
+
.with(@mock_scan_id)
|
142
|
+
.and_return(Nexpose::Scan::Status::FINISHED)
|
143
|
+
.once
|
144
|
+
.ordered
|
145
|
+
|
146
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'should sleep for 3 seconds if the status is still running' do
|
150
|
+
expect(@mock_nexpose_client).to receive(:scan_status)
|
151
|
+
.with(@mock_scan_id)
|
152
|
+
.and_return(Nexpose::Scan::Status::RUNNING)
|
153
|
+
.exactly(3).times
|
154
|
+
.ordered
|
155
|
+
|
156
|
+
expect(@mock_nexpose_client).to receive(:scan_status)
|
157
|
+
.with(@mock_scan_id)
|
158
|
+
.and_return(Nexpose::Scan::Status::FINISHED)
|
159
|
+
.once
|
160
|
+
.ordered
|
161
|
+
|
162
|
+
expect(NexposeRunner::Scan).to receive(:sleep).with(3).exactly(4).times
|
163
|
+
|
164
|
+
NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe 'it should create reports' do
|
169
|
+
it 'should generate, download, and parse an adhoc reports for Vulnerability, Software, and Policies' do
|
170
|
+
expect(Nexpose::AdhocReportConfig).to receive(:new)
|
171
|
+
.with(nil, 'sql')
|
172
|
+
.and_return(@mock_report)
|
173
|
+
|
174
|
+
expect_report_to_be_called_with(CONSTANTS::VULNERABILITY_REPORT_NAME, CONSTANTS::VULNERABILITY_REPORT_QUERY, @mock_vuln_report)
|
175
|
+
expect_report_to_be_called_with(CONSTANTS::SOFTWARE_REPORT_NAME, CONSTANTS::SOFTWARE_REPORT_QUERY, @mock_software_report)
|
176
|
+
expect_report_to_be_called_with(CONSTANTS::POLICY_REPORT_NAME, CONSTANTS::POLICY_REPORT_QUERY, @mock_policy_report)
|
177
|
+
|
178
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should throw exception if vulnerability exists' do
|
183
|
+
expect_report_to_be_called_with(CONSTANTS::VULNERABILITY_REPORT_NAME, CONSTANTS::VULNERABILITY_REPORT_QUERY, @mock_vuln_report)
|
184
|
+
|
185
|
+
expect { NexposeRunner::Scan.start(@expected_connection, @expected_username, @expected_password, @expected_port, @expected_site_name, @expected_ip, @expected_scan_template) }.to raise_error(StandardError, CONSTANTS::VULNERABILITY_FOUND_MESSAGE)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
def expect_report_to_be_called_with(report_name, report_query, report_response)
|
191
|
+
expect(@mock_report).to receive(:add_filter)
|
192
|
+
.with('version', '1.3.0')
|
193
|
+
|
194
|
+
expect(@mock_report).to receive(:add_filter)
|
195
|
+
.with('query', report_query).ordered
|
196
|
+
|
197
|
+
expect(@mock_report).to receive(:add_filter)
|
198
|
+
.with('site', @mock_site_id)
|
199
|
+
|
200
|
+
expect(@mock_report).to receive(:generate).with(@mock_nexpose_client).and_return(report_response).ordered
|
201
|
+
|
202
|
+
expect(CSV).to receive(:open).with(report_name, 'w').ordered
|
203
|
+
end
|
204
|
+
|
205
|
+
def get_mock_nexpose_client
|
206
|
+
mock_nexpose_client = double(Nexpose::Connection)
|
207
|
+
|
208
|
+
allow(mock_nexpose_client).to receive(:call).with(any_args).and_return({})
|
209
|
+
|
210
|
+
allow(mock_nexpose_client).to receive(:scan_status)
|
211
|
+
.with(@mock_scan_id)
|
212
|
+
|
213
|
+
allow(mock_nexpose_client).to receive(:login)
|
214
|
+
.and_return(true)
|
215
|
+
|
216
|
+
allow(Nexpose::Connection).to receive(:new)
|
217
|
+
.and_return(mock_nexpose_client)
|
218
|
+
|
219
|
+
mock_nexpose_client
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_mock_nexpose_site
|
223
|
+
mock_nexpose_site = double(Nexpose::Site)
|
224
|
+
|
225
|
+
allow(mock_nexpose_site).to receive(:call).with(any_args).and_return({})
|
226
|
+
|
227
|
+
allow(mock_nexpose_site).to receive(:scan)
|
228
|
+
.and_return(@mock_scan)
|
229
|
+
|
230
|
+
allow(mock_nexpose_site).to receive(:id)
|
231
|
+
.and_return(@mock_site_id)
|
232
|
+
|
233
|
+
|
234
|
+
allow(mock_nexpose_site).to receive(:add_ip)
|
235
|
+
.with(@expected_ip)
|
236
|
+
|
237
|
+
allow(mock_nexpose_site).to receive(:save)
|
238
|
+
.with(@mock_nexpose_client)
|
239
|
+
|
240
|
+
allow(mock_nexpose_site).to receive(:scan)
|
241
|
+
.with(@mock_nexpose_client)
|
242
|
+
.and_return(@mock_scan)
|
243
|
+
|
244
|
+
allow(Nexpose::Site).to receive(:new)
|
245
|
+
.and_return(mock_nexpose_site)
|
246
|
+
|
247
|
+
mock_nexpose_site
|
248
|
+
end
|
249
|
+
|
250
|
+
def get_mock_report
|
251
|
+
mock_report = double(Nexpose::AdhocReportConfig)
|
252
|
+
|
253
|
+
allow(mock_report).to receive(:call).with(any_args).and_return({})
|
254
|
+
|
255
|
+
allow(mock_report).to receive(:add_filter)
|
256
|
+
.with(any_args)
|
257
|
+
|
258
|
+
allow(mock_report).to receive(:add_filter)
|
259
|
+
.with(any_args)
|
260
|
+
|
261
|
+
allow(mock_report).to receive(:add_filter)
|
262
|
+
.with(any_args)
|
263
|
+
|
264
|
+
allow(mock_report).to receive(:generate).with(any_args)
|
265
|
+
.and_return(@mock_no_vuln_report)
|
266
|
+
|
267
|
+
allow(Nexpose::AdhocReportConfig).to receive(:new)
|
268
|
+
.and_return(mock_report)
|
269
|
+
|
270
|
+
allow(CSV).to receive(:open).with(any_args)
|
271
|
+
|
272
|
+
mock_report
|
273
|
+
end
|
274
|
+
|
275
|
+
def get_mock_scan
|
276
|
+
mock_scan = double(Nexpose::Scan)
|
277
|
+
allow(mock_scan).to receive(:id).and_return(@mock_scan_id)
|
278
|
+
mock_scan
|
279
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: NexposeRunner
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nathan Gibson
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: nexpose
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.8.3
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.8.3
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.0.0
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.0.0
|
69
|
+
description: ''
|
70
|
+
email:
|
71
|
+
- amngibson@gmail.com
|
72
|
+
executables:
|
73
|
+
- scan
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- .rspec
|
79
|
+
- .travis.yml
|
80
|
+
- Gemfile
|
81
|
+
- LICENSE.txt
|
82
|
+
- NexposeRunner.gemspec
|
83
|
+
- README.md
|
84
|
+
- Rakefile
|
85
|
+
- bin/scan
|
86
|
+
- lib/NexposeRunner.rb
|
87
|
+
- lib/NexposeRunner/version.rb
|
88
|
+
- lib/nexpose-runner/constants.rb
|
89
|
+
- lib/nexpose-runner/scan.rb
|
90
|
+
- lib/nexpose-runner/scan_run_description.rb
|
91
|
+
- spec/scan_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: ''
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ! '>='
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.2.2
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: This is a gem that provides the ability to create a new site, add an IP to
|
117
|
+
the site, and perform a scan against the site using a defined/passed scan template,
|
118
|
+
and finally produce a reports for vulnerabilities, installed software, and policy
|
119
|
+
compliance.
|
120
|
+
test_files:
|
121
|
+
- spec/scan_spec.rb
|
122
|
+
- spec/spec_helper.rb
|