Test Failed
Push — master ( ece3f2...27f45a )
by Jan
02:21 queued 12s
created

ssg.build_profile   F

Complexity

Total Complexity 87

Size/Duplication

Total Lines 692
Duplicated Lines 0 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 87
eloc 567
dl 0
loc 692
ccs 0
cts 333
cp 0
rs 2
c 0
b 0
f 0

5 Methods

Rating   Name   Duplication   Size   Complexity  
A XCCDFBenchmark.console_print() 0 13 4
A RuleStats.__init__() 0 22 1
F XCCDFBenchmark.__init__() 0 38 15
F XCCDFBenchmark.show_profile_stats() 0 329 45
F XCCDFBenchmark.get_profile_stats() 0 234 19

1 Function

Rating   Name   Duplication   Size   Complexity  
A make_name_to_profile_mapping() 0 12 3

How to fix   Complexity   

Complexity

Complex classes like ssg.build_profile often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
from __future__ import absolute_import
2
from __future__ import print_function
3
4
import os
5
import sys
6
7
from .build_yaml import ProfileWithInlinePolicies
8
from .xml import ElementTree
9
from .constants import XCCDF11_NS as xccdf_ns
10
from .constants import oval_namespace as oval_ns
11
from .constants import bash_system as bash_rem_system
12
from .constants import ansible_system as ansible_rem_system
13
from .constants import ignition_system as ignition_rem_system
14
from .constants import kubernetes_system as kubernetes_rem_system
15
from .constants import puppet_system as puppet_rem_system
16
from .constants import anaconda_system as anaconda_rem_system
17
from .constants import cce_uri
18
from .constants import ssg_version_uri
19
from .constants import stig_ns, cis_ns, generic_stig_ns, hipaa_ns, anssi_ns
20
from .constants import ospp_ns, cui_ns, xslt_ns
21
console_width = 80
22
23
24
def make_name_to_profile_mapping(profile_files, env_yaml):
25
    name_to_profile = {}
26
    for f in profile_files:
27
        try:
28
            p = ProfileWithInlinePolicies.from_yaml(f, env_yaml)
29
            name_to_profile[p.id_] = p
30
        except Exception as exc:
31
            # The profile is probably doc-incomplete
32
            msg = "Not building profile from {fname}: {err}".format(
33
                fname=f, err=str(exc))
34
            print(msg, file=sys.stderr)
35
    return name_to_profile
36
37
38
class RuleStats(object):
39
    """
40
    Class representing the content of a rule for statistics generation
41
    purposes.
42
    """
43
    def __init__(self, rid=None, roval=None,
44
                 rbash_fix=None, ransible_fix=None,
45
                 rignition_fix=None, rkubernetes_fix=None,
46
                 rpuppet_fix=None, ranaconda_fix=None, rcce=None,
47
                 stig_id=None, cis_ref=None, hipaa_ref=None,
48
                 anssi_ref=None, ospp_ref=None, cui_ref=None):
49
        self.dict = {
50
            'id': rid,
51
            'oval': roval,
52
            'bash_fix': rbash_fix,
53
            'ansible_fix': ransible_fix,
54
            'ignition_fix': rignition_fix,
55
            'kubernetes_fix': rkubernetes_fix,
56
            'puppet_fix': rpuppet_fix,
57
            'anaconda_fix': ranaconda_fix,
58
            'cce': rcce,
59
            'stig_id': stig_id,
60
            'cis_ref': cis_ref,
61
            'hipaa_ref': hipaa_ref,
62
            'anssi_ref': anssi_ref,
63
            'ospp_ref': ospp_ref,
64
            'cui_ref': cui_ref,
65
        }
66
67
68
class XCCDFBenchmark(object):
69
    """
70
    Class for processing an XCCDF benchmark to generate
71
    statistics about the profiles contained within it.
72
    """
73
74
    def __init__(self, filepath, product=""):
75
        self.tree = None
76
        try:
77
            with open(filepath, 'r') as xccdf_file:
78
                file_string = xccdf_file.read()
79
                tree = ElementTree.fromstring(file_string)
80
                self.tree = tree
81
        except IOError as ioerr:
82
            print("%s" % ioerr)
83
            sys.exit(1)
84
85
        self.indexed_rules = {}
86
        for rule in self.tree.findall(".//{%s}Rule" % (xccdf_ns)):
87
            rule_id = rule.get("id")
88
            if rule_id is None:
89
                raise RuntimeError("Can't index a rule with no id attribute!")
