foreman_cve_scanner 0.0.1 → 0.0.2
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 +10 -5
- data/Rakefile +2 -3
- data/app/lib/actions/{cve_scanner_job.rb → foreman_cve_scanner/cve_scanner_job.rb} +10 -11
- data/app/services/foreman_cve_scanner/cve_report_scanner.rb +45 -48
- data/app/views/foreman_cve_scanner/job_templates/install_cve_scanners.erb +47 -9
- data/app/views/foreman_cve_scanner/job_templates/run_cve_scanner.erb +1 -1
- data/lib/foreman_cve_scanner/engine.rb +9 -8
- data/lib/foreman_cve_scanner/version.rb +1 -1
- data/lib/tasks/foreman_cve_scanner_tasks.rake +16 -0
- data/test/fixtures/grype.json +2970 -0
- data/test/fixtures/trivy.json +1765 -0
- data/test/services/foreman_cve_scanner/cve_report_scanner_test.rb +43 -0
- metadata +25 -44
- data/test/factories/foreman_cve_scanner_factories.rb +0 -5
- data/test/unit/foreman_cve_scanner_test.rb +0 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9eb6f60deb30166fd16ad5e1b8900650207187a7f369ec6143c3ae09c4863ba9
|
4
|
+
data.tar.gz: 5858c93d78633437209e5dfe60047f6e208de6b18643d3d6df6ac10b18c47fa2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ab8170a8a127e4be11132dec5ff4325fa910380a6cdfa475feabd71dc22cbf7e48c93fd4ae2071dac78de06fced9a038847ddba4cbf36b29fc61608d3a3e55c6
|
7
|
+
data.tar.gz: b5e3bd7b539425c0c05d00041dca257bc037164a5bf9dc0d326abde21e32bc7ef99f57785f60592f448aac955ed68930575c959bb528e8ebd308a72c891ab571
|
data/README.md
CHANGED
@@ -4,11 +4,8 @@ Plugin to
|
|
4
4
|
1. install trivy/grype CVE scanners on a host using a Foreman Remote Execution (REX) job
|
5
5
|
2. run a CVE scan using a REX job, collect the output and generate a Config Report
|
6
6
|
|
7
|
-
|
8
7
|

