diff --git a/baselines/800-171.yaml b/baselines/800-171.yaml index c5366185..56912b8a 100644 --- a/baselines/800-171.yaml +++ b/baselines/800-171.yaml @@ -7,6 +7,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/baselines/800-53r5_high.yaml b/baselines/800-53r5_high.yaml index c6316eae..9872d422 100644 --- a/baselines/800-53r5_high.yaml +++ b/baselines/800-53r5_high.yaml @@ -7,6 +7,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/baselines/800-53r5_low.yaml b/baselines/800-53r5_low.yaml index 7b640722..428fe889 100644 --- a/baselines/800-53r5_low.yaml +++ b/baselines/800-53r5_low.yaml @@ -7,6 +7,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/baselines/800-53r5_moderate.yaml b/baselines/800-53r5_moderate.yaml index 2c71d231..8de1ccd0 100644 --- a/baselines/800-53r5_moderate.yaml +++ b/baselines/800-53r5_moderate.yaml @@ -7,6 +7,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/baselines/DISA-STIG.yaml b/baselines/DISA-STIG.yaml index 2462c5d7..d3fc9b9c 100644 --- a/baselines/DISA-STIG.yaml +++ b/baselines/DISA-STIG.yaml @@ -7,6 +7,7 @@ authors: | |Allen Golbig|Jamf |Bob Gendler|National Institute of Standards and Technology |=== +parent_values: stig profile: - section: "authentication" rules: diff --git a/baselines/all_rules.yaml b/baselines/all_rules.yaml index 03109b65..860cf282 100644 --- a/baselines/all_rules.yaml +++ b/baselines/all_rules.yaml @@ -7,6 +7,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/baselines/cis_lvl1.yaml b/baselines/cis_lvl1.yaml index eeba04af..4652b000 100644 --- a/baselines/cis_lvl1.yaml +++ b/baselines/cis_lvl1.yaml @@ -8,6 +8,7 @@ authors: | |Ron Colvin|Center for Internet Security |Allen Golbig|Jamf |=== +parent_values: cis_lvl1 profile: - section: "auditing" rules: diff --git a/baselines/cis_lvl2.yaml b/baselines/cis_lvl2.yaml index 0c64fb75..c4581013 100644 --- a/baselines/cis_lvl2.yaml +++ b/baselines/cis_lvl2.yaml @@ -8,6 +8,7 @@ authors: | |Ron Colvin|Center for Internet Security |Allen Golbig|Jamf |=== +parent_values: cis_lvl2 profile: - section: "auditing" rules: diff --git a/baselines/cisv8.yaml b/baselines/cisv8.yaml index 329ed453..0bbe1916 100644 --- a/baselines/cisv8.yaml +++ b/baselines/cisv8.yaml @@ -9,6 +9,7 @@ authors: | |Dan Brodjieski|National Aeronautics and Space Administration |Allen Golbig|Jamf |=== +parent_values: recommended profile: - section: "authentication" rules: diff --git a/rules/audit/audit_configure_capacity_notify.yaml b/rules/audit/audit_configure_capacity_notify.yaml index 43c8d502..5324acc5 100644 --- a/rules/audit/audit_configure_capacity_notify.yaml +++ b/rules/audit/audit_configure_capacity_notify.yaml @@ -30,7 +30,7 @@ macOS: - "12.0" odv: hint: "Percentage of free space." - default: 25 + recommended: 25 stig: 25 tags: - 800-53r5_high diff --git a/rules/audit/audit_retention_configure.yaml b/rules/audit/audit_retention_configure.yaml index f2579732..867e0604 100644 --- a/rules/audit/audit_retention_configure.yaml +++ b/rules/audit/audit_retention_configure.yaml @@ -38,7 +38,7 @@ macOS: - "12.0" odv: hint: "See man audit_control for possible values." - default: 7d + recommended: 7d stig: 7d cis_lvl1: 60d or 1G cis_lvl2: 60d or 1G diff --git a/rules/os/os_install_log_retention_configure.yaml b/rules/os/os_install_log_retention_configure.yaml index 805f7065..50de5e2a 100644 --- a/rules/os/os_install_log_retention_configure.yaml +++ b/rules/os/os_install_log_retention_configure.yaml @@ -40,7 +40,7 @@ macOS: - "12.0" odv: hint: "Number of days." - default: 365 + recommended: 365 cis_lvl1: 365 cis_lvl2: 365 tags: diff --git a/rules/os/os_library_validation_enabled.yaml b/rules/os/os_library_validation_enabled.yaml index a1802e0e..2d6e1aec 100644 --- a/rules/os/os_library_validation_enabled.yaml +++ b/rules/os/os_library_validation_enabled.yaml @@ -10,10 +10,7 @@ check: | result: string: "false" fix: | - [source,bash] - ---- This is implemented by a Configuration Profile. - ---- references: cce: - CCE-91108-1 diff --git a/rules/os/os_policy_banner_loginwindow_enforce.yaml b/rules/os/os_policy_banner_loginwindow_enforce.yaml index 9d540047..593bbf42 100644 --- a/rules/os/os_policy_banner_loginwindow_enforce.yaml +++ b/rules/os/os_policy_banner_loginwindow_enforce.yaml @@ -56,7 +56,7 @@ macOS: - "12.0" odv: hint: "Organization's Policy Text" - default: |- + recommended: |- You are accessing a U.S. Government information system, which includes: 1) this computer, 2) this computer network, 3) all Government-furnished computers connected to this network, and 4) all Government-furnished devices and storage media attached to this network or to a computer on this network. You understand and consent to the following: you may access this information system for authorized use only; unauthorized use of the system is prohibited and subject to criminal and civil penalties; you have no reasonable expectation of privacy regarding any communication or data transiting or stored on this information system at any time and for any lawful Government purpose, the Government may monitor, intercept, audit, and search and seize any communication or data transiting or stored on this information system; and any communications or data transiting or stored on this information system may be disclosed or used for any lawful Government purpose. This information system may contain Controlled Unclassified Information (CUI) that is subject to safeguarding or dissemination controls in accordance with law, regulation, or Government-wide policy. Accessing and using this system indicates your understanding of this warning. stig: |- You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions: diff --git a/rules/os/os_policy_banner_ssh_configure.yaml b/rules/os/os_policy_banner_ssh_configure.yaml index b7de3ed5..0847e99f 100644 --- a/rules/os/os_policy_banner_ssh_configure.yaml +++ b/rules/os/os_policy_banner_ssh_configure.yaml @@ -36,7 +36,7 @@ macOS: - "12.0" odv: hint: "Organization's Policy Text" - default: |- + recommended: |- You are accessing a U.S. Government (USG) Information System (IS) that is provided for USG-authorized use only. By using this IS (which includes any device attached to this IS), you consent to the following conditions: -The USG routinely intercepts and monitors communications on this IS for purposes including, but not limited to, penetration testing, COMSEC monitoring, network operations and defense, personnel misconduct (PM), law enforcement (LE), and counterintelligence (CI) investigations. -At any time, the USG may inspect and seize data stored on this IS. diff --git a/rules/os/os_ssh_server_alive_count_max_configure.yaml b/rules/os/os_ssh_server_alive_count_max_configure.yaml index 9aff9e5e..83ee4b0f 100644 --- a/rules/os/os_ssh_server_alive_count_max_configure.yaml +++ b/rules/os/os_ssh_server_alive_count_max_configure.yaml @@ -46,7 +46,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 0 + recommended: 0 tags: - 800-53r5_moderate - 800-53r5_high diff --git a/rules/os/os_ssh_server_alive_interval_configure.yaml b/rules/os/os_ssh_server_alive_interval_configure.yaml index 56d267d5..95ca0f42 100644 --- a/rules/os/os_ssh_server_alive_interval_configure.yaml +++ b/rules/os/os_ssh_server_alive_interval_configure.yaml @@ -49,7 +49,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 900 + recommended: 900 tags: - 800-53r5_moderate - 800-53r5_high diff --git a/rules/os/os_sshd_client_alive_count_max_configure.yaml b/rules/os/os_sshd_client_alive_count_max_configure.yaml index 4a8c1bc6..0ac6ce14 100644 --- a/rules/os/os_sshd_client_alive_count_max_configure.yaml +++ b/rules/os/os_sshd_client_alive_count_max_configure.yaml @@ -32,7 +32,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 0 + recommended: 0 stig: 0 tags: - 800-53r5_moderate diff --git a/rules/os/os_sshd_client_alive_interval_configure.yaml b/rules/os/os_sshd_client_alive_interval_configure.yaml index 246a2b58..43fd3750 100644 --- a/rules/os/os_sshd_client_alive_interval_configure.yaml +++ b/rules/os/os_sshd_client_alive_interval_configure.yaml @@ -35,7 +35,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 900 + recommended: 900 stig: 900 tags: - 800-53r5_moderate diff --git a/rules/os/os_sshd_login_grace_time_configure.yaml b/rules/os/os_sshd_login_grace_time_configure.yaml index 470f2af3..4ee916b1 100644 --- a/rules/os/os_sshd_login_grace_time_configure.yaml +++ b/rules/os/os_sshd_login_grace_time_configure.yaml @@ -32,7 +32,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 30 + recommended: 30 stig: 30 tags: - stig diff --git a/rules/os/os_sudo_timeout_configure.yaml b/rules/os/os_sudo_timeout_configure.yaml index d22af638..e61388bb 100644 --- a/rules/os/os_sudo_timeout_configure.yaml +++ b/rules/os/os_sudo_timeout_configure.yaml @@ -34,7 +34,7 @@ macOS: - "12.0" odv: hint: "Number of minutes." - default: 0 + recommended: 0 cis_lvl1: 0 cis_lvl2: 0 tags: diff --git a/rules/pwpolicy/pwpolicy_account_inactivity_enforce.yaml b/rules/pwpolicy/pwpolicy_account_inactivity_enforce.yaml index 1828ff67..fa580082 100644 --- a/rules/pwpolicy/pwpolicy_account_inactivity_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_account_inactivity_enforce.yaml @@ -59,7 +59,7 @@ macOS: - "12.0" odv: hint: "Number of days." - default: 35 + recommended: 35 tags: - 800-171 - cnssi-1253 diff --git a/rules/pwpolicy/pwpolicy_account_lockout_enforce.yaml b/rules/pwpolicy/pwpolicy_account_lockout_enforce.yaml index cbf0564c..cfb2cab1 100644 --- a/rules/pwpolicy/pwpolicy_account_lockout_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_account_lockout_enforce.yaml @@ -37,7 +37,7 @@ macOS: - "12.0" odv: hint: "Number of failed attempts." - default: 3 + recommended: 3 stig: 3 cis_lvl1: 5 cis_lvl2: 5 diff --git a/rules/pwpolicy/pwpolicy_account_lockout_timeout_enforce.yaml b/rules/pwpolicy/pwpolicy_account_lockout_timeout_enforce.yaml index 6066b242..2826706c 100644 --- a/rules/pwpolicy/pwpolicy_account_lockout_timeout_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_account_lockout_timeout_enforce.yaml @@ -37,7 +37,7 @@ macOS: - "12.0" odv: hint: "Number of minutes." - default: 15 + recommended: 15 stig: 15 tags: - 800-171 diff --git a/rules/pwpolicy/pwpolicy_history_enforce.yaml b/rules/pwpolicy/pwpolicy_history_enforce.yaml index a3fd7375..8412e0ef 100644 --- a/rules/pwpolicy/pwpolicy_history_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_history_enforce.yaml @@ -44,7 +44,7 @@ macOS: - "12.0" odv: hint: "Number of previous passwords." - default: 5 + recommended: 5 stig: 5 cis_lvl1: 15 cis_lvl2: 15 diff --git a/rules/pwpolicy/pwpolicy_max_lifetime_enforce.yaml b/rules/pwpolicy/pwpolicy_max_lifetime_enforce.yaml index f406a1c2..e42d4c83 100644 --- a/rules/pwpolicy/pwpolicy_max_lifetime_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_max_lifetime_enforce.yaml @@ -45,7 +45,7 @@ macOS: - "12.0" odv: hint: "Number of days." - default: 60 + recommended: 60 stig: 60 tags: - 800-171 diff --git a/rules/pwpolicy/pwpolicy_minimum_length_enforce.yaml b/rules/pwpolicy/pwpolicy_minimum_length_enforce.yaml index 2bcab441..487fd179 100644 --- a/rules/pwpolicy/pwpolicy_minimum_length_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_minimum_length_enforce.yaml @@ -45,7 +45,7 @@ macOS: - "12.0" odv: hint: "Minimum password length." - default: 15 + recommended: 15 stig: 15 cis_lvl1: 15 cis_lvl2: 15 diff --git a/rules/pwpolicy/pwpolicy_minimum_lifetime_enforce.yaml b/rules/pwpolicy/pwpolicy_minimum_lifetime_enforce.yaml index 2d1f97b5..ba820887 100644 --- a/rules/pwpolicy/pwpolicy_minimum_lifetime_enforce.yaml +++ b/rules/pwpolicy/pwpolicy_minimum_lifetime_enforce.yaml @@ -63,7 +63,7 @@ macOS: - "12.0" odv: hint: "Number of hours." - default: 24 + recommended: 24 tags: - 800-171 - cnssi-1253 diff --git a/rules/supplemental/supplemental_cis_manual.yaml b/rules/supplemental/supplemental_cis_manual.yaml index ddee6059..eda052dd 100644 --- a/rules/supplemental/supplemental_cis_manual.yaml +++ b/rules/supplemental/supplemental_cis_manual.yaml @@ -1,5 +1,5 @@ id: supplemental_cis_manual -title: " CIS Manual Recommendations" +title: "CIS Manual Recommendations" discussion: | List of CIS recommendations that are manual check in the CIS macOS Benchmark. diff --git a/rules/sysprefs/sysprefs_loginwindow_loginwindowtext_enable.yaml b/rules/sysprefs/sysprefs_loginwindow_loginwindowtext_enable.yaml index be8e9f7b..926dfec8 100644 --- a/rules/sysprefs/sysprefs_loginwindow_loginwindowtext_enable.yaml +++ b/rules/sysprefs/sysprefs_loginwindow_loginwindowtext_enable.yaml @@ -35,7 +35,7 @@ macOS: - "12.0" odv: hint: "Organization's approved message." - default: Center for Internet Security Test Message + recommended: Center for Internet Security Test Message cis_lvl1: Center for Internet Security Test Message cis_lvl2: Center for Internet Security Test Message tags: diff --git a/rules/sysprefs/sysprefs_screensaver_ask_for_password_delay_enforce.yaml b/rules/sysprefs/sysprefs_screensaver_ask_for_password_delay_enforce.yaml index db12ef9a..6d613cdc 100644 --- a/rules/sysprefs/sysprefs_screensaver_ask_for_password_delay_enforce.yaml +++ b/rules/sysprefs/sysprefs_screensaver_ask_for_password_delay_enforce.yaml @@ -44,7 +44,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 5 + recommended: 5 stig: 5 cis_lvl1: 5 cis_lvl2: 5 diff --git a/rules/sysprefs/sysprefs_screensaver_timeout_enforce.yaml b/rules/sysprefs/sysprefs_screensaver_timeout_enforce.yaml index 2d3d0f48..db544789 100644 --- a/rules/sysprefs/sysprefs_screensaver_timeout_enforce.yaml +++ b/rules/sysprefs/sysprefs_screensaver_timeout_enforce.yaml @@ -45,7 +45,7 @@ macOS: - "12.0" odv: hint: "Number of seconds." - default: 1200 + recommended: 1200 stig: 900 cis_lvl1: 1200 cis_lvl2: 1200 @@ -61,6 +61,7 @@ tags: - cis_lvl2 - cisv8 - stig + - test severity: "medium" mobileconfig: true mobileconfig_info: diff --git a/rules/sysprefs/sysprefs_time_server_configure.yaml b/rules/sysprefs/sysprefs_time_server_configure.yaml index 62c4a77c..73be2337 100644 --- a/rules/sysprefs/sysprefs_time_server_configure.yaml +++ b/rules/sysprefs/sysprefs_time_server_configure.yaml @@ -40,7 +40,7 @@ macOS: - "12.0" odv: hint: "Name of timeserver(s) separated by commas." - default: "time-a.nist.gov,time-b.nist.gov" + recommended: "time-a.nist.gov,time-b.nist.gov" stig: "time-a.nist.gov,time-b.nist.gov" cis_lvl1: "time.apple.com" cis_lvl2: "time.apple.com" diff --git a/scripts/generate_baseline.py b/scripts/generate_baseline.py index c6e0208d..7afd2953 100755 --- a/scripts/generate_baseline.py +++ b/scripts/generate_baseline.py @@ -212,7 +212,7 @@ def available_tags(all_rules): print(tag) return -def output_baseline(rules, os, keyword): +def output_baseline(rules, os, keyword, benchmark="recommended"): inherent_rules = [] permanent_rules = [] na_rules = [] @@ -240,6 +240,7 @@ def output_baseline(rules, os, keyword): output_text = f'title: "macOS {os}: Security Configuration - {keyword}"\n' output_text += f'description: |\n This guide describes the actions to take when securing a macOS {os} system against the {keyword} baseline.\n' output_text += f'authors: |\n |===\n |Name|Organization\n |===\n' + output_text += f'parent_values: "{benchmark}"\n' output_text += 'profile:\n' # sort the rules @@ -345,19 +346,16 @@ def sanitised_input(prompt, type_=None, range_=None, default_=None): else: return ui -def odv_query(rules, keyword): +def odv_query(rules, benchmark): print("The inclusion of any given rule is a risk-based-decision (RBD). While each rule is mapped to an 800-53 control, deploying it in your organization should be part of the decision-making process. \nYou will be prompted to include each rule, and for those with specific organizational defined values (ODV), you will be prompted for those as well.\n") - _established_benchmarks = ['stig', 'cis_lvl1', 'cis_lvl2'] - if any(bm in keyword for bm in _established_benchmarks): + if not benchmark == "recommended": print(f"WARNING: You are attempting to tailor an already established benchmark. Excluding rules or modifying ODVs may not meet the compliance of the established benchmark.\n") - benchmark = keyword - else: - benchmark = "default" - + included_rules = [] queried_rule_ids = [] + include_all = False for rule in rules: get_odv = False @@ -366,30 +364,38 @@ def odv_query(rules, keyword): if any(tag in rule.rule_tags for tag in _always_include): #print(f"Including rule {rule.rule_id} by default") include = "Y" + elif include_all: + include = "Y" + get_odv = True + queried_rule_ids.append(rule.rule_id) + remove_odv_custom_rule(rule) else: if rule.rule_id not in queried_rule_ids: - include = sanitised_input(f"Would you like to include the rule for \"{rule.rule_id}\" in your benchmark? [Y/n]: ", str.lower, range_=('y', 'n'), default_="y") + include = sanitised_input(f"Would you like to include the rule for \"{rule.rule_id}\" in your benchmark? [Y/n/all]: ", str.lower, range_=('y', 'n', 'all'), default_="y") queried_rule_ids.append(rule.rule_id) get_odv = True # remove custom ODVs if there, they will be re-written if needed remove_odv_custom_rule(rule) + if include.upper() == "ALL": + include_all = True + include = "y" if include.upper() == "Y": included_rules.append(rule) if rule.rule_odv == "missing": continue elif get_odv: - if benchmark == "default": + if benchmark == "recommended": print(f'{rule.rule_odv["hint"]}') - if isinstance(rule.rule_odv["default"], int): - odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', int, default_=rule.rule_odv["default"]) - elif isinstance(rule.rule_odv["default"], bool): - odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', bool, default_=rule.rule_odv["default"]) + if isinstance(rule.rule_odv["recommended"], int): + odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', int, default_=rule.rule_odv["recommended"]) + elif isinstance(rule.rule_odv["recommended"], bool): + odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', bool, default_=rule.rule_odv["recommended"]) else: - odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv["default"]}): ', str, default_=rule.rule_odv["default"]) - if odv and odv != rule.rule_odv["default"]: + odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the recommended value ({rule.rule_odv["recommended"]}): ', str, default_=rule.rule_odv["recommended"]) + if odv and odv != rule.rule_odv["recommended"]: write_odv_custom_rule(rule, odv) else: - print(f'{rule.rule_odv["hint"]}') + print(f'\nODV value: {rule.rule_odv["hint"]}') if isinstance(rule.rule_odv[benchmark], int): odv = sanitised_input(f'Enter the ODV for \"{rule.rule_id}\" or press Enter for the default value ({rule.rule_odv[benchmark]}): ', int, default_=rule.rule_odv[benchmark]) elif isinstance(rule.rule_odv[benchmark], bool): @@ -467,10 +473,17 @@ def main(): print("No rules found for the keyword provided, please verify from the following list:") available_tags(all_rules) elif args.tailor: + _established_benchmarks = ['stig', 'cis_lvl1', 'cis_lvl2'] + if any(bm in args.keyword for bm in _established_benchmarks): + benchmark = args.keyword + else: + benchmark = "recommended" + # prompt for name of benchmark to be used for filename + tailored_filename = sanitised_input(f'Enter a name for your tailored benchmark or press Enter for the default value ({args.keyword}): ', str, default_=args.keyword) # prompt for inclusion, add ODV - odv_baseline_rules = odv_query(found_rules, args.keyword) - baseline_output_file = open(f"{build_path}/{args.keyword}.yaml", 'w') - baseline_output_file.write(output_baseline(odv_baseline_rules, version_yaml["os"], args.keyword)) + odv_baseline_rules = odv_query(found_rules, benchmark) + baseline_output_file = open(f"{build_path}/{tailored_filename}.yaml", 'w') + baseline_output_file.write(output_baseline(odv_baseline_rules, version_yaml["os"], args.keyword, benchmark)) else: baseline_output_file = open(f"{build_path}/{args.keyword}.yaml", 'w') baseline_output_file.write(output_baseline(found_rules, version_yaml["os"], args.keyword)) diff --git a/scripts/generate_guidance.py b/scripts/generate_guidance.py index 18dd675f..a8a83c50 100755 --- a/scripts/generate_guidance.py +++ b/scripts/generate_guidance.py @@ -1,13 +1,10 @@ #!/usr/bin/env python3 # filename: generate_guidance.py # description: Process a given baseline, and output guidance files -import types import sys import os.path import plistlib -from unittest import result import xlwt -import io import glob import os import yaml @@ -20,7 +17,6 @@ from xlwt import Workbook from string import Template from itertools import groupby from uuid import uuid4 -from collections import namedtuple class MacSecurityRule(): @@ -283,7 +279,7 @@ class PayloadDict: """ output_file_path = output_path.name preferences_path = os.path.dirname(output_file_path) - + settings_dict = {} for i in self.data['PayloadContent']: @@ -302,7 +298,7 @@ class PayloadDict: for setting in value['Forced']: for key, value in setting['mcx_preference_settings'].items(): settings_dict[key] = value - + #preferences_output_path = open(preferences_output_file, 'wb') plistlib.dump(settings_dict, fp) print(f"Settings plist written to {preferences_output_file}") @@ -321,10 +317,10 @@ class PayloadDict: for key,value in i.items(): if not key.startswith("Payload"): settings_dict[key] = value - + plistlib.dump(settings_dict, output_path) print(f"Settings plist written to {output_path.name}") - + def makeNewUUID(): return str(uuid4()) @@ -368,7 +364,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign except OSError: print("Creation of the directory %s failed" % unsigned_mobileconfig_output_path) - + if signing: signed_mobileconfig_output_path = os.path.join( f'{build_path}', 'mobileconfigs', 'signed') @@ -406,7 +402,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign #for rule in glob.glob('../rules/*/{}.yaml'.format(profile_rule)) + glob.glob('../custom/rules/**/{}.yaml'.format(profile_rule),recursive=True): rule_yaml = get_rule_yaml(rule, custom) - + if rule_yaml['mobileconfig']: for payload_type, info in rule_yaml['mobileconfig_info'].items(): valid = True @@ -432,7 +428,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign profile_errors.append(rule) logging.debug(e) valid = False - + if valid: if payload_type == "com.apple.systemuiserver": for setting_key, setting_value in info['mount-controls'].items(): @@ -486,7 +482,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign displayname=displayname, description=description) - + if payload == "com.apple.ManagedClient.preferences": for item in settings: @@ -515,13 +511,13 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign newProfile.finalizeAndSave(config_file) newProfile.finalizeAndSavePlist(settings_config_file) config_file.close() - + print(f""" CAUTION: These configuration profiles are intended for evaluation in a TEST - environment. Certain configuration profiles (Smartcards), when applied could - leave a system in a state where a user can no longer login with a password. + environment. Certain configuration profiles (Smartcards), when applied could + leave a system in a state where a user can no longer login with a password. Please use caution when applying configuration settings to a system. - + NOTE: If an MDM is already being leveraged, many of these profile settings may be available through the vendor. """) @@ -529,7 +525,7 @@ def generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, sign def default_audit_plist(baseline_name, build_path, baseline_yaml): """"Generate the default audit plist file to define exemptions """ - + # Output folder plist_output_path = os.path.join( f'{build_path}', 'preferences') @@ -552,7 +548,7 @@ def default_audit_plist(baseline_name, build_path, baseline_yaml): if profile_rule.startswith("supplemental"): continue plist_dict[profile_rule] = { "exempt": False } - + plistlib.dump(plist_dict, plist_file) @@ -571,7 +567,7 @@ def generate_script(baseline_name, build_path, baseline_yaml, reference): ## This script will attempt to audit all of the settings based on the installed profile. -## This script is provided as-is and should be fully tested on a system that is not in a production environment. +## This script is provided as-is and should be fully tested on a system that is not in a production environment. ################### Variables ################### @@ -635,9 +631,9 @@ ask() {{ fi # Ask the question - use /dev/tty in case stdin is redirected from somewhere else - /usr/bin/printf "${{YELLOW}} $1 [$prompt] ${{STD}}" + printf "${{YELLOW}} $1 [$prompt] ${{STD}}" read REPLY - + # Default? if [ -z "$REPLY" ]; then REPLY=$default @@ -655,7 +651,7 @@ ask() {{ # function to display menus show_menus() {{ /usr/bin/clear - /bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" + /bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" /bin/echo " M A I N - M E N U" /bin/echo " macOS Security Compliance Tool" /bin/echo "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~" @@ -685,7 +681,7 @@ compliance_count(){{ non_compliant=0 results=$(/usr/libexec/PlistBuddy -c "Print" /Library/Preferences/org.{baseline_name}.audit.plist) - + while IFS= read -r line; do if [[ "$line" =~ "finding = false" ]]; then compliant=$((compliant+1)) @@ -694,8 +690,8 @@ compliance_count(){{ non_compliant=$((non_compliant+1)) fi done <<< "$results" - - # Enable output of just the compliant or non-compliant numbers. + + # Enable output of just the compliant or non-compliant numbers. if [[ $1 = "compliant" ]] then /bin/echo $compliant @@ -713,18 +709,18 @@ generate_report(){{ count=($(compliance_count)) compliant=${{count[1]}} non_compliant=${{count[2]}} - + total=$((non_compliant + compliant)) percentage=$(printf %.2f $(( compliant * 100. / total )) ) /bin/echo - /bin/echo "Number of tests passed: ${{GREEN}}$compliant${{STD}}" - /bin/echo "Number of test FAILED: ${{RED}}$non_compliant${{STD}}" - /bin/echo "You are ${{YELLOW}}$percentage%${{STD}} percent compliant!" + echo "Number of tests passed: ${{GREEN}}$compliant${{STD}}" + echo "Number of test FAILED: ${{RED}}$non_compliant${{STD}}" + echo "You are ${{YELLOW}}$percentage%${{STD}} percent compliant!" pause }} view_report(){{ - + if [[ $lastComplianceScan == "No scans have been run" ]];then /bin/echo "no report to run, please run new scan" pause @@ -738,7 +734,7 @@ generate_stats(){{ count=($(compliance_count)) compliant=${{count[1]}} non_compliant=${{count[2]}} - + total=$((non_compliant + compliant)) percentage=$(printf %.2f $(( compliant * 100. / total )) ) /bin/echo "PASSED: $compliant FAILED: $non_compliant, $percentage percent compliant!" @@ -773,22 +769,22 @@ fi logging.debug(f"{rule}") rule_yaml = get_rule_yaml(rule, custom) - + if rule_yaml['id'].startswith("supplemental"): continue - + arch="" - if "tags" in rule_yaml: + try: if "manual" in rule_yaml['tags']: continue if "arm64" in rule_yaml['tags']: arch="arm64" elif "i386" in rule_yaml['tags']: arch="i386" - else: - arch="" - + except: + pass + # grab the 800-53 controls try: rule_yaml['references']['800-53r5'] @@ -796,14 +792,14 @@ fi nist_80053r5 = 'N/A' else: nist_80053r5 = rule_yaml['references']['800-53r5'] - + if reference == "default": log_reference_id = [rule_yaml['id']] else: - try: + try: rule_yaml['references'][reference] except KeyError: - try: + try: rule_yaml['references']['custom'][reference] except KeyError: log_reference_id = [rule_yaml['id']] @@ -817,8 +813,8 @@ fi log_reference_id = rule_yaml['references'][reference] + [rule_yaml['id']] else: log_reference_id = [rule_yaml['references'][reference]] + [rule_yaml['id']] - - + + # group the controls if not nist_80053r5 == "N/A": nist_80053r5.sort() @@ -865,16 +861,16 @@ if [[ "$arch" == "$rule_arch" ]] || [[ -z "$rule_arch" ]]; then # check to see if rule is exempt unset exempt unset exempt_reason - + exempt=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null -ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt"] +ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt"] EOS ) exempt_reason=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null -ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt_reason"] +ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{7}.audit').objectForKey('{0}'))["exempt_reason"] EOS ) - + if [[ $result_value == "{4}" ]]; then /bin/echo "$(date -u) {5} passed (Result: $result_value, Expected: "{3}")" | /usr/bin/tee -a "$audit_log" /usr/bin/defaults write "$audit_plist" {0} -dict-add finding -bool NO @@ -891,8 +887,8 @@ EOS /bin/sleep 1 fi fi - - + + else /bin/echo "$(date -u) {5} does not apply to this architechture" | tee -a "$audit_log" /usr/bin/defaults write "$audit_plist" {0} -dict-add finding -bool NO @@ -922,12 +918,12 @@ unset exempt unset exempt_reason exempt=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null -ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt"] +ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt"] EOS ) exempt_reason=$(/usr/bin/osascript -l JavaScript << EOS 2>/dev/null -ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt_reason"] +ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('org.{baseline_name}.audit').objectForKey('{rule_yaml['id']}'))["exempt_reason"] EOS ) @@ -969,7 +965,7 @@ if [[ ! -e "$audit_plist" ]]; then pause show_menus read_options - else + else exit 1 fi fi @@ -986,7 +982,7 @@ fi # append to existing logfile /bin/echo "$(date -u) Beginning remediation of non-compliant settings" >> "$audit_log" -# run mcxrefresh +# run mcxrefresh /usr/bin/mcxrefresh -u $CURR_USER_UID @@ -1000,16 +996,12 @@ fi zparseopts -D -E -check=check -fix=fix -stats=stats -compliant=compliant -non_compliant=non_compliant -if [[ $check ]];then - run_scan -elif [[ $fix ]];then - run_fix -elif [[ $stats ]];then - generate_stats -elif [[ $compliant ]];then - compliance_count "compliant" -elif [[ $non_compliant ]];then - compliance_count "non-compliant" +if [[ $check ]] || [[ $fix ]] || [[ $stats ]] || [[ $compliant ]] || [[ $non_compliant ]]; then + if [[ $fix ]]; then run_fix; fi + if [[ $check ]]; then run_scan; fi + if [[ $stats ]];then generate_stats; fi + if [[ $compliant ]];then compliance_count "compliant"; fi + if [[ $non_compliant ]];then compliance_count "non-compliant"; fi else while true; do show_menus @@ -1033,19 +1025,19 @@ fi #fix_script_file.close() compliance_script_file.close() -def fill_in_odv(resulting_yaml, baseline_name): +def fill_in_odv(resulting_yaml, parent_values): fields_to_process = ['title', 'discussion', 'check', 'fix'] _has_odv = False if "odv" in resulting_yaml: try: - odv = str(resulting_yaml['odv'][baseline_name]) + odv = str(resulting_yaml['odv'][parent_values]) _has_odv = True except KeyError: try: odv = str(resulting_yaml['odv']['custom']) _has_odv = True except KeyError: - odv = str(resulting_yaml['odv']['default']) + odv = str(resulting_yaml['odv']['recommended']) _has_odv = True else: pass @@ -1054,23 +1046,25 @@ def fill_in_odv(resulting_yaml, baseline_name): for field in fields_to_process: if "$ODV" in resulting_yaml[field]: resulting_yaml[field]=resulting_yaml[field].replace("$ODV", odv) - + for result_value in resulting_yaml['result']: - resulting_yaml['result'][result_value] = odv - + if "$ODV" in str(resulting_yaml['result'][result_value]): + resulting_yaml['result'][result_value] = odv + if resulting_yaml['mobileconfig_info']: for mobileconfig_type in resulting_yaml['mobileconfig_info']: if isinstance(resulting_yaml['mobileconfig_info'][mobileconfig_type], dict): for mobileconfig_value in resulting_yaml['mobileconfig_info'][mobileconfig_type]: - resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = odv - - - - -def get_rule_yaml(rule_file, custom=False, baseline_name=""): + if "$ODV" in str(resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]): + resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = odv + + + + +def get_rule_yaml(rule_file, custom=False, parent_values="recommended"): """ Takes a rule file, checks for a custom version, and returns the yaml for the rule """ - global resulting_yaml + global resulting_yaml resulting_yaml = {} names = [os.path.basename(x) for x in glob.glob('../custom/rules/**/*.yaml', recursive=True)] file_name = os.path.basename(rule_file) @@ -1086,14 +1080,14 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""): else: with open(rule_file) as r: rule_yaml = yaml.load(r, Loader=yaml.SafeLoader) - + try: og_rule_path = glob.glob('../rules/**/{}'.format(file_name), recursive=True)[0] except IndexError: #assume this is a completely new rule og_rule_path = glob.glob('../custom/rules/**/{}'.format(file_name), recursive=True)[0] resulting_yaml['customized'] = ["customized rule"] - + # get original/default rule yaml for comparison with open(og_rule_path) as og: og_rule_yaml = yaml.load(og, Loader=yaml.SafeLoader) @@ -1115,7 +1109,7 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""): resulting_yaml['references'][ref] = rule_yaml['references'][ref] except KeyError: resulting_yaml['references'][ref] = og_rule_yaml['references'][ref] - try: + try: if "custom" in rule_yaml['references']: resulting_yaml['references']['custom'] = rule_yaml['references']['custom'] if 'customized' in resulting_yaml: @@ -1127,14 +1121,16 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""): pass elif yaml_field == "tags": # try to concatenate tags from both original yaml and custom yaml - if "tags" in rule_yaml: + try: if og_rule_yaml["tags"] == rule_yaml["tags"]: - #print("using default data in yaml field {}".format("tags")) - resulting_yaml['tags'] = og_rule_yaml['tags'] + #print("using default data in yaml field {}".format("tags")) + resulting_yaml['tags'] = og_rule_yaml['tags'] else: #print("Found custom tags... concatenating them") resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags'] - else: + except KeyError: + resulting_yaml['tags'] = og_rule_yaml['tags'] + else: try: if og_rule_yaml[yaml_field] == rule_yaml[yaml_field]: #print("using default data in yaml field {}".format(yaml_field)) @@ -1148,8 +1144,8 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""): resulting_yaml['customized'] = ["customized {}".format(yaml_field)] except KeyError: resulting_yaml[yaml_field] = og_rule_yaml[yaml_field] - - fill_in_odv(resulting_yaml, baseline_name) + + fill_in_odv(resulting_yaml, parent_values) return resulting_yaml @@ -1190,7 +1186,7 @@ def generate_xls(baseline_name, build_path, baseline_yaml): sheet1.write(0, 10, "SRG", headers) sheet1.write(0, 11, "DISA STIG", headers) sheet1.write(0, 12, "CIS Benchmark", headers) - sheet1.write(0, 13, "CIS v8", headers) + sheet1.write(0, 13, "CIS v8", headers) sheet1.write(0, 14, "CCI", headers) sheet1.write(0, 15, "Modifed Rule", headers) sheet1.set_panes_frozen(True) @@ -1201,7 +1197,7 @@ def generate_xls(baseline_name, build_path, baseline_yaml): for rule in baseline_rules: if rule.rule_id.startswith("supplemental") or rule.rule_id.startswith("srg"): continue - + sheet1.write(counter, 0, rule.rule_cce, top) sheet1.col(0).width = 256 * 15 sheet1.write(counter, 1, rule.rule_id, top) @@ -1298,12 +1294,12 @@ def generate_xls(baseline_name, build_path, baseline_yaml): if title not in custom_ref_column: custom_ref_column[title] = column_counter column_counter = column_counter + 1 - sheet1.write(0, custom_ref_column[title], title, headers) + sheet1.write(0, custom_ref_column[title], title, headers) sheet1.col(custom_ref_column[title]).width = 512 * 25 added_ref = (str(ref)).strip('[]\'') added_ref = added_ref.replace(", ", "\n").replace("\'", "") sheet1.write(counter, custom_ref_column[title], added_ref, topWrap) - + tall_style = xlwt.easyxf('font:height 640;') # 36pt @@ -1426,7 +1422,7 @@ def is_asciidoctor_installed(): cmd = "which asciidoctor" process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) output, error = process.communicate() - + # return path to asciidoctor return output.decode("utf-8").strip() @@ -1447,7 +1443,7 @@ def verify_signing_hash(hash): with tempfile.NamedTemporaryFile(mode="w") as in_file: unsigned_tmp_file_path=in_file.name in_file.write("temporary file for signing") - + cmd = f"security cms -S -Z {hash} -i {unsigned_tmp_file_path}" FNULL = open(os.devnull, 'w') process = subprocess.Popen(cmd.split(), stdout=FNULL, stderr=FNULL) @@ -1456,7 +1452,7 @@ def verify_signing_hash(hash): return True else: return False - + def sign_config_profile(in_file, out_file, hash): """Signs the configuration profile using the identity associated with the provided hash """ @@ -1539,28 +1535,32 @@ def main(): log_reference = args.reference else: log_reference = "default" - use_custom_reference = False + use_custom_reference = False except IOError as msg: parser.error(str(msg)) - + baseline_yaml = yaml.load(args.baseline, Loader=yaml.SafeLoader) + try: + parent_values = baseline_yaml['parent_values'] + except KeyError: + parent_values = "recommended" version_file = os.path.join(parent_dir, "VERSION.yaml") with open(version_file) as r: version_yaml = yaml.load(r, Loader=yaml.SafeLoader) - adoc_templates = [ "adoc_rule", - "adoc_supplemental", - "adoc_rule_no_setting", + adoc_templates = [ "adoc_rule", + "adoc_supplemental", + "adoc_rule_no_setting", "adoc_rule_custom_refs", - "adoc_section", - "adoc_header", - "adoc_footer", + "adoc_section", + "adoc_header", + "adoc_footer", "adoc_foreword", - "adoc_scope", - "adoc_authors", - "adoc_acronyms", + "adoc_scope", + "adoc_authors", + "adoc_acronyms", "adoc_additional_docs" ] adoc_templates_dict = {} @@ -1572,7 +1572,7 @@ def main(): adoc_templates_dict[template] = f"../custom/templates/{template}.adoc" else: adoc_templates_dict[template] = f"../templates/{template}.adoc" - + # check for custom PDF theme (must have theme in the name and end with .yml) pdf_theme="mscp-theme.yml" themes = glob.glob('../custom/templates/*theme*.yml') @@ -1581,7 +1581,7 @@ def main(): elif len(themes) == 1 : print(f"Found custom PDF theme: {themes[0]}") pdf_theme = themes[0] - + # Setup AsciiDoc templates @@ -1593,7 +1593,7 @@ def main(): with open(adoc_templates_dict['adoc_rule_no_setting']) as adoc_rule_no_setting_file: adoc_rule_no_setting_template = Template(adoc_rule_no_setting_file.read()) - + with open(adoc_templates_dict['adoc_rule_custom_refs']) as adoc_rule_custom_refs_file: adoc_rule_custom_refs_template = Template(adoc_rule_custom_refs_file.read()) @@ -1605,13 +1605,13 @@ def main(): with open(adoc_templates_dict['adoc_footer']) as adoc_footer_file: adoc_footer_template = Template(adoc_footer_file.read()) - + with open(adoc_templates_dict['adoc_foreword']) as adoc_foreword_file: adoc_foreword_template = adoc_foreword_file.read() + "\n" with open(adoc_templates_dict['adoc_scope']) as adoc_scope_file: adoc_scope_template = Template(adoc_scope_file.read() +"\n") - + with open(adoc_templates_dict['adoc_authors']) as adoc_authors_file: adoc_authors_template = Template(adoc_authors_file.read() + "\n") @@ -1636,7 +1636,7 @@ def main(): adoc_171_show=":show_171:" else: adoc_171_show=":show_171!:" - + if args.gary: adoc_tag_show=":show_tags:" adoc_STIG_show=":show_STIG:" @@ -1683,7 +1683,7 @@ def main(): adoc_output_file.write(adoc_acronyms_template) adoc_output_file.write(adoc_additional_docs_template) - + # Create sections and rules for sections in baseline_yaml['profile']: @@ -1730,8 +1730,8 @@ def main(): else: rule_location = rule_path[0] custom=False - - rule_yaml = get_rule_yaml(rule_location, custom, baseline_name) + + rule_yaml = get_rule_yaml(rule_location, custom, parent_values) # Determine if the references exist and set accordingly try: @@ -1754,7 +1754,7 @@ def main(): nist_80053r5 = 'N/A' else: nist_80053r5 = rule_yaml['references']['800-53r5'] - + try: rule_yaml['references']['800-171r2'] except KeyError: @@ -1908,16 +1908,16 @@ def main(): # Output footer adoc_output_file.write(footer_adoc) adoc_output_file.close() - + if args.profiles: print("Generating configuration profiles...") generate_profiles(baseline_name, build_path, parent_dir, baseline_yaml, signing, args.hash) - + if args.script: print("Generating compliance script...") generate_script(baseline_name, build_path, baseline_yaml, log_reference) default_audit_plist(baseline_name, build_path, baseline_yaml) - + if args.xls: print('Generating excel document...') generate_xls(baseline_name, build_path, baseline_yaml) @@ -1930,7 +1930,7 @@ def main(): process.communicate() else: print("If you would like to generate the HTML file from the AsciiDoc file, install the ruby gem for asciidoctor") - + asciidoctorPDF_path = is_asciidoctor_pdf_installed() # Don't create PDF if we are generating SCAP diff --git a/scripts/generate_scap.py b/scripts/generate_scap.py index 0fd79df2..7af7684b 100755 --- a/scripts/generate_scap.py +++ b/scripts/generate_scap.py @@ -184,9 +184,9 @@ def generate_scap(all_rules, all_baselines, args): loop = 1 if "odv" in og_rule_yaml: loop = len(og_rule_yaml['odv']) - if args.baseline: - loop = 1 + if args.baseline != "None": + loop = 1 for a in range(0, loop): @@ -196,7 +196,7 @@ def generate_scap(all_rules, all_baselines, args): odv_label = list(rule_yaml['odv'].keys())[a] - if args.baseline: + if args.baseline != "None": odv_label = args.baseline if odv_label == "hint": @@ -219,24 +219,24 @@ def generate_scap(all_rules, all_baselines, args): for mobileconfig_type in rule_yaml['mobileconfig_info']: if isinstance(rule_yaml['mobileconfig_info'][mobileconfig_type], dict): for mobileconfig_value in rule_yaml['mobileconfig_info'][mobileconfig_type]: - if "$ODV" == rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]: - rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value].replace("$ODV",odv_value) + if "$ODV" in str(resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value]): + resulting_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value] = rule_yaml['mobileconfig_info'][mobileconfig_type][mobileconfig_value].replace("$ODV",odv_value) except: - odv_label = "default" + odv_label = "recommended" for baseline in all_baselines: found_rules = [] for tag in rule_yaml['tags']: if tag == baseline: - if odv_label != "default" and odv_label == tag: + if odv_label != "recommended" and odv_label == tag or odv_label == "custom": if baseline in generated_baselines: generated_baselines[baseline].append(rule_yaml['id'] + "_" + odv_label) else: generated_baselines[baseline] = [rule_yaml['id'] + "_" + odv_label] continue - elif odv_label == "default": + elif odv_label == "recommended" or odv_label == "custom": if "odv" in rule_yaml: if baseline not in rule_yaml['odv']: @@ -2707,10 +2707,16 @@ def get_rule_yaml(rule_file, custom=False, baseline_name=""): except: pass elif yaml_field == "tags": - if og_rule_yaml["tags"] == rule_yaml["tags"]: + # try to concatenate tags from both original yaml and custom yaml + try: + if og_rule_yaml["tags"] == rule_yaml["tags"]: + #print("using default data in yaml field {}".format("tags")) + resulting_yaml['tags'] = og_rule_yaml['tags'] + else: + #print("Found custom tags... concatenating them") + resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags'] + except KeyError: resulting_yaml['tags'] = og_rule_yaml['tags'] - else: - resulting_yaml['tags'] = og_rule_yaml['tags'] + rule_yaml['tags'] else: try: if og_rule_yaml[yaml_field] == rule_yaml[yaml_field]: