1
+ #!/usr/bin/env ruby
2
+ # == Synopsis
3
+ # This script is designed to co-ordinate parsing of Clair Vulnerability scanner JSON files and production of a concise set findings.
4
+ #
5
+ # WARNING This isn't ready for use yet, don't do it, you'll be sorry!
6
+ #
7
+ # There are 2 modes of operation.
8
+ #
9
+ # Directory mode just takes a parameter of the directory containing the xml files and goes and parses any files found there
10
+ #
11
+ # File mode takes a parameter of a single file and parses that
12
+ #
13
+ #TODO:
14
+ # == Author
15
+ # Author:: Rory McCune
16
+ # Copyright:: Copyright (c) 2018 Rory Mccune
17
+ # License:: GPLv3
18
+ #
19
+ # This program is free software: you can redistribute it and/or modify
20
+ # it under the terms of the GNU General Public License as published by
21
+ # the Free Software Foundation, either version 3 of the License, or
22
+ # (at your option) any later version.
23
+ #
24
+ # This program is distributed in the hope that it will be useful,
25
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
26
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27
+ # GNU General Public License for more details.
28
+ #
29
+ # You should have received a copy of the GNU General Public License
30
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
31
+ #
32
+ # == Options
33
+ # -h, --help Displays help message
34
+ # -v, --version Display the version, then exit
35
+ # -d <dir>, --directory <dir> Only needed in directory mode name of the directory to scan
36
+ # -f <file>, --file <file> Only needed in file mode, name of the file to parse
37
+ # -r <file>, --report <file> Name of file for reporting
38
+ # -l <file> Log debug messages to a file.
39
+ # --reportDirectory <dir> Place the report in a different directory
40
+ #
41
+ # == Usage
42
+ #
43
+ # Directory Mode
44
+ # clairautoanalyzer.rb -m directory -d <directoryname> -r <reportfile>
45
+ # File Mode
46
+ # clairautoanalyzer.rb -m file -f <filename> -r <reportfile>
47
+
48
+
49
+
50
+ class ClairAutoAnalyzer
51
+ VERSION = '0.0.1'
52
+
53
+ def initialize ( commandlineopts )
54
+ #This is StdLib so shouldn't need a rescue for failed require
55
+ require 'json'
56
+ require 'logger'
57
+
58
+ @options = commandlineopts
59
+ @base_dir = @options . report_directory
60
+ @scan_dir = @options . scan_directory
61
+ if !File . exists? ( @base_dir )
62
+ Dir . mkdirs ( @base_dir )
63
+ end
64
+
65
+ if @options . logger
66
+ @log = Logger . new ( @base_dir + '/' + @options . logger )
67
+ else
68
+ @log = Logger . new ( STDOUT )
69
+ end
70
+ #Change this to Logger::DEBUG if you want to debug stuff
71
+ @log . level = Logger ::DEBUG
72
+
73
+ @log . debug ( "Log created at " + Time . now . to_s )
74
+ @log . debug ( "Scan type is : #{ @options . scan_type } " )
75
+ @log . debug ( "Directory being scanned is : #{ @options . scan_directory } " ) if @options . scan_type == :directory
76
+ @log . debug ( "File being scanned is : #{ @options . scan_file } " ) if @options . scan_type == :file
77
+ end
78
+
79
+ def run
80
+ case @options . scan_type
81
+ when :directory
82
+ scan_dirs
83
+ parse_files
84
+ excel_report
85
+ when :file
86
+ @scan_files = Array . new
87
+ @scan_files << @options . scan_file
88
+ parse_files
89
+ excel_report
90
+ end
91
+ end
92
+
93
+ def scan_dirs
94
+ @scan_files = Array . new
95
+ @log . debug ( "Scan directory is #{ @scan_dir } " )
96
+ Dir . entries ( @scan_dir ) . each do |scan |
97
+ next if File . directory? ( @scan_dir + '/' + scan )
98
+ @scan_files << @scan_dir + '/' + scan
99
+ end
100
+ end
101
+
102
+ def parse_files
103
+ @image_results = Hash . new
104
+ @log . debug ( "Files to be looked at : #{ @scan_files . join ( ', ' ) } " )
105
+ @scan_files . each do |file |
106
+ file_content = File . open ( file , 'r' ) . read
107
+ begin
108
+ @log . debug ( "File name is " + file )
109
+ doc = JSON . parse ( file_content )
110
+ rescue JSON ::ParserError => e
111
+ @log . warn ( "We got a parser error on #{ file } " )
112
+ next
113
+ end
114
+
115
+ begin
116
+ @log . debug ( "Got a valid JSON file called #{ file } , processing..." )
117
+ parse_file ( doc )
118
+ rescue Exception => e
119
+ @log . warn ( "We got a parsing error on a valid JSON file #{ file } " )
120
+ @log . warn ( e )
121
+ end
122
+ end
123
+ end
124
+
125
+ def parse_file ( doc )
126
+ image = doc [ 'image' ]
127
+ @image_results [ image ] = Hash . new
128
+ doc [ 'vulnerabilities' ] . each do |vuln |
129
+ @image_results [ image ] [ vuln [ 'vulnerability' ] ] = [ vuln [ 'severity' ] , vuln [ 'featurename' ] ]
130
+ end
131
+ end
132
+
133
+ def excel_report
134
+ begin
135
+ require 'rubyXL'
136
+ rescue LoadError
137
+ puts "Excel report needs the rubyXL gem"
138
+ exit
139
+ end
140
+ workbook = RubyXL ::Workbook . new
141
+ vuln_sheet = workbook . worksheets [ 0 ]
142
+ vuln_sheet . sheet_name = "Docker Image Vulnerabilities"
143
+ vuln_sheet . add_cell ( 0 , 0 , "Image Name" )
144
+ vuln_sheet . add_cell ( 0 , 1 , "CVE ID" )
145
+ vuln_sheet . add_cell ( 0 , 2 , "Severity" )
146
+ vuln_sheet . add_cell ( 0 , 3 , "Affected Package" )
147
+ row_count = 1
148
+ @image_results . each do |image , results |
149
+ results . each do |cve , data |
150
+ vuln_sheet . add_cell ( row_count , 0 , image )
151
+ vuln_sheet . add_cell ( row_count , 1 , cve )
152
+ vuln_sheet . add_cell ( row_count , 2 , data [ 0 ] )
153
+ vuln_sheet . add_cell ( row_count , 3 , data [ 1 ] )
154
+ row_count = row_count + 1
155
+ end
156
+ end
157
+ workbook . write ( @options . report_file + '.xlsx' )
158
+ end
159
+ end
160
+
161
+
162
+
163
+ if __FILE__ == $0
164
+ require 'ostruct'
165
+ require 'optparse'
166
+ options = OpenStruct . new
167
+
168
+ #Set some defaults in the options hash
169
+ options . report_directory = Dir . pwd
170
+ options . report_file = 'testssl-parse-report'
171
+ options . scan_directory = Dir . pwd
172
+ options . scan_file = ''
173
+ options . scan_type = :notset
174
+
175
+
176
+ opts = OptionParser . new do |opts |
177
+ opts . banner = "Clair Auto analyzer #{ ClairAutoAnalyzer ::VERSION } "
178
+
179
+ opts . on ( "-d" , "--directory [DIRECTORY]" , "Directory to scan for Clair json files" ) do |dir |
180
+ options . scan_directory = dir
181
+ options . scan_type = :directory
182
+ end
183
+
184
+ opts . on ( "-f" , "--file [FILE]" , "File to analyze including path" ) do |file |
185
+ options . scan_file = file
186
+ options . scan_type = :file
187
+ end
188
+
189
+ opts . on ( "-r" , "--report [REPORT]" , "Base Report Name" ) do |rep |
190
+ options . report_file = rep
191
+ end
192
+
193
+ opts . on ( "--reportDirectory [REPORTDIRECTORY]" , "Directory to output reports to" ) do |repdir |
194
+ options . report_directory = repdir
195
+ end
196
+
197
+ opts . on ( "-l" , "--log [LOGGER]" , "Log debugging messages to a file" ) do |logger |
198
+ options . logger = logger
199
+ end
200
+
201
+ opts . on ( "-h" , "--help" , "-?" , "--?" , "Get Help" ) do |help |
202
+ puts opts
203
+ exit
204
+ end
205
+
206
+ opts . on ( "-v" , "--version" , "Get Version" ) do |ver |
207
+ puts "Clair Analyzer Version #{ ClairAutoAnalyzer ::VERSION } "
208
+ exit
209
+ end
210
+
211
+ end
212
+
213
+ opts . parse! ( ARGV )
214
+
215
+ #Check for missing required options
216
+ unless ( options . scan_type == :file || options . scan_type == :directory )
217
+ puts "didn't get any arguments or missing scan type"
218
+ puts opts
219
+ exit
220
+ end
221
+
222
+ analysis = ClairAutoAnalyzer . new ( options )
223
+ analysis . run
224
+ end
0 commit comments