abide_dev_utils 0.5.2 → 0.9.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/.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
|