-
Notifications
You must be signed in to change notification settings - Fork 17
/
dockerfile-parser.rb
214 lines (178 loc) · 6.03 KB
/
dockerfile-parser.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env ruby
# TODO: Include parser Directive options (e.g. changing comment characters) https://docs.docker.com/engine/reference/builder/#parser-directives
class DockerfileAnalyzer
VERSION='0.0.1'
def initialize(commandlineopts)
require 'logger'
@options = commandlineopts
@base_dir = @options.report_directory
@scan_dir = @options.scan_directory
if !File.exists?(@base_dir)
Dir.mkdir(@base_dir)
end
if @options.logger
@log = Logger.new(@base_dir + '/' + @options.logger)
else
@log = Logger.new(STDOUT)
end
#Change before release
@log.level = Logger::DEBUG
@log.debug("Log created at " + Time.now.to_s)
@log.debug("Scan type is : " + @options.scan_type.to_s)
@log.debug("Directory being scanned is : " + @options.scan_directory) if @options.scan_type == :directory
puts @options.report_file
@log.debug("File being scanned is : " + @options.scan_file) if @options.scan_type == :file
end
def run
case @options.scan_type
when :directory
scan_dirs
parse_files
analyze_files
report
when :file
@scan_files = Array.new
@scan_files << @options.scan_file
parse_files
analyze_files
report
end
end
def scan_dirs
end
def parse_files
@parsed_dockerfiles = Array.new
@log.debug("Files to be looked at : " + @scan_files.join(', '))
@scan_files.each do |file|
results = Hash.new
file_contents = File.open(file,'r').readlines
#Get rid of the line endings
file_contents.each {|line| line.chomp!}
#Remove Blank Lines
file_contents.delete_if {|line| line.length==0}
#Get all the continued lines onto a single line
file_contents.each_index do |i|
while file_contents[i] =~ /\\$/
file_contents[i].sub!(/\\$/,'')
file_contents[i] = file_contents[i] + file_contents[i+1].lstrip
file_contents.delete_at(i+1)
end
end
results[:dockerfile] = file
#Not gathering all the commands here just the ones we want to analyse
results[:from] = Array.new
results[:run] = Array.new
#CMD or ENTRYPOINT
results[:command] = Array.new
results[:add] = Array.new
results[:copy] = Array.new
results[:env] = Array.new
results[:user] = Array.new
file_contents.each do |line|
cmd = line.split(' ')[0]
@log.debug ("Working on a #{cmd}")
case cmd
when "CMD" || "ENTRYPOINT"
results[:command] << line
when "ENV" || "ARG"
results[:env] << line
when "RUN"
results[:run] << line
when "USER"
results[:user] << line
when "FROM"
results[:from] << line
when "ADD"
results[:add] << line
when "COPY"
results[:copy] << line
end
end
@parsed_dockerfiles << results
end
end
def analyze_files
@findings = Hash.new
@parsed_dockerfiles.each do |results|
target = results[:dockerfile]
@findings[target] = Hash.new
#If there is no user line in the Dockerfile it'll default to root
unless results[:user].length > 0
@findings[target][:root_container] = true
end
# We can't clearly say if ENV or ARG are problems so we'll print
if results[:env].length > 0
@findings[target][:env_to_check] = Array.new
results[:env].each {|env| @findings[target][:env_to_check] << env}
end
# We look for things like wget and curl to check software installs
results[:run].each do |r|
if r =~ /[wget|curl]/
@findings[target][:run_to_check] = Array.new
@findings[target][:run_to_check] << r
end
end
# FROM lines need a manual check to see whether they're good or not
@findings[target][:from_to_check] = Array.new
results[:from].each {|f| @findings[target][:from_to_check] << f}
# latest tags are generallly a bad idea, let's flag that
results[:from].each do |f|
if f =~ /latest$/
@findings[target][:latest] = true
end
end
# Minor point, but we should recommend COPY over ADD
if results[:add].length > 0
@findings[target][:uses_add] = true
end
end
end
def report
end
end
if __FILE__ == $0
require 'ostruct'
require 'optparse'
options = OpenStruct.new
options.report_directory = Dir.pwd
options.report_file = "docker-analysis-report"
options.scan_directory = Dir.pwd
options.scan_file = 'Lorem'
options.scan_type = :notset
options.recursive = false
opts = OptionParser.new do |opts|
opts.banner = "Dockerfile Analyzer #{DockerfileAnalyzer::VERSION}"
opts.on("-d", "--directory [DIRECTORY]", "Directory to scan for Dockerfiles") do |dir|
options.scan_directory = dir
options.scan_type = :directory
end
opts.on("--recursive", "scan for Dockerfiles recursively") do |recurse|
options.recursive = true
end
opts.on("-f", "--file [FILE]", "File to scan for issues") do |file|
options.scan_file = file
options.scan_type = :file
end
opts.on("--reportDirectory [REPORTDIRECTORY", "Directory for the report") do |repdir|
options.report_directory = repdir
end
opts.on("-l", "--logger [LOGGER]", "Log debugging messages to a file") do |logger|
options.logger = logger
end
opts.on("-h", "--help", "-?", "--?", "Get Help") do |help|
puts opts
exit
end
opts.on("-v", "--version", "Get Version") do |ver|
puts "Dockerfile Analyzer Version #{DockerfileAnalyzer::VERSION}"
end
end
opts.parse!(ARGV)
unless (options.scan_type == :file || options.scan_type == :directory)
puts "Didn't get any arguments or missing scan type"
puts opts
exit
end
analysis = DockerfileAnalyzer.new(options)
analysis.run
end