foreman_cve_scanner 0.0.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +28 -16
- data/Rakefile +2 -0
- data/app/controllers/api/v2/cve_scans_controller.rb +66 -0
- data/app/lib/actions/foreman_cve_scanner/cve_scanner_job.rb +3 -18
- data/app/models/concerns/foreman_cve_scanner/host_extensions.rb +16 -0
- data/app/models/foreman_cve_scanner/cve_scan.rb +15 -0
- data/app/models/host_status/cve_status.rb +71 -0
- data/app/services/foreman_cve_scanner/cve_report_scanner.rb +37 -35
- data/app/services/foreman_cve_scanner/scan_importer.rb +105 -0
- data/app/views/api/v2/cve_scans/base.json.rabl +5 -0
- data/app/views/api/v2/cve_scans/index.json.rabl +5 -0
- data/app/views/api/v2/cve_scans/latest.json.rabl +5 -0
- data/app/views/api/v2/cve_scans/main.json.rabl +7 -0
- data/app/views/api/v2/cve_scans/show.json.rabl +5 -0
- data/app/views/foreman_cve_scanner/job_templates/run_cve_scanner.erb +4 -2
- data/config/routes.rb +20 -0
- data/db/migrate/20260221000000_create_foreman_cve_scanner_cve_scans.rb +22 -0
- data/db/seeds.d/75_job_templates.rb +17 -0
- data/lib/foreman_cve_scanner/engine.rb +25 -3
- data/lib/foreman_cve_scanner/version.rb +3 -1
- data/lib/foreman_cve_scanner.rb +4 -1
- data/lib/tasks/foreman_cve_scanner_seeds.rake +12 -0
- data/lib/tasks/rubocop.rake +33 -0
- data/package.json +48 -0
- data/test/actions/foreman_cve_scanner/cve_scanner_job_test.rb +54 -0
- data/test/controllers/api/v2/cve_scans_controller_test.rb +78 -0
- data/test/models/host_status/cve_status_test.rb +65 -0
- data/test/services/foreman_cve_scanner/cve_report_scanner_test.rb +45 -17
- data/test/services/foreman_cve_scanner/scan_importer_test.rb +85 -0
- data/test/test_plugin_helper.rb +2 -0
- data/webpack/components/CveDetailsCard.js +258 -0
- data/webpack/components/CveFindingsModal.js +274 -0
- data/webpack/components/CveHistoryTable.js +58 -0
- data/webpack/components/CveOverviewCard.js +67 -0
- data/webpack/components/CveScansTab.js +192 -0
- data/webpack/components/CveSummaryCell.js +72 -0
- data/webpack/components/SeverityIcon.js +56 -0
- data/webpack/components/__tests__/CveDetailsCard.test.js +126 -0
- data/webpack/components/__tests__/CveFindingsModal.test.js +78 -0
- data/webpack/components/__tests__/CveScansTab.test.js +76 -0
- data/webpack/components/__tests__/cve_helpers.test.js +42 -0
- data/webpack/components/cve_helpers.js +35 -0
- data/webpack/components/cve_scans.scss +200 -0
- data/webpack/fills.js +1 -0
- data/webpack/fills_index.js +38 -0
- data/webpack/index.js +1 -0
- metadata +49 -5
|
@@ -1,21 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'foreman_remote_execution'
|
|
2
4
|
|
|
3
5
|
module ForemanCveScanner
|
|
6
|
+
# Rails engine for the Foreman CVE Scanner plugin.
|
|
4
7
|
class Engine < ::Rails::Engine
|
|
5
8
|
engine_name 'foreman_cve_scanner'
|
|
6
9
|
|
|
7
|
-
initializer 'foreman_cve_scanner.
|
|
10
|
+
initializer 'foreman_cve_scanner.load_app_instance_data' do |app|
|
|
11
|
+
ForemanCveScanner::Engine.paths['db/migrate'].existent.each do |path|
|
|
12
|
+
app.config.paths['db/migrate'] << path
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
initializer 'foreman_cve_scanner.register_plugin', before: :finisher_hook do |app|
|
|
8
17
|
app.reloader.to_prepare do
|
|
9
18
|
Foreman::Plugin.register :foreman_cve_scanner do
|
|
10
19
|
requires_foreman '>= 3.13'
|
|
11
|
-
|
|
12
|
-
|
|
20
|
+
register_global_js_file 'fills'
|
|
21
|
+
|
|
22
|
+
apipie_documented_controllers ["#{ForemanCveScanner::Engine.root}/app/controllers/api/v2/*.rb"]
|
|
23
|
+
|
|
24
|
+
security_block :foreman_cve_scanner do
|
|
25
|
+
permission :view_cve_scans,
|
|
26
|
+
{ 'api/v2/cve_scans': %i[index latest show destroy] },
|
|
27
|
+
resource_type: 'Host'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
add_all_permissions_to_default_roles
|
|
13
31
|
end
|
|
14
32
|
end
|
|
15
33
|
end
|
|
16
34
|
|
|
17
35
|
# Include concerns in this config.to_prepare block
|
|
18
36
|
config.to_prepare do
|
|
37
|
+
require_dependency 'foreman_cve_scanner/host_extensions'
|
|
38
|
+
Host::Managed.include ForemanCveScanner::HostExtensions
|
|
39
|
+
require_dependency 'host_status/cve_status'
|
|
40
|
+
HostStatus.status_registry.add(HostStatus::CveStatus)
|
|
19
41
|
ForemanCveScanner::Engine.register_rex_features
|
|
20
42
|
end
|
|
21
43
|
|
data/lib/foreman_cve_scanner.rb
CHANGED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :foreman_cve_scanner do
|
|
4
|
+
desc 'Seed Foreman CVE Scanner job templates'
|
|
5
|
+
task seed_job_templates: :environment do
|
|
6
|
+
require Rails.root.join('lib/seed_helper')
|
|
7
|
+
require 'foreman_remote_execution'
|
|
8
|
+
paths = Dir["#{ForemanCveScanner::Engine.root}/app/views/foreman_cve_scanner/job_templates/*.erb"]
|
|
9
|
+
SeedHelper.import_templates(paths, 'ForemanCveScanner')
|
|
10
|
+
puts "Seeded #{paths.size} Foreman CVE Scanner job templates"
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
begin
|
|
4
|
+
require 'rubocop/rake_task'
|
|
5
|
+
|
|
6
|
+
test_patterns = [
|
|
7
|
+
"#{ForemanCveScanner::Engine.root}/*.gemspec",
|
|
8
|
+
"#{ForemanCveScanner::Engine.root}/*.rb",
|
|
9
|
+
"#{ForemanCveScanner::Engine.root}/app/**/*.rb",
|
|
10
|
+
"#{ForemanCveScanner::Engine.root}/config/**/*.rb",
|
|
11
|
+
"#{ForemanCveScanner::Engine.root}/db/**/*.rb",
|
|
12
|
+
"#{ForemanCveScanner::Engine.root}/lib/**/*.rake",
|
|
13
|
+
"#{ForemanCveScanner::Engine.root}/lib/**/*.rb",
|
|
14
|
+
"#{ForemanCveScanner::Engine.root}/test/**/*.rb",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
namespace :foreman_cve_scanner do
|
|
18
|
+
desc 'Runs Rubocop style checker'
|
|
19
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
|
20
|
+
task.patterns = test_patterns
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
desc 'Runs Rubocop style checker with xml output for Jenkins'
|
|
24
|
+
RuboCop::RakeTask.new('rubocop:jenkins') do |task|
|
|
25
|
+
task.patterns = test_patterns
|
|
26
|
+
task.requires = ['rubocop/formatter/checkstyle_formatter']
|
|
27
|
+
task.formatters = ['RuboCop::Formatter::CheckstyleFormatter']
|
|
28
|
+
task.options = ['--no-color', '--out', 'rubocop.xml']
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
rescue LoadError
|
|
32
|
+
# 'Rubocop not loaded.'
|
|
33
|
+
end
|
data/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "foreman_cve_scanner",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Run CVE scan on host and collect report",
|
|
5
|
+
"main": "webpack/index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"lib": "lib",
|
|
8
|
+
"test": "test"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"lint": "tfm-lint --plugin -d /webpack",
|
|
12
|
+
"test": "tfm-test --plugin",
|
|
13
|
+
"create-react-component": "yo react-domain"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+ssh://git@github.com/ATIX-AG/foreman_cve_scanner.git"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"@theforeman/vendor": ">= 8.16.0"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@babel/core": "^7.7.0",
|
|
24
|
+
"@theforeman/builder": "^15.0.0",
|
|
25
|
+
"@theforeman/eslint-plugin-foreman": "^15.0.0",
|
|
26
|
+
"@theforeman/find-foreman": "^15.0.0",
|
|
27
|
+
"@theforeman/vendor-dev": "^15.0.0",
|
|
28
|
+
"babel-eslint": ">= 10.0.3",
|
|
29
|
+
"eslint": "^6.7.2",
|
|
30
|
+
"eslint-plugin-react": ">= 7.27.1",
|
|
31
|
+
"eslint-plugin-react-hooks": ">= 4.3.0",
|
|
32
|
+
"eslint-plugin-spellcheck": ">= 0.0.17",
|
|
33
|
+
"prettier": "^1.19.1"
|
|
34
|
+
},
|
|
35
|
+
"keywords": [
|
|
36
|
+
"CVE",
|
|
37
|
+
"Trivy",
|
|
38
|
+
"Grype",
|
|
39
|
+
"security",
|
|
40
|
+
"Foreman"
|
|
41
|
+
],
|
|
42
|
+
"author": "ATIX AG",
|
|
43
|
+
"license": "GPL-3.0",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/ATIX-AG/foreman_cve_scanner/issues"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/ATIX-AG/foreman_cve_scanner#readme"
|
|
48
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_plugin_helper'
|
|
4
|
+
|
|
5
|
+
module ForemanCveScanner
|
|
6
|
+
class CveScannerJobTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@host = FactoryBot.create(:host)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test 'format_output parses json between markers across proxy output entries' do
|
|
12
|
+
job_output = {
|
|
13
|
+
'proxy_output' => {
|
|
14
|
+
'result' => [
|
|
15
|
+
{ 'output_type' => 'stdout', 'output' => "===START\n{\"key\":" },
|
|
16
|
+
{ 'output_type' => 'stdout', 'output' => "1}\n===END\n" },
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
importer = ForemanCveScanner::ScanImporter.new(job_output)
|
|
22
|
+
parsed = importer.send(:format_output, job_output)
|
|
23
|
+
|
|
24
|
+
assert_equal({ 'key' => 1 }, parsed)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
test 'persist_scan! stores scan and metrics' do
|
|
28
|
+
scan_json = JSON.parse(
|
|
29
|
+
File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/trivy.json'))
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
importer = ForemanCveScanner::ScanImporter.new('')
|
|
33
|
+
scan = importer.send(:persist_scan!, @host, 'trivy', scan_json)
|
|
34
|
+
|
|
35
|
+
assert scan.persisted?
|
|
36
|
+
assert scan.total.positive?
|
|
37
|
+
assert_equal @host.id, scan.host_id
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test 'import_for_host! creates scan from rex output' do
|
|
41
|
+
output = [
|
|
42
|
+
'===START',
|
|
43
|
+
File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/trivy.json')),
|
|
44
|
+
'===END',
|
|
45
|
+
].join("\n")
|
|
46
|
+
|
|
47
|
+
importer = ForemanCveScanner::ScanImporter.new(output)
|
|
48
|
+
scan = importer.import_for_host!(@host)
|
|
49
|
+
|
|
50
|
+
assert scan.persisted?
|
|
51
|
+
assert_equal @host.id, scan.host_id
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_plugin_helper'
|
|
4
|
+
|
|
5
|
+
module Api
|
|
6
|
+
module V2
|
|
7
|
+
class CveScansControllerTest < ActionController::TestCase
|
|
8
|
+
def setup
|
|
9
|
+
@host = FactoryBot.create(:host)
|
|
10
|
+
@scan_old = create_scan(created_at: 2.hours.ago, total: 1, low: 1)
|
|
11
|
+
@scan_new = create_scan(created_at: 1.hour.ago, total: 2, high: 2)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
test 'index returns scans for host' do
|
|
15
|
+
get :index, params: { host_id: @host.id }
|
|
16
|
+
assert_response :success
|
|
17
|
+
body = ActiveSupport::JSON.decode(@response.body)
|
|
18
|
+
assert_not_nil body['results']
|
|
19
|
+
assert_equal 2, body['results'].size
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test 'latest returns most recent scan' do
|
|
23
|
+
get :latest, params: { host_id: @host.id }
|
|
24
|
+
assert_response :success
|
|
25
|
+
body = ActiveSupport::JSON.decode(@response.body)
|
|
26
|
+
assert_equal @scan_new.id, body['id']
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
test 'show returns scan by id' do
|
|
30
|
+
get :show, params: { host_id: @host.id, id: @scan_old.id }
|
|
31
|
+
assert_response :success
|
|
32
|
+
body = ActiveSupport::JSON.decode(@response.body)
|
|
33
|
+
assert_equal @scan_old.id, body['id']
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
test 'index returns not found for unknown host' do
|
|
37
|
+
get :index, params: { host_id: 'does-not-exist' }
|
|
38
|
+
assert_response :not_found
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
test 'latest returns no content when no scans exist' do
|
|
42
|
+
ForemanCveScanner::CveScan.delete_all
|
|
43
|
+
|
|
44
|
+
get :latest, params: { host_id: @host.id }
|
|
45
|
+
assert_response :no_content
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# rubocop:disable Metrics/MethodLength
|
|
51
|
+
def create_scan(options = {})
|
|
52
|
+
defaults = {
|
|
53
|
+
created_at: Time.now.utc,
|
|
54
|
+
total: 0,
|
|
55
|
+
critical: 0,
|
|
56
|
+
high: 0,
|
|
57
|
+
medium: 0,
|
|
58
|
+
low: 0,
|
|
59
|
+
}
|
|
60
|
+
options = defaults.merge(options)
|
|
61
|
+
ForemanCveScanner::CveScan.create!(
|
|
62
|
+
host: @host,
|
|
63
|
+
scanner: 'trivy',
|
|
64
|
+
created_at: options[:created_at],
|
|
65
|
+
raw: { 'dummy' => true },
|
|
66
|
+
summary: { 'worst' => 'low' },
|
|
67
|
+
findings: [{ 'id' => 'CVE-0000-0000' }],
|
|
68
|
+
total: options[:total],
|
|
69
|
+
critical: options[:critical],
|
|
70
|
+
high: options[:high],
|
|
71
|
+
medium: options[:medium],
|
|
72
|
+
low: options[:low]
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
# rubocop:enable Metrics/MethodLength
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_plugin_helper'
|
|
4
|
+
|
|
5
|
+
module HostStatus
|
|
6
|
+
class CveStatusTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@host = FactoryBot.create(:host)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test 'no scans returns warning status' do
|
|
12
|
+
status = @host.get_status(HostStatus::CveStatus)
|
|
13
|
+
assert_equal 'No CVE scans', status.to_label
|
|
14
|
+
assert_equal HostStatus::Global::WARN, status.to_global
|
|
15
|
+
assert_equal 0, status.to_status
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
test 'critical or high scan returns error status' do
|
|
19
|
+
create_scan(critical: 1, high: 0, medium: 0, low: 0)
|
|
20
|
+
status = @host.get_status(HostStatus::CveStatus)
|
|
21
|
+
assert_equal 'Critical or high CVEs', status.to_label
|
|
22
|
+
assert_equal HostStatus::Global::ERROR, status.to_global
|
|
23
|
+
assert_equal 3, status.to_status
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
test 'medium scan returns warn status' do
|
|
27
|
+
create_scan(critical: 0, high: 0, medium: 2, low: 0)
|
|
28
|
+
status = @host.get_status(HostStatus::CveStatus)
|
|
29
|
+
assert_equal 'Medium CVEs', status.to_label
|
|
30
|
+
assert_equal HostStatus::Global::WARN, status.to_global
|
|
31
|
+
assert_equal 2, status.to_status
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test 'low scan returns ok status' do
|
|
35
|
+
create_scan(critical: 0, high: 0, medium: 0, low: 1)
|
|
36
|
+
status = @host.get_status(HostStatus::CveStatus)
|
|
37
|
+
assert_equal 'Low CVEs', status.to_label
|
|
38
|
+
assert_equal HostStatus::Global::OK, status.to_global
|
|
39
|
+
assert_equal 1, status.to_status
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
test 'status is registered in registry' do
|
|
43
|
+
assert_includes HostStatus.status_registry, HostStatus::CveStatus
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def create_scan(critical:, high:, medium:, low:)
|
|
49
|
+
total = critical + high + medium + low
|
|
50
|
+
ForemanCveScanner::CveScan.create!(
|
|
51
|
+
host: @host,
|
|
52
|
+
scanner: 'trivy',
|
|
53
|
+
created_at: Time.now.utc,
|
|
54
|
+
raw: { 'dummy' => true },
|
|
55
|
+
summary: { 'worst' => 'low' },
|
|
56
|
+
findings: [{ 'id' => 'CVE-0000-0000' }],
|
|
57
|
+
total: total,
|
|
58
|
+
critical: critical,
|
|
59
|
+
high: high,
|
|
60
|
+
medium: medium,
|
|
61
|
+
low: low
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_plugin_helper'
|
|
2
4
|
|
|
3
5
|
module ForemanCveScanner
|
|
@@ -5,9 +7,9 @@ module ForemanCveScanner
|
|
|
5
7
|
test 'should identify as cve scan' do
|
|
6
8
|
raw = {
|
|
7
9
|
'reporter' => 'cve_scan',
|
|
8
|
-
'scan' => JSON.parse(File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/grype.json')))
|
|
10
|
+
'scan' => JSON.parse(File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/grype.json'))),
|
|
9
11
|
}
|
|
10
|
-
assert_equal ForemanCveScanner::CveReportScanner.identify_origin(raw)
|
|
12
|
+
assert_equal('CveScanner', ForemanCveScanner::CveReportScanner.identify_origin(raw))
|
|
11
13
|
end
|
|
12
14
|
|
|
13
15
|
test 'should raise an exception if invalid report' do
|
|
@@ -18,26 +20,52 @@ module ForemanCveScanner
|
|
|
18
20
|
|
|
19
21
|
test 'trivy scan has valid data' do
|
|
20
22
|
data = JSON.parse(File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/trivy.json')))
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
scanner = ForemanCveScanner::CveReportScanner.new('scan' => data)
|
|
24
|
+
scanner.generate
|
|
25
|
+
assert_equal(10, scanner.logs.count)
|
|
26
|
+
assert_equal('info', scanner.logs[0]['log']['level'])
|
|
27
|
+
expected_message = [
|
|
28
|
+
'CVE-2020-12762: json-c, libfastjson: integer overflow and out-of-',
|
|
29
|
+
'bounds write via a large JSON file # url: ',
|
|
30
|
+
'https://avd.aquasec.com/nvd/cve-2020-12762',
|
|
31
|
+
].join
|
|
32
|
+
assert_equal scanner.logs[0]['log']['messages']['message'], expected_message
|
|
29
33
|
end
|
|
30
34
|
|
|
31
35
|
test 'grype scan has valid data' do
|
|
32
36
|
data = JSON.parse(File.read(File.join(ForemanCveScanner::Engine.root, 'test/fixtures/grype.json')))
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
scanner = ForemanCveScanner::CveReportScanner.new('scan' => data)
|
|
38
|
+
scanner.generate
|
|
39
|
+
assert_equal(18, scanner.logs.count)
|
|
40
|
+
first_severity = data.dig('matches', 0, 'vulnerability', 'severity')
|
|
41
|
+
expected_level = expected_level_for(first_severity)
|
|
42
|
+
assert_equal scanner.logs[0]['log']['level'], expected_level
|
|
43
|
+
expected_message = [
|
|
44
|
+
'CVE-2007-0086: The Apache HTTP Server, when accessed through a TCP ',
|
|
45
|
+
'connection with a large window size, allows remote attackers to cause ',
|
|
46
|
+
'a denial of service (network bandwidth consumption) via a Range header ',
|
|
47
|
+
'that specifies multiple copies of the same fragment. # url: ',
|
|
48
|
+
'https://nvd.nist.gov/vuln/detail/CVE-2007-0086',
|
|
49
|
+
].join
|
|
50
|
+
assert_equal scanner.logs[0]['log']['messages']['message'], expected_message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
test 'detect_scanner returns expected scanner names' do
|
|
54
|
+
assert_equal 'grype', ForemanCveScanner::CveReportScanner.detect_scanner('matches' => [])
|
|
55
|
+
assert_equal 'trivy', ForemanCveScanner::CveReportScanner.detect_scanner('Results' => [])
|
|
56
|
+
assert_equal 'unknown', ForemanCveScanner::CveReportScanner.detect_scanner('foo' => 'bar')
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def expected_level_for(severity)
|
|
62
|
+
map = {
|
|
63
|
+
'CRITICAL' => 'err',
|
|
64
|
+
'HIGH' => 'warning',
|
|
65
|
+
'MEDIUM' => 'info',
|
|
66
|
+
'LOW' => 'debug',
|
|
36
67
|
}
|
|
37
|
-
|
|
38
|
-
assert_equal raw['logs'].count, 18
|
|
39
|
-
assert_equal raw['logs'][0]['log']['level'], 'info'
|
|
40
|
-
assert_equal raw['logs'][0]['log']['messages']['message'], 'CVE-2007-0086: The Apache HTTP Server, when accessed through a TCP connection with a large window size, allows remote attackers to cause a denial of service (network bandwidth consumption) via a Range header that specifies multiple copies of the same fragment. # url: https://nvd.nist.gov/vuln/detail/CVE-2007-0086'
|
|
68
|
+
map.fetch(severity.to_s.strip.upcase, 'info')
|
|
41
69
|
end
|
|
42
70
|
end
|
|
43
71
|
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_plugin_helper'
|
|
4
|
+
|
|
5
|
+
module ForemanCveScanner
|
|
6
|
+
class ScanImporterTest < ActiveSupport::TestCase
|
|
7
|
+
def setup
|
|
8
|
+
@host = FactoryBot.create(:host)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
test 'import_for_host! persists scan from trivy output' do
|
|
12
|
+
output = wrap_output(load_fixture('trivy.json'))
|
|
13
|
+
importer = ForemanCveScanner::ScanImporter.new(output)
|
|
14
|
+
|
|
15
|
+
count_before = ForemanCveScanner::CveScan.count
|
|
16
|
+
scan = importer.import_for_host!(@host)
|
|
17
|
+
|
|
18
|
+
assert_equal count_before + 1, ForemanCveScanner::CveScan.count
|
|
19
|
+
assert_not_nil scan
|
|
20
|
+
assert_equal @host.id, scan.host_id
|
|
21
|
+
assert_equal 'trivy', scan.scanner
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test 'import_for_host! sets totals and findings for trivy output' do
|
|
25
|
+
output = wrap_output(load_fixture('trivy.json'))
|
|
26
|
+
importer = ForemanCveScanner::ScanImporter.new(output)
|
|
27
|
+
|
|
28
|
+
scan = importer.import_for_host!(@host)
|
|
29
|
+
|
|
30
|
+
assert_operator scan.total, :>, 0
|
|
31
|
+
assert_equal scan.total, scan.findings.count
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
test 'import_for_host! persists scan from proxy output' do
|
|
35
|
+
output = load_fixture('grype.json')
|
|
36
|
+
proxy_output = {
|
|
37
|
+
'proxy_output' => {
|
|
38
|
+
'result' => [
|
|
39
|
+
{ 'output_type' => 'stdout', 'output' => "===START\n" },
|
|
40
|
+
{ 'output_type' => 'stdout', 'output' => output },
|
|
41
|
+
{ 'output_type' => 'stdout', 'output' => "\n===END\n" },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
importer = ForemanCveScanner::ScanImporter.new(proxy_output)
|
|
46
|
+
|
|
47
|
+
scan = importer.import_for_host!(@host)
|
|
48
|
+
|
|
49
|
+
assert_not_nil scan
|
|
50
|
+
assert_equal 'grype', scan.scanner
|
|
51
|
+
assert_operator scan.total, :>, 0
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
test 'import_for_host! returns nil when no json markers' do
|
|
55
|
+
importer = ForemanCveScanner::ScanImporter.new('no markers here')
|
|
56
|
+
|
|
57
|
+
scan = importer.import_for_host!(@host)
|
|
58
|
+
|
|
59
|
+
assert_nil scan
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
test 'import_for_host! returns nil when json is invalid' do
|
|
63
|
+
importer = ForemanCveScanner::ScanImporter.new("===START\n{bad\n===END")
|
|
64
|
+
|
|
65
|
+
scan = importer.import_for_host!(@host)
|
|
66
|
+
|
|
67
|
+
assert_nil scan
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def load_fixture(name)
|
|
73
|
+
path = File.join(ForemanCveScanner::Engine.root, 'test/fixtures', name)
|
|
74
|
+
JSON.parse(File.read(path)).to_json
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def wrap_output(json_text)
|
|
78
|
+
[
|
|
79
|
+
'===START',
|
|
80
|
+
json_text,
|
|
81
|
+
'===END',
|
|
82
|
+
].join("\n")
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|