ssg.build_profile.XCCDFBenchmark.__init__()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 46
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 306

Importance

Changes 0
Metric Value
cc 17
eloc 40
nop 3
dl 0
loc 46
ccs 0
cts 39
cp 0
crap 306
rs 1.8
c 0
b 0
f 0

How to fix   Complexity   

Complexity

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