90
91
            if rule_id in self.indexed_rules:
92
                raise RuntimeError("Multiple rules exist with same id attribute: %s!" % rule_id)
93
94
            self.indexed_rules[rule_id] = rule
95
96
        self.cis_ns = cis_ns
97
        self.stig_ns = stig_ns
98
        if product:
99
            constants_path = os.path.join(product, "transforms/constants.xslt")
100
            if os.path.exists(constants_path):
101
                root = ElementTree.parse(constants_path)
102
                cis_var = root.find('./{%s}variable[@name="cisuri"]' % (xslt_ns))
103
                if cis_var is not None and cis_var.text:
104
                    self.cis_ns = cis_var.text
105
106
                stig_var = root.find('./{%s}variable[@name="disa-stigs-uri"]' % (xslt_ns))
107
                if stig_var is not None and stig_var.text:
108
                    self.stig_ns = stig_var.text
109
                elif (stig_var and 'select' in stig_var.attrib and
110
                      stig_var.attrib['select'] == '$disa-stigs-os-unix-linux-uri'):
111
                    self.stig_ns = generic_stig_ns
112
113
114
    def get_profile_stats(self, profile):
115
        """Obtain statistics for the profile"""
116
117
        # Holds the intermediary statistics for profile
118
        profile_stats = {
119
            'profile_id': "",
120
            'ssg_version': 0,
121
            'rules': [],
122
            'rules_count': 0,
123
            'implemented_ovals': [],
124
            'implemented_ovals_pct': 0,
125
            'missing_ovals': [],
126
            'implemented_bash_fixes': [],
127
            'implemented_bash_fixes_pct': 0,
128
            'implemented_ansible_fixes': [],
129
            'implemented_ansible_fixes_pct': 0,
130
            'implemented_ignition_fixes': [],
131
            'implemented_ignition_fixes_pct': 0,
132
            'implemented_kubernetes_fixes': [],
133
            'implemented_kubernetes_fixes_pct': 0,
134
            'implemented_puppet_fixes': [],
135
            'implemented_puppet_fixes_pct': 0,
136
            'implemented_anaconda_fixes': [],
137
            'implemented_anaconda_fixes_pct': 0,
138
            'missing_bash_fixes': [],
139
            'missing_ansible_fixes': [],
140
            'missing_ignition_fixes': [],
141
            'missing_kubernetes_fixes': [],
142
            'missing_puppet_fixes': [],
143
            'missing_anaconda_fixes': [],
144
            'assigned_cces': [],
145
            'assigned_cces_pct': 0,
146
            'missing_cces': [],
147
            'missing_stig_ids': [],
148
            'missing_cis_refs': [],
149
            'missing_hipaa_refs': [],
150
            'missing_anssi_refs': [],
151
            'missing_ospp_refs': [],
152
            'missing_cui_refs': [],
153
            'ansible_parity': [],
154
        }
155
156
        rule_stats = []
157
        ssg_version_elem = self.tree.find("./{%s}version[@update=\"%s\"]" %
158
                                          (xccdf_ns, ssg_version_uri))
159
160
        rules = []
161
162
        if profile == "all":
163
            # "all" is a virtual profile that selects all rules
164
            rules = self.indexed_rules.values()
165
        else:
166
            xccdf_profile = self.tree.find("./{%s}Profile[@id=\"%s\"]" %
167
                                           (xccdf_ns, profile))
168
            if xccdf_profile is None:
169
                print("No such profile \"%s\" found in the benchmark!"
170
                      % profile)
171
                print("* Available profiles:")
172
                profiles_avail = self.tree.findall("./{%s}Profile" % (xccdf_ns))
173
                for _profile in profiles_avail:
174
                    print("** %s" % _profile.get('id'))
175
                sys.exit(1)
176
177
            # This will only work with SSG where the (default) profile has zero
178
            # selected rule. If you want to reuse this for custom content, you
179
            # need to change this to look into Rule/@selected
180
            selects = xccdf_profile.findall("./{%s}select[@selected=\"true\"]" %
181
                                            xccdf_ns)
182
183
            for select in selects:
184
                rule_id = select.get('idref')
185
                xccdf_rule = self.indexed_rules.get(rule_id)
186
                if xccdf_rule is not None:
187
                    # it could also be a Group
188
                    rules.append(xccdf_rule)
189
190
        for rule in rules:
191
            if rule is not None:
192
                oval = rule.find("./{%s}check[@system=\"%s\"]" %
193
                                 (xccdf_ns, oval_ns))
194
                bash_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
195
                                     (xccdf_ns, bash_rem_system))
196
                ansible_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
197
                                        (xccdf_ns, ansible_rem_system))
198
                ignition_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
199
                                        (xccdf_ns, ignition_rem_system))
200
                kubernetes_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
201
                                           (xccdf_ns, kubernetes_rem_system))
202
                puppet_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
203
                                       (xccdf_ns, puppet_rem_system))
204
                anaconda_fix = rule.find("./{%s}fix[@system=\"%s\"]" %
205
                                         (xccdf_ns, anaconda_rem_system))
206
                cce = rule.find("./{%s}ident[@system=\"%s\"]" %
207
                                (xccdf_ns, cce_uri))
208
                stig_id = rule.find("./{%s}reference[@href=\"%s\"]" %
209
                                    (xccdf_ns, self.stig_ns))
210
                cis_ref = rule.find("./{%s}reference[@href=\"%s\"]" %
211
                                    (xccdf_ns, self.cis_ns))
212
                hipaa_ref = rule.find("./{%s}reference[@href=\"%s\"]" %
213
                                    (xccdf_ns, hipaa_ns))
214
                anssi_ref = rule.find("./{%s}reference[@href=\"%s\"]" %
215
                                    (xccdf_ns, anssi_ns))
216
                ospp_ref = rule.find("./{%s}reference[@href=\"%s\"]" %
217
                                     (xccdf_ns, ospp_ns))
218
                cui_ref = rule.find("./{%s}reference[@href=\"%s\"]" %
219
                                    (xccdf_ns, cui_ns))
220
221
                rule_stats.append(
222
                    RuleStats(rule.get("id"), oval,
223
                              bash_fix, ansible_fix, ignition_fix,
224
                              kubernetes_fix, puppet_fix, anaconda_fix,
225
                              cce, stig_id, cis_ref, hipaa_ref, anssi_ref,
226
                              ospp_ref, cui_ref)
227
                )
228
229
        if not rule_stats:
230
            print('Unable to retrieve statistics for %s profile' % profile)
231
            sys.exit(1)
232
233
        rule_stats.sort(key=lambda r: r.dict['id'])
234
235
        for rule in rule_stats:
236
            profile_stats['rules'].append(rule.dict['id'])
237
238
        profile_stats['profile_id'] = profile
239
        if ssg_version_elem is not None:
240
            profile_stats['ssg_version'] = \
241
                'SCAP Security Guide %s' % ssg_version_elem.text
242
        profile_stats['rules_count'] = len(rule_stats)
243
        profile_stats['implemented_ovals'] = \
244
            [x.dict['id'] for x in rule_stats if x.dict['oval'] is not None]
245
        profile_stats['implemented_ovals_pct'] = \
246
            float(len(profile_stats['implemented_ovals'])) / \
247
            profile_stats['rules_count'] * 100
248
        profile_stats['missing_ovals'] = \
249
            [x.dict['id'] for x in rule_stats if x.dict['oval'] is None]
250
251
        profile_stats['implemented_bash_fixes'] = \
252
            [x.dict['id'] for x in rule_stats if x.dict['bash_fix'] is not None]
253
        profile_stats['implemented_bash_fixes_pct'] = \
254
            float(len(profile_stats['implemented_bash_fixes'])) / \
255
            profile_stats['rules_count'] * 100
256
        profile_stats['missing_bash_fixes'] = \
257
            [x.dict['id'] for x in rule_stats if x.dict['bash_fix'] is None]
258
259
        profile_stats['implemented_ansible_fixes'] = \
260
            [x.dict['id'] for x in rule_stats if x.dict['ansible_fix'] is not None]
261
        profile_stats['implemented_ansible_fixes_pct'] = \
262
            float(len(profile_stats['implemented_ansible_fixes'])) / \
263
            profile_stats['rules_count'] * 100
264
        profile_stats['missing_ansible_fixes'] = \
265
            [x.dict['id'] for x in rule_stats if x.dict['ansible_fix'] is None]
266
267
        profile_stats['implemented_ignition_fixes'] = \
268
            [x.dict['id'] for x in rule_stats if x.dict['ignition_fix'] is not None]
269
        profile_stats['implemented_ignition_fixes_pct'] = \
270
            float(len(profile_stats['implemented_ignition_fixes'])) / \
271
            profile_stats['rules_count'] * 100
272
        profile_stats['missing_ignition_fixes'] = \
