mirror of
https://github.com/usnistgov/macos_security.git
synced 2026-02-03 14:03:24 +00:00
feat: add consolidated and granular profile generation
options are now available to include either a single consolidated profile or individual profiles for each setting
This commit is contained in:
@@ -184,6 +184,11 @@ class Macsecurityrule(BaseModelWithAccessors):
|
||||
platforms (dict[str, Platforms]): Platform-specific data for the rule.
|
||||
os_name (str): Name of the operating system.
|
||||
os_type (str): Type of the operating system.
|
||||
os_version: float = Field(default_factory=float)
|
||||
check (str): The commands to evaluate the state of a rule.
|
||||
fix: (str): The commands to remediate and set the configuration for a rule.
|
||||
severity: (str): The category for impact assigned to a rule.
|
||||
default_state: (str): The command to restore the system to the default configuration for a rule.
|
||||
|
||||
Class Methods:
|
||||
load_rules: Load Macsecurityrule objects from YAML files for the given rule IDs.
|
||||
|
||||
@@ -90,7 +90,7 @@ class Payload(BaseModel):
|
||||
"PayloadVersion": self.payload_version,
|
||||
"PayloadUUID": uuid,
|
||||
"PayloadType": payload_type,
|
||||
"PayloadIdentifier": f"alacarte.macOS.{baseline_name}.{uuid}",
|
||||
"PayloadIdentifier": f"mscp.{payload_type}.{uuid}",
|
||||
}
|
||||
|
||||
payload.update(settings)
|
||||
@@ -121,7 +121,7 @@ class Payload(BaseModel):
|
||||
"PayloadVersion": self.payload_version,
|
||||
"PayloadUUID": uuid,
|
||||
"PayloadType": "com.apple.ManagedClient.preferences",
|
||||
"PayloadIdentifier": f"alacarte.macOS.{baseline_name}.{uuid}",
|
||||
"PayloadIdentifier": f"mscp.{domain}.{uuid}",
|
||||
"PayloadContent": {
|
||||
domain: {"Forced": [{"mcx_preference_settings": settings}]}
|
||||
},
|
||||
|
||||
@@ -166,6 +166,20 @@ def parse_cli() -> None:
|
||||
help="Generate configuration profiles for the rules.",
|
||||
action="store_true",
|
||||
)
|
||||
guidance_parser.add_argument(
|
||||
"-P",
|
||||
"--consolidated-profile",
|
||||
default=False,
|
||||
help="Include a single consolidated configuration profile when generating profiles.",
|
||||
action="store_true",
|
||||
)
|
||||
guidance_parser.add_argument(
|
||||
"-G",
|
||||
"--granular-profiles",
|
||||
default=False,
|
||||
help="Include granular per-setting configuration profiles when generating profiles.",
|
||||
action="store_true",
|
||||
)
|
||||
guidance_parser.add_argument(
|
||||
"-r",
|
||||
"--reference",
|
||||
@@ -383,6 +397,10 @@ def parse_cli() -> None:
|
||||
logger.warning("visionOS is not supported at this time.")
|
||||
sys.exit()
|
||||
|
||||
# if generating consolidated profile, assume to do all profiles
|
||||
if args.consolidated_profile or args.granular_profiles:
|
||||
args.profiles = True
|
||||
|
||||
if args.subcommand == "guidance":
|
||||
if args.os_name != "macos" and args.script:
|
||||
logger.error(
|
||||
|
||||
@@ -129,9 +129,23 @@ def generate_guidance(args: argparse.Namespace) -> None:
|
||||
if args.profiles:
|
||||
logger.info("Generating configuration profiles")
|
||||
if not signing:
|
||||
generate_profiles(build_path, baseline_name, baseline)
|
||||
generate_profiles(
|
||||
build_path,
|
||||
baseline_name,
|
||||
baseline,
|
||||
consolidated=args.consolidated_profile,
|
||||
granular=args.granular_profiles,
|
||||
)
|
||||
else:
|
||||
generate_profiles(build_path, baseline_name, baseline, signing, args.hash)
|
||||
generate_profiles(
|
||||
build_path,
|
||||
baseline_name,
|
||||
baseline,
|
||||
signing,
|
||||
args.hash,
|
||||
consolidated=args.consolidated_profile,
|
||||
granular=args.granular_profiles,
|
||||
)
|
||||
|
||||
if args.ddm:
|
||||
logger.info("Generating declarative components")
|
||||
@@ -170,7 +184,13 @@ def generate_guidance(args: argparse.Namespace) -> None:
|
||||
if args.all:
|
||||
logger.info("Generating all support files")
|
||||
logger.info("Generating configuration profiles")
|
||||
generate_profiles(build_path, baseline_name, baseline)
|
||||
generate_profiles(
|
||||
build_path,
|
||||
baseline_name,
|
||||
baseline,
|
||||
consolidated=args.consolidated_profile,
|
||||
granular=args.granular_profiles,
|
||||
)
|
||||
|
||||
logger.info("Generating declarative components")
|
||||
generate_ddm(build_path, baseline, baseline_name)
|
||||
|
||||
@@ -78,6 +78,8 @@ def generate_profiles(
|
||||
baseline: Baseline,
|
||||
signing: bool = False,
|
||||
hash_value: str = "",
|
||||
consolidated: bool = False,
|
||||
granular: bool = False,
|
||||
) -> None:
|
||||
"""
|
||||
Generates configuration profiles based on the provided baseline and saves them to the specified build path.
|
||||
@@ -88,6 +90,8 @@ def generate_profiles(
|
||||
baseline (Baseline): The baseline object containing profile and rule information.
|
||||
signing (bool, optional): Whether to sign the generated profiles. Defaults to False.
|
||||
hash_value (str, optional): The hash value used for signing the profiles. Defaults to an empty string.
|
||||
consolidated (bool, optional): Whether to include a single consolidate profile containing all the settings.
|
||||
granular (bool, optional): Whether to build individual profiles for every setting.
|
||||
|
||||
Returns:
|
||||
None
|
||||
@@ -108,7 +112,7 @@ def generate_profiles(
|
||||
unsigned_output_path: Path = Path(build_path, "mobileconfigs", "unsigned")
|
||||
signed_output_path: Path = Path(build_path, "mobileconfigs", "signed")
|
||||
plist_output_path: Path = Path(build_path, "mobileconfigs", "preferences")
|
||||
create_date: date = date.today()
|
||||
granular_output_path: Path = Path(build_path, "mobileconfigs", "granular")
|
||||
|
||||
manifests_file: dict = open_file(
|
||||
Path(config.get("includes_dir", ""), "supported_payloads.yaml")
|
||||
@@ -116,6 +120,7 @@ def generate_profiles(
|
||||
|
||||
make_dir(unsigned_output_path)
|
||||
make_dir(plist_output_path)
|
||||
make_dir(granular_output_path)
|
||||
|
||||
if signing:
|
||||
make_dir(signed_output_path)
|
||||
@@ -149,6 +154,13 @@ def generate_profiles(
|
||||
for error in profile_errors:
|
||||
logger.info(f"Correct the following rule: {error.rule_id}")
|
||||
|
||||
consolidated_profile = Payload(
|
||||
identifier=f"consolidated.{baseline_name}",
|
||||
organization="macOS Security Compliance Project",
|
||||
displayname=f"{baseline_name} settings",
|
||||
description=f"Consolidated configuration settings for {baseline_name}.",
|
||||
)
|
||||
|
||||
for payload_type, settings_list in grouped_payloads.items():
|
||||
logger.debug("Payload Type: {}", payload_type)
|
||||
logger.debug("Settings List: {}", repr(settings_list))
|
||||
@@ -171,9 +183,8 @@ def generate_profiles(
|
||||
sanitized_payload_type = "".join(
|
||||
c if c.isalnum() or c in "._-" else "_" for c in payload_type
|
||||
)
|
||||
identifier = f"{sanitized_payload_type}.{baseline_name}"
|
||||
identifier = f"mscp.{sanitized_payload_type}.{baseline_name}"
|
||||
description = (
|
||||
f"Created: {create_date}\n"
|
||||
f"Configuration settings for the {payload_type} preference domain."
|
||||
)
|
||||
organization = "macOS Security Compliance Project"
|
||||
@@ -190,9 +201,45 @@ def generate_profiles(
|
||||
for settings in flat_settings:
|
||||
for domain, payload_content in settings.items():
|
||||
new_profile.add_mcx_payload(domain, payload_content, baseline_name)
|
||||
consolidated_profile.add_mcx_payload(
|
||||
domain, payload_content, baseline_name
|
||||
)
|
||||
# generate individual profiles for each setting
|
||||
if granular:
|
||||
for setting, value in payload_content.items():
|
||||
granular_profile = Payload(
|
||||
identifier=f"mscp.{domain}.{setting}",
|
||||
organization=organization,
|
||||
description=f"Configuration for {domain}:{setting}",
|
||||
displayname=f"[{domain}] - {setting}",
|
||||
)
|
||||
|
||||
granular_profile.add_mcx_payload(
|
||||
domain, {setting: value}, setting
|
||||
)
|
||||
granular_profile.save_to_plist(
|
||||
granular_output_path / f"{setting}.mobileconfig"
|
||||
)
|
||||
else:
|
||||
settings: dict = {k: v for d in flat_settings for k, v in d.items()}
|
||||
new_profile.add_payload(payload_type, settings, baseline_name)
|
||||
consolidated_profile.add_payload(payload_type, settings, baseline_name)
|
||||
|
||||
# generate individual profiles for each setting
|
||||
if granular:
|
||||
for setting, value in settings.items():
|
||||
granular_profile = Payload(
|
||||
identifier=f"mscp.{payload_type}.{setting}",
|
||||
organization=organization,
|
||||
description=f"Configuration for {payload_type}:{setting}",
|
||||
displayname=f"[{payload_type}] - {setting}",
|
||||
)
|
||||
granular_profile.add_payload(
|
||||
payload_type, {setting: value}, setting
|
||||
)
|
||||
granular_profile.save_to_plist(
|
||||
granular_output_path / f"{setting}.mobileconfig"
|
||||
)
|
||||
|
||||
new_profile.save_to_plist(unsigned_mobileconfig_file_path)
|
||||
|
||||
@@ -209,6 +256,19 @@ def generate_profiles(
|
||||
f"Configuration profile for {payload_type} saved to {unsigned_mobileconfig_file_path}"
|
||||
)
|
||||
|
||||
# write consolidated profile if enabled
|
||||
if consolidated:
|
||||
consolidated_profile.save_to_plist(
|
||||
unsigned_output_path / f"{baseline_name}.mobileconfig"
|
||||
)
|
||||
|
||||
if signing:
|
||||
sign_config_profile(
|
||||
unsigned_output_path / f"{baseline_name}.mobileconfig",
|
||||
signed_output_path / f"{baseline_name}.mobileconfig",
|
||||
hash_value,
|
||||
)
|
||||
|
||||
managed_client_file: Path = (
|
||||
plist_output_path / "com.apple.ManagedClient.preferences.plist"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user