NexposeRunner 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format documentation
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
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in NexposeRunner-scan.gemspec
4
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/scan ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'nexpose-runner/scan'
4
+
5
+ $stdout.sync = true
6
+ NexposeRunner::Scan.start ARGV[0], ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]
@@ -0,0 +1,4 @@
1
+ module NexposeRunner
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -0,0 +1,5 @@
1
+ require 'NexposeScan/version'
2
+
3
+ module NexposeScan
4
+ # Your code goes here...
5
+ end
@@ -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
@@ -0,0 +1,9 @@
1
+ RSpec.configure do |config|
2
+
3
+ config.mock_with :rspec do |mocks|
4
+ mocks.verify_partial_doubles = true
5
+ end
6
+
7
+ Dir['./spec/support/**/*.rb'].sort.each { |f| require f }
8
+
9
+ end
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