273
            [x.dict['id'] for x in rule_stats if x.dict['ignition_fix'] is None]
274
275
        profile_stats['implemented_kubernetes_fixes'] = \
276
            [x.dict['id'] for x in rule_stats if x.dict['kubernetes_fix'] is not None]
277
        profile_stats['implemented_kubernetes_fixes_pct'] = \
278
            float(len(profile_stats['implemented_kubernetes_fixes'])) / \
279
            profile_stats['rules_count'] * 100
280
        profile_stats['missing_kubernetes_fixes'] = \
281
            [x.dict['id'] for x in rule_stats if x.dict['kubernetes_fix'] is None]
282
283
        profile_stats['implemented_puppet_fixes'] = \
284
            [x.dict['id'] for x in rule_stats if x.dict['puppet_fix'] is not None]
285
        profile_stats['implemented_puppet_fixes_pct'] = \
286
            float(len(profile_stats['implemented_puppet_fixes'])) / \
287
            profile_stats['rules_count'] * 100
288
        profile_stats['missing_puppet_fixes'] = \
289
            [x.dict['id'] for x in rule_stats if x.dict['puppet_fix'] is None]
290
291
        profile_stats['implemented_anaconda_fixes'] = \
292
            [x.dict['id'] for x in rule_stats if x.dict['anaconda_fix'] is not None]
293
294
        profile_stats['missing_stig_ids'] = []
295
        if 'stig' in profile_stats['profile_id']:
296
            profile_stats['missing_stig_ids'] = \
297
                [x.dict['id'] for x in rule_stats if x.dict['stig_id'] is None]
298
299
        profile_stats['missing_cis_refs'] = []
300
        if 'cis' in profile_stats['profile_id']:
301
            profile_stats['missing_cis_refs'] = \
302
                [x.dict['id'] for x in rule_stats if x.dict['cis_ref'] is None]
303
304
        profile_stats['missing_hipaa_refs'] = []
305
        if 'hipaa' in profile_stats['profile_id']:
306
            profile_stats['missing_hipaa_refs'] = \
307
                [x.dict['id'] for x in rule_stats if x.dict['hipaa_ref'] is None]
308
309
        profile_stats['missing_anssi_refs'] = []
310
        if 'anssi' in profile_stats['profile_id']:
311
            profile_stats['missing_anssi_refs'] = \
312
                [x.dict['id'] for x in rule_stats if x.dict['anssi_ref'] is None]
313
314
        profile_stats['missing_ospp_refs'] = []
315
        if 'ospp' in profile_stats['profile_id']:
316
            profile_stats['missing_ospp_refs'] = \
317
                [x.dict['id'] for x in rule_stats if x.dict['ospp_ref'] is None]
318
319
        profile_stats['missing_cui_refs'] = []
320
        if 'cui' in profile_stats['profile_id']:
321
            profile_stats['missing_cui_refs'] = \
322
                [x.dict['id'] for x in rule_stats if x.dict['cui_ref'] is None]
323
324
        profile_stats['implemented_anaconda_fixes_pct'] = \
325
            float(len(profile_stats['implemented_anaconda_fixes'])) / \
326
            profile_stats['rules_count'] * 100
327
        profile_stats['missing_anaconda_fixes'] = \
328
            [x.dict['id'] for x in rule_stats if x.dict['anaconda_fix'] is None]
329
330
        profile_stats['assigned_cces'] = \
331
            [x.dict['id'] for x in rule_stats if x.dict['cce'] is not None]
332
        profile_stats['assigned_cces_pct'] = \
333
            float(len(profile_stats['assigned_cces'])) / \
334
            profile_stats['rules_count'] * 100
335
        profile_stats['missing_cces'] = \
336
            [x.dict['id'] for x in rule_stats if x.dict['cce'] is None]
337
338
        profile_stats['ansible_parity'] = \
339
            [rule_id for rule_id in profile_stats["missing_ansible_fixes"] if rule_id not in profile_stats["missing_bash_fixes"]]
340
        profile_stats['ansible_parity_pct'] = 0
341
        if len(profile_stats['implemented_bash_fixes']):
342
            profile_stats['ansible_parity_pct'] = \
343
                float(len(profile_stats['implemented_bash_fixes']) -
344
                      len(profile_stats['ansible_parity'])) / \
345
                len(profile_stats['implemented_bash_fixes']) * 100
346
347
        return profile_stats
348
349
    def show_profile_stats(self, profile, options):
350
        """Displays statistics for specific profile"""
