From ca618f93636274b56d8cdb90636ecddbaf7172f0 Mon Sep 17 00:00:00 2001 From: Bob Gendler Date: Wed, 24 Sep 2025 10:55:17 -0400 Subject: [PATCH] PR #542 --- scripts/generate_guidance.py | 91 +++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/scripts/generate_guidance.py b/scripts/generate_guidance.py index 5d34e77f..c7107238 100755 --- a/scripts/generate_guidance.py +++ b/scripts/generate_guidance.py @@ -402,7 +402,7 @@ def concatenate_payload_settings(settings): def generate_profiles( - baseline_name, build_path, parent_dir, baseline_yaml, signing, hash="" + baseline_name, build_path, parent_dir, baseline_yaml, signing, hash="", generate_domain=True, generate_consolidated=True ): """Generate the configuration profiles for the rules in the provided baseline YAML file""" @@ -525,6 +525,15 @@ def generate_profiles( ) for error in profile_errors: print(error) + + consolidated_profile = PayloadDict( + identifier="consolidated." + baseline_name, + uuid=False, + organization="macOS Security Compliance Project", + displayname=f"{baseline_name} settings", + description=f"Consolidated configuration settings for {baseline_name}." + ) + # process the payloads from the yaml file and generate new config profile for each type for payload, settings in profile_types.items(): if payload.startswith("."): @@ -572,35 +581,35 @@ def generate_profiles( if payload == "com.apple.ManagedClient.preferences": for item in settings: newProfile.addMCXPayload(item, baseline_name) + consolidated_profile.addMCXPayload(item, baseline_name) # handle these payloads for array settings elif ( (payload == "com.apple.applicationaccess.new") or (payload == "com.apple.systempreferences") or (payload == "com.apple.SetupAssistant.managed") ): - newProfile.addNewPayload( - payload, concatenate_payload_settings(settings), baseline_name - ) + newProfile.addNewPayload(payload, concatenate_payload_settings(settings), baseline_name) + consolidated_profile.addNewPayload(payload, concatenate_payload_settings(settings), baseline_name) else: newProfile.addNewPayload(payload, settings, baseline_name) + consolidated_profile.addNewPayload(payload, settings, baseline_name) + + if generate_domain: + with open(settings_plist_file_path, "wb") as settings_plist_file: + newProfile.finalizeAndSavePlist(settings_plist_file) + with open(unsigned_mobileconfig_file_path, "wb") as unsigned_mobileconfig_file: + newProfile.finalizeAndSave(unsigned_mobileconfig_file) + if signing: + sign_config_profile(unsigned_mobileconfig_file_path, signed_mobileconfig_file_path, hash) + + if generate_consolidated: + consolidated_mobileconfig_file_path = os.path.join(unsigned_mobileconfig_output_path, f"{baseline_name}.mobileconfig") + with open(consolidated_mobileconfig_file_path, "wb") as consolidated_mobileconfig_file: + consolidated_profile.finalizeAndSave(consolidated_mobileconfig_file) if signing: - unsigned_file_path = os.path.join(unsigned_mobileconfig_file_path) - unsigned_config_file = open(unsigned_file_path, "wb") - newProfile.finalizeAndSave(unsigned_config_file) - settings_config_file = open(settings_plist_file_path, "wb") - newProfile.finalizeAndSavePlist(settings_config_file) - unsigned_config_file.close() - # sign the profiles - sign_config_profile(unsigned_file_path, signed_mobileconfig_file_path, hash) - # delete the unsigned - - else: - config_file = open(unsigned_mobileconfig_file_path, "wb") - settings_config_file = open(settings_plist_file_path, "wb") - newProfile.finalizeAndSave(config_file) - newProfile.finalizeAndSavePlist(settings_config_file) - config_file.close() + signed_consolidated_mobileconfig_path = os.path.join(signed_mobileconfig_output_path, f"{baseline_name}.mobileconfig") + sign_config_profile(consolidated_mobileconfig_file_path, signed_consolidated_mobileconfig_path, hash) print( f""" @@ -889,17 +898,16 @@ def default_audit_plist(baseline_name, build_path, baseline_yaml): plist_output_path, "org." + baseline_name + ".audit.plist" ) - plist_file = open(plist_file_path, "wb") + with open(plist_file_path, "wb") as plist_file: + plist_dict = {} - plist_dict = {} + for sections in baseline_yaml["profile"]: + for profile_rule in sections["rules"]: + if profile_rule.startswith("supplemental"): + continue + plist_dict[profile_rule] = {"exempt": False} - for sections in baseline_yaml["profile"]: - for profile_rule in sections["rules"]: - if profile_rule.startswith("supplemental"): - continue - plist_dict[profile_rule] = {"exempt": False} - - plistlib.dump(plist_dict, plist_file) + plistlib.dump(plist_dict, plist_file) def generate_script(baseline_name, audit_name, build_path, baseline_yaml, reference): @@ -2051,7 +2059,14 @@ def create_args(): "-p", "--profiles", default=None, - help="Generate configuration profiles for the rules.", + help="Generate domain-specific configuration profiles for the rules.", + action="store_true", + ) + parser.add_argument( + "-P", + "--consolidated-profile", + default=None, + help="Generate consolidated configuration profile for all rules.", action="store_true", ) parser.add_argument( @@ -2716,10 +2731,20 @@ def main(): else: audit_name = baseline_name - if args.profiles: - print("Generating configuration profiles...") + if args.profiles or args.consolidated_profile: + # Build message based on what's being generated + messages = [] + if args.profiles: + messages.append("domain-specific") + if args.consolidated_profile: + messages.append("consolidated") + + print(f"Generating {' and '.join(messages)} configuration profiles...") + + # Single call to generate_profiles with both parameters generate_profiles( - baseline_name, build_path, parent_dir, baseline_yaml, signing, args.hash + baseline_name, build_path, parent_dir, baseline_yaml, signing, args.hash, + generate_domain=args.profiles, generate_consolidated=args.consolidated_profile ) if args.ddm: