abide_dev_utils 0.5.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rubocop.yml +1 -1
- data/CODEOWNERS +1 -0
- data/abide_dev_utils.gemspec +9 -7
- data/itests.rb +138 -0
- data/lib/abide_dev_utils/cli/comply.rb +38 -20
- data/lib/abide_dev_utils/cli/puppet.rb +136 -11
- data/lib/abide_dev_utils/cli/xccdf.rb +62 -7
- data/lib/abide_dev_utils/comply.rb +446 -78
- data/lib/abide_dev_utils/errors/comply.rb +17 -0
- data/lib/abide_dev_utils/errors/gcloud.rb +27 -0
- data/lib/abide_dev_utils/errors/general.rb +9 -0
- data/lib/abide_dev_utils/errors/ppt.rb +12 -0
- data/lib/abide_dev_utils/errors/xccdf.rb +12 -0
- data/lib/abide_dev_utils/errors.rb +2 -0
- data/lib/abide_dev_utils/gcloud.rb +22 -0
- data/lib/abide_dev_utils/mixins.rb +16 -0
- data/lib/abide_dev_utils/output.rb +7 -3
- data/lib/abide_dev_utils/ppt/api.rb +219 -0
- data/lib/abide_dev_utils/ppt/class_utils.rb +184 -0
- data/lib/abide_dev_utils/ppt/coverage.rb +2 -3
- data/lib/abide_dev_utils/ppt/score_module.rb +162 -0
- data/lib/abide_dev_utils/ppt.rb +138 -49
- data/lib/abide_dev_utils/validate.rb +5 -1
- data/lib/abide_dev_utils/version.rb +1 -1
- data/lib/abide_dev_utils/xccdf.rb +628 -8
- data/lib/abide_dev_utils.rb +1 -0
- metadata +51 -16
- data/lib/abide_dev_utils/utils/general.rb +0 -9
- data/lib/abide_dev_utils/xccdf/cis/hiera.rb +0 -163
- data/lib/abide_dev_utils/xccdf/cis.rb +0 -3
@@ -1,130 +1,498 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'json'
|
3
4
|
require 'yaml'
|
4
5
|
require 'selenium-webdriver'
|
6
|
+
require 'abide_dev_utils/errors/comply'
|
7
|
+
require 'abide_dev_utils/gcloud'
|
5
8
|
require 'abide_dev_utils/output'
|
9
|
+
require 'abide_dev_utils/prompt'
|
10
|
+
require 'pry'
|
6
11
|
|
7
12
|
module AbideDevUtils
|
13
|
+
# Holds module methods and a class for dealing with Puppet Comply
|
8
14
|
module Comply
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
AbideDevUtils::Output.simple
|
28
|
-
|
29
|
-
|
30
|
-
|
15
|
+
include AbideDevUtils::Errors::Comply
|
16
|
+
|
17
|
+
def self.build_report(url, password, config = nil, **opts)
|
18
|
+
ReportScraper.new(url, config, **opts).build_report(password)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.compare_reports(report_a, report_b, **opts)
|
22
|
+
report_name = opts.fetch(:report_name, nil)
|
23
|
+
current_report = ScanReport.new.from_yaml(report_a)
|
24
|
+
last_report = if opts.fetch(:remote_storage, '') == 'gcloud'
|
25
|
+
report_name = report_b if report_name.nil?
|
26
|
+
ScanReport.new.from_yaml(ScanReport.fetch_report(name: report_b))
|
27
|
+
else
|
28
|
+
report_name = File.basename(report_b) if report_name.nil?
|
29
|
+
ScanReport.new.from_yaml(File.read(report_b))
|
30
|
+
end
|
31
|
+
result, details = current_report.report_comparison(last_report, check_goodness: true)
|
32
|
+
if result
|
33
|
+
AbideDevUtils::Output.simple('No negative differences detected...')
|
34
|
+
AbideDevUtils::Output.simple(JSON.pretty_generate(details))
|
35
|
+
else
|
36
|
+
AbideDevUtils::Output.simple('Negative differences detected!', stream: $stderr)
|
37
|
+
AbideDevUtils::Output.simple(JSON.pretty_generate(details), stream: $stderr)
|
38
|
+
end
|
39
|
+
if opts.fetch(:upload, false) && !opts.fetch(:remote_storage, '').empty? && !report_name.nil?
|
40
|
+
AbideDevUtils::Output.simple('Uploading current report...')
|
41
|
+
ScanReport.upload_report(File.expand_path(report_a), name: report_name)
|
42
|
+
AbideDevUtils::Output.simple('Successfully uploaded report.')
|
43
|
+
end
|
44
|
+
result
|
45
|
+
end
|
46
|
+
|
47
|
+
# Class that uses Selenium WebDriver to gather scan reports from Puppet Comply
|
48
|
+
class ReportScraper
|
49
|
+
attr_reader :timeout,
|
50
|
+
:username,
|
51
|
+
:status,
|
52
|
+
:ignorelist,
|
53
|
+
:onlylist,
|
54
|
+
:max_pagination,
|
55
|
+
:screenshot_on_error,
|
56
|
+
:page_source_on_error
|
57
|
+
|
58
|
+
def initialize(url, config = nil, **opts)
|
59
|
+
@url = url
|
60
|
+
@config = config
|
61
|
+
@opts = opts
|
62
|
+
@timeout = fetch_option(:timeout, 10).to_i
|
63
|
+
@username = fetch_option(:username, 'comply')
|
64
|
+
@status = fetch_option(:status)
|
65
|
+
@ignorelist = fetch_option(:ignorelist, [])
|
66
|
+
@onlylist = fetch_option(:onlylist, [])
|
67
|
+
@max_pagination = fetch_option(:max_pagination, 5).to_i
|
68
|
+
@screenshot_on_error = fetch_option(:screenshot_on_error, false)
|
69
|
+
@page_source_on_error = fetch_option(:page_source_on_error, false)
|
70
|
+
end
|
71
|
+
|
72
|
+
def build_report(password)
|
73
|
+
connect(password)
|
74
|
+
scrape_report
|
31
75
|
ensure
|
32
76
|
driver.quit
|
33
77
|
end
|
34
|
-
end
|
35
78
|
|
36
|
-
|
37
|
-
|
38
|
-
yield
|
39
|
-
rescue Selenium::WebDriver::Error::NoSuchElementError => e
|
40
|
-
AbideDevUtils::Output.simple "Ignored exception #{e}", stream: $stderr
|
79
|
+
def file_dir
|
80
|
+
@file_dir ||= File.expand_path('~/abide_dev_utils')
|
41
81
|
end
|
42
|
-
end
|
43
82
|
|
44
|
-
|
45
|
-
|
46
|
-
yield
|
83
|
+
def file_dir=(path)
|
84
|
+
@file_dir = new_file_dir(path)
|
47
85
|
end
|
48
|
-
end
|
49
86
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
87
|
+
private
|
88
|
+
|
89
|
+
attr_reader :progress
|
90
|
+
|
91
|
+
def fetch_option(option, default = nil)
|
92
|
+
return @opts.fetch(option, default) if @config.nil?
|
93
|
+
|
94
|
+
@opts.key?(option) ? @opts[option] : @config.fetch(option, default)
|
54
95
|
end
|
55
|
-
end
|
56
96
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
driver.find_element(id: 'password').send_keys password
|
61
|
-
driver.find_element(id: 'kc-login').click
|
62
|
-
end
|
97
|
+
def node_report_links
|
98
|
+
@node_report_links ||= find_node_report_links
|
99
|
+
end
|
63
100
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
101
|
+
def driver
|
102
|
+
@driver ||= new_driver
|
103
|
+
end
|
104
|
+
|
105
|
+
def output
|
106
|
+
AbideDevUtils::Output
|
107
|
+
end
|
108
|
+
|
109
|
+
def prompt
|
110
|
+
AbideDevUtils::Prompt
|
111
|
+
end
|
112
|
+
|
113
|
+
def new_progress(node_name)
|
114
|
+
@progress = AbideDevUtils::Output.progress title: "Building report for #{node_name}", total: nil
|
115
|
+
end
|
116
|
+
|
117
|
+
def new_driver
|
118
|
+
options = Selenium::WebDriver::Chrome::Options.new
|
119
|
+
options.args = @opts.fetch(:driveropts, %w[
|
120
|
+
--headless
|
121
|
+
--test-type
|
122
|
+
--disable-gpu
|
123
|
+
--no-sandbox
|
124
|
+
--no-first-run
|
125
|
+
--no-default-browser-check
|
126
|
+
--ignore-certificate-errors
|
127
|
+
--start-maximized
|
128
|
+
])
|
129
|
+
output.simple 'Starting headless Chrome...'
|
130
|
+
Selenium::WebDriver.for(:chrome, options: options)
|
131
|
+
end
|
132
|
+
|
133
|
+
def find_element(subject = driver, **kwargs)
|
134
|
+
driver.manage.window.resize_to(1920, 1080)
|
135
|
+
subject.find_element(**kwargs)
|
136
|
+
end
|
137
|
+
|
138
|
+
def wait_on(timeout: @timeout,
|
139
|
+
ignore_nse: false,
|
140
|
+
quit_driver: true,
|
141
|
+
quiet: false,
|
142
|
+
ignore: [Selenium::WebDriver::Error::NoSuchElementError],
|
143
|
+
&block)
|
144
|
+
raise 'wait_on must be passed a block' unless block
|
145
|
+
|
146
|
+
value = nil
|
147
|
+
if ignore_nse
|
148
|
+
begin
|
149
|
+
Selenium::WebDriver::Wait.new(ignore: [], timeout: timeout, interval: 1).until do
|
150
|
+
value = yield
|
151
|
+
end
|
152
|
+
rescue Selenium::WebDriver::Error::NoSuchElementError
|
153
|
+
return value
|
154
|
+
rescue StandardError => e
|
155
|
+
raise_error(e, AbideDevUtils::Comply::WaitOnError, quit_driver: quit_driver, quiet: quiet)
|
156
|
+
end
|
157
|
+
else
|
158
|
+
begin
|
159
|
+
Selenium::WebDriver::Wait.new(ignore: ignore, timeout: timeout, interval: 1).until do
|
160
|
+
value = yield
|
161
|
+
end
|
162
|
+
rescue StandardError => e
|
163
|
+
raise_error(e, AbideDevUtils::Comply::WaitOnError, quit_driver: quit_driver, quiet: quiet)
|
164
|
+
end
|
165
|
+
end
|
166
|
+
value
|
167
|
+
end
|
168
|
+
|
169
|
+
def new_file_dir(path)
|
170
|
+
return File.expand_path(path) if Dir.exist?(File.expand_path(path))
|
171
|
+
|
172
|
+
create_dir = prompt.yes_no("Directory #{path} does not exist. Create directory?")
|
173
|
+
return unless create_dir
|
174
|
+
|
175
|
+
require 'fileutils'
|
176
|
+
FileUtils.mkdir_p path
|
177
|
+
end
|
178
|
+
|
179
|
+
def raise_error(original, err_class = nil, quit_driver: true, quiet: false)
|
180
|
+
output.simple 'Something went wrong!' unless quiet
|
181
|
+
if screenshot_on_error
|
182
|
+
output.simple 'Taking a screenshot of current page state...' unless quiet
|
183
|
+
screenshot
|
184
|
+
end
|
185
|
+
|
186
|
+
if page_source_on_error
|
187
|
+
output.simple 'Saving page source of current page...' unless quiet
|
188
|
+
page_source
|
189
|
+
end
|
190
|
+
|
191
|
+
driver.quit if quit_driver
|
192
|
+
actual_err_class = err_class.nil? ? original.class : err_class
|
193
|
+
raise actual_err_class, original.message
|
194
|
+
end
|
195
|
+
|
196
|
+
def screenshot
|
197
|
+
driver.save_screenshot(File.join(file_dir, "comply_error_#{Time.now.to_i}.png"))
|
198
|
+
rescue Errno::ENOENT
|
199
|
+
save_default = prompt.yes_no(
|
200
|
+
"Directory #{file_dir} does not exist. Save screenshot to current directory?"
|
201
|
+
)
|
202
|
+
driver.save_screenshot(File.join(File.expand_path('.'), "comply_error_#{Time.now.to_i}.png")) if save_default
|
203
|
+
end
|
204
|
+
|
205
|
+
def page_source
|
206
|
+
File.open(File.join(file_dir, "comply_error_#{Time.now.to_i}.txt"), 'w') { |f| f.write(driver.page_source) }
|
207
|
+
rescue Errno::ENOENT
|
208
|
+
save_default = prompt.yes_no(
|
209
|
+
"Directory #{file_dir} does not exist. Save page source to current directory?"
|
210
|
+
)
|
211
|
+
if save_default
|
212
|
+
File.open(File.join(File.expand_path('.'), "comply_error_#{Time.now.to_i}.html"), 'w') do |f|
|
213
|
+
f.write(driver.page_source)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def bypass_ssl_warning_page
|
219
|
+
wait_on(ignore_nse: true) do
|
220
|
+
find_element(id: 'details-button').click
|
221
|
+
find_element(id: 'proceed-link').click
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
def login_to_comply(password)
|
226
|
+
output.simple "Logging into Comply at #{@url}..."
|
227
|
+
wait_on { driver.find_element(id: 'username') }
|
228
|
+
find_element(id: 'username').send_keys username
|
229
|
+
find_element(id: 'password').send_keys password
|
230
|
+
find_element(id: 'kc-login').click
|
231
|
+
error_text = wait_on(ignore_nse: true) { find_element(class: 'kc-feedback-text').text }
|
232
|
+
return if error_text.nil? || error_text.empty?
|
233
|
+
|
234
|
+
raise ComplyLoginFailedError, error_text
|
235
|
+
end
|
236
|
+
|
237
|
+
def filter_node_report_links(node_report_links)
|
238
|
+
if onlylist.empty? && ignorelist.empty?
|
239
|
+
output.simple 'No filters set, using all node reports...'
|
240
|
+
return node_report_links
|
241
|
+
end
|
72
242
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
links.each do |link|
|
77
|
-
if !onlylist.nil? && !onlylist.empty?
|
78
|
-
next unless onlylist.include?(link.text)
|
79
|
-
elsif !ignorelist.nil? && !ignorelist.empty?
|
80
|
-
next if ignorelist.include?(link.text)
|
243
|
+
unless onlylist.empty?
|
244
|
+
output.simple 'Onlylist found, filtering node reports...'
|
245
|
+
return node_report_links.select { |l| onlylist.include?(l[:name]) }
|
81
246
|
end
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
247
|
+
|
248
|
+
output.simple 'Ignorelist found, filtering node reports...'
|
249
|
+
node_report_links.reject { |l| ignorelist.include?(l[:name]) }
|
250
|
+
end
|
251
|
+
|
252
|
+
def find_node_report_table(subject)
|
253
|
+
wait_on { find_element(subject, class: 'metric-containers-failed-hosts-count') }
|
254
|
+
hosts = find_element(subject, class: 'metric-containers-failed-hosts-count')
|
255
|
+
table = find_element(hosts, class: 'rc-table')
|
256
|
+
wait_on { find_element(table, tag_name: 'tbody') }
|
257
|
+
find_element(table, tag_name: 'tbody')
|
258
|
+
end
|
259
|
+
|
260
|
+
def wait_for_node_report_links(table_body)
|
261
|
+
wait_on(timeout: 2, quit_driver: false, quiet: true) { table_body.find_element(tag_name: 'a') }
|
262
|
+
output.simple 'Found node report links...'
|
263
|
+
table_body.find_elements(tag_name: 'a')
|
264
|
+
rescue AbideDevUtils::Comply::WaitOnError
|
265
|
+
[]
|
266
|
+
end
|
267
|
+
|
268
|
+
def find_node_report_links
|
269
|
+
output.simple 'Finding nodes with scan reports...'
|
270
|
+
node_report_links = []
|
271
|
+
(1..max_pagination).each do |page|
|
272
|
+
output.simple "Trying page #{page}..."
|
273
|
+
driver.get("#{@url}/dashboard?page=#{page}&limit=50")
|
274
|
+
table_body = find_node_report_table(driver)
|
275
|
+
elems = wait_for_node_report_links(table_body)
|
276
|
+
if elems.empty?
|
277
|
+
output.simple "No links found on page #{page}, stopping search..."
|
278
|
+
break
|
279
|
+
end
|
280
|
+
|
281
|
+
elems.each do |elem|
|
282
|
+
node_report_links << { name: elem.text, url: elem.attribute('href') }
|
283
|
+
end
|
284
|
+
end
|
285
|
+
driver.get(@url)
|
286
|
+
filter_node_report_links(node_report_links)
|
287
|
+
end
|
288
|
+
|
289
|
+
def connect(password)
|
290
|
+
output.simple "Connecting to #{@url}..."
|
291
|
+
driver.get(@url)
|
292
|
+
bypass_ssl_warning_page
|
293
|
+
login_to_comply(password)
|
294
|
+
end
|
295
|
+
|
296
|
+
def normalize_cis_rec_name(name)
|
297
|
+
nstr = name.downcase
|
298
|
+
nstr.delete!('(/|\\|\+|:|\'|")')
|
299
|
+
nstr.gsub!(/(\s|\(|\)|-|\.)/, '_')
|
300
|
+
nstr.strip!
|
301
|
+
nstr
|
302
|
+
end
|
303
|
+
|
304
|
+
def scrape_report
|
305
|
+
output.simple 'Building scan reports, this may take a while...'
|
306
|
+
all_checks = {}
|
307
|
+
original_window = driver.window_handle
|
308
|
+
node_report_links.each do |link|
|
309
|
+
node_name = link[:name]
|
310
|
+
link_url = link[:url]
|
311
|
+
new_progress(node_name)
|
86
312
|
driver.manage.new_window(:tab)
|
313
|
+
progress.increment
|
87
314
|
wait_on { driver.window_handles.length == 2 }
|
88
315
|
progress.increment
|
89
316
|
driver.switch_to.window driver.window_handles[1]
|
90
317
|
driver.get(link_url)
|
91
|
-
wait_on {
|
318
|
+
wait_on { find_element(class: 'details-scan-info') }
|
92
319
|
progress.increment
|
93
|
-
wait_on {
|
320
|
+
wait_on { find_element(class: 'details-table') }
|
94
321
|
progress.increment
|
95
|
-
report = {}
|
96
|
-
|
97
|
-
scan_info_table = driver.find_element(class: 'details-scan-info')
|
322
|
+
report = { 'scan_results' => {} }
|
323
|
+
scan_info_table = find_element(class: 'details-scan-info')
|
98
324
|
scan_info_table_rows = scan_info_table.find_elements(tag_name: 'tr')
|
99
325
|
progress.increment
|
100
|
-
check_table_body =
|
326
|
+
check_table_body = find_element(tag_name: 'tbody')
|
101
327
|
check_table_rows = check_table_body.find_elements(tag_name: 'tr')
|
102
328
|
progress.increment
|
103
329
|
scan_info_table_rows.each do |row|
|
104
|
-
key =
|
105
|
-
value =
|
106
|
-
report[key.downcase.
|
330
|
+
key = find_element(row, tag_name: 'h5').text
|
331
|
+
value = find_element(row, tag_name: 'strong').text
|
332
|
+
report[key.downcase.tr(':', '').tr(' ', '_')] = value
|
107
333
|
progress.increment
|
108
334
|
end
|
109
335
|
check_table_rows.each do |row|
|
110
336
|
chk_objs = row.find_elements(tag_name: 'td')
|
111
337
|
chk_objs.map!(&:text)
|
112
338
|
if status.nil? || status.include?(chk_objs[1].downcase)
|
113
|
-
|
114
|
-
|
115
|
-
|
339
|
+
name_parts = chk_objs[0].match(/^([0-9.]+) (.+)$/)
|
340
|
+
key = normalize_cis_rec_name(name_parts[2])
|
341
|
+
unless report['scan_results'].key?(chk_objs[1])
|
342
|
+
report['scan_results'][chk_objs[1]] = {}
|
343
|
+
end
|
344
|
+
report['scan_results'][chk_objs[1]][key] = {
|
345
|
+
'name' => name_parts[2].chomp,
|
346
|
+
'number' => name_parts[1].chomp
|
116
347
|
}
|
117
348
|
end
|
118
349
|
progress.increment
|
119
350
|
end
|
120
351
|
all_checks[node_name] = report
|
121
352
|
driver.close
|
122
|
-
|
353
|
+
output.simple "Created report for #{node_name}"
|
354
|
+
rescue ::StandardError => e
|
355
|
+
raise_error(e)
|
123
356
|
ensure
|
124
357
|
driver.switch_to.window original_window
|
125
358
|
end
|
359
|
+
all_checks
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
# Contains multiple NodeScanReport objects
|
364
|
+
class ScanReport
|
365
|
+
attr_reader :node_scan_reports
|
366
|
+
|
367
|
+
def from_yaml(report)
|
368
|
+
@scan_report = if report.is_a? Hash
|
369
|
+
report
|
370
|
+
elsif File.file?(report)
|
371
|
+
File.open(report.to_s, 'r') { |f| YAML.safe_load(f.read) }
|
372
|
+
else
|
373
|
+
YAML.safe_load(report)
|
374
|
+
end
|
375
|
+
@node_scan_reports = build_node_scan_reports
|
376
|
+
self
|
377
|
+
end
|
378
|
+
|
379
|
+
def to_h
|
380
|
+
node_scan_reports.each_with_object({}) do |node, h|
|
381
|
+
h[node.name] = node.hash
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
def to_yaml
|
386
|
+
to_h.to_yaml
|
387
|
+
end
|
388
|
+
|
389
|
+
def self.storage_bucket
|
390
|
+
@storage_bucket ||= AbideDevUtils::GCloud.storage_bucket
|
391
|
+
end
|
392
|
+
|
393
|
+
def self.fetch_report(name: 'comply_report.yaml')
|
394
|
+
report = storage_bucket.file(name)
|
395
|
+
report.download.read
|
396
|
+
end
|
397
|
+
|
398
|
+
def self.upload_report(report, name: 'comply_report.yaml')
|
399
|
+
storage_bucket.create_file(report, name)
|
400
|
+
end
|
401
|
+
|
402
|
+
def report_comparison(other, check_goodness: false)
|
403
|
+
comparison = []
|
404
|
+
node_scan_reports.zip(other.node_scan_reports).each do |cr, lr|
|
405
|
+
comparison << { cr.name => { diff: {}, node_presense: :new } } if lr.nil?
|
406
|
+
comparison << { lr.name => { diff: {}, node_presense: :dropped } } if cr.nil?
|
407
|
+
comparison << { cr.name => { diff: cr.diff(lr), node_presence: :same } } unless cr.nil? || lr.nil?
|
408
|
+
end
|
409
|
+
comparison.inject(&:merge)
|
410
|
+
return good_comparison?(comparison) if check_goodness
|
411
|
+
|
412
|
+
compairison
|
413
|
+
end
|
414
|
+
|
415
|
+
def good_comparison?(report_comparison)
|
416
|
+
good = true
|
417
|
+
not_good = {}
|
418
|
+
report_comparison.each do |node_report|
|
419
|
+
node_name = node_report.keys[0]
|
420
|
+
report = node_report[node_name]
|
421
|
+
next if report[:diff].empty?
|
422
|
+
|
423
|
+
not_good[node_name] = {}
|
424
|
+
unless report.dig(:diff, :passing, :other).nil?
|
425
|
+
good = false
|
426
|
+
not_good[node_name][:new_not_passing] = report[:diff][:passing][:other]
|
427
|
+
end
|
428
|
+
unless report.dig(:diff, :failing, :self).nil?
|
429
|
+
good = false
|
430
|
+
not_good[node_name][:new_failing] = report[:diff][:failing][:self]
|
431
|
+
end
|
432
|
+
end
|
433
|
+
[good, not_good]
|
434
|
+
end
|
435
|
+
|
436
|
+
private
|
437
|
+
|
438
|
+
def build_node_scan_reports
|
439
|
+
node_scan_reports = []
|
440
|
+
@scan_report.each do |node_name, node_hash|
|
441
|
+
node_scan_reports << NodeScanReport.new(node_name, node_hash)
|
442
|
+
end
|
443
|
+
node_scan_reports.sort_by(&:name)
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Class representation of a Comply node scan report
|
448
|
+
class NodeScanReport
|
449
|
+
attr_reader :name, :passing, :failing, :error, :not_checked, :informational, :benchmark, :last_scan, :profile
|
450
|
+
|
451
|
+
DIFF_PROPERTIES = %i[passing failing error not_checked informational].freeze
|
452
|
+
|
453
|
+
def initialize(node_name, node_hash)
|
454
|
+
@name = node_name
|
455
|
+
@hash = node_hash
|
456
|
+
@passing = node_hash.dig('scan_results', 'Pass') || {}
|
457
|
+
@failing = node_hash.dig('scan_results', 'Fail') || {}
|
458
|
+
@error = node_hash.dig('scan_results', 'Error') || {}
|
459
|
+
@not_checked = node_hash.dig('scan_results', 'Not checked') || {}
|
460
|
+
@informational = node_hash.dig('scan_results', 'Informational') || {}
|
461
|
+
@benchmark = node_hash['benchmark']
|
462
|
+
@last_scan = node_hash['last_scan']
|
463
|
+
@profile = node_hash.fetch('custom_profile', nil) || node_hash.fetch('profile', nil)
|
464
|
+
create_equality_methods
|
465
|
+
end
|
466
|
+
|
467
|
+
def diff(other)
|
468
|
+
diff = {}
|
469
|
+
DIFF_PROPERTIES.each do |prop|
|
470
|
+
diff[prop] = send("#{prop.to_s}_equal?".to_sym, other.send(prop)) ? {} : property_diff(prop, other)
|
471
|
+
end
|
472
|
+
diff
|
473
|
+
end
|
474
|
+
|
475
|
+
private
|
476
|
+
|
477
|
+
def create_equality_methods
|
478
|
+
DIFF_PROPERTIES.each do |prop|
|
479
|
+
meth_name = "#{prop.to_s}_equal?"
|
480
|
+
self.class.define_method(meth_name) do |other|
|
481
|
+
property_equal?(prop, other)
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
def property_diff(property, other)
|
487
|
+
{
|
488
|
+
self: send(property).keys - other.send(property).keys,
|
489
|
+
other: other.send(property).keys - send(property).keys
|
490
|
+
}
|
491
|
+
end
|
492
|
+
|
493
|
+
def property_equal?(property, other_property)
|
494
|
+
send(property) == other_property
|
126
495
|
end
|
127
|
-
all_checks
|
128
496
|
end
|
129
497
|
end
|
130
498
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'abide_dev_utils/errors/base'
|
4
|
+
|
5
|
+
module AbideDevUtils
|
6
|
+
module Errors
|
7
|
+
module Comply
|
8
|
+
class ComplyLoginFailedError < GenericError
|
9
|
+
@default = 'Failed to login to Comply:'
|
10
|
+
end
|
11
|
+
|
12
|
+
class WaitOnError < GenericError
|
13
|
+
@default = 'wait_on failed due to error:'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'abide_dev_utils/errors/base'
|
4
|
+
|
5
|
+
module AbideDevUtils
|
6
|
+
module Errors
|
7
|
+
module GCloud
|
8
|
+
class MissingCredentialsError < GenericError
|
9
|
+
@default = <<~EOERR
|
10
|
+
Storage credentials not given. Please set environment variable ABIDE_GCLOUD_CREDENTIALS.
|
11
|
+
EOERR
|
12
|
+
end
|
13
|
+
|
14
|
+
class MissingProjectError < GenericError
|
15
|
+
@default = <<~EOERR
|
16
|
+
Storage project not given. Please set the environment variable ABIDE_GCLOUD_PROJECT.
|
17
|
+
EOERR
|
18
|
+
end
|
19
|
+
|
20
|
+
class MissingBucketNameError < GenericError
|
21
|
+
@default = <<~EOERR
|
22
|
+
Storage bucket name not given. Please set the environment variable ABIDE_GCLOUD_BUCKET.
|
23
|
+
EOERR
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -29,6 +29,11 @@ module AbideDevUtils
|
|
29
29
|
@default = 'Path is not a directory:'
|
30
30
|
end
|
31
31
|
|
32
|
+
# Raised when a file extension is not correct
|
33
|
+
class FileExtensionIncorrectError < GenericError
|
34
|
+
@default = 'File extension does not match specified extension:'
|
35
|
+
end
|
36
|
+
|
32
37
|
# Raised when a searched for service is not found in the parser
|
33
38
|
class ServiceNotFoundError < GenericError
|
34
39
|
@default = 'Service not found:'
|
@@ -48,5 +53,9 @@ module AbideDevUtils
|
|
48
53
|
class NotHashableError < GenericError
|
49
54
|
@default = 'Object does not respond to #to_hash or #to_h:'
|
50
55
|
end
|
56
|
+
|
57
|
+
class CliOptionsConflict < GenericError
|
58
|
+
@default = "Console options conflict."
|
59
|
+
end
|
51
60
|
end
|
52
61
|
end
|
@@ -24,6 +24,18 @@ module AbideDevUtils
|
|
24
24
|
class FailedToCreateFileError < GenericError
|
25
25
|
@default = 'Failed to create file:'
|
26
26
|
end
|
27
|
+
|
28
|
+
class ClassFileNotFoundError < GenericError
|
29
|
+
@default = 'Class file was not found:'
|
30
|
+
end
|
31
|
+
|
32
|
+
class ClassDeclarationNotFoundError < GenericError
|
33
|
+
@default = 'Class declaration was not found:'
|
34
|
+
end
|
35
|
+
|
36
|
+
class InvalidClassNameError < GenericError
|
37
|
+
@default = 'Not a valid Puppet class name:'
|
38
|
+
end
|
27
39
|
end
|
28
40
|
end
|
29
41
|
end
|
@@ -12,5 +12,17 @@ module AbideDevUtils
|
|
12
12
|
class StrategyInvalidError < GenericError
|
13
13
|
@default = 'Invalid strategy selected. Should be either \'name\' or \'num\''
|
14
14
|
end
|
15
|
+
|
16
|
+
class ControlPartsError < GenericError
|
17
|
+
@default = 'Failed to extract parts from control name:'
|
18
|
+
end
|
19
|
+
|
20
|
+
class ProfilePartsError < GenericError
|
21
|
+
@default = 'Failed to extract parts from profile name:'
|
22
|
+
end
|
23
|
+
|
24
|
+
class UnsupportedXCCDFError < GenericError
|
25
|
+
@default = "XCCDF type is unsupported!"
|
26
|
+
end
|
15
27
|
end
|
16
28
|
end
|