351
352
        profile_stats = self.get_profile_stats(profile)
353
        rules_count = profile_stats['rules_count']
354
        impl_ovals_count = len(profile_stats['implemented_ovals'])
355
        impl_bash_fixes_count = len(profile_stats['implemented_bash_fixes'])
356
        impl_ansible_fixes_count = len(profile_stats['implemented_ansible_fixes'])
357
        impl_ignition_fixes_count = len(profile_stats['implemented_ignition_fixes'])
358
        impl_kubernetes_fixes_count = len(profile_stats['implemented_kubernetes_fixes'])
359
        impl_puppet_fixes_count = len(profile_stats['implemented_puppet_fixes'])
360
        impl_anaconda_fixes_count = len(profile_stats['implemented_anaconda_fixes'])
361
        missing_stig_ids_count = len(profile_stats['missing_stig_ids'])
362
        missing_cis_refs_count = len(profile_stats['missing_cis_refs'])
363
        missing_hipaa_refs_count = len(profile_stats['missing_hipaa_refs'])
364
        missing_anssi_refs_count = len(profile_stats['missing_anssi_refs'])
365
        missing_ospp_refs_count = len(profile_stats['missing_ospp_refs'])
366
        missing_cui_refs_count = len(profile_stats['missing_cui_refs'])
367
        impl_cces_count = len(profile_stats['assigned_cces'])
368
369
        if options.format == "plain":
370
            if not options.skip_overall_stats:
371
                print("\nProfile %s:" % profile)
372
                print("* rules:            %d" % rules_count)
373
                print("* checks (OVAL):    %d\t[%d%% complete]" %
374
                      (impl_ovals_count,
375
                       profile_stats['implemented_ovals_pct']))
376
377
                print("* fixes (bash):     %d\t[%d%% complete]" %
378
                      (impl_bash_fixes_count,
379
                       profile_stats['implemented_bash_fixes_pct']))
380
                print("* fixes (ansible):  %d\t[%d%% complete]" %
381
                      (impl_ansible_fixes_count,
382
                       profile_stats['implemented_ansible_fixes_pct']))
383
                print("* fixes (ignition):  %d\t[%d%% complete]" %
384
                      (impl_ignition_fixes_count,
385
                       profile_stats['implemented_ignition_fixes_pct']))
386
                print("* fixes (kubernetes):  %d\t[%d%% complete]" %
387
                      (impl_kubernetes_fixes_count,
388
                       profile_stats['implemented_kubernetes_fixes_pct']))
389
                print("* fixes (puppet):   %d\t[%d%% complete]" %
390
                      (impl_puppet_fixes_count,
391
                       profile_stats['implemented_puppet_fixes_pct']))
392
                print("* fixes (anaconda): %d\t[%d%% complete]" %
393
                      (impl_anaconda_fixes_count,
394
                       profile_stats['implemented_anaconda_fixes_pct']))
395
396
                print("* CCEs:             %d\t[%d%% complete]" %
397
                      (impl_cces_count,
398
                       profile_stats['assigned_cces_pct']))
399
400
            if options.implemented_ovals and \
401
               profile_stats['implemented_ovals']:
402
                print("** Rules of '%s' " % profile +
403
                      "profile having OVAL check: %d of %d [%d%% complete]" %
404
                      (impl_ovals_count, rules_count,
405
                       profile_stats['implemented_ovals_pct']))
406
                self.console_print(profile_stats['implemented_ovals'],
407
                                   console_width)
408
409
            if options.implemented_fixes:
410
                if profile_stats['implemented_bash_fixes']:
411
                    print("*** Rules of '%s' profile having "
412
                          "a bash fix script: %d of %d [%d%% complete]"
413
                          % (profile, impl_bash_fixes_count, rules_count,
414
                             profile_stats['implemented_bash_fixes_pct']))
415
                    self.console_print(profile_stats['implemented_bash_fixes'],
416
                                       console_width)
417
418
                if profile_stats['implemented_ansible_fixes']:
419
                    print("*** Rules of '%s' profile having "
420
                          "a ansible fix script: %d of %d [%d%% complete]"
421
                          % (profile, impl_ansible_fixes_count, rules_count,
422
                             profile_stats['implemented_ansible_fixes_pct']))
423
                    self.console_print(
424
                        profile_stats['implemented_ansible_fixes'],
425
                        console_width)
426
427
                if profile_stats['implemented_ignition_fixes']:
428
                    print("*** Rules of '%s' profile having "
429
                          "a ignition fix script: %d of %d [%d%% complete]"
430
                          % (profile, impl_ignition_fixes_count, rules_count,
431
                             profile_stats['implemented_ignition_fixes_pct']))
432
                    self.console_print(
433
                        profile_stats['implemented_ignition_fixes'],
434
                        console_width)
435
436
                if profile_stats['implemented_kubernetes_fixes']:
437
                    print("*** Rules of '%s' profile having "
438
                          "a kubernetes fix script: %d of %d [%d%% complete]"
439
                          % (profile, impl_kubernetes_fixes_count, rules_count,
440
                             profile_stats['implemented_kubernetes_fixes_pct']))
441
                    self.console_print(
442
                        profile_stats['implemented_kubernetes_fixes'],
443
                        console_width)
444
445
                if profile_stats['implemented_puppet_fixes']:
446
                    print("*** Rules of '%s' profile having "
447
                          "a puppet fix script: %d of %d [%d%% complete]"
448
                          % (profile, impl_puppet_fixes_count, rules_count,
449
                             profile_stats['implemented_puppet_fixes_pct']))
450
                    self.console_print(
451
                        profile_stats['implemented_puppet_fixes'],
452
                        console_width)
453
454
                if profile_stats['implemented_anaconda_fixes']:
455
                    print("*** Rules of '%s' profile having "
456
                          "a anaconda fix script: %d of %d [%d%% complete]"
457
                          % (profile, impl_anaconda_fixes_count, rules_count,
458
                             profile_stats['implemented_anaconda_fixes_pct']))
459
                    self.console_print(
460
                        profile_stats['implemented_anaconda_fixes'],
461
                        console_width)
462
463
            if options.assigned_cces and \
464
               profile_stats['assigned_cces']:
465
                print("*** Rules of '%s' " % profile +
466
                      "profile having CCE assigned: %d of %d [%d%% complete]" %
467
                      (impl_cces_count, rules_count,
468
                       profile_stats['assigned_cces_pct']))
469
                self.console_print(profile_stats['assigned_cces'],
470
                                   console_width)
471
472
            if options.missing_ovals and profile_stats['missing_ovals']:
473
                print("*** Rules of '%s' " % profile + "profile missing " +
474
                      "OVAL: %d of %d [%d%% complete]" %
475
                      (rules_count - impl_ovals_count, rules_count,
476
                       profile_stats['implemented_ovals_pct']))
477
                self.console_print(profile_stats['missing_ovals'],
478
                                   console_width)
479
480
            if options.missing_fixes:
481
                if profile_stats['missing_bash_fixes']:
482
                    print("*** rules of '%s' profile missing "
483
                          "a bash fix script: %d of %d [%d%% complete]"
484
                          % (profile, rules_count - impl_bash_fixes_count,
485
                             rules_count,
486
                             profile_stats['implemented_bash_fixes_pct']))
487
                    self.console_print(profile_stats['missing_bash_fixes'],
488
                                       console_width)
489
490
                if profile_stats['missing_ansible_fixes']:
491
                    print("*** rules of '%s' profile missing "
492
                          "a ansible fix script: %d of %d [%d%% complete]"
493
                          % (profile, rules_count - impl_ansible_fixes_count,
494
                             rules_count,
495
                             profile_stats['implemented_ansible_fixes_pct']))
496
                    self.console_print(profile_stats['missing_ansible_fixes'],
497
                                       console_width)
498
499
                if profile_stats['missing_ignition_fixes']:
500
                    print("*** rules of '%s' profile missing "
501
                          "a ignition fix script: %d of %d [%d%% complete]"
502
                          % (profile, rules_count - impl_ignition_fixes_count,
503
                             rules_count,
504
                             profile_stats['implemented_ignition_fixes_pct']))
505
                    self.console_print(profile_stats['missing_ignition_fixes'],
506
                                       console_width)
507
508
                if profile_stats['missing_kubernetes_fixes']:
509
                    print("*** rules of '%s' profile missing "
510
                          "a kubernetes fix script: %d of %d [%d%% complete]"
511
                          % (profile, rules_count - impl_kubernetes_fixes_count,
512
                             rules_count,
513
                             profile_stats['implemented_kubernetes_fixes_pct']))
514
                    self.console_print(profile_stats['missing_kubernetes_fixes'],
515
                                       console_width)
516
517
                if profile_stats['missing_puppet_fixes']:
