ssg.build_profile.RuleStats.__init__()   C
last analyzed

Complexity

Conditions 9

Size

Total Lines 44
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 90

Importance

Changes 0
Metric Value
cc 9
eloc 41
nop 17
dl 0
loc 44
ccs 0
cts 18
cp 0
crap 90
rs 6.5626
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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