|
9
8
|
|
10
|
-
Not finished yet....
|
11
|
-
|
12
9
|
|
13
10
|
*Introdction here*
|
14
11
|
|
@@ -19,11 +16,19 @@ for how to install Foreman plugins
|
|
19
16
|
|
20
17
|
## Usage
|
21
18
|
|
22
|
-
|
19
|
+
- Run the REX job to install trivy and/or grype
|
20
|
+
- Run the REX job to scan a host
|
21
|
+
- Go to the Config Report page for a host to view the scan report
|
23
22
|
|
24
23
|
## TODO
|
25
24
|
|
26
|
-
|
25
|
+
- Better possiblities to filter the Config Report (maybe an extension to ConfigReport in Foreman)
|
26
|
+
- Have a scheduled REX Job to scan the hosts
|
27
|
+
- Make it visible on the Host Details page or on Foreman directly, if a high priority CVE on a host occurs
|
28
|
+
- Export a CVE scan
|
29
|
+
- Deliver trivy / grype via Katello
|
30
|
+
- More tests
|
31
|
+
- API
|
27
32
|
|
28
33
|
## Contributing
|
29
34
|
|
data/Rakefile
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
#!/usr/bin/env rake
|
2
1
|
begin
|
3
2
|
require 'bundler/setup'
|
4
3
|
rescue LoadError
|
@@ -14,7 +13,7 @@ end
|
|
14
13
|
|
15
14
|
RDoc::Task.new(:rdoc) do |rdoc|
|
16
15
|
rdoc.rdoc_dir = 'rdoc'
|
17
|
-
rdoc.title = '
|
16
|
+
rdoc.title = 'ForemanCveScanner'
|
18
17
|
rdoc.options << '--line-numbers'
|
19
18
|
rdoc.rdoc_files.include('README.rdoc')
|
20
19
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
@@ -38,7 +37,7 @@ task default: :test
|
|
38
37
|
begin
|
39
38
|
require 'rubocop/rake_task'
|
40
39
|
RuboCop::RakeTask.new
|
41
|
-
rescue
|
40
|
+
rescue StandardError => _e
|
42
41
|
puts 'Rubocop not loaded.'
|
43
42
|
end
|
44
43
|
|
@@ -15,16 +15,16 @@ module Actions
|
|
15
15
|
|
16
16
|
def finalize(*_args)
|
17
17
|
host = Host.find(input[:host_id])
|
18
|
-
if host.
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
18
|
+
return if host.blank?
|
19
|
+
|
20
|
+
report = {
|
21
|
+
'host' => host.name,
|
22
|
+
'logs' => [],
|
23
|
+
'scan' => format_output(task.main_action.continuous_output.humanize),
|
24
|
+
'reported_at' => Time.now.utc.to_s,
|
25
|
+
'reporter' => 'cve_scan'
|
26
|
+
}
|
27
|
+
ConfigReportImporter.import(report)
|
28
28
|
end
|
29
29
|
|
30
30
|
private
|
@@ -46,4 +46,3 @@ module Actions
|
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
@@ -35,27 +35,21 @@ module ForemanCveScanner
|
|
35
35
|
@logs
|
36
36
|
end
|
37
37
|
|
38
|
-
|
39
|
-
@logs
|
40
|
-
end
|
41
|
-
|
42
|
-
def status
|
43
|
-
@status
|
44
|
-
end
|
38
|
+
attr_reader :logs, :status
|
45
39
|
|
46
40
|
def metrics
|
47
41
|
res = @status
|
48
42
|
res['total'] = @status.values.sum
|
49
|
-
|
43
|
+
res
|
50
44
|
end
|
51
45
|
|
52
46
|
private
|
53
47
|
|
54
48
|
def generate_log_from_unified(id, entry)
|
55
|
-
|
49
|
+
{
|
56
50
|
'log': {
|
57
51
|
'level': consume_severity_level(entry['severity']),
|
58
|
-
'messages': {
|
52
|
+
'messages': {
|
59
53
|
message: "#{id}: #{entry['title']} # url: #{entry['url']}"
|
60
54
|
},
|
61
55
|
sources: {
|
@@ -66,68 +60,71 @@ module ForemanCveScanner
|
|
66
60
|
end
|
67
61
|
|
68
62
|
def consume_severity_level(severity)
|
69
|
-
@status[severity.downcase] = 0 unless @status.
|
63
|
+
@status[severity.downcase] = 0 unless @status.key?(severity.downcase)
|
70
64
|
@status[severity.downcase] += 1
|
71
65
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
return log
|
66
|
+
case severity
|
67
|
+
when 'CRITICAL'
|
68
|
+
'err'
|
69
|
+
when 'HIGH'
|
70
|
+
'warning'
|
71
|
+
when 'MEDIUM'
|
72
|
+
'info'
|
73
|
+
when 'LOW'
|
74
|
+
'debug'
|
75
|
+
else
|
76
|
+
'info'
|
77
|
+
end
|
85
78
|
end
|
86
79
|
|
87
80
|
def generate_grype_entry(entry)
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
81
|
+
{
|
82
|
+
'name' => entry['artifact']['name'],
|
83
|
+
'version' => entry['artifact']['version'],
|
84
|
+
'title' => entry['vulnerability']['description'].gsub(/[\[\]"\\]/, ''),
|
85
|
+
'severity' => entry['vulnerability']['severity'],
|
86
|
+
'url' => entry['vulnerability']['dataSource']
|
87
|
+
}
|
95
88
|
end
|
96
89
|
|
97
90
|
def generate_trivy_entry(entry)
|
98
|
-
unified = {
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
91
|
+
unified = {
|
92
|
+
'name' => entry['PkgName'],
|
93
|
+
'version' => entry['InstalledVersion'],
|
94
|
+
'title' => entry['Title'].gsub(/[\[\]"\\]/, ''),
|
95
|
+
'severity' => entry['Severity'],
|
96
|
+
'url' => entry['PrimaryURL'],
|
97
|
+
'status' => entry['Status'],
|
98
|
+
'fixed' => entry['FixedVersion'] || 'open'
|
99
|
+
}
|
100
|
+
unified['published'] = entry['PublishedDate'] if entry.key?('PublishedDate')
|
101
|
+
unified
|
108
102
|
end
|
109
103
|
|
104
|
+
# rubocop:disable Metrics/AbcSize
|
110
105
|
def generate_unified_vuls
|
111
|
-
|
106
|
+
raise ::Foreman::Exception, _('Invalid CVE scanner report') unless @raw_data.key?('scan')
|
112
107
|
|
108
|
+
j = @raw_data['scan']
|
113
109
|
vuls = {}
|
114
|
-
if j.
|
110
|
+
if j.key?('matches') # Grype
|
115
111
|
j['matches'].each do |vul|
|
116
112
|
vuls[vul['vulnerability']['id']] = generate_grype_entry(vul)
|
117
113
|
end
|
118
|
-
elsif j.
|
114
|
+
elsif j.key?('Results') # Trivy
|
119
115
|
j['Results'].each do |r|
|
120
|
-
next unless r.
|
116
|
+
next unless r.key? 'Vulnerabilities'
|
121
117
|
r['Vulnerabilities'].each do |vul|
|
122
118
|
vuls[vul['VulnerabilityID']] = generate_trivy_entry(vul)
|
123
119
|
end
|
124
|
-
end
|
120
|
+
end
|
125
121
|
else
|
126
|
-
Rails.logger.error
|
127
|
-
raise ::Foreman::Exception
|
122
|
+
Rails.logger.error 'Unsupported cve scanner report format'
|
123
|
+
raise ::Foreman::Exception, _('Unsupported cve scanner report format')
|
128
124
|
end
|
129
125
|
|
130
126
|
vuls
|
131
127
|
end
|
128
|
+
# rubocop:enable Metrics/AbcSize
|
132
129
|
end
|
133
130
|
end
|
@@ -7,27 +7,65 @@ job_category: Security
|
|
7
7
|
provider_type: script
|
8
8
|
template_inputs:
|
9
9
|
- name: trivy_version
|
10
|
+
description: 'Trivy version to install'
|
10
11
|
required: true
|
11
12
|
input_type: user
|
12
13
|
advanced: false
|
13
|
-
|
14
|
-
default: 0.48.1
|
15
|
-
hidden_value: false
|
14
|
+
default: 0.53.0
|
16
15
|
- name: grype_version
|
16
|
+
description: 'Grype version to install'
|
17
17
|
required: true
|
18
18
|
input_type: user
|
19
19
|
advanced: false
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
default: 0.79.3
|
21
|
+
- name: scanner_to_install
|
22
|
+
description: 'Choose which scanner to install (Default: both)'
|
23
|
+
required: false
|
24
|
+
input_type: user
|
25
|
+
advanced: false
|
26
|
+
default: both
|
27
|
+
options: "both\r\ntrivy\r\ngrype"
|
23
28
|
%>
|
24
29
|
# Install CVE scanners from github
|
25
30
|
<%
|
26
31
|
trivy_version = input('trivy_version')
|
27
32
|
grype_version = input('grype_version')
|
28
33
|
|
29
|
-
|
30
|
-
|
34
|
+
case @host.operatingsystem.family.to_s
|
35
|
+
when 'Debian'
|
36
|
+
pkg = 'deb'
|
37
|
+
when 'Redhat', 'Suse'
|
38
|
+
pkg = 'rpm'
|
39
|
+
else
|
40
|
+
raise("OS '#{@host.operatingsystem.family}' not supported by template #{template_name}")
|
41
|
+
end
|
42
|
+
|
43
|
+
case @host.architecture.to_s
|
44
|
+
when 'x86_64'
|
45
|
+
trivy_arch = 'Linux-64bit'
|
46
|
+
grype_arch = 'linux-amd64'
|
47
|
+
when 'ppc64le'
|
48
|
+
trivy_arch = 'Linux-PPC64LE'
|
49
|
+
grype_arch = 'linux-ppc64le'
|
50
|
+
when 'aarch64'
|
51
|
+
trivy_arch = 'Linux-ARM64'
|
52
|
+
grype_arch = 'linux-arm64'
|
53
|
+
else
|
54
|
+
raise("Architecture '#{@host.architecture}' not supported by template #{template_name}")
|
55
|
+
end
|
56
|
+
|
57
|
+
trivy_url = "https://github.com/aquasecurity/trivy/releases/download/v#{trivy_version}/trivy_#{trivy_version}_#{trivy_arch}.#{pkg}"
|
58
|
+
grype_url = "https://github.com/anchore/grype/releases/download/v#{grype_version}/grype_#{grype_version}_#{grype_arch}.#{pkg}"
|
59
|
+
|
60
|
+
case @host.operatingsystem.family
|
61
|
+
when 'Debian'
|
62
|
+
trivy_install_cmd = "wget -o /tmp/outfile.deb #{trivy_url} && dpkg -i /tmp/outfile.deb; rm -f /tmp/outfile.deb"
|
63
|
+
grype_install_cmd = "wget -o /tmp/outfile.deb #{grype_url} && dpkg -i /tmp/outfile.deb; rm -f /tmp/outfile.deb"
|
64
|
+
when 'Redhat', 'Suse'
|
65
|
+
trivy_install_cmd = "rpm -ivh #{trivy_url}"
|
66
|
+
grype_install_cmd = "rpm -ivh #{grype_url}"
|
67
|
+
end
|
31
68
|
-%>
|
32
69
|
|
33
|
-
|
70
|
+
<%= trivy_install_cmd if input('scanner_to_install') == 'both' || input('scanner_to_install') == 'trivy' %>
|
71
|
+
<%= grype_install_cmd if input('scanner_to_install') == 'both' || input('scanner_to_install') == 'grype' %>
|
@@ -54,7 +54,7 @@ scanners = {
|
|
54
54
|
|
55
55
|
if scanner == 'trivy'
|
56
56
|
cmd = "#{scanners[:trivy][target]} #{path}"
|
57
|
-
options += " --scanners vuln --quiet --format json"
|
57
|
+
options += " --exit-code 0 --scanners vuln --quiet --format json"
|
58
58
|
elsif scanner == 'grype'
|
59
59
|
cmd = "#{scanners[:grype][target]}:#{path}"
|
60
60
|
options += " --quiet --quiet --output json"
|
@@ -1,15 +1,16 @@
|
|
1
|
+
require 'foreman_remote_execution'
|
2
|
+
|
1
3
|
module ForemanCveScanner
|
2
4
|
class Engine < ::Rails::Engine
|
3
5
|
engine_name 'foreman_cve_scanner'
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
register_report_origin 'CveScanner', 'ConfigReport'
|
7
|
+
initializer 'foreman_cve_scanner.register_plugin', :before => :finisher_hook do |app|
|
8
|
+
app.reloader.to_prepare do
|
9
|
+
Foreman::Plugin.register :foreman_cve_scanner do
|
10
|
+
requires_foreman '>= 3.13'
|
11
|
+
register_report_scanner ForemanCveScanner::CveReportScanner
|
12
|
+
register_report_origin 'CveScanner', 'ConfigReport'
|
13
|
+
end
|
13
14
|
end
|
14
15
|
end
|
15
16
|
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Tests
|
4
|
+
namespace :test do
|
5
|
+
desc 'Foreman Cve Scanner plugin tests'
|
6
|
+
Rake::TestTask.new(:foreman_cve_scanner) do |t|
|
7
|
+
test_dir = File.join(__dir__, '..', '..', 'test')
|
8
|
+
t.libs << ['test', test_dir]
|
9
|
+
t.pattern = "#{test_dir}/**/*_test.rb"
|
10
|
+
t.test_files = [Rails.root.join('test/unit/foreman/access_permissions_test.rb')]
|
11
|
+
t.verbose = false
|
12
|
+
t.warning = false
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
Rake::Task[:test].enhance ['test:foreman_cve_scanner']
|