518
                    print("*** rules of '%s' profile missing "
519
                          "a puppet fix script: %d of %d [%d%% complete]"
520
                          % (profile, rules_count - impl_puppet_fixes_count,
521
                             rules_count,
522
                             profile_stats['implemented_puppet_fixes_pct']))
523
                    self.console_print(profile_stats['missing_puppet_fixes'],
524
                                       console_width)
525
526
                if profile_stats['missing_anaconda_fixes']:
527
                    print("*** rules of '%s' profile missing "
528
                          "a anaconda fix script: %d of %d [%d%% complete]"
529
                          % (profile, rules_count - impl_anaconda_fixes_count,
530
                             rules_count,
531
                             profile_stats['implemented_anaconda_fixes_pct']))
532
                    self.console_print(profile_stats['missing_anaconda_fixes'],
533
                                       console_width)
534
535
            if options.missing_stig_ids and profile_stats['missing_stig_ids']:
536
                print("*** rules of '%s' profile missing "
537
                      "STIG IDs: %d of %d have them [%d%% missing]"
538
                      % (profile, rules_count - missing_stig_ids_count,
539
                         rules_count,
540
                         (100.0 * missing_stig_ids_count / rules_count)))
541
                self.console_print(profile_stats['missing_stig_ids'],
542
                                   console_width)
543
544
            if options.missing_cis_refs and profile_stats['missing_cis_refs']:
545
                print("*** rules of '%s' profile missing "
546
                      "CIS Refs: %d of %d have them [%d%% missing]"
547
                      % (profile, rules_count - missing_cis_refs_count,
548
                         rules_count,
549
                         (100.0 * missing_cis_refs_count / rules_count)))
550
                self.console_print(profile_stats['missing_cis_refs'],
551
                                   console_width)
552
553
            if options.missing_hipaa_refs and profile_stats['missing_hipaa_refs']:
554
                print("*** rules of '%s' profile missing "
555
                      "HIPAA Refs: %d of %d have them [%d%% missing]"
556
                      % (profile, rules_count - missing_hipaa_refs_count,
557
                         rules_count,
558
                         (100.0 * missing_hipaa_refs_count / rules_count)))
559
                self.console_print(profile_stats['missing_hipaa_refs'],
560
                                   console_width)
561
562
            if options.missing_anssi_refs and profile_stats['missing_anssi_refs']:
563
                print("*** rules of '%s' profile missing "
564
                      "ANSSI Refs: %d of %d have them [%d%% missing]"
565
                      % (profile, rules_count - missing_anssi_refs_count,
566
                         rules_count,
567
                         (100.0 * missing_anssi_refs_count / rules_count)))
568
                self.console_print(profile_stats['missing_anssi_refs'],
569
                                   console_width)
570
571
            if options.missing_ospp_refs and profile_stats['missing_ospp_refs']:
572
                print("*** rules of '%s' profile missing "
573
                      "OSPP Refs: %d of %d have them [%d%% missing]"
574
                      % (profile, rules_count - missing_ospp_refs_count,
575
                         rules_count,
576
                         (100.0 * missing_ospp_refs_count / rules_count)))
577
                self.console_print(profile_stats['missing_ospp_refs'],
578
                                   console_width)
579
580
            if options.missing_cui_refs and profile_stats['missing_cui_refs']:
581
                print("*** rules of '%s' profile missing "
582
                      "CUI Refs: %d of %d have them [%d%% missing]"
583
                      % (profile, rules_count - missing_cui_refs_count,
584
                         rules_count,
585
                         (100.0 * missing_cui_refs_count / rules_count)))
586
                self.console_print(profile_stats['missing_cui_refs'],
587
                                   console_width)
588
589
            if options.missing_cces and profile_stats['missing_cces']:
590
                print("***Rules of '%s' " % profile + "profile missing " +
591
                      "CCE identifier: %d of %d [%d%% complete]" %
592
                      (rules_count - impl_cces_count, rules_count,
593
                       profile_stats['assigned_cces_pct']))
594
                self.console_print(profile_stats['missing_cces'],
595
                                   console_width)
596
597
            if options.ansible_parity:
598
                print("*** rules of '%s' profile with bash fix that implement "
599
                      "ansible fix scripts: %d out of %d [%d%% complete]"
600
                      % (profile, impl_bash_fixes_count - len(profile_stats['ansible_parity']),
601
                         impl_bash_fixes_count,
602
                         profile_stats['ansible_parity_pct']))
603
                self.console_print(profile_stats['ansible_parity'],
604
                                   console_width)
605
606
        elif options.format == "html":
607
            del profile_stats['implemented_ovals']
608
            del profile_stats['implemented_bash_fixes']
609
            del profile_stats['implemented_ansible_fixes']
610
            del profile_stats['implemented_ignition_fixes']
611
            del profile_stats['implemented_kubernetes_fixes']
612
            del profile_stats['implemented_puppet_fixes']
613
            del profile_stats['implemented_anaconda_fixes']
614
            del profile_stats['assigned_cces']
615
616
            profile_stats['missing_stig_ids_count'] = missing_stig_ids_count
617
            profile_stats['missing_cis_refs_count'] = missing_cis_refs_count
618
            profile_stats['missing_hipaa_refs_count'] = missing_hipaa_refs_count
619
            profile_stats['missing_anssi_refs_count'] = missing_anssi_refs_count
620
            profile_stats['missing_ospp_refs_count'] = missing_ospp_refs_count
621
            profile_stats['missing_cui_refs_count'] = missing_cui_refs_count
622
            profile_stats['missing_ovals_count'] = len(profile_stats['missing_ovals'])
623
            profile_stats['missing_bash_fixes_count'] = len(profile_stats['missing_bash_fixes'])
624
            profile_stats['missing_ansible_fixes_count'] = len(profile_stats['missing_ansible_fixes'])
625
            profile_stats['missing_ignition_fixes_count'] = len(profile_stats['missing_ignition_fixes'])
626
            profile_stats['missing_kubernetes_fixes_count'] = \
627
                    len(profile_stats['missing_kubernetes_fixes'])
628
            profile_stats['missing_puppet_fixes_count'] = len(profile_stats['missing_puppet_fixes'])
629
            profile_stats['missing_anaconda_fixes_count'] = len(profile_stats['missing_anaconda_fixes'])
630
            profile_stats['missing_cces_count'] = len(profile_stats['missing_cces'])
631
632
            del profile_stats['implemented_ovals_pct']
633
            del profile_stats['implemented_bash_fixes_pct']
634
            del profile_stats['implemented_ansible_fixes_pct']
635
            del profile_stats['implemented_ignition_fixes_pct']
636
            del profile_stats['implemented_kubernetes_fixes_pct']
637
            del profile_stats['implemented_puppet_fixes_pct']
638
            del profile_stats['implemented_anaconda_fixes_pct']
639
            del profile_stats['assigned_cces_pct']
640
            del profile_stats['ssg_version']
641
            del profile_stats['ansible_parity_pct']
642
643
            return profile_stats
644
        else:
645
            # First delete the not requested information
646
            if not options.missing_ovals:
647
                del profile_stats['missing_ovals']
648
            if not options.missing_fixes:
649
                del profile_stats['missing_bash_fixes']
650
                del profile_stats['missing_ansible_fixes']
651
                del profile_stats['missing_ignition_fixes']
652
                del profile_stats['missing_kubernetes_fixes']
653
                del profile_stats['missing_puppet_fixes']
654
                del profile_stats['missing_anaconda_fixes']
655
                del profile_stats['missing_stig_ids']
656
                del profile_stats['missing_cis_refs']
657
                del profile_stats['missing_hipaa_refs']
658
                del profile_stats['missing_anssi_refs']
659
                del profile_stats['missing_ospp_refs']
660
                del profile_stats['missing_cui_refs']
661
            if not options.missing_cces:
662
                del profile_stats['missing_cces']
663
            if not options.implemented_ovals:
664
                del profile_stats['implemented_ovals']
665
            if not options.implemented_fixes:
666
                del profile_stats['implemented_bash_fixes']
667
                del profile_stats['implemented_ansible_fixes']
668
                del profile_stats['implemented_ignition_fixes']
669
                del profile_stats['implemented_kubernetes_fixes']
670
                del profile_stats['implemented_puppet_fixes']
671
                del profile_stats['implemented_anaconda_fixes']
672
            if not options.assigned_cces:
673
                del profile_stats['assigned_cces']
674
675
            del profile_stats['rules']
676
677
            return profile_stats
678
679
    def console_print(self, content, width):
680
        """Prints the 'content' array left aligned, each time 45 characters
681
           long, each row 'width' characters wide"""
682
683
        msg = ''
684
        for item in content:
685
            if len(msg) + len(item) < width - 6:
686
                msg += '   ' + "%-45s" % item
687
            else:
688
                print("%s" % msg)
689
                msg = '   ' + "%-45s" % item
690
        if msg != '':
691
            print("%s" % msg)
692